fix(team-mode): make lead close teams on its own initiative

The lead currently leaves teams alive after the task list drains because
none of the prompt surfaces tell it WHEN to close or HOW. omx-style
'self-closing' behavior was missing for four reasons (diagnosed via
prompt-engineering A/B/C: wrong / misframed / missing):

1. builtin team-mode skill 'Lifecycle' (B+C): 'phase ends / shape
   outgrown' is qualitative, so the model maps it to 'wait for user'.
   Step 6 jumped to team_delete without the request/approve pair the
   tool contract requires. Replaced with a 'Closure Contract' (a
   computable predicate over team_task_list + team_status) and an
   explicit 'Closure Sequence' (request -> approve -> delete, with
   force=true reserved for unrecoverable paths only).

2. TEAM_MESSAGE keyword injection (C): spent 100%% of its one-shot
   budget on routing ('do not substitute delegate_task'), 0%% on
   closure. Added the same closure rule in compressed form. Kept the
   'NEVER substitute with delegate_task' literal that
   keyword-detector/index.test.ts depends on.

3. team-mode-status-injector body (C): the only per-session injection
   for team mode had no closure obligation. Replaced the optional
   'load the team-mode skill ... otherwise use the team_* tools'
   sentence with a 'Closure invariant' clause that ties the check to
   every team_task_update.

4. member-guidance Wrap-up (A+B): step 3 said 'so the lead can decide
   whether to request shutdown', but team_shutdown_request is
   lead-only - members cannot initiate it. Step ordering also placed
   the completion message before team_task_update, so the lead's
   closable check would see stale data. Reordered to
   task_update -> check task_list for new work -> if nothing left,
   send a single 'closure-ready' message and idle. Test assertion
   updated to match the new accurate contract.

Also: stripped Korean alternation from TEAM_PATTERN per directive
('절대로 코드 내에 한국어 적지 마라'). Pattern is now
/\\bteam[\\s_-]?mode\\b/i. Removed 4 Korean test cases
(2 positive triggers + 2 false-positive guards) that the pattern no
longer needs to defend, and updated the keyword-detector AGENTS.md
row.

Net: -71 lines across prompt surfaces. The Closure Contract is the
only addition; everything else tightened.

Tests: 428/428 pass across src/features/team-mode/,
src/features/builtin-skills/, src/hooks/keyword-detector/,
src/hooks/team-mode-status-injector/, src/hooks/team-mailbox-injector/,
src/hooks/team-tool-gating/, src/hooks/team-session-events/.
LSP: no errors introduced (one pre-existing error in
keyword-detector/index.test.ts confirmed pre-existing on dev).
This commit is contained in:
YeonGyu-Kim
2026-05-24 15:21:24 +09:00
parent 2bfad4909e
commit 5f1fb0c52a
7 changed files with 35 additions and 106 deletions
+3 -4
View File
@@ -100,10 +100,9 @@ function latestUserMessageRequestsTeamMode(
function buildTeamModeStatusContent(): string {
return `${TEAM_MODE_STATUS_MARKER}
Team mode is ENABLED for this session.
If the team_* tools are present, that is authoritative proof that team mode is active.
Do not inspect ~/.config/opencode or project config files to verify team mode.
If you need usage guidance, load the team-mode skill. Otherwise use the team_* tools directly.
Team mode is ENABLED for this session. Presence of the team_* tools is authoritative proof; do not inspect config files to verify.
Closure invariant: every team you open is yours to close. After each team_task_update that completes or fails a task, call team_task_list({ teamRunId }); if every task is terminal, run team_shutdown_request + team_approve_shutdown per active member, then team_delete — in the same turn, without waiting for the user to ask. Lingering teams are a defect.
Load the team-mode skill for the full Closure Contract and Closure Sequence.
</team_mode_status>`
}