Skip to content

Teams autopilot

The team autopilot is how a Hive team's leader orchestrates its workers - spawning them, handing out tasks, collecting results, and completing the team - from inside its own agent loop, with no human driving terminals. Every managed team member is a headless Claude, Codex, OpenCode, or OpenRouter runtime managed by the daemon. CLI providers coordinate through the hive-team MCP server wired at spawn. Native OpenRouter receives the equivalent hive_team function from the daemon. A skill file in the working directory guides both paths.

For Claude, Codex, and OpenCode, hived does not own the LLM loop: it exposes a deterministic coordination tool surface and the provider CLI stays in charge. For OpenRouter, hived owns the HTTP/SSE conversation and bounded workspace tool loop directly, including the team coordination function.

Mechanism

  1. Headless agents. The daemon starts Claude, Codex, OpenCode, and OpenRouter leaders and workers in SDK mode. OpenRouter uses Hive's native HTTP/SSE tool loop and never launches an OpenCode or OpenRouter process. Providers without a headless backend are rejected for workers; such a leader falls back to an interactive PTY.

  2. Coordination wiring. Claude, Codex, and OpenCode receive provider-native configuration pointing at hive team mcp, a stdio JSON-RPC server that exposes the coordination tools as mcp__hive-team__<name>. Native OpenRouter receives a hive_team function in the daemon-owned tool loop, which invokes the same Hive CLI commands. Both paths use fresh authenticated daemon connections, so they are immune to daemon restarts between calls. Identity comes from the session env (HIVE_TEAM_ID, HIVE_SESSION_ID, HIVE_TEAM_ROLE, HIVE_DAEMON_SOCKET, HIVE_TOKEN, HIVE_TLS). Before creating a new managed leader or worker, hived verifies that the colocated hive CLI matches its version and supports team mcp. A missing or stale CLI produces a clear creation error instead of an agent with a broken tool surface.

  3. Skill materialisation. At leader creation (and again on each worker spawn) the daemon writes - idempotently, using a hash-sidecar pattern so user edits are preserved:

    • .claude/skills/hive-team/SKILL.md and .claude/skills/hive-team/coordinator.md (the agent-facing behaviour and tool reference)
    • .claude/settings.json wiring the Stop hook (hive internal stop)
    • .hive/team.json - read-only daemon-owned snapshot of the member's own identity and service allocation (branch, database name, port range) for shell-fork resilience. It is deliberately not a roster: the roster is live state and comes from team_status / GetTeamRoster.
    • .hive/<file>.sha256 sidecars for each managed file
  4. Push message delivery. Teammate messages (team_send / team_broadcast, and the prompt carried by assign / handoff) are pushed into the recipient agent's conversation as a framed user turn:

    [Hive team "<team-name>" - message from <sender>]
    <payload>

    A busy recipient sees it queued after its current turn; an idle or not-yet-started worker is booted by the first message. Agents never poll. Each message is also written to the recipient's output ring as a __STATUS__: frame purely so attached UIs can render the message log.

  5. Outbox persistence. Before delivery every relayed message is written to a team_outbox(team_id, session_id, seq, payload, delivered_at) row in the daemon's SQLite (at-least-once, monotonic seq), so an agent that was offline can replay missed messages with team_inbox / hive team inbox --since <seq>. This is a crash-recovery path only - normal delivery is the live push above.

  6. Sync primitives. Three orchestration verbs:

    • team_handoff - spawn worker + block until it reports
    • team_assign - spawn worker, return immediately
    • team_send / team_broadcast - DM or fan-out via the relay path plus team_wait to collect a parallel fan-out.
  7. Fallback completion via Stop hook. Claude's Stop hook fires when a worker's agent loop ends. It calls hive internal stop, which reads the env-derived identity plus the transcript path from the hook payload and issues ReportWorkerResult{success: null, reported_via: "stop_hook_fallback"} carrying the worker's last message. This exists so a worker that dies without calling team_report_result still unblocks the leader's team_wait - not to certify the work. The daemon refuses to store a success that arrived this way (a --failure from the hook is kept, since a dead process is a real signal), so a leader can never merge on a green flag nobody asserted. The shared hook exits without reporting when the session role is leader, so a completed leader turn cannot be recorded as a worker result.

  8. Per-worker service allocation. Every autopilot worker is spawned with HIVE_WORKER_INDEX (its slot, 0..4, recycled when a worker reports), HIVE_WORKER_DB_NAME, HIVE_WORKER_PORT_BASE / HIVE_WORKER_PORT_COUNT, and HIVE_WORKER_BRANCH. The allocation is deterministic per (team, slot) so a restarted worker finds the services it created. Git isolation alone still leaves five workers fighting over one dev database and one set of ports.

  9. Assignment queue. team_assign past the 5-worker cap records a queued assignment instead of failing; when a worker reports, the daemon starts the next queued entry, relays its prompt, and the new worker tells the leader which assignment it picked up. The queue is persisted with the team.

