blob: ee4421753f89f681563d86d0e4cb7f4f070845f7 [file] [view]
# Expert Teams (lead/member collaboration)
Expert teams let a **lead** persona fan a task out to specialist **member** experts,
each running as a tool-scoped child agent, then synthesize their results. The runtime
keeps the lead as the final adjudicator while adding a bounded, durable mailbox and
atomic self-claim over Maka's existing Task Ledger. It is rebuilt on Maka's existing
child-agent machinery rather than introducing a second orchestration engine (see
[expert-team-implementation.md](archive/expert-team-implementation.md)).
## Model
- **Capability archetype** one of the built-in agent profiles (`local_read`,
`web_research`, `implementation`) in [`agent-catalog.ts`](../packages/runtime/src/agent-catalog.ts).
It fixes the tool set, permission mode, category policy, and workspace contract.
- **Expert** (`ExpertDefinition`) a persona that runs *under* an archetype. It may
**narrow** (never widen) the archetype's tools to a subset. So an expert can never
exceed the policy of the archetype it runs under — this is stricter than either
competitor and keeps Maka's permission-safety invariant.
- **Expert team** (`ExpertTeamDefinition`) a lead persona (runs as the main session)
plus N dispatchable members. Members can exchange bounded direct messages and
broadcasts within one team run. They may claim one eligible shared Task Ledger item
atomically, but the lead retains completion and result-adjudication authority.
Definitions live in [`expert-catalog.ts`](../packages/runtime/src/expert-catalog.ts).
Each member materializes into an ordinary `AgentDefinition` with a deterministic id
`expert:<teamId>:<memberId>`, so the existing child-turn machinery (tool scoping,
permission gating, worktree fail-closed) runs it unchanged, and a spawn resolves
statelessly from the id alone.
## Runtime flow
1. A session labeled `mode:expert-team:<teamId>` (constant `EXPERT_TEAM_LABEL_PREFIX`
in [`@maka/core`](../packages/core/src/expert-team.ts)) activates:
- the **lead system-prompt fragment** (`buildExpertTeamLeadSystemPromptFragment`)
the orchestrator persona, the member roster, the dispatch protocol, and the
fan-in discipline; slotted into the desktop system prompt next to Deep Research.
- the **`expert_dispatch` tool** (`buildExpertDispatchTool`) a team-bound tool
whose `member` param is a closed enum of the team's members.
2. The lead calls `expert_dispatch({ member, task })`. To run members concurrently it
emits several calls in one turn — the runtime executes concurrent child spawns
(distinct child turns, no shared mutex; bounded by `MAX_ACTIVE_SUBAGENT_TOOLS_PER_TURN`).
3. Each dispatch resolves the member's materialized `AgentDefinition` and spawns it via
the same `spawnChildAgent` capability `agent_spawn` uses. The child gets the member's
scoped capability tools plus `team_message`, `team_inbox`, `team_task_list`, and
`team_task_claim`; a read-only archetype still cannot write files or use the network.
4. Messages are appended to `sessions/<sessionId>/agent-mailbox.jsonl`, scoped by team
and parent lead `AgentRun`. Sender `AgentRun`/turn attribution comes from trusted
runtime context; named recipients resolve through the trusted team roster to stable
role addresses. Cursor reads make polling bounded and deterministic.
5. A member may use `team_task_claim` to atomically claim one pending/blocked task
owned by the current parent lead `AgentRun`. Tasks from ordinary or older lead runs
are not discoverable or claimable. Conflicting claims fail closed; members receive
no general task mutation or completion tool.
6. **Fan-in** is the child result's bounded `summary` plus `artifactIds` pointers
members return digests, not raw payloads. The lead synthesizes one ranked result.
A completed child outcome leaves its claimed task `in_progress` as evidence for lead
review; failed, cancelled, and permission-waiting outcomes settle through the existing
Task Ledger outcome contract.
Members never receive `expert_dispatch` (child turns are gated in the backend factory),
so there are no nested teams.
### Role mailbox and cursor semantics
Direct-message recipients are role addresses within one parent lead `AgentRun`, not
individual child invocations. The lead uses the stable `lead` address; each member uses
its deterministic `expert:<teamId>:<memberId>` agent id. Repeated or concurrent
dispatches of the same member therefore share one role mailbox. This supports durable
handoff between invocations, but it is not an invocation-private channel.
`team_inbox` cursors are caller-owned. The store does not persist a read cursor for a
role or child invocation: callers pass the last observed `nextSeq` back as `after_seq`.
A fresh invocation that omits `after_seq` reads the role's available history from the
start of the current lead run, including direct messages observed by an earlier
invocation of that member. A new parent lead `AgentRun` starts a separate mailbox scope.
Use distinct expert member roles when work requires separate direct-message inboxes.
## Definition resolution
`spawnChildAgent` / `startChildTurn` resolve a spec id through
`requireResolvedAgentDefinition` (`getBuiltinAgentDefinition(id) ?? getExpertAgentDefinition(id)`),
so a child id can be a built-in agent or an expert member. Built-in ids keep their
original error messages; unknown expert ids get an expert-specific error.
## Built-in team
**Code Review Team** (`code-review`) — a read-only review crew (all `local_read`,
tools `Read`/`Glob`/`Grep`), so it runs within current capabilities (no worktree
executor needed):
- lead: scopes the change, dispatches reviewers, merges into one ranked review.
- `correctness-reviewer` — logic errors, edge cases, races, broken invariants.
- `simplification-reviewer` — duplication, dead code, reuse opportunities.
- `test-coverage-reviewer` — untested paths and missing cases.
## Starting a team session
The feature is reachable through a main-process IPC / preload bridge:
```ts
// list the built-in teams (id, name, description, members)
const { teams } = await window.maka.expertTeam.list();
// start a team session; creates a session labeled mode:expert-team:<teamId>
// in read-only (explore) mode and optionally sends the first message
const result = await window.maka.expertTeam.start({ teamId: 'code-review', prompt: 'Review the current diff.' });
// → { ok: true, sessionId } | { ok: false, reason: 'unknown_team' | 'setup_required' | 'send_failed', ... }
```
Any session carrying the label is a fully functional team lead — the label is the only
special state.
## Scope / follow-ups
Shipped: the runtime engine (catalog, resolver, dispatch tool, lead fragment), desktop
prompt + tool wiring, the start/list IPC + preload + typings, and unit tests across
core / runtime / desktop-main.
Shipped in the collaboration slice: a durable per-session/team-run mailbox, bounded
role-addressed direct messages and broadcasts, trusted sender AgentRun/Turn
attribution, caller-owned inbox cursors, and atomic
self-claim of one eligible shared Task Ledger item per child turn. Mailbox history is
reloaded from its append-only log after process restart; corruption fails closed, and
new lead runs do not silently inherit messages from an older run.
Deliberately deferred (documented, not built): automatic message wake/injection (members
poll `team_inbox`); detached or cross-machine member lifecycles; a renderer team-picker
panel; worktree-isolated writing members (fail-closed today); a remote expert marketplace;
and the digital-colleague / IM / cloud layer.