Skip to content

SPEC-3 — cross-harness peers (CC + Pi backends, profile routing, session_key resume) - #4

Merged
rz1989s merged 16 commits into
mainfrom
feat/spec-3-cross-harness-peers
Jul 24, 2026
Merged

rz1989s merged 16 commits into
mainfrom
feat/spec-3-cross-harness-peers

Conversation

@rz1989s

@rz1989s rz1989s commented Jul 24, 2026

Copy link
Copy Markdown
Member

SPEC-3 — Cross-harness peers (CC + Pi backends)

Makes the fleet dual-arsenal: a subagent run targets one of two backends (Pi or Claude Code) chosen by the agent profile's backend frontmatter field. The moat translates into CC via prompt/flag mechanisms (vision gap declared), backend-native session_key resume works in both, and a new /fleet Backends view shows availability, version, schema, and per-backend hook parity.

Implements specs/SPEC-3-cross-harness-peers.md (15 sections + decision log) via plans/SPEC-3-cross-harness-peers.md (14 TDD tasks). Spec + plan committed to main first (977b54c, 949a587); implementation on this branch.

Decisions (brainstorm)

  • Q1=B graceful degradation — per-backend armory chip (t✓ m✓ v~ CC vs t✓ m✓ v✓ Pi); vision pass-through-only gap declared
  • Q2=A profile pins backend; fan-out = two profiles; engine unchanged, no backend tool param
  • Q3=A backend-native resume; sessionKey per profile; backendSessionId on run; SessionManager.open / claude --resume
  • Q4=A streaming claude -p --output-format stream-json; detectClaude() version + schema smoke + flag-support probe at init; fail-loud at the backend
  • Q5=B ChildSessionFactory stays the seam; BackendRegistry + Backend descriptor hold metadata (YAGNI against a BackendPort)

What's new

  • src/backend/BackendRegistry + Backend descriptor, BackendHookParity (declared), ResumeStore (file-backed sessionKey → backendSessionId), claude-events (NDJSON → ChildSessionEvent), claude-detector (version + schema smoke + flag-support probe), ClaudeChildSession (child-process adapter), createClaudeChildFactory
  • src/engine/spawnSubagent.tsChildSessionEvent.backendSessionId + RunRecord.backendSessionId/sessionKey + SpawnOptions.childFactorybackendRegistry (engine routes by agentDef.backend, fails fast when unavailable)
  • src/registry/frontmatter.tsbackend (default pi) + sessionKey (default = name) fields + validation
  • src/panel/ — new Backends view (read-only; r:Refresh + i:Info) + Agents-view [backend] badge
  • agents/general-purpose-cc.md — builtin sibling to general-purpose (day-one dual-arsenal)
  • src/index.tsdetectClaude() at init; builds BackendRegistry; registers pi (always) + claude (availability reflects detection); Pi factory file-backed SessionManager + session_init emission (the one SPEC-2 module touched — recorded in §3.1)

Done bar (v0.3)

  • backend: claude profile spawns a real claude -p child through the CC factory; routed via BackendRegistry; tracked in armory-todo like any Pi run
  • ✅ Memory hydration in CC (baked into --append-system-prompt); todo excluded (--disallowed-tools + prompt-nudge fallback); vision pass-through-only (v~ declared + visible in chip)
  • session_key resume in both backends (SessionManager.open / claude --resume); stale-id fallback to fresh run with visible warning
  • /fleet Backends view + Agents-view backend badge; claude absent/schema-drifted → fail fast at spawn
  • ✅ Day-one dual-arsenal: general-purpose (pi) + general-purpose-cc (claude)

Verification

  • pnpm typecheck clean
  • pnpm test:run106/106 pass (65 prior + 41 new across 11 new test files)
  • node --import tsx scripts/spec-3-smoke.mts4/4 (pi row via real Ollama Cloud; CC rows via real claude -p stream-json — wiring end-to-end: detect → spawn → NDJSON parse → session_id capture → resume)
  • No AI attribution in commits/files

Note on CC execution

RECTOR's claude CLI OAuth is currently expired, so real CC task output is an auth-failure message — that's an environment issue to re-auth (claude login), not a fleet bug. The fleet wiring (detection, spawning, NDJSON parsing, session_id capture, resume) is verified end-to-end by the smoke.

Deferred (recorded in spec §12)

Fan-out synthesis (SPEC-6); per-spawn backend override (SPEC-6); cross-backend resume (never); BackendPort lift (when 4th backend lands); Codex (post-v1); per-backend model list (SPEC-5b); inline Backends editing (SPEC-6); async CC runs (SPEC-5a); cost accounting (SPEC-6).

