Skip to main content

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

LaneOwnerScope
Backend (registration, routing, drain semantics)Donnakind=daemon registration, prism_status separation, structured-failure on stale targets, daemon heartbeat acceptance
Daemon binary coreDonnaNew daemon/ Node binary: WS client + local IPC server + lifecycle FSM + plugin loader + metrics + structured logger
Codex surface pluginTexiCodex shim plugin per Plugin contract (notify_surface / resume_surface / status). Off-limits to others per memory feedback_texi_codex_lane.md
Claude Code surface pluginDonnaClaude Code shim plugin (sibling of Codex plugin)
Claude Desktop surface pluginDonnaFallback path; degraded delivery to next explicit drain
Dashboard live wirePorscheOne-file flip in dashboard/src/daemons/scraper.ts:tick() after emit names commit. Slice-3 candidate: drain-rate counter (v0.3 metric)
Install laneLafondaPer-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 refreshDesireeContinuous journaling, inconsistency-watch on every PR, docs/* refresh check on every commit per memory feedback_doc_refresh_on_commit
PO oversightDonnaPhase-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.
PhaseLaneOwnerStatePR
A1.aBackend agent_sessions.kind migrationDonnashipped#52
A1.bkind=daemon param + skip master electionDonnashipped stacks on #52#53
A1.c.1SessionStore kind + prism_status separationDonnashipped stacks on #53#54
A1.c.2prism_signal_status verb v0.1Donnashipped stacks on #54#55
A1.auditRouting-registry liveness probeDonnashipped independent#56
A2Daemon binary skeleton + camelCase foldDonnashipped#51
A2.liveReal WS + IPC + register callDonnashipped stacks on #51#57
B2.metricsPrometheus /metrics endpoint replaces stubDonnashipped stacks on #57#58
B2.reconcileReconcile-on-reconnect (FSM RECONCILING → CONNECTED)Donnashipped stacks on #58#59
A3Codex plugin SDK scaffolded in mcp-node/src/daemon/pluginsTexiin flightTBD
A4Dashboard holdPorscheholding (passive/reactive); slice 1+2 already shipped (PRs #47/#48)
A4Install holdLafondadrafting templates + verb sketches; matrix #46 + cleanup #49 already shipped
A5Scribe + doc refreshDesireecontinuous (journals #15–#42+)
B1.codexCodex surface pluginTexigated on A3 SDK + cross-lane shim coordination
B1.claude_codeClaude Code surface pluginDonnagated on cross-lane mcp-node-IPC channel
B1.claude_desktopClaude Desktop fallback pluginDonnagated on Desktop’s surface-side notification path
B2.logsOS-native log sinks (os_log / journalctl --user / Event Viewer)Lafonda recommendedinstall-lane lives best with the OS-native logger commands; surfacing the cross-lane move
B3Dashboard live-wire PRPorschegated on Texi emit-name commit (after A3)
B4Install lane (per-OS launcher integration)Lafondagated on E2E daemon-runnable test against backend
C1E2E smokeallgated on B1 + B4
C2Stress + soakallgated 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