@melihmucuk/pi-crew 1.0.14 → 1.0.15

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.
@@ -1,186 +0,0 @@
1
- # pi-crew Architecture
2
-
3
- This document explains the technical architecture of `@melihmucuk/pi-crew`, focusing on what makes this extension unique.
4
-
5
- For pi fundamentals, see pi docs: `extensions.md`, `sdk.md`, `session.md`. Project-level guardrails are in `AGENTS.md`.
6
-
7
- ## 1. What pi-crew adds
8
-
9
- `pi-crew` is a non-blocking subagent orchestration extension. It lets one pi session delegate work to isolated subagent sessions without blocking the caller. Results are delivered back as `crew-result` custom messages.
10
-
11
- Primary components:
12
-
13
- - `extension/runtime/crew-runtime.ts` - Process-level singleton owning all subagent state
14
- - `extension/runtime/subagent-registry.ts` - In-memory subagent registry
15
- - `extension/runtime/delivery-coordinator.ts` - Owner-based result routing
16
- - `extension/runtime/overflow-recovery.ts` - Context overflow retry tracking for subagent prompts
17
- - `extension/bootstrap-session.ts` - Subagent session construction with extension filtering
18
- - `extension/agent-discovery.ts` - Subagent definition discovery and validation
19
-
20
- ## 2. Core runtime components
21
-
22
- ### 2.1 CrewRuntime singleton
23
-
24
- `CrewRuntime` is a process-level singleton that survives pi runtime replacement (`/resume`, `/new`, `/fork`, `/reload`). When pi discards an old extension instance and creates a new one, the new instance reconnects to the same `crewRuntime` and picks up existing subagent state.
25
-
26
- Responsibilities:
27
-
28
- - Create subagent state records
29
- - Bootstrap isolated subagent sessions
30
- - Run subagent prompt cycles with overflow recovery
31
- - Transition subagents between states
32
- - Deliver results to owner sessions
33
-
34
- ### 2.2 Delivery coordinator
35
-
36
- Routes subagent results to the correct session at the correct time. Key behaviors:
37
-
38
- - Tracks active session via `ActiveRuntimeBinding` (set on `session_start`, cleared on `session_shutdown`)
39
- - Queues results when owner session is inactive
40
- - Flushes queued results when owner session activates on any `session_start`; resume/fork are the important replacement paths because subagents survive runtime replacement within the same process
41
- - Uses `triggerTurn: false/true` split to preserve ordering between `crew-result` and `crew-remaining`
42
-
43
- Underlying delivery: see pi's `sendMessage({ deliverAs, triggerTurn })` in extensions.md.
44
-
45
- `crew_list` uses the same idle/streaming delivery rules for its `crew-list-warning` custom message when active subagents exist. The warning is separate from tool output so the list remains a one-time snapshot while anti-polling guidance is delivered as a visible message.
46
-
47
- ### 2.3 Overflow recovery
48
-
49
- Subagent prompt cycles are wrapped by overflow recovery tracking. The tracker observes `agent_end`, `compaction_start`, `compaction_end`, `auto_retry_start`, and `auto_retry_end` events to distinguish normal completion from context-overflow compaction and retry.
50
-
51
- Outcomes:
52
-
53
- - No overflow observed → prompt outcome is based on the final assistant message.
54
- - Overflow compaction completes with retry and the retry reaches a terminal `agent_end` → recovered; prompt outcome is based on the final assistant message.
55
- - Overflow handling times out, is cancelled, or compaction does not retry → failed; the subagent settles as `error` unless the final assistant message already reported an error.
56
-
57
- ### 2.4 Subagent registry
58
-
59
- In-memory, process-scoped: `Map<subagentId, SubagentState>`
60
-
61
- - Owner session filtering
62
- - Runtime ID generation (`<name>-<hex>`)
63
-
64
- Does not persist across process restarts. Subagent session files remain for post-hoc inspection.
65
-
66
- ## 3. Session bootstrapping
67
-
68
- When `crew_spawn` executes:
69
-
70
- 1. Resolve subagent definition from discovery sources
71
- 2. Resolve model (fallback to caller session model if invalid)
72
- 3. Resolve tools, skills
73
- 4. Create `DefaultResourceLoader` with `extensionsOverride` that excludes `pi-crew`
74
- 5. Call `sessionManager.newSession({ parentSession })` for parent-child linkage
75
- 6. Create `AgentSession` with resolved configuration
76
- 7. Send task prompt asynchronously
77
-
78
- **Extension filtering:** Subagent sessions must not load `pi-crew` again. Prevents recursive orchestration loops.
79
-
80
- ## 4. Delivery model
81
-
82
- ### 4.1 Owner-based routing
83
-
84
- Results belong to the session that spawned the subagent. Owner identity uses `getSessionId()`, not file path (in-memory sessions have undefined paths).
85
-
86
- ### 4.2 Idle vs streaming
87
-
88
- Check owner session state before delivery:
89
-
90
- - **Idle (`isIdle() = true`):** Send with `triggerTurn: true`
91
- - **Streaming (`isIdle() = false`):** Send with `deliverAs: "steer"` and `triggerTurn: true`
92
-
93
- Critical: `deliverAs: "steer"` to an idle session leaves the message unprocessed (no active turn loop).
94
-
95
- ### 4.3 Deferred flush
96
-
97
- Pending message flush after `session_start` is deferred to next macrotask. Synchronous delivery loses custom message persistence (pi-core emits `session_start` before reconnecting agent listener during resume). While a flush is scheduled, new deliveries for that owner are queued so ordering is preserved.
98
-
99
- ### 4.4 TTL cleanup
100
-
101
- Pending messages older than 24 hours are discarded during `flushPending`.
102
-
103
- ## 5. Subagent state lifecycle
104
-
105
- ### 5.1 States
106
-
107
- - `running` - Actively processing
108
- - `waiting` - Interactive subagent awaiting `crew_respond` or `crew_done`
109
- - `done` - Completed successfully
110
- - `error` - Failed with error
111
- - `aborted` - Cancelled
112
-
113
- ### 5.2 State transitions
114
-
115
- After prompt cycle completion, inspect assistant stop reason:
116
-
117
- - `stopReason: "error"` → status `error`
118
- - `stopReason: "aborted"` → status `aborted`
119
- - Normal completion + `interactive: true` → status `waiting`
120
- - Normal completion + non-interactive → status `done`
121
-
122
- ### 5.3 Interactive subagents
123
-
124
- `interactive: true` subagents enter `waiting` after each response. They accept follow-up messages via `crew_respond` until explicitly closed with `crew_done`. Closing does NOT emit a duplicate `crew-result`.
125
-
126
- ### 5.4 Tool completion behavior
127
-
128
- `crew_respond` returns immediately and delivers the subagent response asynchronously. Successful `crew_abort` results terminate the current tool turn after aborting owned subagents.
129
-
130
- ## 6. Ownership and isolation
131
-
132
- Invariants:
133
-
134
- 1. `crew_list`, `crew_abort`, `crew_respond`, `crew_done`, status widget: session-scoped. Only owner sees/controls.
135
- 2. `/pi-crew-abort`: cross-session emergency escape hatch.
136
- 3. `session_shutdown` always deactivates delivery binding. On replacement paths (`reload`, `new`, `resume`, `fork`), subagents continue running. On `quit`, the extension aborts all running subagents. `SIGINT` also aborts via a process hook, and `beforeExit` remains a fallback.
137
-
138
- ## 7. Subagent definition model
139
-
140
- Discovery priority:
141
-
142
- 1. Project: `<cwd>/.pi/agents/*.md`
143
- 2. User global: `~/.pi/agent/agents/*.md`
144
- 3. Bundled: `agents/` in package
145
-
146
- Higher priority wins. Same-name duplicates in same directory produce warning.
147
-
148
- Frontmatter: `name`, `description`, `model`, `thinking`, `tools`, `skills`, `compaction`, `interactive`
149
-
150
- Tools/skills semantics:
151
-
152
- - **Omitted:** Use full supported allowlist
153
- - **Empty list (`tools: []`):** Grant none
154
-
155
- JSON overrides: `~/.pi/agent/pi-crew.json` (global), `<cwd>/.pi/pi-crew.json` (project). Project wins.
156
-
157
- ## 8. Behavioral invariants
158
-
159
- 1. Spawned subagent must not block caller session.
160
- 2. Results route to owning session (by ID), not currently active session.
161
- 3. Subagent sessions must not load `pi-crew`.
162
- 4. `crew_respond` returns immediately; result delivered asynchronously.
163
- 5. `crew_done` cleans up only; no duplicate result message.
164
- 6. Queued results flush when owner session becomes active.
165
- 7. `crew-result` messages appear before `crew-remaining` notes (ordering via `triggerTurn` split).
166
- 8. `crew-list-warning` is delivered as a separate custom message when `crew_list` is called while the owner has active subagents.
167
- 9. Pending messages preserved for inactive sessions; TTL (24h) prevents memory leak.
168
- 10. Active subagent state survives runtime replacement within same process.
169
- 11. Graceful quit aborts subagents through `session_shutdown.reason === "quit"`; replacement paths do not.
170
-
171
- ## 9. Reading guide
172
-
173
- 1. `README.md` - Product surface
174
- 2. `AGENTS.md` - Architecture guardrails
175
- 3. `extension/index.ts` - Session event wiring
176
- 4. `extension/runtime/crew-runtime.ts` - Orchestration, state transitions
177
- 5. `extension/runtime/delivery-coordinator.ts` - Owner routing, queueing
178
- 6. `extension/bootstrap-session.ts` - Session construction
179
- 7. `extension/agent-discovery.ts` - Definition validation
180
- 8. `extension/integration/` - Tools, command, renderers
181
-
182
- ## 10. Verification
183
-
184
- ```bash
185
- npm run typecheck
186
- ```
@@ -1,59 +0,0 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
- import type { CrewRuntime } from "../runtime/crew-runtime.js";
3
-
4
- export function registerCrewCommand(pi: ExtensionAPI, crew: CrewRuntime): void {
5
- pi.registerCommand("pi-crew-abort", {
6
- description: "Abort an active subagent",
7
-
8
- getArgumentCompletions(argumentPrefix) {
9
- const activeAgents = crew.getAbortableAgents();
10
- if (activeAgents.length === 0) return null;
11
- return activeAgents
12
- .filter((agent) => agent.id.startsWith(argumentPrefix))
13
- .map((agent) => ({
14
- value: agent.id,
15
- label: `${agent.id} (${agent.agentName})`,
16
- }));
17
- },
18
-
19
- async handler(args, ctx) {
20
- const trimmed = args.trim();
21
-
22
- if (trimmed) {
23
- const success = crew.abort(trimmed, { reason: "Aborted by user command" });
24
- if (!success) {
25
- ctx.ui.notify(`No active subagent with id "${trimmed}"`, "error");
26
- } else {
27
- ctx.ui.notify(`Subagent ${trimmed} aborted`, "info");
28
- }
29
- return;
30
- }
31
-
32
- const activeAgents = crew.getAbortableAgents();
33
- if (activeAgents.length === 0) {
34
- ctx.ui.notify("No active subagents", "info");
35
- return;
36
- }
37
-
38
- const options = activeAgents.map((agent) => ({
39
- id: agent.id,
40
- label: `${agent.id} (${agent.agentName})`,
41
- }));
42
- const selected = await ctx.ui.select(
43
- "Select subagent to abort",
44
- options.map((option) => option.label),
45
- );
46
- if (!selected) return;
47
-
48
- const selectedOption = options.find((option) => option.label === selected);
49
- if (!selectedOption) return;
50
-
51
- const success = crew.abort(selectedOption.id, { reason: "Aborted by user command" });
52
- if (success) {
53
- ctx.ui.notify(`Subagent ${selectedOption.id} aborted`, "info");
54
- } else {
55
- ctx.ui.notify(`Subagent ${selectedOption.id} already finished`, "error");
56
- }
57
- },
58
- });
59
- }