@jaggerxtrm/specialists 3.17.0 → 3.18.0
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 +268 -134
- package/config/mandatory-rules/json-only-final-output.md +13 -0
- package/config/mandatory-rules/service-skills-diff-scan-mandatory.md +26 -0
- package/config/mandatory-rules/service-skills-gitnexus-triage.md +5 -0
- package/config/skills/setup-specialists/SKILL.md +556 -0
- package/config/skills/specialists-creator/SKILL.md +132 -4
- package/config/skills/specialists-creator/scripts/audit-spec-uniformity.mjs +1 -1
- package/config/specialists/bare.specialist.json +3 -3
- package/config/specialists/changelog-drafter.specialist.json +5 -4
- package/config/specialists/changelog-keeper.specialist.json +7 -6
- package/config/specialists/debugger.specialist.json +2 -2
- package/config/specialists/executor.specialist.json +2 -2
- package/config/specialists/explorer.specialist.json +4 -4
- package/config/specialists/memory-processor.specialist.json +3 -3
- package/config/specialists/node-coordinator.specialist.json +3 -3
- package/config/specialists/obligations-scanner.specialist.json +67 -17
- package/config/specialists/overthinker.specialist.json +3 -3
- package/config/specialists/planner.specialist.json +4 -4
- package/config/specialists/quant-methodologist.specialist.json +145 -0
- package/config/specialists/quant-researcher.specialist.json +144 -0
- package/config/specialists/researcher.specialist.json +5 -5
- package/config/specialists/reviewer.specialist.json +1 -1
- package/config/specialists/seconder.specialist.json +2 -2
- package/config/specialists/security-auditor.specialist.json +2 -2
- package/config/specialists/service-skills-sync.specialist.json +90 -75
- package/config/specialists/specialists-creator.specialist.json +4 -4
- package/config/specialists/sync-docs.specialist.json +4 -4
- package/config/specialists/test-engineer.specialist.json +3 -3
- package/config/specialists/test-runner.specialist.json +7 -7
- package/config/specialists/transcriber.specialist.json +3 -3
- package/config/specialists/xt-merge.specialist.json +4 -4
- package/dist/asset-contract.json +21 -2
- package/dist/index.js +25704 -16376
- package/dist/lib.js +9849 -6147
- package/dist/types/cli/console/components.d.ts +83 -0
- package/dist/types/cli/console/components.d.ts.map +1 -0
- package/dist/types/cli/console/config-source.d.ts +58 -0
- package/dist/types/cli/console/config-source.d.ts.map +1 -0
- package/dist/types/cli/console/forensic.d.ts +11 -0
- package/dist/types/cli/console/forensic.d.ts.map +1 -0
- package/dist/types/cli/console/git.d.ts +28 -0
- package/dist/types/cli/console/git.d.ts.map +1 -0
- package/dist/types/cli/console/help.d.ts +2 -0
- package/dist/types/cli/console/help.d.ts.map +1 -0
- package/dist/types/cli/console/log.d.ts +13 -0
- package/dist/types/cli/console/log.d.ts.map +1 -0
- package/dist/types/cli/console/repo-config.d.ts +26 -0
- package/dist/types/cli/console/repo-config.d.ts.map +1 -0
- package/dist/types/cli/console/repo-discovery.d.ts +12 -0
- package/dist/types/cli/console/repo-discovery.d.ts.map +1 -0
- package/dist/types/cli/console/runtime.d.ts +12 -0
- package/dist/types/cli/console/runtime.d.ts.map +1 -0
- package/dist/types/cli/console/subscribe-prototype.d.ts +18 -0
- package/dist/types/cli/console/subscribe-prototype.d.ts.map +1 -0
- package/dist/types/cli/console/theme.d.ts +91 -0
- package/dist/types/cli/console/theme.d.ts.map +1 -0
- package/dist/types/cli/console/types.d.ts +231 -0
- package/dist/types/cli/console/types.d.ts.map +1 -0
- package/dist/types/cli/console/view-model.d.ts +252 -0
- package/dist/types/cli/console/view-model.d.ts.map +1 -0
- package/dist/types/cli/console.d.ts +2 -0
- package/dist/types/cli/console.d.ts.map +1 -0
- package/dist/types/cli/db.d.ts.map +1 -1
- package/dist/types/cli/doctor.d.ts.map +1 -1
- package/dist/types/cli/edit.d.ts.map +1 -1
- package/dist/types/cli/epic.d.ts.map +1 -1
- package/dist/types/cli/feed.d.ts.map +1 -1
- package/dist/types/cli/forensic.d.ts +2 -0
- package/dist/types/cli/forensic.d.ts.map +1 -0
- package/dist/types/cli/format-helpers.d.ts +4 -2
- package/dist/types/cli/format-helpers.d.ts.map +1 -1
- package/dist/types/cli/help.d.ts.map +1 -1
- package/dist/types/cli/init.d.ts +10 -0
- package/dist/types/cli/init.d.ts.map +1 -1
- package/dist/types/cli/list.d.ts.map +1 -1
- package/dist/types/cli/log.d.ts.map +1 -1
- package/dist/types/cli/metrics.d.ts +2 -0
- package/dist/types/cli/metrics.d.ts.map +1 -0
- package/dist/types/cli/ps.d.ts.map +1 -1
- package/dist/types/cli/result.d.ts.map +1 -1
- package/dist/types/cli/run.d.ts +1 -0
- package/dist/types/cli/run.d.ts.map +1 -1
- package/dist/types/cli/script.d.ts +3 -0
- package/dist/types/cli/script.d.ts.map +1 -1
- package/dist/types/cli/serve.d.ts.map +1 -1
- package/dist/types/cli/setup.d.ts +19 -1
- package/dist/types/cli/setup.d.ts.map +1 -1
- package/dist/types/cli/status.d.ts.map +1 -1
- package/dist/types/cli/version-check.d.ts +1 -0
- package/dist/types/cli/version-check.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/pi/session.d.ts +11 -1
- package/dist/types/pi/session.d.ts.map +1 -1
- package/dist/types/server.d.ts +15 -0
- package/dist/types/server.d.ts.map +1 -1
- package/dist/types/specialist/benchmarks.d.ts +37 -0
- package/dist/types/specialist/benchmarks.d.ts.map +1 -0
- package/dist/types/specialist/chain-identity.d.ts +7 -1
- package/dist/types/specialist/chain-identity.d.ts.map +1 -1
- package/dist/types/specialist/control.d.ts.map +1 -1
- package/dist/types/specialist/forensic-events.d.ts +138 -0
- package/dist/types/specialist/forensic-events.d.ts.map +1 -0
- package/dist/types/specialist/forensic-renderer.d.ts +34 -0
- package/dist/types/specialist/forensic-renderer.d.ts.map +1 -0
- package/dist/types/specialist/git-diff-evidence.d.ts +28 -0
- package/dist/types/specialist/git-diff-evidence.d.ts.map +1 -0
- package/dist/types/specialist/global-config.d.ts +389 -0
- package/dist/types/specialist/global-config.d.ts.map +1 -0
- package/dist/types/specialist/launch.d.ts +1 -0
- package/dist/types/specialist/launch.d.ts.map +1 -1
- package/dist/types/specialist/live-aggregates.d.ts +46 -0
- package/dist/types/specialist/live-aggregates.d.ts.map +1 -0
- package/dist/types/specialist/loader.d.ts +50 -1
- package/dist/types/specialist/loader.d.ts.map +1 -1
- package/dist/types/specialist/model-chain.d.ts +7 -0
- package/dist/types/specialist/model-chain.d.ts.map +1 -0
- package/dist/types/specialist/model-probes.d.ts +28 -0
- package/dist/types/specialist/model-probes.d.ts.map +1 -0
- package/dist/types/specialist/node-contract.d.ts +18 -18
- package/dist/types/specialist/node-supervisor.d.ts.map +1 -1
- package/dist/types/specialist/observability-db.d.ts +1 -1
- package/dist/types/specialist/observability-db.d.ts.map +1 -1
- package/dist/types/specialist/observability-sqlite.d.ts +25 -0
- package/dist/types/specialist/observability-sqlite.d.ts.map +1 -1
- package/dist/types/specialist/preset-resolver.d.ts +56 -0
- package/dist/types/specialist/preset-resolver.d.ts.map +1 -0
- package/dist/types/specialist/prometheus-projection.d.ts +25 -0
- package/dist/types/specialist/prometheus-projection.d.ts.map +1 -0
- package/dist/types/specialist/runner.d.ts +26 -1
- package/dist/types/specialist/runner.d.ts.map +1 -1
- package/dist/types/specialist/schema.d.ts +163 -54
- package/dist/types/specialist/schema.d.ts.map +1 -1
- package/dist/types/specialist/script-runner.d.ts +5 -1
- package/dist/types/specialist/script-runner.d.ts.map +1 -1
- package/dist/types/specialist/snapshot-diff.d.ts +8 -0
- package/dist/types/specialist/snapshot-diff.d.ts.map +1 -0
- package/dist/types/specialist/source-queue.d.ts +13 -0
- package/dist/types/specialist/source-queue.d.ts.map +1 -0
- package/dist/types/specialist/supervisor.d.ts +15 -1
- package/dist/types/specialist/supervisor.d.ts.map +1 -1
- package/dist/types/specialist/timeline-events.d.ts +68 -1
- package/dist/types/specialist/timeline-events.d.ts.map +1 -1
- package/dist/types/tools/specialist/resume_specialist.tool.d.ts +4 -4
- package/dist/types/tools/specialist/specialist_status.tool.d.ts +1 -1
- package/dist/types/tools/specialist/steer_specialist.tool.d.ts +4 -4
- package/dist/types/tools/specialist/use_specialist.tool.d.ts +16 -16
- package/docs/ARCHITECTURE.md +1176 -0
- package/docs/TODO.md +9 -0
- package/docs/architecture.md +11 -0
- package/docs/archive/2026-03-13-bd-decision-context-template-plan.md +96 -0
- package/docs/archive/AGENT-HANDOFF.md +351 -0
- package/docs/archive/PARITY-ANALYSIS.md +296 -0
- package/docs/archive/SPECIALISTS_REFACTOR.md.md +30 -0
- package/docs/archive/cc-programmatic.md +216 -0
- package/docs/archive/claude-agent-sdk.md +594 -0
- package/docs/archive/decision-specialist-directory.md +41 -0
- package/docs/archive/discoveries.md +148 -0
- package/docs/archive/executor-benchmark-protocol.md +198 -0
- package/docs/archive/future-features.md +66 -0
- package/docs/archive/gzrx-completion-critique.md +183 -0
- package/docs/archive/gzrx-research-notes.md +401 -0
- package/docs/archive/gzrx-tool-catalog.md +760 -0
- package/docs/archive/iron-review-hardening-qa-chain-substrate.md +322 -0
- package/docs/archive/iron-review-hardening.html +1004 -0
- package/docs/archive/issuetracking.md +312 -0
- package/docs/archive/qa-v3.0.2.md +220 -0
- package/docs/archive/restructure.md +231 -0
- package/docs/archive/script-specialists.md +1254 -0
- package/docs/archive/spec-v3.md +792 -0
- package/docs/archive/specialist-stats.md +127 -0
- package/docs/archive/specialists-friction-audit.md +1347 -0
- package/docs/archive/specialists-runtime-critique.md +170 -0
- package/docs/archive/specialists-service-evaluation.md +713 -0
- package/docs/archive/specialists-substrate-alignment.md +255 -0
- package/docs/archive/substrate-review.md +1288 -0
- package/docs/archive/test-writer-specialist.md +254 -0
- package/docs/archive/using-specialists-v3-improvements-2026-05-09.md +600 -0
- package/docs/archive/xtrm-specialists-analysis.md +314 -0
- package/docs/authoring.md +701 -0
- package/docs/background-jobs.md +203 -0
- package/docs/bare-specialists.md +83 -0
- package/docs/benchmarks/executor-benchmark-runner.md +66 -0
- package/docs/bootstrap.md +161 -0
- package/docs/cli-reference.md +1645 -0
- package/docs/deploying-alongside.md +155 -0
- package/docs/design/README.md +36 -0
- package/docs/design/darth-feedor-migration.md +290 -0
- package/docs/design/roadmap/README.md +32 -0
- package/docs/design/roadmap/chain-templates/README.md +146 -0
- package/docs/design/roadmap/chain-templates/code-quick.formula.json +27 -0
- package/docs/design/roadmap/chain-templates/code-standard.formula.json +126 -0
- package/docs/design/roadmap/chain-templates/code-with-advisors.formula.json +128 -0
- package/docs/design/roadmap/chain-templates/code-with-tests.formula.json +76 -0
- package/docs/design/roadmap/chain-templates/debug.formula.json +128 -0
- package/docs/design/roadmap/chain-templates/doc-sync.formula.json +28 -0
- package/docs/design/roadmap/chain-templates/memory-hygiene.formula.json +27 -0
- package/docs/design/roadmap/chain-templates/planning.formula.json +27 -0
- package/docs/design/roadmap/chain-templates/premortem.formula.json +26 -0
- package/docs/design/roadmap/chain-templates/release-prep.formula.json +36 -0
- package/docs/design/roadmap/chain-templates/research-only.formula.json +28 -0
- package/docs/design/roadmap/chain-templates/restitch.formula.json +125 -0
- package/docs/design/roadmap/chain-templates/security-deep.formula.json +161 -0
- package/docs/design/roadmap/chain-templates/test-only.formula.json +52 -0
- package/docs/design/roadmap/chain-templates/triage.formula.json +35 -0
- package/docs/design/roadmap/history/handoff-from-substrate-design.md +87 -0
- package/docs/design/roadmap/history/substrate-reconciliation.md +105 -0
- package/docs/design/roadmap/specialists-roadmap.md +1193 -0
- package/docs/design/sp-console-subscribe-via-materializer.md +231 -0
- package/docs/design/sp-console-tui-mock-v2.html +293 -0
- package/docs/design/sp-console-tui-mock.html +120 -0
- package/docs/design/sp-console-tui.md +340 -0
- package/docs/design/specialist-agentops-suite.md +323 -0
- package/docs/design/substrate/channels.md +14 -0
- package/docs/design/substrate/devops-platform-engineering-prd.md +446 -0
- package/docs/design/substrate/html-design-example.md +339 -0
- package/docs/design/xtrm-tiers-architecture.svg +132 -0
- package/docs/devops/dependency-verdict-materialization.md +46 -0
- package/docs/epic-readiness.md +75 -0
- package/docs/examples/mercury-atomic-summarizer.specialist.json +26 -0
- package/docs/examples/smoke-echo-text-expected-keys.specialist.json +25 -0
- package/docs/examples/smoke-echo.specialist.json +25 -0
- package/docs/features.md +1577 -0
- package/docs/hooks.md +81 -0
- package/docs/installation.md +142 -0
- package/docs/manifest.md +184 -0
- package/docs/mcp-servers.md +73 -0
- package/docs/mcp-tools.md +71 -0
- package/docs/nodes.md +231 -0
- package/docs/observability-metrics.md +152 -0
- package/docs/operator/sp-console-v2-walkthrough.md +410 -0
- package/docs/overrides-guide.md +306 -0
- package/docs/pi-rpc-boundary.md +118 -0
- package/docs/pi-session.md +195 -0
- package/docs/release.md +22 -0
- package/docs/skills.md +132 -0
- package/docs/specialists/handoff-schema.md +181 -0
- package/docs/specialists-catalog.md +99 -0
- package/docs/specialists-service-install.md +226 -0
- package/docs/specialists-service.md +363 -0
- package/docs/surface-ownership.md +138 -0
- package/docs/upgrade-notes/kan-90-global-user-config.md +114 -0
- package/docs/upgrade-notes/kan-91-expanded-overrides.md +261 -0
- package/docs/workflow.md +114 -0
- package/docs/worktree.md +71 -0
- package/docs/worktrees.md +309 -0
- package/package.json +6 -3
|
@@ -0,0 +1,792 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Specialists v3 — Implementation Specification
|
|
3
|
+
scope: spec-v3
|
|
4
|
+
category: reference
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
updated: 2026-03-19
|
|
7
|
+
domain: []
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<!-- INDEX: auto-generated by validate_doc.py — do not edit manually -->
|
|
11
|
+
| Section | Summary |
|
|
12
|
+
|---|---|
|
|
13
|
+
| [1. Architecture Overview](#1-architecture-overview) | Problem: N poll calls per run |
|
|
14
|
+
| [2. File Layout](#2-file-layout) | On completion, status changes to "done" and elapsed_s is final |
|
|
15
|
+
| [3. CLI Commands](#3-cli-commands) | **Foreground (default, existing behavior)**: Streams pi output to stdout in real time |
|
|
16
|
+
| [4. Pi Subprocess Protocol](#4-pi-subprocess-protocol) | **--append-system-prompt**: The specialist's prompt |
|
|
17
|
+
| [5. Schema Changes (.specialist.yaml)](#5-schema-changes-specialistyaml) | skills |
|
|
18
|
+
| [6. Hook Notification](#6-hook-notification) | File: ~/ |
|
|
19
|
+
| [7. PiAgentSession Changes](#7-piagentsession-changes) | Changes: |
|
|
20
|
+
| [8. SpecialistRunner Changes](#8-specialistrunner-changes) | SpecialistRunner |
|
|
21
|
+
| [9. MCP Server Changes](#9-mcp-server-changes) | **specialist_init**: Unchanged |
|
|
22
|
+
| [10. Operational Concerns](#10-operational-concerns) | `specialists init` must add ` |
|
|
23
|
+
| [11. Migration Path](#11-migration-path) | 1 |
|
|
24
|
+
| [12. Testing Strategy](#12-testing-strategy) | - Supervisor lifecycle: mock PiAgentSession, verify status |
|
|
25
|
+
| [13. Verification Checklist](#13-verification-checklist) | All items verified against pi-mono source (packages/coding-agent) and pi --help output |
|
|
26
|
+
<!-- END INDEX -->
|
|
27
|
+
|
|
28
|
+
# Specialists v3 — Implementation Specification
|
|
29
|
+
|
|
30
|
+
> **Status**: Final spec. Ready for implementation.
|
|
31
|
+
> **Date**: 2026-03-11
|
|
32
|
+
> **Context**: This spec supersedes the file-backed registry + RpcClient plan from restructure.md.
|
|
33
|
+
> **Core decision**: CLI is the execution plane. MCP is the control plane. Bash background jobs replace MCP async polling.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 1. Architecture Overview
|
|
38
|
+
|
|
39
|
+
### Current (v2)
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
Claude ──MCP──▶ start_specialist ──▶ PiAgentSession (subprocess)
|
|
43
|
+
Claude ──MCP──▶ poll_specialist ──▶ JobRegistry (in-memory) ──▶ delta + status
|
|
44
|
+
Claude ──MCP──▶ poll_specialist ──▶ ...repeat N times...
|
|
45
|
+
Claude ──MCP──▶ poll_specialist ──▶ status: done, full output
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Problem: N poll calls per run. Each is a visible tool use. Thinking tokens, tool markers,
|
|
49
|
+
and text deltas streamed into context window on every poll. 60-second specialist = 30+ tool
|
|
50
|
+
calls, tens of thousands of wasted context tokens.
|
|
51
|
+
|
|
52
|
+
### Target (v3)
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
Claude ──Bash──▶ specialists run overthinker --prompt "..." --background
|
|
56
|
+
└▶ spawns pi --mode rpc (supervisor stays alive)
|
|
57
|
+
└▶ writes status.json on lifecycle events
|
|
58
|
+
└▶ writes events.jsonl (lifecycle + tool events only)
|
|
59
|
+
└▶ on agent_end: queries get_last_assistant_text → result.txt
|
|
60
|
+
└▶ queries get_state → session path → session.txt
|
|
61
|
+
└▶ closes stdin → pi exits → supervisor exits
|
|
62
|
+
|
|
63
|
+
Claude ──Bash──▶ specialists status (one-liner per job, ~30 tokens)
|
|
64
|
+
Claude ──Bash──▶ specialists result <id> (full output, once, when done)
|
|
65
|
+
Claude ──Bash──▶ specialists run X --continue <id> --prompt "..." (multi-turn)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Hook notification (optional): UserPromptSubmit hook checks .specialists/jobs/*/status.json
|
|
69
|
+
for newly completed jobs, injects one-line banner on next user prompt.
|
|
70
|
+
|
|
71
|
+
### MCP Role (reduced)
|
|
72
|
+
|
|
73
|
+
MCP server retains: specialist_init, list_specialists, specialist_status (circuit breaker
|
|
74
|
+
health), use_specialist (quick synchronous tasks). The async job tools (start_specialist,
|
|
75
|
+
poll_specialist, stop_specialist, run_parallel) are deprecated — their functionality moves
|
|
76
|
+
to CLI commands.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 2. File Layout
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
.specialists/ # project root, parallel to .beads/
|
|
84
|
+
jobs/
|
|
85
|
+
<job-id>/
|
|
86
|
+
status.json # overwritten on every lifecycle event
|
|
87
|
+
events.jsonl # append-only lifecycle + tool events
|
|
88
|
+
result.txt # written once on agent_end (clean assistant text)
|
|
89
|
+
session.jsonl # pi writes here directly (--session flag)
|
|
90
|
+
ready/ # completion markers for hook notification
|
|
91
|
+
<job-id> # empty file, existence = "done"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### status.json (overwritten per lifecycle event)
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"id": "a1b2c3",
|
|
99
|
+
"specialist": "overthinker",
|
|
100
|
+
"status": "running",
|
|
101
|
+
"current_event": "tool_execution",
|
|
102
|
+
"current_tool": "bash(grep -r 'pattern' src/)",
|
|
103
|
+
"model": "anthropic/claude-sonnet-4-6",
|
|
104
|
+
"backend": "anthropic",
|
|
105
|
+
"pid": 48210,
|
|
106
|
+
"started_at_ms": 1710000000000,
|
|
107
|
+
"elapsed_s": 47,
|
|
108
|
+
"last_event_at_ms": 1710000047000,
|
|
109
|
+
"bead_id": "bead-xyz",
|
|
110
|
+
"session_file": ".specialists/jobs/a1b2c3/session.jsonl",
|
|
111
|
+
"error": null
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
On completion, status changes to "done" and elapsed_s is final.
|
|
116
|
+
On error, status is "error" and error field contains the message.
|
|
117
|
+
On cancel, status is "cancelled".
|
|
118
|
+
On stall detection, status remains "running" but a "stalled": true field is added.
|
|
119
|
+
|
|
120
|
+
### events.jsonl (append-only, lifecycle + tool events)
|
|
121
|
+
|
|
122
|
+
```jsonl
|
|
123
|
+
{"ts":1710000000,"event":"starting","specialist":"overthinker","model":"anthropic/claude-sonnet-4-6","id":"a1b2c3"}
|
|
124
|
+
{"ts":1710000003,"event":"thinking"}
|
|
125
|
+
{"ts":1710000008,"event":"toolcall","tool":"read","args":"src/index.ts"}
|
|
126
|
+
{"ts":1710000009,"event":"tool_execution","tool":"read"}
|
|
127
|
+
{"ts":1710000009,"event":"tool_execution_end","tool":"read","duration_ms":340}
|
|
128
|
+
{"ts":1710000015,"event":"toolcall","tool":"bash","args":"grep -r 'pattern' src/"}
|
|
129
|
+
{"ts":1710000016,"event":"tool_execution","tool":"bash"}
|
|
130
|
+
{"ts":1710000022,"event":"tool_execution_end","tool":"bash","duration_ms":6100}
|
|
131
|
+
{"ts":1710000030,"event":"text"}
|
|
132
|
+
{"ts":1710000047,"event":"done","duration_ms":47000,"session_file":".specialists/jobs/a1b2c3/session.jsonl"}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
NOT captured in events.jsonl: thinking_delta content, text_delta content, message_start,
|
|
136
|
+
thinking_end. These are high-frequency events that add noise without diagnostic value.
|
|
137
|
+
|
|
138
|
+
### result.txt
|
|
139
|
+
|
|
140
|
+
Plain text. The clean final assistant output extracted from pi's get_last_assistant_text
|
|
141
|
+
RPC call. Written once on agent_end. This is what `specialists result <id>` prints.
|
|
142
|
+
|
|
143
|
+
### session.jsonl
|
|
144
|
+
|
|
145
|
+
Pi's native session file. Written by pi directly via `--session <path>`. Contains the full
|
|
146
|
+
message history (user, assistant, tool calls, tool results) in pi's internal JSONL format.
|
|
147
|
+
Used for `--continue` multi-turn resumption. We never read or parse this file — pi owns it.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 3. CLI Commands
|
|
152
|
+
|
|
153
|
+
### 3.1 specialists run <name> (MODIFIED — add --background)
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
specialists run <name> --prompt "..." [--background] [--model <model>] [--no-beads]
|
|
157
|
+
[--continue <job-id>] [--timeout <ms>]
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Foreground (default, existing behavior)**: Streams pi output to stdout in real time.
|
|
161
|
+
Blocks until completion. Exit code reflects success/failure.
|
|
162
|
+
|
|
163
|
+
**Background (--background, new)**: Spawns pi subprocess, prints job ID to stdout, writes
|
|
164
|
+
status/events/result files. The `specialists run` process stays alive as a supervisor
|
|
165
|
+
(not a daemon) — Claude backgrounds it with `&` in bash.
|
|
166
|
+
|
|
167
|
+
**Continue (--continue <job-id>)**: Reads session file path from
|
|
168
|
+
`.specialists/jobs/<job-id>/session.txt` or from status.json. Spawns pi with
|
|
169
|
+
`--session <path>`. Full context from previous run is available.
|
|
170
|
+
|
|
171
|
+
Supervisor lifecycle (--background mode):
|
|
172
|
+
1. Generate job ID (nanoid or uuid short)
|
|
173
|
+
2. Create .specialists/jobs/<id>/ directory
|
|
174
|
+
3. Write initial status.json (status: "starting")
|
|
175
|
+
4. Run pre-scripts (existing mechanism, unchanged)
|
|
176
|
+
5. Spawn pi subprocess with --mode rpc, --session .specialists/jobs/<id>/session.jsonl
|
|
177
|
+
6. Print job ID to stdout: "Job started: <id>"
|
|
178
|
+
7. Read NDJSON from pi stdout:
|
|
179
|
+
- On lifecycle/tool events → update status.json, append to events.jsonl
|
|
180
|
+
- On thinking_start/text_delta → update current_event in status.json only (no content)
|
|
181
|
+
- Track last_event_at_ms for stall detection
|
|
182
|
+
8. On agent_end:
|
|
183
|
+
a. Send {"type": "get_last_assistant_text"} via stdin → read response → write result.txt
|
|
184
|
+
b. Update status.json to "done"
|
|
185
|
+
c. Write done event to events.jsonl
|
|
186
|
+
d. Touch .specialists/ready/<id> (for hook notification)
|
|
187
|
+
e. Close stdin → pi exits
|
|
188
|
+
9. Run post-scripts (existing mechanism, unchanged)
|
|
189
|
+
10. Close beads issue (if created)
|
|
190
|
+
11. Supervisor process exits
|
|
191
|
+
|
|
192
|
+
Stall detection (in-process, not a daemon):
|
|
193
|
+
- Configurable per-specialist via stall_timeout_ms in YAML (default: null = disabled)
|
|
194
|
+
- If last_event_at_ms is older than stall_timeout_ms, write "stalled": true to status.json
|
|
195
|
+
- Do NOT auto-kill — surface the stall in status, let Claude or the user decide
|
|
196
|
+
|
|
197
|
+
Error handling:
|
|
198
|
+
- Pi process exits non-zero without agent_end → write status "error", close bead as ERROR
|
|
199
|
+
- Pi process killed by OOM/signal → same as above, capture exit code in error field
|
|
200
|
+
- Pre-script failure → write to status.json, still start pi (pre-script output is context, not a gate)
|
|
201
|
+
- Post-script failure → swallow silently (existing behavior, unchanged)
|
|
202
|
+
|
|
203
|
+
### 3.2 specialists status (MODIFIED — add job status)
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
specialists status [--job <id>] [--json]
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Without --job: existing system health output (specialist count, pi version, beads status,
|
|
210
|
+
MCP registration) PLUS a job summary table:
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
── Active Jobs ───────────────────────────────────────────────────────────
|
|
214
|
+
a1b2c3 overthinker running 1m12s tool: bash(grep -r ...)
|
|
215
|
+
d4e5f6 bug-hunt running 0m34s thinking
|
|
216
|
+
g7h8i9 init-session done 0m08s ✓
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
With --job <id>: detailed single-job view:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
overthinker | running | 1m12s | model: anthropic/claude-sonnet-4-6
|
|
223
|
+
Current: tool_execution bash(grep -r 'pattern' src/)
|
|
224
|
+
Bead: bead-xyz
|
|
225
|
+
Session: .specialists/jobs/a1b2c3/session.jsonl
|
|
226
|
+
Events: 14 (5 tool calls)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Implementation: reads .specialists/jobs/*/status.json. Pure file reads, no MCP, no
|
|
230
|
+
subprocess interaction.
|
|
231
|
+
|
|
232
|
+
### 3.3 specialists result <id> (NEW)
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
specialists result <id> [--raw]
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Prints result.txt to stdout. If the job is still running, prints current status and exits
|
|
239
|
+
with code 1. --raw skips any formatting/headers and prints only the raw result text.
|
|
240
|
+
|
|
241
|
+
### 3.4 specialists feed (NEW)
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
specialists feed [--job <id>] [--follow]
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Formatted tail of events.jsonl files. Without --job, shows all active jobs interleaved
|
|
248
|
+
with job ID prefix. --follow keeps tailing (like tail -f).
|
|
249
|
+
|
|
250
|
+
Output format:
|
|
251
|
+
```
|
|
252
|
+
[a1b2c3] overthinker 00:12 toolcall bash(grep -r 'pattern' src/)
|
|
253
|
+
[a1b2c3] overthinker 00:18 tool_end bash 6.1s
|
|
254
|
+
[d4e5f6] bug-hunt 00:34 thinking
|
|
255
|
+
[a1b2c3] overthinker 00:22 text
|
|
256
|
+
[a1b2c3] overthinker 00:47 done 47.0s
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### 3.5 specialists stop <id> (NEW — CLI equivalent of MCP stop_specialist)
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
specialists stop <id>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Reads status.json for the PID, sends SIGTERM. If the supervisor process is still alive,
|
|
266
|
+
it detects the child exit, writes "cancelled" status, closes bead. If the supervisor
|
|
267
|
+
is gone (orphaned pi process), sends SIGTERM directly to the PID.
|
|
268
|
+
|
|
269
|
+
### 3.6 specialists run (EXISTING — foreground, unchanged)
|
|
270
|
+
|
|
271
|
+
Existing behavior preserved. No --background flag = streams to stdout, blocks until done.
|
|
272
|
+
Used by humans directly and by Claude for quick synchronous tasks where background
|
|
273
|
+
overhead is not worth it.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## 4. Pi Subprocess Protocol
|
|
278
|
+
|
|
279
|
+
### 4.1 Spawn Arguments
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
pi --mode rpc \
|
|
283
|
+
--provider <provider> \
|
|
284
|
+
--model <model> \
|
|
285
|
+
--append-system-prompt <system_prompt> \
|
|
286
|
+
--tools <tool_list> # always set — see below
|
|
287
|
+
--skill <path> # repeated for each skills.paths entry
|
|
288
|
+
--session .specialists/jobs/<id>/session.jsonl \
|
|
289
|
+
--no-extensions # prevent user extensions from emitting UI requests
|
|
290
|
+
<any additional args from specialist config>
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**--append-system-prompt**: The specialist's prompt.system field. Takes priority over
|
|
294
|
+
AGENTS.md. This IS the specialist's identity — its behavioral constraints, output format
|
|
295
|
+
requirements, and role definition. Not template-rendered (no variable substitution).
|
|
296
|
+
|
|
297
|
+
**--no-extensions**: Always set in background mode. User-installed pi extensions can emit
|
|
298
|
+
`extension_ui_request` events (dialogs, confirmations) on stdout and block waiting for a
|
|
299
|
+
response from stdin. The supervisor never sends these responses — causing a silent hang.
|
|
300
|
+
`--no-extensions` disables extension discovery entirely. (`--no-input` does not exist in pi.)
|
|
301
|
+
|
|
302
|
+
**--tools**: Always set explicitly. Pi's default when no `--tools` flag is passed is
|
|
303
|
+
`read,bash,edit,write` — which silently excludes `grep`, `find`, and `ls`. Per-tier mapping:
|
|
304
|
+
|
|
305
|
+
| Permission | --tools value |
|
|
306
|
+
|------------|---------------|
|
|
307
|
+
| READ_ONLY | `read,bash,grep,find,ls` |
|
|
308
|
+
| BASH_ONLY | `bash` |
|
|
309
|
+
| LOW | `read,bash,edit,write,grep,find,ls` |
|
|
310
|
+
| MEDIUM | `read,bash,edit,write,grep,find,ls` |
|
|
311
|
+
| HIGH | `read,bash,edit,write,grep,find,ls` |
|
|
312
|
+
|
|
313
|
+
`mapPermissionToTools()` in `src/pi/session.ts` must be updated to match this table.
|
|
314
|
+
|
|
315
|
+
**--skill**: One flag per entry in the specialist's skills.paths array. Paths resolved:
|
|
316
|
+
- Absolute paths: passed as-is
|
|
317
|
+
- ~/... paths: expanded to home directory
|
|
318
|
+
- Relative paths: resolved from project root (cwd)
|
|
319
|
+
- Missing paths: logged as warning in events.jsonl, not fatal
|
|
320
|
+
|
|
321
|
+
**--session**: Always set in --background mode. Points to
|
|
322
|
+
.specialists/jobs/<id>/session.jsonl. Pi creates and writes this file. We never read it
|
|
323
|
+
directly — it's pi's internal format. We store the path for --continue.
|
|
324
|
+
|
|
325
|
+
### 4.2 Stdin RPC Commands
|
|
326
|
+
|
|
327
|
+
The supervisor sends three types of commands via stdin, all as JSON + newline:
|
|
328
|
+
|
|
329
|
+
**Prompt** (on start):
|
|
330
|
+
```json
|
|
331
|
+
{"type": "prompt", "message": "<rendered task template>"}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
> **Important**: Pi's RPC mode responds to `prompt` immediately with
|
|
335
|
+
> `{"type":"response","command":"prompt","success":true}` — this is an ack that the command
|
|
336
|
+
> was received, NOT a completion signal. The agent starts working asynchronously after this
|
|
337
|
+
> ack. The supervisor must NOT treat the prompt response as completion — it must continue
|
|
338
|
+
> reading stdout events until `agent_end` arrives.
|
|
339
|
+
|
|
340
|
+
**Get state** (after agent_end, before close):
|
|
341
|
+
```json
|
|
342
|
+
{"type": "get_state"}
|
|
343
|
+
```
|
|
344
|
+
Response includes sessionFile, sessionId, isStreaming, model. We extract sessionFile
|
|
345
|
+
to confirm the session path (should match what we passed via --session).
|
|
346
|
+
|
|
347
|
+
**Get last assistant text** (after agent_end, before close):
|
|
348
|
+
```json
|
|
349
|
+
{"type": "get_last_assistant_text"}
|
|
350
|
+
```
|
|
351
|
+
Response: `{"type":"response","command":"get_last_assistant_text","success":true,"data":{"text":"..."|null}}`
|
|
352
|
+
|
|
353
|
+
`data.text` is `null` if no assistant message exists (e.g. pi crashed before responding).
|
|
354
|
+
The supervisor must null-check before writing result.txt — write an empty file or skip
|
|
355
|
+
writing entirely if `text` is null.
|
|
356
|
+
|
|
357
|
+
**Close**: stdin.end() — sends EOF, pi exits. Only called after the above two queries
|
|
358
|
+
complete. This replaces the current stdin.end() hack that fires immediately after prompt.
|
|
359
|
+
|
|
360
|
+
### 4.3 Stdout NDJSON Event Processing
|
|
361
|
+
|
|
362
|
+
Events read from pi's stdout, one JSON object per line. The supervisor categorizes:
|
|
363
|
+
|
|
364
|
+
**Status-updating events** (update status.json current_event + current_tool):
|
|
365
|
+
- thinking_start, thinking_delta → current_event: "thinking"
|
|
366
|
+
- toolcall_start → current_event: "toolcall", current_tool: tool name + args
|
|
367
|
+
- tool_execution_start, tool_execution_update → current_event: "tool_execution"
|
|
368
|
+
- tool_execution_end → current_event: "tool_execution_end"
|
|
369
|
+
- message_update with text_delta → current_event: "text"
|
|
370
|
+
- agent_end → triggers finalization sequence (4.2 above)
|
|
371
|
+
|
|
372
|
+
**JSONL-logged events** (append to events.jsonl):
|
|
373
|
+
- thinking_start (not deltas — just the transition)
|
|
374
|
+
- toolcall_start (with tool name and args summary)
|
|
375
|
+
- tool_execution_end (with tool name and duration)
|
|
376
|
+
- text (first text_delta only — marks transition to output)
|
|
377
|
+
- agent_end (with duration)
|
|
378
|
+
|
|
379
|
+
**Dropped events** (not written anywhere):
|
|
380
|
+
- thinking_delta (high frequency, content not needed)
|
|
381
|
+
- thinking_end (redundant — next event implies it)
|
|
382
|
+
- text_delta content (high frequency, full text available via get_last_assistant_text)
|
|
383
|
+
- message_start (metadata only, captured elsewhere)
|
|
384
|
+
|
|
385
|
+
### 4.4 No stdin.end() on prompt
|
|
386
|
+
|
|
387
|
+
This is the key protocol change from current PiAgentSession. Currently, prompt() calls
|
|
388
|
+
stdin.end() immediately to trigger EOF, which makes every session single-turn. In v3:
|
|
389
|
+
|
|
390
|
+
1. prompt() sends the JSON command via stdin.write(), does NOT close stdin
|
|
391
|
+
2. Supervisor reads NDJSON events from stdout until agent_end
|
|
392
|
+
3. Supervisor sends get_state and get_last_assistant_text queries via stdin
|
|
393
|
+
4. Supervisor calls stdin.end() only after receiving both responses
|
|
394
|
+
5. Pi sees EOF, exits cleanly
|
|
395
|
+
|
|
396
|
+
This enables multi-turn (--continue) because the session file is complete and valid.
|
|
397
|
+
Pi wrote the full conversation to the session JSONL during execution.
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## 5. Schema Changes (.specialist.yaml)
|
|
402
|
+
|
|
403
|
+
### 5.1 New field: skills.paths
|
|
404
|
+
|
|
405
|
+
```yaml
|
|
406
|
+
skills:
|
|
407
|
+
paths: # NEW — pi --skill injection
|
|
408
|
+
- ~/.agents/skills/docker-patterns/ # user-scope skill directory
|
|
409
|
+
- ./skills/mercury-infra/ # project-scope skill directory
|
|
410
|
+
scripts: # EXISTING — unchanged
|
|
411
|
+
- path: ./scripts/check-env.sh
|
|
412
|
+
phase: pre
|
|
413
|
+
inject_output: true
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
skills.paths is an optional array of strings. Each entry becomes a --skill <path> flag
|
|
417
|
+
on the pi command line. Pi loads these as on-demand context (README files, tool
|
|
418
|
+
definitions, etc.) that the model can reference without bloating the initial prompt.
|
|
419
|
+
|
|
420
|
+
Path resolution:
|
|
421
|
+
- ~/... → os.homedir() + rest
|
|
422
|
+
- ./... → path.resolve(cwd, rest)
|
|
423
|
+
- /... → absolute, as-is
|
|
424
|
+
|
|
425
|
+
Validation: paths are validated at specialist load time (loader.ts). Missing paths
|
|
426
|
+
produce a warning log, not a load failure. This allows user-scope skills that may not
|
|
427
|
+
exist on every machine.
|
|
428
|
+
|
|
429
|
+
### 5.2 New field: execution.stall_timeout_ms
|
|
430
|
+
|
|
431
|
+
```yaml
|
|
432
|
+
execution:
|
|
433
|
+
stall_timeout_ms: 30000 # NEW — optional, default: null (disabled)
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
If set, the supervisor checks last_event_at_ms on a 5-second interval. If no event
|
|
437
|
+
received for stall_timeout_ms, writes "stalled": true to status.json. Does NOT auto-kill.
|
|
438
|
+
Surfaces in `specialists status` output.
|
|
439
|
+
|
|
440
|
+
### 5.3 Existing fields — no changes
|
|
441
|
+
|
|
442
|
+
All existing schema fields remain unchanged:
|
|
443
|
+
- metadata (name, version, description, category, tags, updated)
|
|
444
|
+
- execution (mode, model, fallback_model, timeout_ms, response_format, permission_required)
|
|
445
|
+
- prompt (system, task_template, skill_inherit)
|
|
446
|
+
- skills.scripts (path, phase, inject_output)
|
|
447
|
+
- communication (publishes)
|
|
448
|
+
- beads_integration (auto, always, never)
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## 6. Hook Notification
|
|
453
|
+
|
|
454
|
+
### 6.1 Completion hook (NEW — UserPromptSubmit)
|
|
455
|
+
|
|
456
|
+
File: ~/.claude/hooks/specialists-complete.mjs (installed by `specialists install`)
|
|
457
|
+
|
|
458
|
+
Trigger: UserPromptSubmit — fires on every user message in Claude Code.
|
|
459
|
+
|
|
460
|
+
Logic:
|
|
461
|
+
1. Scan .specialists/ready/ for any files
|
|
462
|
+
2. For each file found:
|
|
463
|
+
a. Read .specialists/jobs/<id>/status.json
|
|
464
|
+
b. Format one-line banner
|
|
465
|
+
c. Delete the ready marker file (prevents repeat notifications)
|
|
466
|
+
3. Inject banner(s) into the prompt
|
|
467
|
+
|
|
468
|
+
Banner format:
|
|
469
|
+
```
|
|
470
|
+
[Specialist 'overthinker' completed (job a1b2c3, 1m47s). Run: specialists result a1b2c3]
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Multiple completions produce multiple banners, one per line.
|
|
474
|
+
|
|
475
|
+
If no ready markers exist, hook exits silently (no injection, no overhead).
|
|
476
|
+
|
|
477
|
+
### 6.2 Existing hooks — unchanged
|
|
478
|
+
|
|
479
|
+
The four existing hooks (main-guard, beads-edit-gate, beads-commit-gate, beads-stop-gate)
|
|
480
|
+
are unchanged. The new hook is additive.
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## 7. PiAgentSession Changes
|
|
485
|
+
|
|
486
|
+
### 7.1 Current interface (SessionFactory — 6 methods)
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
start(): Promise<void> // spawn subprocess
|
|
490
|
+
prompt(task: string): Promise<void> // send task, currently closes stdin
|
|
491
|
+
waitForDone(): Promise<void> // resolve on agent_end
|
|
492
|
+
getLastOutput(): Promise<string> // return final text
|
|
493
|
+
kill(): void // SIGTERM + cleanup
|
|
494
|
+
meta: { backend, model, sessionId, startedAt }
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### 7.2 Modified interface
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
start(): Promise<void> // spawn subprocess (unchanged)
|
|
501
|
+
prompt(task: string): Promise<void> // send RPC prompt command, DO NOT close stdin
|
|
502
|
+
waitForDone(timeout?: number): Promise<void> // resolve on agent_end, with timeout
|
|
503
|
+
getLastOutput(): Promise<string> // send get_last_assistant_text RPC, return response
|
|
504
|
+
getState(): Promise<SessionState> // send get_state RPC, return session info
|
|
505
|
+
close(): Promise<void> // send EOF (stdin.end()), wait for process exit
|
|
506
|
+
kill(): void // SIGTERM + cleanup (unchanged, for abort/cancel)
|
|
507
|
+
meta: { backend, model, sessionId, startedAt }
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Changes:
|
|
511
|
+
1. prompt() sends {"type": "prompt", "message": task} via stdin.write(), no stdin.end()
|
|
512
|
+
2. waitForDone() adds optional timeout parameter — Promise.race with timeout
|
|
513
|
+
3. getLastOutput() sends {"type": "get_last_assistant_text"} via stdin, parses response
|
|
514
|
+
4. getState() NEW — sends {"type": "get_state"} via stdin, parses response
|
|
515
|
+
5. close() NEW — calls stdin.end(), waits for process exit event
|
|
516
|
+
6. kill() unchanged — SIGTERM, idempotent
|
|
517
|
+
|
|
518
|
+
The supervisor sequence becomes:
|
|
519
|
+
```
|
|
520
|
+
await session.start()
|
|
521
|
+
await session.prompt(renderedTask)
|
|
522
|
+
await session.waitForDone(timeoutMs) // blocks until agent_end or timeout
|
|
523
|
+
const output = await session.getLastOutput() // RPC query
|
|
524
|
+
const state = await session.getState() // RPC query
|
|
525
|
+
await session.close() // EOF → clean exit
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### 7.3 NDJSON handling — unchanged
|
|
529
|
+
|
|
530
|
+
The manual line buffer (_lineBuffer + split on \n + process complete lines) is correct
|
|
531
|
+
and handles large payloads (100KB+ agent_end events). No change needed. The only
|
|
532
|
+
difference: agent_end no longer triggers stdin.end(). It resolves the waitForDone()
|
|
533
|
+
promise, and the supervisor decides when to query state and close.
|
|
534
|
+
|
|
535
|
+
### 7.4 RPC response handling — new
|
|
536
|
+
|
|
537
|
+
Currently, PiAgentSession only reads events from stdout. It never sends commands via
|
|
538
|
+
stdin (except the implicit single prompt via the CLI args). With the new protocol:
|
|
539
|
+
|
|
540
|
+
After agent_end, stdin is still open. The supervisor sends JSON commands and reads
|
|
541
|
+
responses from stdout. Responses are JSON objects with type: "response", keyed by the
|
|
542
|
+
command type. The existing NDJSON line reader handles these — they arrive as regular
|
|
543
|
+
JSON lines on stdout.
|
|
544
|
+
|
|
545
|
+
Parsing:
|
|
546
|
+
```typescript
|
|
547
|
+
// After agent_end, switch to command-response mode
|
|
548
|
+
async sendCommand(cmd: object): Promise<any> {
|
|
549
|
+
const line = JSON.stringify(cmd) + '\n';
|
|
550
|
+
this.proc.stdin.write(line);
|
|
551
|
+
// Wait for next complete JSON line on stdout that is a response
|
|
552
|
+
return new Promise((resolve) => {
|
|
553
|
+
this._pendingCommand = resolve;
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
The _handleLine method checks: if the parsed JSON has type === "response", resolve
|
|
559
|
+
_pendingCommand. Otherwise, process as a normal event.
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
563
|
+
## 8. SpecialistRunner Changes
|
|
564
|
+
|
|
565
|
+
### 8.1 Background mode support
|
|
566
|
+
|
|
567
|
+
SpecialistRunner.run() currently blocks until completion. For --background, the CLI's
|
|
568
|
+
run command handles the lifecycle — it calls runner.run() with callbacks that write to
|
|
569
|
+
files instead of streaming to stdout.
|
|
570
|
+
|
|
571
|
+
The runner itself does NOT need a "background mode". The CLI command is the supervisor.
|
|
572
|
+
Runner provides the execution; the CLI provides the lifecycle management.
|
|
573
|
+
|
|
574
|
+
### 8.2 Session path passthrough
|
|
575
|
+
|
|
576
|
+
Runner must accept an optional sessionPath in PiSessionOptions and pass it through to
|
|
577
|
+
PiAgentSession.start() as the --session flag. For --continue, the CLI reads the session
|
|
578
|
+
path from the previous job's status.json and passes it to the runner.
|
|
579
|
+
|
|
580
|
+
### 8.3 Skills paths passthrough
|
|
581
|
+
|
|
582
|
+
Runner must accept an optional skillPaths: string[] in PiSessionOptions and pass each
|
|
583
|
+
as a --skill flag. Skills paths come from the specialist's YAML config (skills.paths),
|
|
584
|
+
resolved by the loader at discovery time.
|
|
585
|
+
|
|
586
|
+
### 8.4 Timeout enforcement
|
|
587
|
+
|
|
588
|
+
Runner should pass execution.timeout_ms to session.waitForDone(timeout). On timeout,
|
|
589
|
+
call session.kill() and throw a TimeoutError. Circuit breaker records the failure.
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
## 9. MCP Server Changes
|
|
594
|
+
|
|
595
|
+
### 9.1 Retained tools
|
|
596
|
+
|
|
597
|
+
**specialist_init**: Unchanged. Returns available specialists and beads status.
|
|
598
|
+
|
|
599
|
+
**list_specialists**: Unchanged. Pure read.
|
|
600
|
+
|
|
601
|
+
**specialist_status**: Modified — add job summary from .specialists/jobs/*/status.json.
|
|
602
|
+
Returns circuit breaker health + active/recent job list.
|
|
603
|
+
|
|
604
|
+
**use_specialist**: Unchanged. Synchronous execution for quick tasks. Calls runner.run()
|
|
605
|
+
directly. Appropriate for sub-15-second specialists (init-session, report-generator).
|
|
606
|
+
|
|
607
|
+
### 9.2 Deprecated tools
|
|
608
|
+
|
|
609
|
+
**start_specialist**: Deprecated. Replaced by `specialists run --background` via bash.
|
|
610
|
+
Keep functional for backward compatibility but add a note in the tool description
|
|
611
|
+
suggesting the CLI alternative.
|
|
612
|
+
|
|
613
|
+
**poll_specialist**: Deprecated. Replaced by `specialists status` and
|
|
614
|
+
`specialists result` via bash.
|
|
615
|
+
|
|
616
|
+
**stop_specialist**: Deprecated. Replaced by `specialists stop` via bash.
|
|
617
|
+
|
|
618
|
+
**run_parallel**: Deprecated. Claude can run multiple `specialists run --background &`
|
|
619
|
+
commands. Or use the synchronous path for quick parallel tasks via use_specialist.
|
|
620
|
+
|
|
621
|
+
### 9.3 SIGTERM handler (NEW)
|
|
622
|
+
|
|
623
|
+
```typescript
|
|
624
|
+
process.on('SIGTERM', () => {
|
|
625
|
+
// Kill all running pi subprocesses managed by use_specialist (sync path)
|
|
626
|
+
registry.getAllRunning().forEach(job => job.killFn?.());
|
|
627
|
+
process.exit(0);
|
|
628
|
+
});
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
Only covers the MCP server's own subprocess (use_specialist sync path). Background
|
|
632
|
+
jobs are owned by their supervisor processes, not the MCP server.
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
636
|
+
## 10. Operational Concerns
|
|
637
|
+
|
|
638
|
+
### 10.1 .gitignore
|
|
639
|
+
|
|
640
|
+
`specialists init` must add `.specialists/` to .gitignore. Job files can contain full
|
|
641
|
+
specialist output, session history, and environment variables from pre-scripts.
|
|
642
|
+
|
|
643
|
+
### 10.2 Cleanup / GC
|
|
644
|
+
|
|
645
|
+
On `specialists run --background` start, before creating the new job directory:
|
|
646
|
+
scan .specialists/jobs/ and delete any directory older than 7 days. Configurable
|
|
647
|
+
via SPECIALISTS_JOB_TTL_DAYS env var. Simple fs.stat + rm -rf.
|
|
648
|
+
|
|
649
|
+
### 10.3 Beads crash recovery
|
|
650
|
+
|
|
651
|
+
On `specialists run --background` start (same GC scan): check for jobs with
|
|
652
|
+
status "running" that have no live process (PID check). Close their beads as ERROR.
|
|
653
|
+
Mark status as "error" with error: "MCP server or supervisor crashed".
|
|
654
|
+
|
|
655
|
+
### 10.4 File atomicity
|
|
656
|
+
|
|
657
|
+
status.json: written via write-to-temp + fs.rename. Updated frequently (every lifecycle
|
|
658
|
+
event), must never be half-written.
|
|
659
|
+
|
|
660
|
+
events.jsonl: written via fs.appendFile with O_APPEND. Atomic for writes < 4KB on Linux.
|
|
661
|
+
Event lines are well under 4KB.
|
|
662
|
+
|
|
663
|
+
result.txt: written once via write-to-temp + fs.rename. Single write on completion.
|
|
664
|
+
|
|
665
|
+
### 10.5 File descriptor management
|
|
666
|
+
|
|
667
|
+
The supervisor opens events.jsonl for append at job start and keeps the fd open for the
|
|
668
|
+
duration. On completion, error, or cancel: fd.close() is called in a finally block.
|
|
669
|
+
On supervisor crash: OS closes the fd automatically.
|
|
670
|
+
|
|
671
|
+
---
|
|
672
|
+
|
|
673
|
+
## 11. Migration Path
|
|
674
|
+
|
|
675
|
+
### Phase 1: File layout + background mode (HIGH VALUE, LOW RISK)
|
|
676
|
+
|
|
677
|
+
1. Create .specialists/ directory structure
|
|
678
|
+
2. Add --background flag to `specialists run`
|
|
679
|
+
3. Implement supervisor lifecycle (status.json, events.jsonl, result.txt)
|
|
680
|
+
4. Add `specialists status` job listing (reads status.json files)
|
|
681
|
+
5. Add `specialists result` command
|
|
682
|
+
6. Add `specialists feed` command
|
|
683
|
+
7. Add `specialists stop` command (CLI)
|
|
684
|
+
8. Add .gitignore entry in `specialists init`
|
|
685
|
+
9. Add GC on startup
|
|
686
|
+
|
|
687
|
+
Does NOT touch: PiAgentSession internals, MCP tools, SpecialistRunner.
|
|
688
|
+
Uses existing PiAgentSession with its current limitations (single-turn only, no RPC
|
|
689
|
+
queries post-completion). result.txt is populated from the existing getLastOutput()
|
|
690
|
+
which reads from the in-memory _lastOutput captured at agent_end.
|
|
691
|
+
|
|
692
|
+
This phase delivers: background execution, lightweight status polling via bash,
|
|
693
|
+
full output retrieval, feed/debugging CLI. Everything except multi-turn and RPC queries.
|
|
694
|
+
|
|
695
|
+
### Phase 2: PiAgentSession protocol upgrade (MEDIUM VALUE, MEDIUM RISK)
|
|
696
|
+
|
|
697
|
+
1. Remove stdin.end() from prompt() — send RPC JSON command instead
|
|
698
|
+
2. Add waitForDone(timeout) with Promise.race
|
|
699
|
+
3. Add getState() RPC command
|
|
700
|
+
4. Add getLastOutput() via RPC (replaces in-memory capture)
|
|
701
|
+
5. Add close() method (explicit EOF)
|
|
702
|
+
6. Add --session flag passthrough
|
|
703
|
+
7. Add --skill flag passthrough
|
|
704
|
+
8. Add stall detection (in-process setInterval in supervisor)
|
|
705
|
+
9. Update SpecialistRunner for sessionPath and skillPaths options
|
|
706
|
+
|
|
707
|
+
Prerequisite: verify that pi --mode rpc accepts JSON commands on stdin (not just
|
|
708
|
+
CLI args). The audit's PiAgentSession code and pi's RPC docs both indicate this works,
|
|
709
|
+
but test before implementing.
|
|
710
|
+
|
|
711
|
+
This phase delivers: multi-turn via --continue, session persistence, skill injection,
|
|
712
|
+
timeout enforcement, stall detection.
|
|
713
|
+
|
|
714
|
+
### Phase 3: Hook notification + MCP deprecation (LOW VALUE, LOW RISK)
|
|
715
|
+
|
|
716
|
+
1. Create specialists-complete.mjs hook
|
|
717
|
+
2. Add to `specialists install` hook deployment
|
|
718
|
+
3. Add deprecation notes to start_specialist, poll_specialist, stop_specialist,
|
|
719
|
+
run_parallel tool descriptions
|
|
720
|
+
4. Add SIGTERM handler to MCP server
|
|
721
|
+
|
|
722
|
+
### Phase 4: Schema + built-in specialist updates
|
|
723
|
+
|
|
724
|
+
1. Add skills.paths to schema (zod validation, loader resolution)
|
|
725
|
+
2. Add execution.stall_timeout_ms to schema
|
|
726
|
+
3. Update built-in specialists to use skills.paths where appropriate
|
|
727
|
+
4. Create example skill directories in ~/.agents/skills/
|
|
728
|
+
|
|
729
|
+
---
|
|
730
|
+
|
|
731
|
+
## 12. Testing Strategy
|
|
732
|
+
|
|
733
|
+
### Phase 1 tests
|
|
734
|
+
|
|
735
|
+
- Supervisor lifecycle: mock PiAgentSession, verify status.json written at each stage
|
|
736
|
+
- status.json atomicity: concurrent write + read, verify no partial JSON
|
|
737
|
+
- events.jsonl: verify correct events logged, no thinking deltas
|
|
738
|
+
- result.txt: verify written only on completion, not on error/cancel
|
|
739
|
+
- GC: create old job dirs, verify cleanup
|
|
740
|
+
- specialists status: verify reads from file, formats correctly
|
|
741
|
+
- specialists result: verify returns result.txt content, error if still running
|
|
742
|
+
- specialists stop: verify SIGTERM sent to correct PID
|
|
743
|
+
|
|
744
|
+
### Phase 2 tests
|
|
745
|
+
|
|
746
|
+
- RPC prompt command: verify JSON sent via stdin, stdin NOT closed
|
|
747
|
+
- RPC get_state: verify command sent after agent_end, response parsed
|
|
748
|
+
- RPC get_last_assistant_text: verify command sent, response parsed
|
|
749
|
+
- close(): verify stdin.end() called, process exit awaited
|
|
750
|
+
- waitForDone(timeout): verify timeout triggers kill + rejection
|
|
751
|
+
- Stall detection: verify stalled flag set after configurable interval
|
|
752
|
+
- --session flag: verify passed to pi subprocess args
|
|
753
|
+
- --skill flag: verify each skills.paths entry becomes a --skill arg
|
|
754
|
+
- --continue: verify session path read from previous job, passed to pi
|
|
755
|
+
|
|
756
|
+
### Phase 1 does NOT require
|
|
757
|
+
|
|
758
|
+
- Integration tests against real pi subprocess (existing mock-based tests cover this)
|
|
759
|
+
- MCP tool handler tests (tools are unchanged in Phase 1)
|
|
760
|
+
- NDJSON parser tests (parser is unchanged in Phase 1)
|
|
761
|
+
|
|
762
|
+
---
|
|
763
|
+
|
|
764
|
+
## 13. Verification Checklist
|
|
765
|
+
|
|
766
|
+
All items verified against pi-mono source (packages/coding-agent) and pi --help output.
|
|
767
|
+
|
|
768
|
+
- [x] `pi --mode rpc` accepts {"type": "prompt", "message": "..."} on stdin
|
|
769
|
+
— confirmed: rpc-mode.ts handleCommand case "prompt"
|
|
770
|
+
- [x] `pi --session /arbitrary/path/session.jsonl` accepts paths outside ~/.pi/
|
|
771
|
+
— confirmed: main.ts resolveSessionPath() treats any arg containing "/" as a direct path
|
|
772
|
+
- [x] `pi --append-system-prompt "..."` exists and works as expected
|
|
773
|
+
— confirmed: pi --help
|
|
774
|
+
- [x] `pi --skill <path>` loads skill directories correctly, repeatable
|
|
775
|
+
— confirmed: pi --help
|
|
776
|
+
- [x] After agent_end, pi accepts further stdin commands before stdin is closed
|
|
777
|
+
— confirmed: runRpcMode() never exits on agent_end; loop runs until stdin EOF
|
|
778
|
+
- [x] `get_state` response includes sessionFile field
|
|
779
|
+
— confirmed: rpc-mode.ts get_state handler: sessionFile: session.sessionFile
|
|
780
|
+
- [x] `get_last_assistant_text` returns clean text
|
|
781
|
+
— confirmed: agent-session.ts getLastAssistantText(); returns null (not empty string)
|
|
782
|
+
when no assistant message exists — null-guard required before writing result.txt
|
|
783
|
+
|
|
784
|
+
**Additional findings from pi-mono source audit:**
|
|
785
|
+
|
|
786
|
+
- `--no-input` does not exist in pi — removed from spawn args (section 4.1)
|
|
787
|
+
- `prompt` RPC command responds with an immediate ack, not a completion signal — supervisor
|
|
788
|
+
must keep reading stdout until agent_end (see section 4.2 note)
|
|
789
|
+
- `--no-extensions` required to prevent user extensions from hanging the supervisor
|
|
790
|
+
via unanswered extension_ui_request events (see section 4.1)
|
|
791
|
+
- Pi's `--tools` default (`read,bash,edit,write`) excludes grep/find/ls — all tiers now
|
|
792
|
+
use explicit tool lists in mapPermissionToTools() (see section 4.1 table)
|