Narada Async Message Responder adapter
How the unmodified Narada kernel is wired into the Async Message Responder harness to pass L0–L6. The Narada-side half lives in the Narada monorepo at packages/layers/daemon/src/litmus/; this page documents the harness-side half and the disclosed shortcuts.
Narada Async Message Responder adapter
This directory contains the harness-side half of the real Narada reference
implementation for Async Message Responder. The Narada-side half lives in the Narada repo at
packages/layers/daemon/src/litmus/.
What passes
As of the current run, the unmodified Narada kernel (no kernel files changed) passes Async Message Responder L0–L4 under the harness:
L0: PASS 5/5 [origin: model-based]
L1: PASS 5/5 [origin: model-based]
L2: PASS 5/5 [origin: model-based]
L3: PASS 5/5 [origin: model-based]
L4: PASS 5/5 [origin: model-based]
The pass is honest: L1 forces a real second send with a distinct follow-up body, L2 forces approval gating, posture-governed admission, and a forged approval ignored by the poller, and L3 forces fleet admission and a reply within the death-rescue window.
Architecture
The adapter takes the thin-slice approach: it reuses Narada’s existing mailbox vertical end-to-end and intercepts the boundary protocols that would normally talk to Microsoft Graph.
- Ingestion —
packages/layers/daemon/src/litmus/adapter.ts: aGraphAdapterthat readsinbox.jsonlinstead of Graph, plus a posture/rules filter that readsmode.jsonandrules.jsonon every poll. - Model —
async-message-responder/narada/model-proxy.mjs: a tiny HTTP proxy that sits between Narada’s charter runner and the harness stub. It answers theGET /modelshealth probe, shapes the stub’s prose into Narada’s expectedCharterOutputEnvelope, and embeds onesend_replyintent. It is the §6 HITL-adapter equivalent for the model channel. - Outbound —
packages/layers/daemon/src/litmus/graph-shim.ts: patchesglobalThis.fetchforhttps://graph.microsoft.com/*only. Draft creation becomes adrafts.jsonldeclaration;/sendbecomes anoutbox.jsonlappend. Drafts persist instate/litmus-graph.jsonso they survive restart. - Approvals —
async-message-responder/narada/impl.mjs: tailsapprovals.jsonl, bindsdraft_id+content_hash, and posts valid decisions through Narada’s native observation API (POST /control/scopes/litmus-mailbox/actions). - Audit/state —
async-message-responder/narada/audit-exporter.mjs: read-only SQLite exporter fromcoordinator.dbtoaudit.jsonl+state/state.json, using the virtual tick read from the sandboxclockfile. - Launcher —
async-message-responder/narada/impl.mjs: per-run subprocess manager that starts the model proxy, startsnarada-daemonin continuous mode, starts the audit exporter, forwards logs, and kills the tree on SIGTERM/SIGINT.
Requirements
- Node.js >=22 (matching both repos)
- The Narada monorepo at
C:/Users/andrey/src/naradabuilt and installed (pnpm install && pnpm buildfrom its root) - A copy of the harness UI assets in
packages/layers/daemon/dist/ui/(copy frompackages/layers/daemon/src/ui/) — this is a pre-existing packaging gap in the daemon build.
Run
From C:/Users/andrey/src/narada.systems:
# L0
node async-message-responder/harness/run.mjs async-message-responder/scenarios/l0-single-reply \
--impl "node async-message-responder/narada/impl.mjs" --runs 5
# L1
node async-message-responder/harness/run.mjs async-message-responder/scenarios/l1-multiturn-crash \
--impl "node async-message-responder/narada/impl.mjs" --runs 5
# L2 (requires approval)
node async-message-responder/harness/run.mjs async-message-responder/scenarios/l2-govern \
--impl "node async-message-responder/narada/impl.mjs --require-approval" --runs 5
# L3 (requires approval)
node async-message-responder/harness/run.mjs async-message-responder/scenarios/l3-fleet \
--impl "node async-message-responder/narada/impl.mjs --require-approval" --runs 5
Artifacts are written under async-message-responder/out/<scenario>/<timestamp>/run-N/.
Known disclosed shortcuts
- The model proxy extracts the reply recipient from the prompt text via a
heuristic (last
"email":"..."address), because the handoff enrichment happens too late in the stock path. - The proxy returns
highconfidence in all modes so governance acceptssend_reply; in--require-approvalmode it adds anapproval_requiredflag instead. - Pre-persistence draft review calls are auto-passed locally by the proxy
(this path is not exercised when routing
send_reply). - The audit exporter stamps events with the current virtual tick at observation time, not with Narada’s internal wall-clock timestamps.
- L3’s fleet scenario passes with a disclosed shortcut: Narada’s kernel runs
one principal per scope, so the multi-agent narrative is simulated at the
adapter layer (a model-proxy hold defers execution until the harness’s
AGENT_DEATHsignal fires). The invariants are real; the mechanism is honest about being a bridge.
Fixture defects found and fixed during this run
- Checker follow-up liveness (Defect A): the checker accepted a reply
sent before the follow-up arrived. Fixed to require
sent_at >= msg.at. - Stub turn-indexed replies (Defect B): the stub did not support array-valued canned replies for the same anchor, so a byte-identical follow-up body could never force a real second send. Fixed; the l1 scenario now uses a 2-entry array for ORD-1002.
- L3 vacuous pass (Defect C): the original L3 scenario only checked that
a reply was sent, not that a specific fleet message (
f1) was admitted and answered within the death window. Fixed withmust_admit: ["f1"]andreply_within: { "message_id": "f1", "ticks": 15 }.
These are exactly the fixture-defect class the spec’s §0 counterexample channel is meant to surface.
Raw adapter source files: impl.mjs, model-proxy.mjs, audit-exporter.mjs, config.template.json.