Skip to content

Instantly share code, notes, and snippets.

@nazt
Created April 28, 2026 02:21
Show Gist options
  • Select an option

  • Save nazt/d5bb67b7fd9ae2ed91232c157e38aa1d to your computer and use it in GitHub Desktop.

Select an option

Save nazt/d5bb67b7fd9ae2ed91232c157e38aa1d to your computer and use it in GitHub Desktop.
Anatomy of `maw team` — reincarnation-based AI agent coordination (deep read of plugin source: storage architecture, two membership concepts, federation consent, when to use vs in-process Agent)

Anatomy of maw team — reincarnation-based AI agent coordination

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.


Why this matters

Two patterns now exist in the same toolchain for "spin up agents to do work in parallel":

  1. 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.
  2. 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.


Storage architecture (three layers)

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.


Two concepts of "membership"

This is the design choice that took the longest to pick up. There are two registries with the word "members":

A. Spawn-time members (TeamConfig.members, manifest.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).

B. Oracle members (OracleTeamRegistry.members)

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.

Reincarnation: the headline feature

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."


Shutdown + --merge (knowledge preservation)

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.


Resume + lives

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?"


Federation: maw team invite (consent-gated)

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.


maw hey team:<name> fan-out

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.


Send mechanism (live → vault fallback)

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.


Task ops (per-team task store)

~/.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.


When each pattern wins

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."


Composition pattern

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.


What I haven't tried

  • --exec split: spawning a real new Claude Code subprocess from a maw team spawn call (requires $TMUX). The default prints the command and lets the human run it; --exec automates the launch.
  • Cross-node consent flow: actually doing MAW_CONSENT=1 maw team invite end-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 mega and maw avengers: sibling plugins. Different orchestration shapes — TBD what they add over maw team.

The one-line summary

Agent parallelizes a session. maw team persists 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment