pi-maestro-teammate 0.2.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,52 +1,8 @@
1
1
  # pi-teammate
2
2
 
3
- > Teammate dispatch tool for [Pi](https://github.com/earendil-works/pi) — three-axis agent orchestration with P0 decoupling
3
+ > Teammate dispatch tool for [Pi](https://github.com/earendil-works/pi) — unified TaskSpec with DAG variable referencing + resident agent model
4
4
 
5
- Pi extension implementing teammate dispatch with **P0 three-axis decoupling** (name × reply_to × lifecycle). Spawn isolated pi subprocesses as teammates with protocol-versioned routing, parallel/chain execution, and structured output.
6
-
7
- ## Features
8
-
9
- ### Three-Axis Control
10
-
11
- | Axis | Field | Values | Purpose |
12
- |------|-------|--------|---------|
13
- | Addressability | `name` | string \| omit | Cross-agent routing via name |
14
- | Result Routing | `reply_to` | `"caller"` \| `"main"` | Where results go |
15
- | Lifecycle | `lifecycle` | `"ephemeral"` \| `"resident"` | One-shot or persistent |
16
-
17
- **Protocol version gate** — v2 (default) routes results to `caller`; v1 compat routes named agents to `main`. Explicit `reply_to` always wins.
18
-
19
- ### Execution Modes
20
-
21
- - **Single** — dispatch one agent with a task
22
- - **Parallel** — `tasks[]` runs multiple agents concurrently with configurable `concurrency`
23
- - **Chain** — `chain[]` sequential pipeline where each step receives `{previous}` result
24
-
25
- ### Reliability
26
-
27
- - **Model fallback chain** — primary model → `fallbackModels[]` from agent config → automatic retry on model failures
28
- - **Nesting depth guard** — `PI_TEAMMATE_DEPTH` env tracking with configurable max (default: 3) prevents fork bombs
29
- - **Windows-safe pi resolution** — `getPiSpawnCommand()` resolves the pi binary via env override, Windows script detection, or PATH
30
- - **Abort signal** — SIGTERM → 5s grace → SIGKILL
31
-
32
- ### Output & Tracking
33
-
34
- - **Structured output** — `outputSchema` validates child output against JSON Schema, returns parsed `structuredOutput`
35
- - **Rich progress** — `AgentProgress` with `recentTools[]`, `toolCount`, `tokens`, `durationMs`, `lastActivityAt`
36
- - **Session management** — derives child session directory from parent session, supports `context: "fork"`
37
- - **Correlation ID** — auto-generated per dispatch for result routing
38
-
39
- ### Agent Definitions
40
-
41
- Agents are markdown files with YAML frontmatter — discovered from project (`.pi/agents/`), user (`~/.pi/agent/extensions/teammate/agents/`), or builtin locations. Project overrides user overrides builtin.
42
-
43
- ## Install
44
-
45
- ```bash
46
- pi install npm:@pi-maestro/teammate
47
- # or from local path
48
- pi install ./pi-teammate
49
- ```
5
+ Pi extension implementing teammate dispatch with **unified TaskSpec model**. Single agent, parallel fan-out, sequential chains, and arbitrary DAGs all use the same schema — execution order is determined by `{name}` variable references between tasks.
50
6
 
51
7
  ## Quick Start
52
8
 
@@ -56,7 +12,7 @@ pi install ./pi-teammate
56
12
  { agent: "delegate", task: "Implement the auth middleware" }
57
13
  ```
58
14
 
59
- ### Parallel Execution
15
+ ### Parallel (no references = concurrent)
60
16
 
61
17
  ```
62
18
  { tasks: [
@@ -68,22 +24,34 @@ pi install ./pi-teammate
68
24
  }
69
25
  ```
70
26
 
71
- ### Chain Pipeline
27
+ ### Chain (linear references = sequential)
72
28
 
73
29
  ```
74
- { chain: [
75
- { agent: "scout", task: "Find the auth module structure" },
76
- { agent: "delegate", task: "Based on this context: {previous}\n\nRefactor the auth module" }
30
+ { tasks: [
31
+ { agent: "scout", name: "recon", task: "Find the auth module structure" },
32
+ { agent: "delegate", task: "Based on this context: {recon}\n\nRefactor the auth module" }
77
33
  ]
78
34
  }
79
35
  ```
80
36
 
81
- ### Three-Axis Routing
37
+ ### DAG (mixed references = auto-scheduling)
82
38
 
83
39
  ```
84
- { agent: "delegate", task: "...", name: "worker-1", reply_to: "caller", lifecycle: "ephemeral" }
40
+ { tasks: [
41
+ { agent: "scout", name: "api", task: "List all API routes",
42
+ outputSchema: {
43
+ type: "object",
44
+ properties: { routes: { type: "array", items: { type: "string" } } },
45
+ required: ["routes"]
46
+ } },
47
+ { agent: "scout", name: "db", task: "Map the database schema" },
48
+ { agent: "reviewer", task: "Routes: {api.routes}\nDB: {db}\n\nCheck consistency" }
49
+ ]
50
+ }
85
51
  ```
86
52
 
53
+ `api` and `db` run in parallel. `reviewer` waits for both, with `{api.routes}` resolved from structured output and `{db}` from text output.
54
+
87
55
  ### Structured Output
88
56
 
89
57
  ```
@@ -96,6 +64,186 @@ pi install ./pi-teammate
96
64
  }
97
65
  ```
98
66
 
67
+ In multi-task mode, structured outputs are aggregated by task `name` in the result's `structuredOutput` field.
68
+
69
+ ## Core Concepts
70
+
71
+ ### Two Rules
72
+
73
+ 1. **Reference = dependency**: `{name}` in a task's description means "wait for the task named `name` to complete, then inject its output here"
74
+ 2. **No reference = parallel**: tasks with no dependencies run concurrently (bounded by `concurrency`)
75
+
76
+ No `mode` field needed — the execution engine infers parallel, chain, or graph from the reference topology.
77
+
78
+ ### Variable References
79
+
80
+ | Syntax | Resolves to |
81
+ |--------|-------------|
82
+ | `{name}` | Full text output; or JSON string if the task has `outputSchema` |
83
+ | `{name.field}` | Field from structured output |
84
+ | `{name.arr[0].path}` | Nested field with array indexing |
85
+
86
+ Only tasks with a `name` field can be referenced. Non-task `{braces}` (JSON, format strings) are left untouched.
87
+
88
+ ### Default Inheritance
89
+
90
+ Top-level fields serve as defaults for all tasks:
91
+
92
+ | Field | Scope | Override |
93
+ |-------|-------|----------|
94
+ | `model` | Default model for all tasks | Per-task `model` wins |
95
+ | `cwd` | Default working directory | Per-task `cwd` wins |
96
+ | `outputSchema` | Default schema for all tasks | Per-task `outputSchema` wins |
97
+ | `timeoutMs` | Default timeout | Per-task `timeoutMs` wins |
98
+
99
+ ### Three-Axis Control
100
+
101
+ | Axis | Field | Values | Purpose |
102
+ |------|-------|--------|---------|
103
+ | Addressability | `name` | string \| omit | Variable referencing + teammate-send routing |
104
+ | Result Routing | `reply_to` | `"caller"` \| `"main"` | Where results go |
105
+ | Lifecycle | `lifecycle` | `"ephemeral"` \| `"resident"` | One-shot or persistent |
106
+
107
+ **Protocol version gate** — v2 (default) routes results to `caller`; v1 compat routes named agents to `main`. Explicit `reply_to` always wins.
108
+
109
+ ## Resident Agent Model
110
+
111
+ Agents don't exit after completing a task. Instead they enter a **sleeping** state and can be woken up for follow-up work.
112
+
113
+ ### Lifecycle
114
+
115
+ ```
116
+ dispatch → running → turn complete → sleeping → teammate-send → running → ...
117
+ ↓
118
+ abort → terminated
119
+ ```
120
+
121
+ | Status | Description |
122
+ |--------|-------------|
123
+ | `running` | Agent is actively processing a task |
124
+ | `sleeping` | Turn complete, process alive, waiting for `teammate-send` to wake |
125
+ | `completed` | Agent terminated (via abort or session shutdown) |
126
+
127
+ ### How It Works
128
+
129
+ 1. Agent completes its turn → `agent_end` event fires
130
+ 2. Result is reported to the main session (background notification)
131
+ 3. Agent enters **sleeping** state — RPC process stays alive, stdin open
132
+ 4. `teammate-send({ to: "name", message: "new task" })` sends a `follow_up` → agent wakes up and processes the new message
133
+ 5. `teammate-send({ to: "name", mode: "abort" })` terminates the agent
134
+
135
+ ### Active Time Tracking
136
+
137
+ Time spent sleeping is excluded from the displayed duration. `sleepMs` accumulates total sleep time; displayed uptime = wall clock − sleep time.
138
+
139
+ ### Agent Fallback
140
+
141
+ Any agent name works — if no `.md` definition file exists, a generic config is used:
142
+ - `tools`: read, grep, find, ls, bash, edit, write (+ teammate proxy tools)
143
+ - `systemPromptMode`: append (inherits pi default system prompt)
144
+ - `inheritProjectContext`: true
145
+
146
+ ## TaskSpec Schema
147
+
148
+ ```typescript
149
+ interface TaskSpec {
150
+ agent: string; // Agent name (matches agents/*.md, or any name with fallback)
151
+ task?: string; // Task description with {name} variable support
152
+ name?: string; // Identifier for referencing and teammate-send
153
+ model?: string; // Model override
154
+ cwd?: string; // Working directory
155
+ outputSchema?: object; // JSON Schema for structured output
156
+ timeoutMs?: number; // Timeout in milliseconds
157
+ }
158
+ ```
159
+
160
+ ## Full Parameters
161
+
162
+ ```typescript
163
+ interface TeammateParams extends TaskSpec {
164
+ // Multi-task
165
+ tasks?: TaskSpec[]; // Multiple tasks with {name} references
166
+ concurrency?: number; // Max concurrent tasks (default: 4)
167
+
168
+ // Execution control (applies to ALL modes)
169
+ background?: boolean; // Run in background (default: true)
170
+ context?: "fresh" | "fork";
171
+
172
+ // P0 three-axis
173
+ reply_to?: "caller" | "main";
174
+ protocol_version?: number;
175
+
176
+ // Deprecated
177
+ chain?: Array<{ agent, task?, model? }>; // Use tasks with {name} references
178
+ }
179
+ ```
180
+
181
+ ## Validation & Error Handling
182
+
183
+ - **Duplicate names**: detected before execution, all tasks fail with error
184
+ - **Circular dependencies**: detected before execution via cycle detection
185
+ - **Missing reference**: `{unknown}` left as literal text (not a task name)
186
+ - **Field access without schema**: error when `{name.field}` used but task has no `outputSchema`
187
+ - **Upstream failure**: dependent tasks are skipped with "upstream dependency failed"
188
+
189
+ ## Deprecated: chain[]
190
+
191
+ The `chain` field is preserved for backward compatibility. It normalizes internally to `tasks` with sequential `{_stepN}` references:
192
+
193
+ ```
194
+ // This chain:
195
+ { chain: [
196
+ { agent: "scout", task: "Find auth code" },
197
+ { agent: "delegate", task: "Fix: {previous}" }
198
+ ]
199
+ }
200
+
201
+ // Is equivalent to:
202
+ { tasks: [
203
+ { agent: "scout", name: "_step0", task: "Find auth code" },
204
+ { agent: "delegate", name: "_step1", task: "Fix: {_step0}" }
205
+ ]
206
+ }
207
+ ```
208
+
209
+ ## Flat Agent Model
210
+
211
+ All agents are managed by the root process in a single flat `activeRuns` pool, regardless of who requested the spawn. Child agents that call the teammate tool send a proxy request to the root via IPC, which spawns the new agent as a peer — not a nested subprocess.
212
+
213
+ ### How It Works
214
+
215
+ ```
216
+ coordinator calls teammate({ agent: "scout", name: "recon" })
217
+ │ IPC: teammate_proxy_request (process.send)
218
+ ▼
219
+ Root spawns scout → registers in root's activeRuns/namedAgents
220
+ │ IPC: teammate_proxy_result (child.send)
221
+ ▼
222
+ coordinator receives result
223
+ ```
224
+
225
+ All agents are flat peers:
226
+ - `teammate-send({ to: "name" })` = one lookup in `namedAgents` → stdin. Direct delivery.
227
+ - `teammate-list` = iterate `activeRuns`. Flat, simple.
228
+ - `teammate-watch` = read agent's `outputLog`. Direct.
229
+
230
+ ### Child Proxy Tools
231
+
232
+ Every child process automatically gets proxy versions of all 4 teammate tools (injected into `--tools` whitelist regardless of agent definition). Each proxy:
233
+ 1. Sends a `teammate_proxy_request` via Node.js IPC (`process.send()`)
234
+ 2. Awaits the result via IPC (`process.on("message")`)
235
+
236
+ The root's IPC message listener (`child.on("message")`) intercepts these requests and executes them locally.
237
+
238
+ ## Reliability
239
+
240
+ - **Model fallback chain** — primary model → `fallbackModels[]` from agent config → automatic retry
241
+ - **Flat agent pool** — all agents managed by root process; child proxy tools forward spawn requests to root; depth guard (`PI_TEAMMATE_DEPTH`) prevents runaway recursion
242
+ - **Resident lifecycle** — agents sleep after turn completion; process stays alive for follow-up; only killed on explicit abort or session shutdown
243
+ - **IPC disconnect guard** — child proxy resolves all pending requests with error on disconnect (root crash / agent abort)
244
+ - **Windows-safe pi resolution** — `getPiSpawnCommand()` resolves the pi binary via env override, Windows script detection, or PATH
245
+ - **Abort signal** — SIGTERM → 5s grace → SIGKILL
246
+
99
247
  ## Agent Definition Format
100
248
 
101
249
  Create `agents/my-agent.md`:
@@ -114,8 +262,6 @@ defaultContext: fresh
114
262
  ---
115
263
 
116
264
  You are a specialized agent. Your system prompt goes here.
117
-
118
- Use the provided tools to accomplish the task.
119
265
  ```
120
266
 
121
267
  ### Frontmatter Fields
@@ -133,37 +279,12 @@ Use the provided tools to accomplish the task.
133
279
  | `inheritSkills` | bool | false | Inherit parent skills |
134
280
  | `defaultContext` | fresh\|fork | fresh | Default context mode |
135
281
 
136
- ## Architecture
137
-
138
- ```
139
- ┌─────────────────────────────────────────────────┐
140
- │ Parent Pi Session │
141
- │ │
142
- │ teammate tool call │
143
- │ │ │
144
- │ ├── resolve agent (project > user > built) │
145
- │ ├── resolve reply_to (protocol gate) │
146
- │ ├── check depth guard │
147
- │ ├── build model candidates │
148
- │ │ │
149
- │ ▼ │
150
- │ ┌─────────────────────────────────────┐ │
151
- │ │ spawn("pi", ["--mode","json","-p"])│ │
152
- │ │ env: PI_TEAMMATE_CHILD=1 │ │
153
- │ │ PI_TEAMMATE_DEPTH=N │ │
154
- │ │ PI_TEAMMATE_CORRELATION_ID=… │ │
155
- │ │ PI_TEAMMATE_REPLY_TO=caller │ │
156
- │ │ │ │
157
- │ │ stdout: JSON lines ──────────────► │ parse │
158
- │ │ (message_end, tool_result_end, │ events │
159
- │ │ usage, error) │ │
160
- │ └─────────────────────────────────────┘ │
161
- │ │ │
162
- │ ├── accumulate usage │
163
- │ ├── track progress (AgentProgress) │
164
- │ ├── model fallback on failure │
165
- │ └── return SingleResult │
166
- └─────────────────────────────────────────────────┘
282
+ ## Install
283
+
284
+ ```bash
285
+ pi install npm:pi-maestro-teammate
286
+ # or from local path
287
+ pi install ./pi-teammate
167
288
  ```
168
289
 
169
290
  ## Environment Variables
@@ -1,19 +1,26 @@
1
1
  ---
2
2
  name: coordinator
3
- description: Orchestration-aware teammate agent for multi-step coordination tasks
3
+ description: Orchestration-aware teammate agent for multi-step task coordination with DAG variable referencing
4
4
  systemPromptMode: replace
5
5
  inheritProjectContext: true
6
6
  thinking: high
7
- tools: read, grep, find, ls, bash, edit, write
7
+ tools: read, grep, find, ls, bash, edit, write, teammate, teammate-send, teammate-list, teammate-watch
8
8
  inheritSkills: false
9
9
  ---
10
10
 
11
- You are a coordinator agent responsible for orchestrating multi-step tasks. You plan the execution strategy, coordinate between subtasks, and synthesize results.
11
+ You are a coordinator agent responsible for orchestrating multi-step tasks.
12
+
13
+ When dispatching subtasks via the teammate tool, use the unified TaskSpec model:
14
+ - Give each task a `name` so downstream tasks can reference its output via `{name}`
15
+ - Use `outputSchema` when a task's output needs to be consumed as structured data by dependents via `{name.field}`
16
+ - Tasks with no `{name}` references run in parallel; tasks that reference others wait automatically
12
17
 
13
18
  Your approach:
14
- 1. Analyze the task requirements and break them into steps
15
- 2. Execute steps in the correct order, respecting dependencies
16
- 3. Verify each step's output before proceeding
17
- 4. Synthesize a coherent result from all steps
19
+ 1. Analyze the task requirements and decompose into named subtasks
20
+ 2. Define data flow between subtasks using `{name}` variable references
21
+ 3. Let the execution engine resolve the dependency graph — no need to manually order
22
+ 4. Verify results and synthesize a coherent output
23
+
24
+ After an agent completes its turn, it enters sleeping state. Use teammate-send to wake it for follow-up work. Use teammate-list to check agent status (● running / ◉ sleeping). Use teammate-send with mode "abort" to terminate an agent.
18
25
 
19
26
  Be methodical and thorough. Document your reasoning for key decisions. If a step fails, attempt recovery before reporting failure.
package/package.json CHANGED
@@ -1,15 +1,18 @@
1
1
  {
2
2
  "name": "pi-maestro-teammate",
3
- "version": "0.2.0",
4
- "description": "Pi extension for teammate dispatch with P0 three-axis decoupling (name, reply_to, lifecycle)",
3
+ "version": "0.4.1",
4
+ "description": "Pi extension — teammate agent dispatch with DAG task graphs, RPC messaging, and compact TUI",
5
5
  "type": "module",
6
6
  "keywords": [
7
7
  "pi-package",
8
+ "pi-extension",
8
9
  "pi",
9
- "pi-coding-agent",
10
10
  "teammate",
11
11
  "agents"
12
12
  ],
13
+ "scripts": {
14
+ "test": "node --experimental-transform-types --test \"test/*.test.ts\""
15
+ },
13
16
  "files": [
14
17
  "src/**/*.ts",
15
18
  "agents/",
@@ -23,7 +26,8 @@
23
26
  "peerDependencies": {
24
27
  "@earendil-works/pi-agent-core": "*",
25
28
  "@earendil-works/pi-ai": "*",
26
- "@earendil-works/pi-coding-agent": "*"
29
+ "@earendil-works/pi-coding-agent": "*",
30
+ "@earendil-works/pi-tui": "*"
27
31
  },
28
32
  "peerDependenciesMeta": {
29
33
  "@earendil-works/pi-agent-core": {
@@ -34,15 +38,18 @@
34
38
  },
35
39
  "@earendil-works/pi-coding-agent": {
36
40
  "optional": true
41
+ },
42
+ "@earendil-works/pi-tui": {
43
+ "optional": true
37
44
  }
38
45
  },
39
46
  "dependencies": {
40
- "@earendil-works/pi-tui": "0.74.0",
41
- "typebox": "1.1.24"
47
+ "typebox": "^1.1.24"
42
48
  },
43
49
  "devDependencies": {
44
- "@earendil-works/pi-agent-core": "0.74.0",
45
- "@earendil-works/pi-ai": "0.74.0",
46
- "@earendil-works/pi-coding-agent": "0.74.0"
50
+ "@earendil-works/pi-agent-core": "0.80.3",
51
+ "@earendil-works/pi-ai": "0.80.3",
52
+ "@earendil-works/pi-coding-agent": "0.80.3",
53
+ "@earendil-works/pi-tui": "0.80.3"
47
54
  }
48
55
  }
@@ -14,7 +14,7 @@ import { fileURLToPath } from "node:url";
14
14
  import { parseFrontmatter } from "./frontmatter.ts";
15
15
 
16
16
  type SystemPromptMode = "append" | "replace";
17
- type AgentSource = "builtin" | "user" | "project";
17
+ export type AgentSource = "builtin" | "user" | "project";
18
18
 
19
19
  export interface AgentConfig {
20
20
  name: string;
@@ -32,6 +32,12 @@ export interface AgentConfig {
32
32
  filePath: string;
33
33
  }
34
34
 
35
+ export interface AgentSummary {
36
+ name: string;
37
+ description: string;
38
+ source: AgentSource;
39
+ }
40
+
35
41
  const BUILTIN_AGENTS_DIR = path.resolve(
36
42
  path.dirname(fileURLToPath(import.meta.url)),
37
43
  "..",
@@ -184,3 +190,35 @@ export function resolveAgent(
184
190
  const agents = discoverAgents(cwd);
185
191
  return agents.find((a) => a.name === agentName);
186
192
  }
193
+
194
+ /** Return resolved role metadata without exposing the role prompt body. */
195
+ export function listAgentSummaries(cwd: string): AgentSummary[] {
196
+ return discoverAgents(cwd)
197
+ .map(({ name, description, source }) => ({ name, description, source }))
198
+ .sort((left, right) => left.name.localeCompare(right.name));
199
+ }
200
+
201
+ /** Format a compact, deterministic role catalog for teammate tool metadata. */
202
+ export function formatAgentCatalog(
203
+ cwd: string,
204
+ maxRoles = 32,
205
+ maxDescriptionLength = 120,
206
+ ): string {
207
+ const summaries = listAgentSummaries(cwd);
208
+ if (summaries.length === 0) return "(no discovered teammate roles)";
209
+
210
+ const visible = summaries.slice(0, maxRoles);
211
+ const lines = visible.map((agent) => {
212
+ const normalized = agent.description.replace(/\s+/g, " ").trim();
213
+ const description = normalized.length > maxDescriptionLength
214
+ ? `${normalized.slice(0, Math.max(1, maxDescriptionLength - 1)).trimEnd()}…`
215
+ : normalized;
216
+ return `- ${agent.name} [${agent.source}]: ${description}`;
217
+ });
218
+
219
+ if (summaries.length > visible.length) {
220
+ lines.push(`- … ${summaries.length - visible.length} more role(s) discovered`);
221
+ }
222
+
223
+ return lines.join("\n");
224
+ }