diff --git a/docs/0-requirements.md b/docs/0-requirements.md index 8ad1c80..6861a05 100644 --- a/docs/0-requirements.md +++ b/docs/0-requirements.md @@ -172,6 +172,9 @@ push の形(参照実装 `Liplus-Project/github-webhook-mcp` `local-mcp/src/in - 宛先は任意である。必須にすると人間の入力費用が上がる。 - 宛先の相手は人間でもセッションでもよく、指定の仕方は変わらない。画面の宛先の選択肢は「自分以外の参加者」であり、人間も同じ列に並ぶ。 - 届いた発言はすべて他の参加者のものである。自分の発言は戻らないため、エージェントは自分を認識する必要がない。 +- 先に誰かが答えていたら、その発言を読んでから自分の発言を決める。送る直前にも届いている発言をもう一度見て、言おうとしていたことが既に言われていたら送らず、足りないことがあるときだけ足す。全体宛の問いに全員が答える必要はない。宛先なしの問いに二人の参加者が同内容を二重に返した実測(2026-08-22)に対し、口頭で「順番を待ってその会話を見てから話し始める」と伝えたところ挙動は成立した。足りていなかったのは参加者の能力ではなく、これが部屋の規律に書かれていないことである。 +- 順番は沈黙とは別軸である。宛先の節が扱うのは誰に向いた発言かであり、こちらは先に答えが出たときにどうするかである。混ぜて「答えなくてよい」だけを渡すと、全体宛の発言に誰も答えない状態を招く。書き方は「黙れ」ではなく「見てから決める」に寄せる。 +- 作法が「見よ」と言えるのは届いた発言までである。参加者は床の状態——誰が今組み立てているか——を覗けず、部屋のツールは書き込み専用の `say_to_room` 一本である。届いた発言なら、組み立て中に届いた分も channel が随時運んでいるため送信直前に読み直せる。同時に書き始めた二人は取りこぼす。その残りは状態を出す側と床を読む側の二点セットであり、この作法を入れてなお同時発火が痛むと観測されてから判断する(#47)。規律の無い状態での観測を根拠に protocol を変えない。 ### MCP サーバの実装方式 @@ -200,6 +203,7 @@ liplus-desktop の `stream_parser.rs` および `spawn_stream_pty` / `spawn_stre - サーバの `.mcp.json` 登録(既存内容はマージして保持、登録鍵は参加者ごと、前回起動のエントリは掃除)と、成立条件を満たす CLI 起動フラグの適用 - 部屋の作法(`instructions`)の初版 - 発言の宛先(部屋 → `post.to` → channel の `meta.to`、送信は `say_to_room` の `to`)と、それを判定材料として名指しする `instructions` +- 順番の作法(先に届いた答えを読んでから決める、送信直前の読み直し。`instructions` のみで、protocol もツールも増やしていない) - 参加者モデル(統一 `post` フレーム、発言者以外の全参加者への配送、接続同一性による自分の発言の抑止と名簿の同一性、人間を含む名簿) - 参加の時点の名乗り(起動ごとに選ぶ名前と色。人間側の名前欄と同じ形、`localStorage` に保持) - 宣言色(`hello` の `hue` / `room_join` の `hue`。宣言 > 自分の accent > 名前からの導出) diff --git a/sidecar/src/index.ts b/sidecar/src/index.ts index a919a04..b7dbb4f 100644 --- a/sidecar/src/index.ts +++ b/sidecar/src/index.ts @@ -129,6 +129,11 @@ const INSTRUCTIONS = [ "- 一度の発言は簡潔に。長い説明が必要なときは、まず要点だけ返してください。", "- 他の参加者の発言を、自分の文脈として取り込まないでください。それぞれが", " 自分の文脈から同じ会話に参加しています。", + "- 先に誰かが答えていたら、その発言を読んでから自分の発言を決めてください。", + " 全体宛の問いに、全員が答える必要はありません。", + "- 送る直前に、届いている発言をもう一度見てください。組み立てている間にも", + " 発言は届きます。言おうとしていたことが既に言われていたら送らず、", + " 足りないことがあるときだけ足してください。", ].join("\n"); const mcp = new Server( diff --git a/sidecar/test/round-trip.test.mjs b/sidecar/test/round-trip.test.mjs index 3ea2815..8e2ae0b 100644 --- a/sidecar/test/round-trip.test.mjs +++ b/sidecar/test/round-trip.test.mjs @@ -177,6 +177,26 @@ test("a room post reaches the channel, and say_to_room reaches the room", async /人間と AI を区別しません/, "instructions must state that participants are not split into human and AI", ); + // Turn-taking. The addressee clauses filter who a message is for; these say + // what to do when someone already answered. Both halves are required: read + // the earlier answer before deciding, and look again at what arrived while + // the message was being composed, since the composing agent cannot see the + // floor and the arrivals are all it has to look at (#49). + assert.match( + init.result.instructions ?? "", + /先に誰かが答えていたら、その発言を読んでから自分の発言を決めて/, + "instructions must tell the agent to read an earlier answer before deciding its own", + ); + assert.match( + init.result.instructions ?? "", + /送る直前に、届いている発言をもう一度見て/, + "instructions must tell the agent to re-read what arrived just before sending", + ); + assert.match( + init.result.instructions ?? "", + /全員が答える必要はありません/, + "instructions must state that a room-wide question needs no answer from everyone", + ); notify("notifications/initialized", {});