Context accounting and leader refresh

A worker's context is disposable: it starts empty at team_assign and dies with the task. A leader's is not. It carries every roster dump, worker report and merge preview for the whole life of the team, and re-sends all of it on every turn - so an all-day leader gets steadily more expensive and steadily less able to hold the round it is actually running.

Measurement. The daemon parses the token usage each provider runtime emits into per-session counters. Claude's assistant lines move the live context figure and its result line closes the turn (it also states the window the turn ran in), while Codex's token_count events carry running totals that are adopted rather than added. Native OpenRouter usage comes from streamed API response metadata. GetTeamRoster returns the result as the member's usage: context_tokens, context_window, context_window_source (reported by the provider or assumed from the model id until the first turn completes), context_pct, the input and output totals, and turns. No usage block at all means the member has not taken a turn yet. hive team roster shows the percentage as a CTX column; team_status repeats the caller's own row under you.usage with context_advice derived from it.

Refresh. RefreshTeamLeader (team_refresh_context, hive team refresh-context) schedules the restart ~5 seconds out - the caller is mid-turn by definition - then stops the leader's runtime, drops the provider conversation id that would otherwise be passed as --resume, zeroes the counters, and sends the carryover note as the first prompt of the new conversation. Team standing orders are prepended to that prompt like any other, so they survive; the session, worktree, branch, environment, workers, queued assignments and merge state are untouched. Nothing else about the old conversation does survive, which is why the skill has the leader checkpoint to disk and publish team_summary before calling it.

The skill's thresholds: nothing under 60%, stop growing the context between 60 and 75%, checkpoint and refresh past 75%.

Stopping one runtime and starting its replacement in the same breath can leave two generations active for a moment, so each SDK spawn carries a generation number. A completion event from a superseded generation is ignored, so an old runtime ending cannot clear the new session handle.

Intake before fan-out

A leader whose team has no leader_instructions yet is un-briefed, and the daemon says so in the prompt overlay: interview the operator before spawning anything. One numbered message covers goal and definition of done, autonomy (plan first / ask before merging / full auto), cost posture (max parallel workers, default tier, whether deep is allowed), provider mix, setup (target branch, build and test commands, off-limits paths, shared services), and reporting cadence, each with a default so "defaults" is a complete answer.

The leader records the result with team_instructions. That both retires the intake overlay - the daemon only injects it while the field is empty - and makes the answers standing orders every later worker inherits. Setting Leader instructions at creation time, or answering in the opening prompt, skips the interview.

Hive workers versus provider subagents

The Hive roster is the source of truth. A worker exists only after team_assign, team_handoff, or SpawnWorker returns a worker session UUID and team_status includes it. Claude Task subagents and .claude/agents definitions are provider-local helpers, not Hive team members. They have no Hive session UUID, do not appear in the app, and cannot satisfy a request to create Hive workers.

Every operator prompt sent to a Hive leader reinforces this distinction. Terms such as "worker", "teammate", "delegate", and "parallel agent" mean managed Hive workers unless the operator explicitly asks for provider-native agent definitions. If the provider's coordination path is unavailable, the leader must report the failure plainly.

Team tool surface

CLI providers receive these operations through hive team mcp as mcp__hive-team__<name>. Native OpenRouter receives one hive_team function that invokes the matching CLI command.

ToolArgsNotes
team_statusnoneLive roster: per member name, role, agent/model, state (working/idle/exited/unknown), branch, worktree, last activity, token usage, result + provenance; plus queued assignments and unverified_results. The caller's own row is repeated under you.usage with context_advice. Call first.
team_sendto (session UUID), messageDM one teammate. Returns delivered/undelivered ids; persisted to the outbox.
team_broadcastmessageSame, to every other member - the returned lists say who actually got it.
team_assignprompt, name?, agent?, model?, tier?, role?, permission_mode?Leader: spawn worker + hand it the prompt, return immediately. Past the cap the reply is queued: true with a queue_position. agent is claude, codex, opencode, or openrouter; tier (fast/balanced/deep) resolves to a model on the running node.
team_handoffprompt, name?, agent?, model?, tier?, role?, permission_mode?, timeout_seconds?Leader: spawn worker + block for its report. Default 60s, max 3600s. timed_out: true means still running - use team_wait.
team_waitworker_session_ids?, timeout_seconds?Leader: block on pending workers. Returns completed/pending ids + worker_results with provenance. Re-call after timeout; completions are never lost.
team_report_resultsuccess, result?Worker: record your result (reported_via: explicit), wake the leader's wait. Keep under 4 KB.
team_instructionsworker_instructions?, leader_instructions?Leader: standing orders injected into every member's next message, including workers spawned later. The team's shared preflight.
team_mergeworker_session_ids?, target_branch?, dry_run?Leader: per-worker commits ahead, changed files, and conflicting paths. Defaults to a dry run; dry_run: false merges the clean branches and aborts any that conflict.
team_summaryresult?Leader: publish a progress summary. Teams are persistent - this does not end the team.
team_modelsprovider?, refresh?Models this node can give a worker, with tier and the recommended pick per tier.
team_refresh_contextcarryoverLeader: discard its own conversation and restart it from empty, seeded by carryover. Scheduled ~5s out so the calling turn can finish.
team_inboxsince_seq?Replay persisted messages. Only needed for crash catch-up.

