pi-mono-team-mode
Pi extension for flat peer-agent orchestration — named, addressable teammates with resumable context (mirrors Claude Code's team-mate model)
Package details
Install pi-mono-team-mode from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-mono-team-mode- Package
pi-mono-team-mode- Version
2.1.0- Published
- Apr 29, 2026
- Downloads
- 1,670/mo · 317/wk
- Author
- emanuelcasco
- License
- unknown
- Types
- extension
- Size
- 181.3 KB
- Dependencies
- 0 dependencies · 4 peers
Pi manifest JSON
{
"extensions": [
"./index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-mono-team-mode
A faithful port of Claude Code's team-mode mode to the pi coding agent. Named workers are spawned as pi subprocesses, the coordinator ends its turn after launching, and completion arrives as a <task-notification> user-role message that wakes the coordinator event-driven — no polling, no leader subprocess.
Sibling of
pi-mono-team-mode.team-modeis leader-driven (a coordinator subprocess runs on its own task graph).team-modemaps 1:1 to Claude Code's semantics instead.
Parity with Claude Code
Everything below mirrors claude-code/src/ behavior (coordinator/coordinatorMode.ts, tools/AgentTool, tools/SendMessageTool, tools/Task*Tool, utils/swarm/teammatePromptAddendum.ts).
| Claude Code | team-mode |
|---|---|
Agent({ description, prompt, name?, team_name?, subagent_type?, isolation?, run_in_background? }) |
agent(...) — same schema |
subagent({ tasks })-style fan-out |
delegate({ tasks: [...] }) |
subagent({ chain })-style sequencing |
delegate({ task, chain: [...] }) |
SendMessage({ to, message }) |
send_message(...) |
TaskStop({ task_id }) |
task_stop(...) |
TaskOutput({ task_id }) |
task_output(...) |
TaskCreate({ subject, description, activeForm?, metadata? }) |
task_create(...) |
TaskUpdate({ task_id, status?, owner?, addBlocks?, addBlockedBy?, ... }) |
task_update(...) |
TaskGet({ task_id }) |
task_get(...) |
TaskList({ status?, owner? }) |
task_list(...) |
TeamCreate / TeamDelete |
team_create / team_delete |
<task-notification> XML wakes coordinator |
Emitted via pi.sendMessage({ triggerTurn: true }) on teammate end |
Coordinator system prompt (CLAUDE_CODE_COORDINATOR_MODE=1) |
PI_TEAM_MATE_COORDINATOR=1 |
Teammate prompt addendum (TEAMMATE_SYSTEM_PROMPT_ADDENDUM) |
Prepended to every teammate's system prompt |
Task ids namespaced agent-* |
Same namespace; task_stop and send_message accept it |
The execution model
Workers are event-driven, not polled. You don't write "spawn, wait, spawn the next" loops:
Turn 1 (coordinator):
agent({ description: "research auth", prompt: "..." }) → task_id: agent-r-ab1
agent({ description: "research billing", prompt: "..." })→ task_id: agent-b-c3d
"Investigating both — I'll report back." [turn ends]
...workers run in parallel; zero coordinator tokens burned...
Between turns:
<task-notification>
<task-id>agent-r-ab1</task-id>
<status>completed</status>
<summary>Agent "research auth" completed</summary>
<result>Found null pointer in src/auth/validate.ts:42...</result>
<usage><duration_ms>14230</duration_ms></usage>
</task-notification>
Turn 2 (coordinator, woken by the notification):
"Found the bug. I'll fix it."
send_message({ to: "agent-r-ab1", message: "Fix src/auth/validate.ts:42..." })
[turn ends]
For a DAG of dependent tasks, the coordinator:
- Seeds the TODO list via
task_create(each task withblocks/blockedByset viatask_update). - Spawns a worker for each immediately-unblocked task, setting
task_update({ owner })on the matching TODO. - Ends its turn.
- On each
<task-notification>, marks the TODO completed, checks what's newly unblocked, spawns the next wave.
The coordinator never waits — it runs briefly, reactively, on each notification.
Install
pi install npm:pi-mono-team-mode
# or load locally
pi -e /path/to/pi-extensions/extensions/team-mode/index.ts
Coordinator mode
Set PI_TEAM_MATE_COORDINATOR=1 on the pi session you want to act as coordinator:
PI_TEAM_MATE_COORDINATOR=1 pi
This injects the coordinator system prompt at every turn via the before_agent_start hook. The prompt teaches the LLM the <task-notification> flow, the delegation discipline, and the "synthesize — don't hand off understanding" rule lifted straight from Claude Code.
Teammate specs
Drop a markdown file into .pi/teammates/<role>.md (or .claude/teammates/<role>.md):
---
name: reviewer
description: reviews diffs for bugs and style violations
modelTier: deep
tools: read, bash, grep
---
You are a careful code reviewer. ...
Frontmatter fields: name, description, needsWorktree, hasMemory, modelTier, tools (comma-separated). The body becomes the teammate's system prompt. The Claude Code teammate addendum (send_message instructions) is prepended automatically.
Storage
~/.pi/agent/extensions/team-mode/
├── model-config.json # provider/tier/role + taskCompletedHook
├── teammates/<agent-id>/record.json
├── teammates/<agent-id>/sessions/<id>.jsonl # pi --session target
├── teams/<team-id>/record.json
├── tasks/<parent-session-id>/<task-id>.json # shared TODO list
└── runtime/<parent-session-id>/index.json # teammate name → agent-id
Override with PI_TEAM_MATE_STORAGE_ROOT for tests.
Quality gates
Add to model-config.json:
{ "taskCompletedHook": "pnpm test --run" }
When a task transitions to completed, the hook runs in the task's cwd. Non-zero exit reverts the task to failed and attaches hook output (stdout + stderr, first 8 KB) to result. 2-minute timeout. Stale PID-based lock files are auto-recovered after 10 s.
Concurrency model
- Each task is its own file under
tasks/<parent-session-id>/<task-id>.json. task_updatetakes an exclusive filesystem lock (<task-id>.lockviaopen(..., "wx")) and bumps a CASversioncounter.- Stale locks (>10 s) are reclaimed automatically — safe across teammate subprocesses.
Delegate groups + live progress
delegate({ tasks: [...] })runs bounded parallel workers and returns aggregated output blocks.delegate({ task, chain: [...] })runs sequential chains with{task},{previous},{chain_dir}substitution and optional innerparallelfan-out.- Per-step
outputandreadslet chain steps exchange large artifacts through files in{chain_dir}. - Parallel caps:
PI_TEAM_MATE_MAX_PARALLEL(default8) andPI_TEAM_MATE_PARALLEL_CONCURRENCY(default4). - Live TUI widget now renders a multi-line ● Agents panel (spinner, turns/tool-uses/tokens/elapsed, activity hint, queued tail).
<task-notification>messages are rendered as styled completion boxes (status glyph, counters, duration, transcript path).
Slash commands & keybinding
/teammate list | status <name> | stop <name>/team list | create <name> | delete <id>/tasks list | show <id> | clear- Ctrl+Shift+T — show the shared task list (pi's built-in Ctrl+T toggles thinking blocks)
Differences from Claude Code (honest)
- Broadcast
to: "*"is not implemented. Swarm/persistent teammate pattern with mailboxes isn't needed for the one-shot worker model; eachsend_messageresumes a teammate viapi --session, but injecting into an actively-running worker turn is not supported. - Tool restriction in coordinator mode is prompt-only, not enforced. Claude Code's coordinator mode actually strips Bash/Edit/Write from the tool pool; pi's extension API doesn't expose tool removal from the main session. The coordinator prompt tells the LLM not to use those tools, but the LLM could still call them.
TaskOutputon a running worker returns the teammate's last-saved record, not live stdout streaming. Completed workers return their final message faithfully.
Tests
cd extensions/team-mode
npm test # 69 tests