feat(team-mode): align member and lead prompts with Claude Code free-code reference

Bring the team member addendum and the team-mode skill in line with the
Claude Code experimental Agent Teams reference (../free-code) on the
behavioral framing the LLM needs, while keeping every omo-specific
constraint (TeamRunId param, lead-only tool blocklist, eligibility
registry, sisyphus-junior routing) intact.

Member addendum (src/features/team-mode/member-guidance.ts) — adopt
free-code teammatePromptAddendum.ts wisdom that was missing:

- "User interacts primarily with the team lead" — members no longer try
  to address the user directly; they coordinate via task system + DMs.
- Stronger emphasis that plain assistant text is invisible to the team;
  team_send_message is mandatory, not optional.
- Broadcast (`to: "*"`) called out explicitly with "sparingly" guidance
  and the lead-only constraint.
- "Idle is normal" section: idle ≠ done, idle teammates can still
  receive messages, the lead must not treat idle as an error.
- "Communication rules" section forbidding structured JSON status
  messages and forbidding terminal tools as a peer-inspection backdoor;
  always reference teammates by NAME, not session ID.
- Auto-delivery framing: messages arrive as new turns, no manual inbox
  polling, no need to quote a teammate's message back to the lead.

Team-mode skill (src/features/builtin-skills/skills/team-mode.ts) —
adopt free-code TeamCreateTool/prompt.ts sections that were missing:

- Task ownership: any agent can set/change `owner` via team_task_update.
- Automatic message delivery: <peer_message ...> envelope flow.
- Teammate idle state: idle is the expected post-turn state; peer DM
  summaries surface in the lead's idle notification.
- Discovering team members: team_status + ~/.omo/teams/ config; refer by
  name, never raw session IDs.
- Task list coordination: claim by ID order (lowest first), check after
  every completion, escalate blocking deadlocks to the lead.
- Communication rules: same anti-patterns as the member addendum, scoped
  for the lead's awareness (no JSON status, no terminal peer-inspection,
  no delegate-task from inside members).

Test (create.test.ts) updated to track the renamed section header
("Lead-only tools you must NOT call") and to lock the new free-code
parity assertions ("user interacts primarily with the team lead",
"Idle is normal", "structured JSON status messages").

All other contract tests (team-mode skill keyword/section tests,
member parser, runtime tests) untouched and still passing.

Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode).
This commit is contained in:
YeonGyu-Kim
2026-04-30 16:40:27 +09:00
parent f6643e7e77
commit c90be7f9a6
3 changed files with 75 additions and 14 deletions
@@ -99,6 +99,43 @@ Do not use \`oracle\`, \`prometheus\`, or other non-eligible agents here. For th
6. The targeted member or the lead handles \`team_approve_shutdown\` or \`team_reject_shutdown\`.
7. Lead removes the team with \`team_delete\`.
## Task ownership
Any agent can set or change task ownership via \`team_task_update\` with the \`owner\` field. Members typically claim work by setting \`owner: "<their-name>"\` and \`status: "claimed"\` (or directly \`"in_progress"\`). The lead can also pre-assign work by creating tasks with \`owner\` set.
## Automatic message delivery
Messages sent via \`team_send_message\` are automatically delivered to the recipient as new conversation turns — no manual inbox polling. If a recipient is mid-turn, the message is queued and injected when its turn ends, wrapped in a \`<peer_message ...>\` envelope. The UI surfaces a brief notification with the sender's name. When reporting on teammate messages, do NOT quote the original — it has already been rendered.
## Teammate idle state
Teammates go idle after every turn — this is normal and expected. A teammate going idle immediately after sending a message does NOT mean they are done or unavailable. Idle simply means they are waiting for input.
- Idle teammates can still receive messages; sending one wakes them up.
- The system emits idle notifications automatically. The lead does not need to react to every idle event — only when assigning new work or following up.
- Do not treat idle as an error. A teammate that sent a message and went idle has done its job and is awaiting reply.
- Peer DMs include a brief summary in the lead's idle notification, giving the lead visibility into peer collaboration without the full message text.
## Discovering team members
Members and the lead use \`team_status({ teamRunId })\` to see who is active, their session IDs, message backlog, and tmux pane assignments. The team config also lives at \`~/.omo/teams/{name}/config.json\` for declared teams. Always refer to teammates by their NAME (e.g., \`"lead"\`, \`"researcher"\`) — never by raw session IDs.
## Task list coordination
Members should:
1. Check \`team_task_list\` periodically, **especially after completing each task**, to find newly unblocked work.
2. Claim unassigned, unblocked tasks via \`team_task_update\` (set \`owner\` and \`status: "claimed"\` or \`"in_progress"\`). Prefer tasks in ID order (lowest first) — earlier tasks usually establish context for later ones.
3. Create new tasks via \`team_task_create\` when they identify additional work.
4. Mark tasks completed via \`team_task_update\` with \`status: "completed"\`, then re-check the task list.
5. If all available tasks are blocked, send a \`team_send_message\` to the lead to either resolve blockers or assign different work.
## Communication rules
- Do NOT send structured JSON status messages like \`{"type":"idle",...}\` or \`{"type":"task_completed",...}\`. Communicate in plain natural language.
- Do NOT use terminal tools (Bash, file readers) to inspect another teammate's session, inbox, or pane — always go through \`team_send_message\` and \`team_status\`.
- Members must NOT call \`delegate-task\` — its budget is zero inside team members. Use \`team_send_message\` to coordinate with peers instead.
## Lead-only tools
- \`team_create\` - create a team from a declaration.
+33 -12
View File
@@ -4,22 +4,43 @@ export function buildTeammateCommunicationAddendum(_config: TeamModeConfig): str
return `
# Team Communication
You are running as a team member. Your text responses are NOT visible to other team members or the lead.
You are running as a team member. The user interacts primarily with the team lead — your work is coordinated through the task system and teammate messaging, not through direct user interaction.
IMPORTANT: For ALL team_* tool calls, use the TeamRunId shown above as the \`teamRunId\` parameter. Do NOT use the team name.
IMPORTANT: Just writing a response in text is NOT visible to others on your team. You MUST use the \`team_send_message\` tool to communicate. Plain assistant text is invisible to the lead and to other teammates.
Do not call lead-only lifecycle tools such as \`team_shutdown_request\`, \`team_delete\`, \`team_approve_shutdown\`, or \`team_reject_shutdown\`.
For ALL team_* tool calls, use the TeamRunId shown above as the \`teamRunId\` parameter. Do NOT use the team name.
Use these tools instead:
- team_send_message: Send results, blockers, or completion updates to the lead. Use \`to: "lead"\` for the lead, \`to: "<name>"\` for a specific member. Include \`summary\` and \`references\` when they help the lead triage quickly.
- team_task_update: Update your task status. Move to \`status: "in_progress"\` when you start working, and \`status: "completed"\` when done. \`status: "claimed"\` is optional if you want to explicitly claim before you begin.
- team_task_list: See all team tasks and their status.
- team_task_get: Get details of a specific task.
- delegate-task: Do not call this from inside team members.
## Tools you should use
- \`team_send_message\` — Send results, blockers, completion updates, or peer DMs. Use \`to: "lead"\` for the lead, \`to: "<name>"\` for a specific teammate, and \`to: "*"\` sparingly for team-wide broadcasts. Include \`summary\` and \`references\` when they help triage quickly.
- \`team_task_update\` — Update your task status. Move to \`status: "in_progress"\` when you start working, and \`status: "completed"\` when done. \`status: "claimed"\` is optional if you want to explicitly claim before you begin. Any team member can also reassign tasks via the \`owner\` field.
- \`team_task_list\` — Check periodically, **especially after completing each task**, to find newly unblocked work. Prefer tasks in ID order (lowest ID first) — earlier tasks usually set up context for later ones.
- \`team_task_get\` — Inspect one task in detail.
- \`delegate-task\` — Do NOT call this from inside team members. The budget is zero.
## Lead-only tools you must NOT call
\`team_shutdown_request\`, \`team_delete\`, \`team_approve_shutdown\`, \`team_reject_shutdown\`. Broadcast (\`to: "*"\`) on \`team_send_message\` is also lead-only.
## Automatic message delivery
Messages from teammates and the lead are automatically delivered to you as new conversation turns. You do NOT need to manually poll or read inbox files. If a message arrives mid-turn, it is queued and delivered when your current turn ends. When you report on a teammate message, you do NOT need to quote it back — the lead has already seen it.
## Idle is normal
Going idle after sending a message is the expected flow — it does NOT mean you are done or unavailable. Idle simply means you are waiting for input. Idle teammates can still receive messages; the next \`team_send_message\` to you wakes you up. Do not treat your own idle state — or another teammate's — as an error.
## Communication rules
- Do NOT send structured JSON status messages like \`{"type":"idle",...}\` or \`{"type":"task_completed",...}\`. Communicate in plain natural language when you message teammates.
- Do NOT use terminal tools (Bash, file readers) to inspect another teammate's session, inbox, or pane. Send a \`team_send_message\` instead.
- Always refer to teammates by their NAME (e.g., \`to: "lead"\`, \`to: "researcher"\`), never by internal session IDs.
## Wrap-up
When you finish your assigned work, ALWAYS:
1. Send your results to lead via team_send_message
2. Mark your task as completed via team_task_update
3. Send a completion message to lead so the lead can decide whether to request shutdown
1. Send your results to the lead via \`team_send_message\`.
2. Mark your task as completed via \`team_task_update\`.
3. Send a completion message to the lead so the lead can decide whether to request shutdown.
`
}
@@ -150,12 +150,15 @@ describe("createTeamRun", () => {
const firstPrompt = (launchMock.mock.calls as Array<[LaunchInput]>)[0]?.[0].prompt ?? ""
// then
expect(firstPrompt).toContain("Do not call lead-only lifecycle tools")
expect(firstPrompt).toContain("Lead-only tools you must NOT call")
expect(firstPrompt).not.toContain("3. Request shutdown via `team_shutdown_request`")
expect(firstPrompt).toContain("Include `summary` and `references`")
expect(firstPrompt).toContain("Move to `status: \"in_progress\"` when you start working")
expect(firstPrompt).toContain("delegate-task: Do not call this")
expect(firstPrompt).toContain("Do NOT call this from inside team members")
expect(firstPrompt).toContain("lead can decide whether to request shutdown")
expect(firstPrompt).toContain("user interacts primarily with the team lead")
expect(firstPrompt).toContain("Idle is normal")
expect(firstPrompt).toContain("structured JSON status messages")
})
test("rolls back launched members in reverse order when a later spawn fails", async () => {