A deep read of the maw team plugin source. What it actually is, how it composes with the lighter in-process agent pattern, and when each is the right tool.
Two patterns now exist in the same toolchain for "spin up agents to do work in parallel":
- In-process
Agent— short-lived subprocesses spawned by the running Claude Code instance. They share a TaskList, can DM each other, and disappear when the parent session ends. maw team— fleet-level agents that survive across sessions, accumulate memory per role, and can be invited across machines.
The two look superficially similar (both end up as Claude subprocesses in tmux panes). They are not interchangeable. The difference is where the state lives and what survives shutdown.
maw team writes to three different filesystems:
~/.claude/teams/<team>/config.json ← tool-store stub
└ used by: list/status/shutdown to see the team
ψ/memory/mailbox/teams/<team>/manifest.json ← vault truth (PERSISTENT)
└ source of resume/reincarnation
└ contains: members[], invitees[] (federation)
~/.config/maw/teams/<team>/
├ tasks/<id>.json + _counter.json ← per-team task store
└ oracle-members.json ← oracle registry (separate concept)
The vault manifest at ψ/memory/mailbox/teams/<team>/manifest.json is the durable record. The tool-store config is a bridge — without it, maw team list and cleanup can't see the team. The plugin syncs both on every operation.
ψ/memory/mailbox/<role>/ is where individual agents accumulate memory:
ψ/memory/mailbox/<role>/
├ standing-orders.md ← long-term role definition (durable)
├ <ts>_findings.md ← session findings, accumulated
└ team-<team>-inbox.json ← merged inbox from a past team
This directory is role-keyed, not team-keyed. So a role like wake-keeper has one mailbox that follows them across whatever team they join.
This is the design choice that took the longest to pick up. There are two registries with the word "members":
These are the ephemeral spawned agents — what maw team spawn produces:
interface TeamMember {
name: string; // role name, e.g. "wake-keeper"
agentId?: string; // claude-code agent uuid
agentType?: string; // "team-lead" or other
tmuxPaneId?: string;
color?: string;
model?: string;
backendType?: string;
}Tied to a live tmux pane. Disappears when the pane dies (unless --merge ran during shutdown).
These are persistent oracle identities — separate, longer-lived:
interface OracleMember {
oracle: string; // oracle name (e.g. "mawjs-oracle")
role: string; // role within team (e.g. "wake-keeper")
addedAt: string;
}Stored at ~/.config/maw/teams/<team>/oracle-members.json. Used for maw hey team:<team> fan-out routing — the message goes to every oracle-member of that team (excluding the sender by default, via excludeSelf: true).
The two registries don't overlap. A spawn-member is "an agent currently running for this team's task." An oracle-member is "an oracle that should receive team broadcasts." You can have one without the other:
- A team with only oracle-members and no spawned agents = a routing group for fan-out without active workers.
- A team with only spawned agents and no oracle-members = a task team that won't receive broadcasts.
- The full pattern uses both: oracle-members define who receives
team:<>messages, spawn-members define who is currently working on tasks.
When you maw team spawn <team> <role>:
1. Read past life from ψ/memory/mailbox/<role>/
├ standing-orders.md (full content)
└ latest *_findings.md (last 30 lines)
2. Build spawn prompt:
You are '<role>' on team '<team>'.
<user-supplied prompt>
## Standing Orders (from past life)
<standing-orders.md content>
## Last Known Findings
<tail of latest findings>
3. Write to ψ/memory/mailbox/teams/<team>/<role>-spawn-prompt.md
4. Either:
--exec → tmux split-window with `claude --prompt-file <path>`
(default) → print the command for the human to run manually
The new spawn opens with full memory of who they are (standing-orders.md) and what their predecessor was working on (latest_findings.md). They're not a fresh AI; they're the continuation of a role.
This compounds across sessions. On day 1, wake-keeper fixes a wake bug and writes findings. On day 5, you spawn wake-keeper again — they remember every past wake bug they've seen. On day 30, they have months of accumulated wake-domain expertise.
This is not possible with Agent. Agent spawns are anonymous and stateless. There is no "the same agent as last time."
The shutdown path is the dual of reincarnation. Without --merge, you lose the role's memory. With it:
maw team shutdown <team> --merge
1. For each alive member m:
writeShutdownRequest(team, m, "team teardown via maw team shutdown")
→ appends {type: "shutdown_request", request_id, reason} to inbox
wait up to 30s for pane to die
2. --force survivors get tmux kill-pane'd
3. mergeTeamKnowledge(team, teammates):
For each m (including dead ones):
copy inboxes/<m>.json → ψ/memory/mailbox/<m>/team-<team>-inbox.json
copy <m>_findings.md → ψ/memory/mailbox/<m>/
4. Archive manifest:
copy ~/.claude/teams/<team>/config.json
→ ψ/memory/mailbox/teams/<team>/manifest.json (preserve)
5. cleanupTeamDir(team):
rm -rf ~/.claude/teams/<team>/
rm -rf ~/.claude/tasks/<team>/
--merge is the bridge from "ephemeral spawned member" to "persistent role memory." Without it, the spawn-member's findings die with the pane.
A bug worth knowing about: --merge must run even when alive=0. An earlier version gated the merge on at least one member being alive, which silently lost data when all members had already exited (#393 Bug G). The current code merges before cleanup regardless of alive count.
maw team resume <name> reads the archived manifest from the vault and re-spawns each member. Because each spawn calls cmdTeamSpawn, each member gets their reincarnation prompt automatically. The team comes back with full memory.
maw team lives <agent> shows the role's incarnation history — standing orders, count of findings files, sizes. A diagnostic for "what does this role remember?"
The most underrated piece. maw team invite <team> <peer> adds a remote oracle as a team invitee. Default is permissive; the interesting path is MAW_CONSENT=1:
1. Resolve peer in namedPeers → peerUrl
2. Check trust: isTrusted(myNode, peerNode, "team-invite")
├ trusted → record invitee, done
└ not trusted → fall through
3. requestConsent({
from: myNode,
to: peerIdForTrust,
action: "team-invite",
summary: "team='X' lead='Y' invitee='Z' (node) url='...' scope='member'",
peerUrl: peer.url,
})
4. Print PIN + "on <peer>: maw consent approve <request-id> <pin>"
exit 2
5. Operator on the peer host approves out-of-band
6. Re-run `maw team invite` — now isTrusted() returns true, records invitee
Trust scope is (myNode, peerNode, "team-invite") — distinct from hey or plugin-install trust. Granting a peer permission to send maw hey does not grant them permission to invite you to a team. Each scope is approved separately.
Recorded invitees show up in manifest.invitees[]. They're not the same as spawn-members — they're peers who can join the team (e.g. their oracle gets fan-out messages, contributes to merge knowledge). Phase 1 of #644 was the underlying maw hey consent; Phase 2 is this; Phase 3 is plugin-install consent. Same consent primitive, three scopes.
This is where oracle-members earn their keep:
maw hey team:wake-keepers "new wake bug filed — #771"
→ getOracleMembers("wake-keepers", currentOracle)
→ filterMembers honors excludeSelf flag (default true)
→ resolves to list of oracle names
→ delivers to each via the underlying maw hey transport
The default excludeSelf: true prevents the sender from receiving their own broadcast (#742 follow-up). Override with excludeSelf: false in the registry if you want self-inclusive fan-out.
This is the routing benefit that Agent cannot provide: addressable groups of persistent oracles, not just transient subprocesses in your own session.
maw team send <team> <agent> <msg> has two paths:
1. loadTeam(team) — if config.json exists in ~/.claude/teams/
writeMessage to ~/.claude/teams/<team>/inboxes/<agent>.json
(the live agent picks up via inbox poll)
2. fallback: ψ/memory/mailbox/<agent>/msg-<timestamp>.json
(async delivery — agent reads on next spawn)
So send works whether or not the team is currently spawned. If the agent is alive, they get the message synchronously. If they're not, the message waits in their vault for next reincarnation.
~/.config/maw/teams/<team>/tasks/
├ _counter.json → {next: <id>}
├ 1.json, 2.json, ... → {id, subject, description?, status, assignee?, ...}
└ ...
Verbs: add, tasks, done, assign, delete, deleteAll. Plain JSON files, monotonic id counter. Status: pending | in_progress | completed. Assigning a task to an agent automatically sets it to in_progress.
This is parallel to the Claude Code TaskList tool. It exists so that maw fleet members (which may not be Claude Code instances at all) can read/write tasks without going through the Claude Code TaskList tools.
| Scenario | Tool |
|---|---|
| Quick parallel work, single session, short timeline | Agent (in-process) |
| Long-lived role with memory across sessions | maw team + reincarnation |
| Cross-machine coordination with consent gating | maw team invite |
Addressable broadcast group (team:<name> fan-out) |
maw team oracle-invite + maw hey team:<> |
| Tight integration with Claude Code TaskList/SendMessage | Agent |
Visibility outside the current Claude Code session (e.g. in maw ls) |
maw team |
Fast iteration with run_in_background: true |
Agent (no equivalent in maw team) |
| Knowledge that compounds over weeks | maw team with --merge discipline |
The honest reading: Agent is the right tool for parallel ship workflows that wrap up in one session. maw team is the right tool for roles that should outlive any one session.
A team made of oracle-members on multiple machines, each running a long-lived role with reincarnation memory, is a different kind of artifact than what Agent produces. It's closer to "a small distributed organization of AIs" than "a parallelized batch job."
For a project with both transient and persistent needs:
# Persistent backbone (set up once, lives across sessions):
maw team create project-keepers
maw team oracle-invite oracle-A --team project-keepers --role wake-keeper
maw team oracle-invite oracle-B --team project-keepers --role release-keeper
maw team invite project-keepers <peer-host> # cross-machine
# When a wave of work hits, layer transient parallelism on top:
/parallel-ship 769 770 771 # spawns Agent-based implementers + shipper
# in the calling oracle's session,
# for this batch only
The persistent team handles routing, identity, accumulated memory. The transient agents handle the burst of throughput on a specific batch. The two compose — they don't compete.
--execsplit: spawning a real new Claude Code subprocess from amaw team spawncall (requires$TMUX). The default prints the command and lets the human run it;--execautomates the launch.- Cross-node consent flow: actually doing
MAW_CONSENT=1 maw team inviteend-to-end with PIN relay. Not yet exercised. - Long-horizon reincarnation: I've only seen the skeleton in source. The compounding effect of a role that has 50+ findings files would be worth measuring — does it stay coherent, or does the prompt size start to drown the new context?
maw megaandmaw avengers: sibling plugins. Different orchestration shapes — TBD what they add overmaw team.
Agentparallelizes a session.maw teampersists roles across sessions and machines. Use the former for bursts; use the latter for backbones.
Both are useful. They are not the same tool.
Read of ~/.maw/plugins/team/ source, 2026-04-28.