Files
nucleic/docs/M0_RESULTS.md
T
abkslmandClaude Opus 4.8 43279a038e Land M0 deliverable: NucleicCore package + spike + fixtures
M0 (the stream-json spike) was completed earlier but its code was left
uncommitted in a sibling worktree and never landed on any branch. This
commits it verbatim as the M1 baseline:

- NucleicCore: AgentEvent/JSONValue model, AgentBackend protocol,
  ApprovalCoordinator, ProcessHost, Transcript writer/reader,
  ClaudeStreamDecoder, in-process MCP approval server, ClaudeCodeBackend
- nucleic-spike CLI + fake-claude wire stub
- 41 hermetic tests (golden + contract + drift) and live captures under
  fixtures/claude/2.1.167/
- PLAN.md / ADAPTERS.md updated with M0 findings; docs/M0_RESULTS.md

Built and tested with `swift build/test --build-system native` (iCloud
codesign workaround).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-12 00:34:10 -07:00

6.1 KiB

Nucleic — M0 Spike Results (2026-06-12)

M0 is done: the normalized AgentEvent contract is implemented, tested hermetically, and proven against the real claude CLI (2.1.167), including the full interactive-approval round-trip. This doc records what was built, what the live captures pinned, and what changed in the design docs as a result.

What was built

Swift package (Package.swift, Swift 6, strict concurrency):

Component File Doc it implements
JSONValue lenient JSON model Sources/NucleicCore/JSONValue.swift BACKEND_PROTOCOL §3.1
AgentEvent + payloads (Codable, {"type":…} discriminator) Sources/NucleicCore/AgentEvent.swift BACKEND_PROTOCOL §3
AgentBackend protocol, capabilities, specs Sources/NucleicCore/Backend.swift BACKEND_PROTOCOL §2
Approvals model + Decision Sources/NucleicCore/Approval.swift BACKEND_PROTOCOL §4
RiskClassifier Sources/NucleicCore/RiskClassifier.swift BACKEND_PROTOCOL §4
ApprovalCoordinator (first-responder-wins, always-rules) Sources/NucleicCore/ApprovalCoordinator.swift RUNTIME §5
ProcessHost (line-framed stdio) Sources/NucleicCore/ProcessHost.swift ADAPTERS §0.1
TranscriptWriter/Reader (canonical seq) Sources/NucleicCore/Transcript.swift BACKEND_PROTOCOL §6, RUNTIME §2.1
Claude stream-json → AgentEvent decoder Sources/NucleicCore/Claude/ClaudeStreamDecoder.swift ADAPTERS §1.4
In-process MCP approval server (HTTP, bearer-token) Sources/NucleicCore/Claude/MCPApprovalServer.swift ADAPTERS §1.2
ClaudeCodeBackend Sources/NucleicCore/Claude/ClaudeCodeBackend.swift BACKEND_PROTOCOL §7.1
nucleic-spike CLI Sources/nucleic-spike/ PLAN M0
fake-claude wire-protocol stub Sources/fake-claude/ OBSERVABILITY B.4
41 tests (Tier 0 golden + Tier 2 contract + drift) Tests/NucleicCoreTests/ OBSERVABILITY Part B
swift build --build-system native      # see "Build notes" below
swift test  --build-system native
.build/arm64-apple-macosx/debug/nucleic-spike --prompt "…" --cwd <repo> --capture --approvals interactive

Live-capture findings (the residual 🟡s from ADAPTERS §6, now pinned)

Captures live in fixtures/claude/2.1.167/ (native NDJSON + normalized golden events, recorded by the spike on synthetic /tmp repos — no proprietary content). The RealCaptureDriftTests suite decodes them on every test run; re-recording on a CLI upgrade is the drift report.

  1. stdin user-message envelope — CONFIRMED. The nested form works: {"type":"user","message":{"role":"user","content":[{"type":"text","text":…}]}}. Both live turns ran with it.
  2. Approval round-trip — CONFIRMED end-to-end. Our HTTP MCP server shows status:"connected" in the init event; the gated Write produced a tools/call(approve) with {tool_name, input, tool_use_id}tool_use_id is present and matches the subsequent tool_use block id. The {"behavior":"allow","updatedInput":…} reply unblocked the tool; the {"behavior":"deny","message":…} reply produced an error tool_result carrying our message verbatim, and no file was written.
  3. ⚠️ --permission-mode default must be pinned. The host's user-level defaultMode (e.g. "auto") auto-accepts edits before the permission tool is consulted, silently bypassing Nucleic's approval flow. The adapter now passes --permission-mode default whenever ApprovalPolicy == .interactive. This is the most important M0 finding.
  4. stream_event partial shapes — CONFIRMED (message_start, content_block_start, text_delta deltas, content_block_stop, message_stop), interleaved with whole assistant messages exactly as ADAPTERS §1.4 predicted.
  5. thinking block shape — CONFIRMED: {"type":"thinking","thinking":"…","signature":"…"}.
  6. New native shapes (CLI 2.1.167, not in the docs) — passed through as .raw by the lenient decoder, exactly as designed; candidates to model later:
    • {"type":"system","subtype":"status",…} (pre-turn status)
    • {"type":"system","subtype":"post_turn_summary",…} (turn summary/needs_action)
    • {"type":"rate_limit_event",…} (rate-limit telemetry — likely worth surfacing in UI)
  7. Child inherits host config. The spawned CLI loaded the user's global plugins/MCP servers (init tools list included Cloudflare/Stripe plugin tools). For Nucleic sessions we likely want a more hermetic child (e.g. --strict-mcp-config, curated --settings) — deferred to M1.
  8. Decoder fix from live data: a denied Write initially synthesized a phantom fileChange. Synthesis now waits for the successful tool_result (deniedWriteEmitsNoFileChange regression test).

Adapter ordering note (amends nothing, clarifies ADAPTERS §0.4)

Approval events (HTTP channel) and stdout events are emitted by independent tasks; the adapter guarantees per-channel order plus causality (approvalRequestedapprovalResolved → the post-reply toolResult), not a total order. The canonical total order is assigned at the single-writer ingest (TranscriptWriter/SessionController, RUNTIME §2.1) — observed in practice when the fake-claude stub raced its own stdout.

Build notes (this machine)

  • The repo lives in iCloud Drive; macOS stamps files with com.apple.provenance xattrs, which the new SwiftBuild backend's codesign step rejects ("resource fork, Finder information, or similar detritus"). Workaround: swift build/test --build-system native (deprecated flag, still functional in 6.3.2). Revisit when the package moves out of iCloud or SwiftBuild handles the xattr.
  • codex is not installed here; the Codex adapter (M3) was untouched.

What M1 should pick up first

  1. SessionController actor + the single-writer ingest pipeline (RUNTIME §2) — the spike currently plays that role inline.
  2. WorktreeManager (create/diff/integrate, WORKTREE_MANAGER) — wholly unstarted.
  3. Hermetic child config (finding 7) and modeling rate_limit_event (finding 6).
  4. GRDB metadata store; the spike persists only the JSONL transcript.