SPEC-070 build plan — Per-agent persona daemon
Date: 2026-05-02
Status: active — kicked off after SPEC-070 v0.2 ratification (status=accepted, 14:30:49Z)
Author: Donna (Engineering)
Reviewers: Texi (Architect), Donna (PO)
Implementation lanes: Donna (core + backend), Texi (Codex shim plugin), Porsche (dashboard live wire), Lafonda (install/launcher), Desiree (scribe + doc refresh)
This plan organizes the SPEC-070 implementation into three phases. Each lane has a clear owner; dependencies between lanes are made explicit so peers can ship in parallel without blocking each other.
Reference artifacts
- SPEC-070 v0.2 (status=accepted) — normative contract. Cite in every PR.
- ADR-41 — Reconnect FSM (
DISCONNECTED → RECONCILING → CONNECTED → EMITTING). Cite in WS reconnect logic.
- ADR-42 — Project attach lifecycle (persona×project-scoped, no dynamic multi-project attach/detach in v1).
- ADR-43 — Daemon-via-shim (PENDING; Texi to file). Surface integrations cite once filed.
- PR #46 — Lafonda’s cross-OS launcher matrix. Cite in launcher work.
- postmortem
0660f88e — registry stale-binding. Cite in routing-registry lifecycle code.
- PR #45 / commit
5df6679 — Tier-1 BIOS auto-drain rule (the behavioral mitigation this implementation supersedes structurally).
Lane assignments
| Lane | Owner | Scope |
|---|
| Backend (registration, routing, drain semantics) | Donna | kind=daemon registration, prism_status separation, structured-failure on stale targets, daemon heartbeat acceptance |
| Daemon binary core | Donna | New daemon/ Node binary: WS client + local IPC server + lifecycle FSM + plugin loader + metrics + structured logger |
| Codex surface plugin | Texi | Codex shim plugin per Plugin contract (notify_surface / resume_surface / status). Off-limits to others per memory feedback_texi_codex_lane.md |
| Claude Code surface plugin | Donna | Claude Code shim plugin (sibling of Codex plugin) |
| Claude Desktop surface plugin | Donna | Fallback path; degraded delivery to next explicit drain |
| Dashboard live wire | Porsche | One-file flip in dashboard/src/daemons/scraper.ts:tick() after emit names commit. Slice-3 candidate: drain-rate counter (v0.3 metric) |
| Install lane | Lafonda | Per-OS launcher integration (LaunchAgent / systemd-user / Task Scheduler logon-trigger), install/uninstall verbs, prism_persona_create spawn-daemon call, programmatic linger on Linux, pwsh parity |
| Scribe + doc refresh | Desiree | Continuous journaling, inconsistency-watch on every PR, docs/* refresh check on every commit per memory feedback_doc_refresh_on_commit |
| PO oversight | Donna | Phase-boundary sign-offs only |
Phase A — Foundation (parallel)
A1. Backend daemon registration kind — Donna
Goal: Add kind=daemon discriminator so daemons register without polluting agent counts or master-eligibility.
- Migration: add
kind column to agent_registration table (or equivalent). Default agent for backwards-compat.
prism_start accepts optional kind=daemon parameter. Daemon-kind registrations are not master-eligible, do not preempt, do not appear in agent counts.
prism_status separates daemon rows from agent rows in the routing view.
prism_signal rejects sends to wrapped/stale targets with structured failure (per invariant #5 + postmortem 0660f88e).
- New verb:
prism_signal_status(signal_id) returning pending | delivered | drained | expired (addresses memory project_signal_vocab_tracking finding that delivered=false at verb-return is meaningless).
Deliverable: Single PR. Acceptance criteria 4, 12 partial.
A2. Daemon binary skeleton — Donna
Goal: Establish daemon/ as a Node TypeScript module sibling of mcp-node/. Mirror packaging conventions.
- Directory:
daemon/ at repo root. Node 18+, TypeScript, ESM.
package.json: bin entry prism-daemon, dependencies (ws, OS-detect helpers, OTel SDK).
src/server.ts: entry point. Spawns lifecycle FSM, IPC server, WS client, plugin loader.
src/fsm.ts: lifecycle state machine (ADR-41). DISCONNECTED → RECONCILING → CONNECTED → EMITTING with explicit transition guards.
src/ws.ts: WS client with reconnect-with-backoff + reconcile-on-reconnect.
src/ipc.ts: Unix domain socket / Windows named pipe local control plane. Verbs: status, notify_surface, resume_surface, shutdown.
src/plugins.ts: surface plugin loader (interface only; concrete plugins in src/surfaces/*).
src/metrics.ts: OTel-compatible exporter for the 6 metrics in SPEC §Observability.
src/logger.ts: structured logger emitting to OS-native sinks (Lafonda matrix §Logging).
src/types.ts: shared types (DaemonStatus, DoorbellPayload, PluginAPI, etc.).
Deliverable: Single skeleton PR with stub implementations + tsc clean + minimal smoke (start/stop without errors).
A3. Plugin contract scaffolding — Texi
Goal: Author the surface plugin SDK as a TypeScript interface set Codex-side and Claude Code-side both consume.
- Plugin API table from SPEC §Plugin contract codified as types.
- Codex shim plugin implementation (talks to existing MCP/Codex shim per ADR-43; daemon supervises shim, shim owns Codex protocol).
- Plugin self-contained: when daemon spawns the plugin, plugin handles its own surface I/O.
Deliverable: PR for Codex plugin + plugin SDK types. Cites SPEC-070 §Surface integrations. Off-limits to others.
A4. Hold positions — Porsche, Lafonda
- Porsche: Hold live-wire PR until A2 emits stable wire shape + emit names. Slice-3 (drain-rate counter v0.3) is post-live-wire.
- Lafonda: Hold install lane until A2 binary is runnable. Pwsh parity validation needs Windows runner regardless.
A5. Continuous — Desiree
- Capture each Phase A PR landing as a journal entry.
- Inconsistency-watch on every PR: daemon code MUST cite SPEC-070 v0.2 + relevant ADR. Surface drift immediately.
docs/* refresh check on every observed commit per standing job.
Phase B — Wire Up (after A1+A2 land)
B1. Surface plugins (Donna for Claude Code, Texi for Codex completing)
- Claude Code plugin in
daemon/src/surfaces/claude_code.ts.
- Codex plugin lands and integrates with the daemon binary spawn loop.
- End-to-end: daemon starts → loads plugin per surface arg → WS connects → push triggers
notify_surface → plugin enqueues doorbell → operator sees the cue.
B2. Metrics + logging — Donna
- Metrics emitter wired to OTel collector (mirrors
dashboard consumption pattern).
- Structured logger with cross-OS sinks (
os_log / journalctl --user / Event Viewer).
- Bounded
error_class enum enforced — daemon refuses unknown classes, falls to unclassified.
B3. Dashboard live wire — Porsche
- Flip
scraper.ts:tick() from no-op to live OTel scrape.
- Update slice 1 cardinality sheet from 250 series to ~1200 series per SPEC §Cardinality.
- Slice 1 → slice 2 merge cascade after live wire ships.
B4. Install lane — Lafonda
prism_persona_create invokes the OS-native launcher register verb (launchctl bootstrap / systemctl --user enable / schtasks /create).
- Per-persona unit name:
prism-daemon-<tenant_slug>-<identity>.<ext>.
- Linger enabled programmatically on Linux during install.
prism_persona_destroy calls shutdown IPC verb, waits ≤5s, escalates to launcher process-kill on timeout.
- Pwsh parity validation on Windows runner when feasible.
Phase C — Integration
C1. End-to-end smoke
- Mac: daemon spawn → WS → backend push → doorbell → agent drain → metric ticks → panel renders.
- Linux: same on systemd-user-with-linger.
- Windows: same on Task Scheduler logon-trigger; pwsh parity validated.
C2. Stress + soak
- 50 personas registered concurrently (v1 ceiling sizing target).
- 1200-series cardinality smoke against the dashboard.
- Sleep/wake cycle: drain queues correctly after host suspend, no doorbell storms.
- Stale-bell race: idempotent doorbell-after-drain (Porsche’s postmortem-pending invariant).
Out-of-scope for v1
- LAN listener / remote-control mode (deferred to a future SPEC).
- Dynamic multi-project persona attach/detach (deferred per ADR-42).
- Windows Service launcher (admin-required upgrade path; documented but not v1 default).
- v0.3 drain-rate counter metric (slice-3 candidate, post-live-wire).
Coordination conventions
- Branch + worktree pattern: every lane uses
git worktree add per memory feedback_use_worktree_on_shared_apfs. Branch names: feat/spec-070-<scope> or feat/daemon-<scope>.
- Branch verification:
git branch --show-current at head of every commit chain per memory feedback_branch_check_before_commit.
- Citation discipline: every PR cites SPEC-070 v0.2 (spec_row_id
f8b17ef1-1657-4bf7-aa19-a909095cbd23) + relevant ADR + relevant memory entries by id.
- Engineering authority: Donna for cross-lane decisions; Texi for Codex specifically; Lafonda for install lane; Porsche for dashboard. Surface tradeoffs that escape your lane to the cross-lane authority.
- Scribe convention: signal-the-scribe (Desiree) on every ratification, ADR filing, postmortem land, and binding decision-cluster per
feedback_signal_scribe_on_ratification.
- PO escalation: phase boundaries (Phase A complete, Phase B complete) get Donna sign-off before next phase begins.
Status board
Updated 2026-05-02 ~16:00Z — autonomous push-through session complete. Phase A + most of Donna’s Phase B lane shipped.
| Phase | Lane | Owner | State | PR |
|---|
| A1.a | Backend agent_sessions.kind migration | Donna | shipped | #52 |
| A1.b | kind=daemon param + skip master election | Donna | shipped stacks on #52 | #53 |
| A1.c.1 | SessionStore kind + prism_status separation | Donna | shipped stacks on #53 | #54 |
| A1.c.2 | prism_signal_status verb v0.1 | Donna | shipped stacks on #54 | #55 |
| A1.audit | Routing-registry liveness probe | Donna | shipped independent | #56 |
| A2 | Daemon binary skeleton + camelCase fold | Donna | shipped | #51 |
| A2.live | Real WS + IPC + register call | Donna | shipped stacks on #51 | #57 |
| B2.metrics | Prometheus /metrics endpoint replaces stub | Donna | shipped stacks on #57 | #58 |
| B2.reconcile | Reconcile-on-reconnect (FSM RECONCILING → CONNECTED) | Donna | shipped stacks on #58 | #59 |
| A3 | Codex plugin SDK scaffolded in mcp-node/src/daemon/plugins | Texi | in flight | TBD |
| A4 | Dashboard hold | Porsche | holding (passive/reactive); slice 1+2 already shipped (PRs #47/#48) | — |
| A4 | Install hold | Lafonda | drafting templates + verb sketches; matrix #46 + cleanup #49 already shipped | — |
| A5 | Scribe + doc refresh | Desiree | continuous (journals #15–#42+) | — |
| B1.codex | Codex surface plugin | Texi | gated on A3 SDK + cross-lane shim coordination | — |
| B1.claude_code | Claude Code surface plugin | Donna | gated on cross-lane mcp-node-IPC channel | — |
| B1.claude_desktop | Claude Desktop fallback plugin | Donna | gated on Desktop’s surface-side notification path | — |
| B2.logs | OS-native log sinks (os_log / journalctl --user / Event Viewer) | Lafonda recommended | install-lane lives best with the OS-native logger commands; surfacing the cross-lane move | — |
| B3 | Dashboard live-wire PR | Porsche | gated on Texi emit-name commit (after A3) | — |
| B4 | Install lane (per-OS launcher integration) | Lafonda | gated on E2E daemon-runnable test against backend | — |
| C1 | E2E smoke | all | gated on B1 + B4 | — |
| C2 | Stress + soak | all | gated on C1 | — |
Donna lane is materially complete
What’s left for Donna in implementation:
CONNECTED → EMITTING FSM transition + actual doorbell-to-plugin dispatch (gated on surface plugin existing — cross-lane)
- v0.2 of
prism_signal_status (split delivered/drained, add expired) — deferred until forcing function
- v0.2 of metrics emitter if drift surfaces
Cross-lane coordination still needed
Surface plugins are the next gate. The plugin needs a daemon→shim IPC channel — daemon emits a doorbell; shim presents it to the agent. Current mcp-node doesn’t have a “receive doorbell” endpoint. Texi’s A3 plugin SDK is scaffolding the contract; full integration needs a small mcp-node change to accept doorbells over local IPC. That’s cross-lane work between Donna (daemon) + Texi (mcp-node Codex shim).
This board is the source of truth for build progress. Update via PR to this doc as states change.Last modified on June 11, 2026