Release

After merge, tag v0.3.0release.yml publishes @getpipher/armory-fleet@0.3.0 + creates the GitHub Release (mirrors the v0.2.0 flow).

rz1989s added 16 commits July 24, 2026 14:39
…, session_key resume)

Brainstorming output (5 Q&A + design §1-§7). Decisions:
- Q1=B graceful degradation; per-backend armory chip (t✓ m✓ v~ for CC vs t✓ m✓ v✓ for Pi)
- Q2=A profile pins backend; fan-out = two profiles; engine unchanged
- Q3=A backend-native resume; sessionKey per profile; backendSessionId on run
- Q4=A streaming claude -p --output-format stream-json; version-detect at adapter; fail-loud at backend
- Q5=B ChildSessionFactory stays the seam; BackendRegistry + Backend descriptor hold metadata

15 sections + decision log. Additive only: ~8 new src files, 4 mod, 1 new builtin, 1 new smoke script. Pi factory + all SPEC-2 modules untouched except the one-line inMemory→file-backed SessionManager for resume.
Implementation plan for SPEC-3. Tasks (bottom-up, each independently testable):
1. BackendHookParity + Backend + BackendRegistry
2. ResumeStore (file-backed sessionKey→backendSessionId)
3. Frontmatter backend + sessionKey
4. Engine: ChildSessionEvent.backendSessionId + runRecord fields + backendRegistry routing
5. claude-events NDJSON → ChildSessionEvent mapper
6. detectClaude (version + stream-json schema smoke + flag-support probe)
7. ClaudeChildSession (ChildSession over claude -p child process)
8. createClaudeChildFactory (compose invocation, memory-in-prompt, resume)
9. Pi factory file-backed SessionManager + session_init emission (the one SPEC-2 module touched)
10. general-purpose-cc builtin + discovery backend-validation
11. /fleet Backends view + Agents-view backend badge
12. index.ts wiring (BackendRegistry + detectClaude at init)
13. Real-pi smoke script + term-driven checklist
14. CI gate — typecheck + full suite + release.yml staging

Self-reviewed: spec coverage complete, no placeholders, type consistency verified, the one runtime unknown (exact claude -p flag set) resolved by detectClaude() at init.
…runRecord

SpawnOptions.childFactory → backendRegistry; engine looks up backend by
agentDef.backend + fails fast when unavailable. ChildSessionEvent gains
backendSessionId; RunRecord gains backendSessionId + sessionKey. The
session_init event stamps the run record for resume.

Ripples to the tool + panel + index wiring (pulled forward from Task 12 to
keep typecheck green per-task): SubagentToolDeps + FleetPanelDeps now carry
backendRegistry; index.ts builds a minimal pi-only BackendRegistry (Task 12
adds claude detection). Existing tests inject a registry wrapping their
fakes via a regWith() helper.
…ough tool+panel

buildDefaultBackendRegistry now runs detectClaude() and registers the CC
backend (availability reflects detection; registered regardless so the
Backends view shows it). Pi factory + CC factory share a single ResumeStore.
The deps construction awaits the registry build (claude detection is async).
Detector schema smoke now scans ALL stream-json lines for the system/init event
(real CC emits hook_started/hook_response system events before init; first-line-only
would false-fail). The factory's claude invocation includes --verbose (CC requires it
with --output-format stream-json).

Smoke verified: 4/4 rows pass (pi row 2 via real Ollama Cloud; CC rows 3-4 via real
claude -p stream-json — wiring end-to-end: detect → spawn → NDJSON parse → session_id
capture → resume). NOTE: RECTOR's claude OAuth is expired, so CC task output is an
auth-failure message; the fleet wiring is correct, the auth is an env issue to re-auth.
…ildSession stdin guard

Code review of PR #4 (self-review, requesting-code-review skill):
- detectClaude: run() error handler now captures err.message into stderr so
  the ENOENT check works when the default 'claude' is not on PATH (previously
  returned a misleading 'version failed' object instead of null). Added a test
  that empties PATH to force the ENOENT path.
- ClaudeChildSession.prompt: guard against null proc.stdin (would hang the
  engine's await session.prompt() forever); throws an actionable error instead.

Other observations noted as acceptable-for-v0.3 (not fixed): CC 'result' with
is_error:true maps to turn_end per spec §4.2 (auth failure surfaces as finalText);
positional-task + stdin double-pass is a harmless smell the smoke verifies works.
@rz1989s
rz1989s merged commit 5bb75fb into main Jul 24, 2026
1 check passed
@rz1989s
rz1989s deleted the feat/spec-3-cross-harness-peers branch July 24, 2026 08:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant