fix(missions): agent teams do not form in print mode — say so where it is set

MEASURED, against the CLI in our own image (2.1.223): with
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 and an explicit request to "spawn two
teammates", `claude -p` did the work with two SUBAGENTS, wrote both files, and
created no ~/.claude/teams/ directory at all. The docs allow for it — "Claude may
sometimes use subagents instead of creating a team" — and headless appears to be
always: the whole feature is described around an interactive agent panel, which a
print-mode session does not have.

So Slice 3's switch, as written yesterday, set a flag with no mechanism behind it.
The first team mission caught it, because the probe was built to look for teammates
rather than to assume them.

Corrected rather than removed:
  - `team_env` documents the measurement at the point the flag is set, so the next
    reader does not have to rediscover it. The flag stays: harmless, and free if a
    later version supports teams non-interactively.
  - the prompt addendum now asks for parallel DELEGATION rather than naming
    teammates, which is what print mode can actually deliver — and it keeps the two
    anti-patterns worth stating (own different files; do not split one change into
    stages).
  - the "no teammates" warning was blaming the flag and the config path. It now
    judges on the SUBAGENT count, which is the mechanism in play, and a zero
    teammate count is documented as expected rather than as a fault.

What the switch buys today is real but smaller than the plan assumed: it changes
the prompt so the lead parallelises across files instead of working through them
alone. Whether that beats solo on our own missions is still unmeasured, and the
plan's prediction — that it will not be faster — stands untested.

482 tests pass, clippy clean.

UNEXPLAINED, filed as #54: that team run's `topology_runs` row is `failed` while
the log line that sits immediately before the UPDATE never printed — zero matches
for 'microvm phase' in the container's whole log. The prime suspect is
`topology_worker`'s stuck-run reaper, which fails runs that are `running` with no
step records and does not filter by tier; a microvm run has no step records by
design. If that is it, any sufficiently long VM phase is failed out from under
itself. The system failed safely here — the empty-delivery guard caught that
nothing was produced, and nothing false was reported — but the cause is not known
and it is not being written up as if it were.
This commit is contained in:
Omar Sobh
2026-08-06 07:22:05 -07:00
parent cb48f7ff3b
commit 0d25a94a84
+31 -9
View File
@@ -76,8 +76,21 @@ fn wants_claude_code_team(engine: Option<&str>) -> bool {
/// Environment that turns agent teams on. Empty for a solo mission. /// Environment that turns agent teams on. Empty for a solo mission.
/// ///
/// Experimental and disabled by default in the CLI, so nothing changes for a /// **MEASURED: agent teams do not form under `claude -p`.** With this flag set and
/// mission that did not ask. Kept separate from /// an explicit request to "spawn two teammates", the CLI in our image (2.1.223)
/// did the work with two SUBAGENTS, wrote both files, and created no
/// `~/.claude/teams/` directory at all. The docs allow for it — "Claude may
/// sometimes use subagents instead of creating a team" — and headless appears to be
/// always. The whole agent-teams feature is described around an interactive agent
/// panel, which a print-mode session does not have.
///
/// The flag is still set, because it is harmless and costs nothing if a later
/// version does support teams non-interactively. What the switch actually buys
/// today is the prompt addendum, which does change behaviour: it gets the lead to
/// parallelise across files via subagents instead of working through them alone.
/// Read the outcome from the subagent count, not from a teammate count.
///
/// Kept separate from
/// [`crate::mission_runtime::microvm_provider_env`] so the credential path stays /// [`crate::mission_runtime::microvm_provider_env`] so the credential path stays
/// exactly as narrow as it is — that function is subscription-only by /// exactly as narrow as it is — that function is subscription-only by
/// construction, and widening it to carry feature flags is how an API key ends up /// construction, and widening it to carry feature flags is how an API key ends up
@@ -133,8 +146,8 @@ fn vm_prompt(task: &str) -> String {
fn team_prompt_addendum() -> String { fn team_prompt_addendum() -> String {
format!( format!(
"\nWORKING AS A TEAM\n\ "\nWORKING AS A TEAM\n\
You may spawn teammates — independent Claude Code sessions that share a \ Delegate the independent parts of this task and work them in parallel, \
task list with you and can message each other. Use at most \ rather than doing them one after another yourself. Use at most \
{MAX_TEAMMATES}, and fewer when fewer will do: teammates cost tokens \ {MAX_TEAMMATES}, and fewer when fewer will do: teammates cost tokens \
and coordination, and three focused ones beat five scattered ones.\n\ and coordination, and three focused ones beat five scattered ones.\n\
Split the work so each teammate owns DIFFERENT FILES. Two teammates \ Split the work so each teammate owns DIFFERENT FILES. Two teammates \
@@ -448,12 +461,17 @@ async fn run_inside(
} }
}, },
}; };
if wants_claude_code_team(engine) && teammates.unwrap_or(0) == 0 { // A team mission that fanned out to NOBODY is just a solo run that spent the
// same tokens, and nothing else in the output would say so. Judged on the
// subagent count, because that is the mechanism print mode actually uses —
// teammates never form here (see `team_env`), so a zero teammate count is
// expected and not itself a problem.
if wants_claude_code_team(engine) && subagents.unwrap_or(0) == 0 {
eprintln!( eprintln!(
"microvm_executor: {} asked for an agent team and no teammates were found — \ "microvm_executor: {} asked for a team and delegated to nobody — it ran \
it ran SOLO. Agent teams are experimental; check that \ SOLO. Print mode satisfies a team request with subagents rather than \
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS reached the guest and that the \ teammates, so check that the Agent tool is in the allowlist and that \
team-config path is what this CLI version writes.", the task is actually divisible.",
vm.vm_id() vm.vm_id()
); );
} }
@@ -686,6 +704,10 @@ mod tests {
// into stages across teammates loses context at every handoff. // into stages across teammates loses context at every handoff.
assert!(addendum.contains("DIFFERENT FILES"), "{addendum}"); assert!(addendum.contains("DIFFERENT FILES"), "{addendum}");
assert!(addendum.to_lowercase().contains("wait for your teammates"), "{addendum}"); assert!(addendum.to_lowercase().contains("wait for your teammates"), "{addendum}");
// Print mode delivers this as subagents, so the addendum must not depend on
// teammates existing — it asks for parallel delegation, whatever the
// mechanism turns out to be.
assert!(addendum.contains("Delegate"), "{addendum}");
} }
/// The teammate probe reads the team config, not the subagent transcripts — /// The teammate probe reads the team config, not the subagent transcripts —