7
6 Team
Dai Ha edited this page 2026-08-31 10:36:10 +07:00

6. Team

fleetd delivers one turn to one session. This page describes the layer above that delivery: how a lead splits work, starts members, and collects their work. The delivery rules belong to Message Server.

The team shape

A primary lead orchestrates the work. It splits a job into units, starts members for the units, sends each member its own brief, and decides the final result. Only a primary can start, stop, or drain members. An architect may send work, but a worker may not (Authz.java:49-70).

Leads may coordinate with peer leads. They do not assign tasks to each other. A lead addresses a member by its sessionId, not by a role (FleetMcp.java:1096-1108). fleet_list reports peer leads and members, including each member's sessionId, role, and profile (FleetMcp.java:1185-1202).

The diagram shows the normal direction of work. A member reply goes back through fleetd after the member finishes its turn.

flowchart TB
    Lead["Lead"]
    Fleet["fleetd"]
    Peer["Peer lead"]
    MemberA["Member A"]
    MemberB["Member B"]

    Lead -->|"spawn and send"| Fleet
    Fleet -->|"deliver work"| MemberA
    Fleet -->|"deliver work"| MemberB
    MemberA -->|"reply"| Fleet
    MemberB -->|"reply"| Fleet
    Fleet -->|"return or hold reply"| Lead
    Lead <-->|"coordinate"| Peer

The lead delegates downward to members. Peer leads only coordinate.

Addressing and roles

fleet_send has one required field: content. Its optional addressing fields are sessionId, turnId, and coordId (FleetMcp.java:1096-1108).

Use Field Meaning
Send work to a member sessionId The member's herdr terminal ID.
Answer a member's fleet_ask turnId Routes the answer into that member's same turn.
Coordinate with a peer lead on another daemon coordId Routes a coordination message to that lead's mailbox.

fleet_send also accepts wait and timeoutMs. wait defaults to true. With wait: false, the call returns a ticket. The lead checks that ticket with fleet_poll (FleetMcp.java:1088-1108, FleetMcp.java:1125-1134).

Do not use role or prompt with fleet_send. They are not fields in its schema (FleetMcp.java:1096-1108).

There are two member authorization roles: worker and architect. A worker can reply, ask, and read. An architect can also send work. Only a primary can spawn, stop, or drain members (Authz.java:44-71).

The fleet_spawn field named role is separate from that authorization table. It selects a member contract: dev, reviewer, or architect. profile selects the backend. The two fields are independent, so a reviewer can use the same profile as the developer it reviews (FleetMcp.java:1148-1173).

Claude Code and OpenCode members

A profile chooses one launcher kind. The supported kinds are claude-code, which is the default, and opencode (FleetConfig.java:265-270). Both use the shared member transport for pane placement, readiness, teardown, listing, and working directory handling (OpenCodeLauncher.java:30-35).

The launchers differ in important ways:

Kind Launch and configuration MCP servers
claude-code Uses command-line flags. The model and other profile arguments come from its launch arguments. When configured, its inline --mcp-config includes the fleet server. It also includes intellij when the IDE MCP URL is configured.
opencode Writes an ephemeral opencode.json, points OPENCODE_CONFIG to it, and passes the provider/model selector with -m. It reads provider credentials from its global auth.json. When configured, its file config includes the remote fleet server. It also includes the remote intellij server when the IDE MCP URL is configured.

Claude Code mounts MCP through --mcp-config (ClaudeCodeLauncher.java:283-356). OpenCode mounts MCP through its generated file and OPENCODE_CONFIG (OpenCodeLauncher.java:39-49, OpenCodeLauncher.java:340-367). The fleet and intellij servers are conditional on profile configuration in both launchers.

The subscription guard applies to Claude Code profiles. OpenCode has no ANTHROPIC_BASE_URL injection and no SubscriptionGuard (OpenCodeLauncher.java:37-47). The guard's allowed off-subscription hosts come from configuration. The default list is empty, so this page names no backend host (FleetConfig.java:1149-1163).

Start members, then send work

For independent units, the lead starts all needed members before it sends briefs. It then sends all briefs without waiting for each one. This avoids turning independent work into serial work.

1. fleet_spawn for each unit
2. fleet_send with each returned sessionId and wait: false
3. fleet_poll each returned ticket
4. fleet_ack each reply after the lead processes it
5. review the change, then merge if the lead accepts it

Example calls use sessionId and content, not a role address:

fleet_spawn {
  role: "dev",
  profile: "chosen-profile",
  worktree: true,
  ticket: "task-123"
}

fleet_send {
  sessionId: "returned-session-id",
  content: "Implement the assigned unit.",
  wait: false
}

fleet_poll { ticket: "returned-ticket" }
fleet_ack { target: "returned-session-id", msgId: "returned-message-id" }

fleet_spawn returns a sessionId for fleet_send and a paneId for fleet_stop (FleetMcp.java:1156-1173). fleet_ack needs the member session ID and the reply message ID (FleetMcp.java:1137-1145).

Profile placement and capacity

The lead may name a profile in fleet_spawn. That bypasses automatic placement, but maxLoad still applies. If that named profile is at its cap, the spawn fails; it does not silently move to another profile (CompositePeerLauncher.java:260-273, CompositePeerLauncher.java:326-397).

When a spawn omits profile, fleetd builds candidates from the requested contract's profile pool. An absent or empty pool uses all configured profiles (CompositePeerLauncher.java:276-286, CompositePeerLauncher.java:401-431).

Each profile can set:

  • maxLoad, the maximum number of live members. An absent value means unlimited. A value of 0 allows no live members (FleetConfig.java:251-264).
  • weight, the relative value for automatic placement. A value of zero or less excludes the profile from automatic placement, but an explicit profile can still select it if it has capacity (FleetConfig.java:242-260).

Available candidates exclude weight-disabled, unreachable, quarantined, and at-cap profiles (PlacementPolicyUtil.java:14-36). The configured placement policy then chooses from those candidates. The default is fixed; round-robin and weighted are also supported (PlacementPolicies.java:12-40).

The weighted policy uses smooth weighted round-robin. It adds weights, picks the highest score, then subtracts the total weight from that winner. Over time, the selection follows weight ratios. It is not a cheapest-first policy (WeightedRoundRobinPolicy.java:7-48).

Limits of this page

This page does not describe the exact worker turn lifecycle or the transport between fleetd and herdr. See Message Server for delivery details.