Tool errors come back as isError: true with a single-line message, e.g. team <id> not found, active worker cap of 5 reached, provider '<x>' has no headless backend, HIVE_TEAM_ID is not set.

CLI mirror

The hive team CLI exposes the same operations for shell scripts and humans, resolving identity from the same HIVE_* env vars:

CommandPurpose
hive team handoff <team> <worker-name> --prompt <p>Spawn worker, block until it reports, print result
hive team assign <team> <worker-name> --prompt <p>Spawn worker, return immediately
hive team send <team> [--to <id>|--broadcast] --message <s>DM or fan-out
hive team inbox <team> [--since <seq>]At-least-once inbox replay; NDJSON
hive team workers <team>Roster + status per worker
hive team roster <team> [--json]Live member table: state, context %, branch, result provenance, queued assignments
hive team refresh-context <team> [--carryover <s>]Restart the leader's conversation from empty with <s> as its first prompt
hive team merge <team> [--worker <id>]... [--target-branch <b>] [--apply]Conflict preview per worker branch; --apply performs the clean merges
hive team report <team> --success|--failure [--result <s>]Worker-side result reporting (≤4 KB)
hive team complete <team> [--result <s>]Leader marks team done
hive team wait <team> [--for <ids>] [--timeout 60] [--wait-id <id>]Bounded block with resume token
hive team statusPrint env-derived identity and team summary
hive team mcpRun the stdio MCP server (wired by the daemon through provider-native configuration; not for manual use)

Worktree isolation

Workers spawned via team_handoff / team_assign get a fresh git worktree rooted at <leader-cwd>/.hive/worktrees/<worker-session-id> on a branch named hive/team-<team-prefix>/worker-<worker-prefix>. If the leader's cwd is not a git repo the daemon falls back to a plain sandbox directory under .hive/sandboxes/<worker-id>. On team delete the daemon makes a best-effort git worktree remove (or rm -rf for sandbox dirs). Both worktrees and sandbox dirs are chowned to the session's run-as user when the daemon runs as root, so the two paths do not end up with different owners.

The daemon owns the branch name and reports it on the roster and in HIVE_WORKER_BRANCH; workers commit on it rather than creating their own, which is what lets team_merge find the work.

The cap is 5 active workers per team. A worker stops consuming capacity when it records a result, while its row remains available as team history. Assignments past the cap queue and start automatically - no manual batching.

Permission posture

The team's permission_mode decides how much autonomy every headless agent it spawns gets. Hive translates the posture into each provider's flags because a headless agent has no human to answer prompts:

  • bypass (default) - full autonomy. OpenCode uses --auto; native OpenRouter includes command execution.
  • accept_edits - allow edits. Native OpenRouter exposes read and write tools without arbitrary commands.
  • plan - planning posture. OpenCode uses --agent plan --auto; native OpenRouter exposes read-only tools.

Set it with hive team create --permission-mode <mode>. See docs/teams.md.

team_assign / team_handoff also take a per-worker permission_mode that can only narrow the team's (plan < accept_edits < bypass) - a worker can never be granted more than its team has. The posture a worker actually spawned with is recorded on the roster. Give reviewers plan and implementers accept_edits rather than running five unattended bypass agents in a repo with deploy workflows.

Result provenance

Every recorded result carries reported_via:

ValueMeaningsuccess
explicitThe worker called team_report_result (or an operator reported for it).Its own claim
stop_hook_fallbackThe worker's process ended without reporting; the Stop hook salvaged its last assistant message.null, or false when the process failed - never true

team_status lists affected workers under unverified_results, the app shows them as Unverified (amber) rather than Done, and hive team roster prints unverified. Check such a worker with team_merge --dry-run (does its branch actually have commits?) or re-assign the task before counting it as finished.

Cross-references

Hive - remote AI coding agents over WebSocket.