Canonical guide for every AI tool working in this repo (Claude Code, OpenCode, Omoi, …).
CLAUDE.md is a symlink to this file — edit AGENTS.md only.
✅ FIX PLAN CLOSED (2026-06-13):
docs/MASTER-FIX-PLAN.md— all 8 workstreams resolved (kill-D1, sandbox tool plumbing, repo-image kill, CORS/deploy, CI conformance harness, Void decision, doc/ADR cleanup, Modal hygiene). The doc is now a shipped log with per-WS evidence + commits; each landed fix is guarded by a tripwire test. Re-ground withnpm run verify:claims+scripts/verify-claims/deep-verify.mdbefore assuming anything reopened. Backing audits:docs/gaps-and-issues/2026-06-12-{doc-rot,infra-reality}-audit.md.
How this is loaded. This file is the always-on entry. For navigation, start with
CODEBASE_MAP.md— the layout map for the entire codebase. The small always-on safety rules live as separate blocks in.opencode/rules/*.mddeep "why" is not inlined — it lives indocs/references/01-…05.mdand the design pack; load those on demand. Keep this file dense and lean.
agent-coordinator is the control plane for a hosted coding-agent SDK — a Cloudflare Worker that:
- Authenticates users (Better Auth: cookies, Bearer,
x-api-key) and scopes everything to a(userId, organizationId)tuple. - Persists session metadata in D1 (Drizzle-managed).
- Routes each session to a per-session
SessionDODurable Object that owns its own SQLite, the WebSocket hub (clients + one sandbox), the FIFO message queue, and an alarm-driven watchdog. - Asks Modal (the data plane,
packages/modal-infra/) to spawn a sandbox running OpenCode + an agent bridge that dials back to the DO over WSS.
Consumer pattern: docs/consumer-saas-example.md. Subsystem deep-dives (the "why"): docs/references/01-…05.md.
npm run dev # wrangler dev — control plane only, http://localhost:8789 (matches BETTER_AUTH_URL in .dev.vars; OAuth callback origin)
npm run dev:local # full local stack: control plane (:8789) + chat (:5174) together, with a wedged-port guard. Flags: -- --real | --cp-only | --migrate. Prereq: ./dev.sh up (pg0) + .dev.vars
npm run setup # one-time bootstrap: install + verify .dev.vars + ./dev.sh up (pg0) + local migrations → ready for dev:local
npm run doctor # read-only health check: Node, .dev.vars, pg0, ports, D1/pg migration drift, stale dev procs, non-local DATABASE_URL warning
npm run deploy # wrangler deploy to Cloudflare
npm run deploy:control-plane # prod deploy via acdeploy CLI (also deploy:chat, deploy:all; or `npm run acdeploy -- <args>`)
npm run env:up # local deps pg0 + LiteLLM via acdev CLI (also env:down, env:verify; or `npm run acdev -- <args>`)
npm test # vitest run (Workers pool, miniflare, real DO + Postgres test DB)
npm run test:watch # interactive
npx vitest run test/route.test.ts -t "creates a session" # single test by name
npm run pg:generate # drizzle-kit generate from src/db/pg/* → drizzle-pg/*.sql
npm run pg:migrate:local # apply Postgres migrations to local pg0
npm run pg:migrate:remote # apply to remote Neon (needs DATABASE_URL)
npm run cf-typegen # regenerate worker-configuration.d.ts from wrangler.jsonc
npm run auth:generate # regenerate src/db/auth-schema.ts from the auth.ts plugin settest/e2e/*.sh are manual smoke scripts against a deployed env — not part of npm test.
Modal-side Python: see packages/modal-infra/README.md (deploy with modal deploy deploy.py).
These small blocks in .opencode/rules/ are imperatives (OpenCode loads them automatically;
everyone else: read on the trigger). Violating them can break tenant isolation or desync the system.
| Rule block | Covers | Read when touching |
|---|---|---|
.opencode/rules/multi-tenant-invariants.md |
DO-ID format, resolveContext as sole org resolver, RESERVED_SYSTEM_KEYS merge order, per-tenant AES-GCM, GitHub-App tokens, log-no-secrets |
auth, secrets, spawn/env-var build, DO routing, crypto, logging |
.opencode/rules/provider-neutrality.md |
no default provider/model anywhere; provider = model.split("/")[0]; bare model → "unknown" never "anthropic" |
model/provider routing, sandbox config, bridge.py/entrypoint.py/types.py, Modal API |
.opencode/rules/architectural-rules.md |
skeleton-vs-motion separation, port/adapter invariants, event-sourced state, idempotent effects, registry bijection, deterministic domain | port interfaces, adapters, controller, event sink, state machines, DI wiring, cutover |
.opencode/rules/cross-file-sync.md |
bridge protocol (both sides), duplicated Modal HMAC generator, generated files, modal deploy deploy.py only |
bridge events, auth/env/DB schema, Modal auth/deploy |
Request → DO routing (src/index.ts, the only Hono entry). DO ID is resolved per session:
- Real session: D1
session_indexrow → DO ID${organizationId}:${sessionId}. Load-bearing — gives tenant isolation by construction (one org can't name another's DO). - Sandbox-only / no D1 row: DO ID
sandbox:${sessionId}(smoke tests + sandbox-bridge handshake; auth happens in the DO via token-hash). resolveSessionAndDo()+requireAuth()enforce callerorganizationId== session's. The WS upgradeGET /api/sessions/:id/wsruns auth inline (the standard middleware path conflicts with the upgrade).
Auth precedence (requireAuth): (1) x-api-key → Better Auth apiKey plugin (referenceId=userId, optional metadata.organizationId; membership re-checked every call) → (2) Authorization: Bearer → direct session.token lookup (fast path) → (3) cookie via auth.api.getSession(). resolveContext() (src/lib/resolve-context.ts) is the single place that decides the organizationId — valid activeOrganizationId or fail-closed; else earliest membership; no memberships → personal workspace auto-created.
Generated auth schema. src/db/pg/auth-schema.ts is generated by Better Auth from the plugin set in the root auth.ts stub (CLI-only). Change plugins in src/lib/auth.ts → mirror in auth.ts → npm run auth:generate. src/db/pg/app-schema.ts is yours (session_index, audit_log, tenant_secret, env profiles, …). The drizzle-pg config reads src/db/pg/index.ts. Never hand-edit auth-schema.ts or worker-configuration.d.ts.
SessionDO (src/do/session-do.ts) — one DO/session, hibernates when idle. Survives hibernation: a private SQLite (session, participants, messages, events, artifacts, sandbox_state, ws_client_mapping; initSchema() recreates idempotently), hibernation-safe WS (ctx.acceptWebSocket() + auto ping/pong), and alarms (exec timeout 5 min, heartbeat stale 1 min, connecting timeout 30 s, inactivity 30 min → snapshot/cleanup). The FIFO queue = messages rows (pending|processing|completed|failed); processMessageQueue() is the only dispatcher (short-circuits if any processing or no sandbox), called on message create, execution_complete, and sandbox connect. Bridge handshake: auth_token_hash (SHA-256) stored in sandbox_state before spawn; bridge presents bare token once, DO never re-sees it; sandbox WS carries X-Sandbox-ID. Critical events (execution_complete, error, snapshot_ready, push_complete, push_error) get an ackId, re-sent until ACKed. POST /init = idempotent prod session-init (INSERT OR REPLACE); POST /start = HTTP spawn trigger (fire-and-forget, 200 spawning); ?action=__init|__trigger_alarm|__set_processing_time are test-only (__ prefix).
Modal data plane glue (src/lib/): github-token-resolver.ts (GitHub-App installation tokens, TTL-cached, 5-min margin — always for clone/push, never user OAuth); secret-repo.ts+crypto.ts (tenant_secret, per-tenant AES-GCM via HKDF of ENCRYPTION_KEY+organization_id — cross-tenant decrypt impossible); mcp-config.ts (validates user MCP configs — rejects shell metachars, path traversal, absolute paths); Modal HMAC = Bearer <ts>.<HMAC-SHA256(MODAL_API_SECRET, ts)> (session-do.ts:generateModalToken). In spawnSandbox()/buildSandboxEnvVars(), system vars override user secrets for RESERVED_SYSTEM_KEYS — do not reorder this merge (re-opens a privilege-escalation path).
Repo-image stack removed (2026-06-12). The dead repo-image feature — the gated CP cron (src/cron/repo-image-build.ts), the Modal */30 scheduler (image_builder.py), and the api_build_repo_image/api_delete_provider_image Modal endpoints — was deleted in the merged WS3+WS1 cut. There is no scheduled worker handler and no crons trigger. Pre-built images are delivered solely by the env-profile composite path (api_validate_environment → agent_runtime_layer → provider_image_id → spawn from_id). See docs/MASTER-FIX-PLAN.md WS3.
Front door = npm scripts (see Common commands). One-time bootstrap: npm run setup. Daily driver: npm run dev:local (control plane :8789 + chat :5174 together, with a wedged-port guard + clean teardown). Health/triage: npm run doctor (read-only). Deps only: npm run env:up (pg0 + LiteLLM). Reset a LOCAL db: npm run db:reset:local (-- --test targets the test db). All are zero-dep scripts/*.ts run via Node native TS stripping.
Data stores. Each Durable Object (SessionDO, OrgEventsDO) owns a private internal SQLite (DO-only; not a shared DB). Postgres via the HYPERDRIVE binding (pg0 on 127.0.0.1:5440 locally, Neon in prod) backs everything external: Better Auth (users/orgs/sessions/api keys), session_index (DO routing, src/db/pg/app-schema.ts), and app data (agent_runs, tenant_secret, env profiles, audit_log). src/lib/auth.ts = drizzleAdapter(getPgDb(env.HYPERDRIVE.connectionString)). D1 was removed 2026-06-12 (only the dead cron used it). Local dev db = agent_coordinator; the test suite is isolated in agent_coordinator_test.
Three load-bearing local-dev gotchas:
- The pg pool is per-request EVERYWHERE — never cached at module scope (prod included).
getPgDb(src/db/pg/pool.ts) builds a freshpg.Poolper call and never caches across requests. In workerd a pg socket opened in one request's IO context can't be reused from a later request's context —pool.query()checks out that dead socket and the request hangs ("…code had hung…"). This was originally thought to be a local-only issue (no Hyperdrive proxy), gated behindglobalThis.__PG_NO_POOL_CACHE__for dev/test only — but production hung the same way (a cached connection dies on Neon idle / scale-to-zero, biting auth sign-up + provider-key writes + every PG endpoint), so the cache, the flag, thepg-no-pool-cachetest setupFile, andclosePgPoolare all removed: per-request pools unconditionally. Hyperdrive does the real server-side pooling;withPgDb(src/db/pg/client.ts) is the explicit open/run/close variant. DATABASE_URLis a prod footgun for:remote/ raw drizzle-kit.npm run pg:migrate:localis now loopback-hardcoded — it pins the local pg0 URL and ignores any ambientDATABASE_URL(matchingdb:reset:local, and protecting thepg:migrate:localcall insidedev:local), so a stray prod Neon URL can never be migrated by a:localcommand. The ambient value still wins forpg:migrate:remoteand a rawdrizzle-kit migrate;setupstill pins the local URL explicitly;doctorwarns whenDATABASE_URLis non-local. Keep it unset for local work.pg:migrate:local(drizzle-kit) still can't replay0006from scratch — but the test migrator now can.drizzle-pg/0006adds theinstallation_repository → github_installation(installation_id)FK before the unique key it needs, so a raw drizzle-kit replay of0006onto an empty db errors (SQLSTATE 42830), and editing0006would change its hash and break__drizzle_migrationsidempotency on live dev/CI dbs — never edit it. The custom migrator intest/helpers/pg-global-setup.tsnow tolerates this (defers any FK that fails 42830, retries after the whole chain applies — by which point0007's named UNIQUE constraint exists;eb4af34), so the test/CITEST_DATABASE_URLfrom-scratch path replays cleanly andCREATE DATABASE … TEMPLATEcloning is now a speed optimization, not a workaround. Butnpm run setup'spg:migrate:localuses drizzle-kit (no deferral), so fresh-machine setup still expects a pg0 that already carries the schema (clone/branch to provision).drizzle-pg/0009_fix_fk_orderingidempotently guarantees the unique constraint (aDO $$guard, no-op on migrated dbs) but is journaled after0006, so it can't rescue a raw0006replay.
@cloudflare/vitest-pool-workers runs every test inside a real Workers isolate + miniflare. Postgres-backed tests run against an isolated agent_coordinator_test DB via TEST_DATABASE_URL (test/helpers/pg-global-setup.ts replays the drizzle-pg chain from scratch). Import from cloudflare:test: SELF (the worker — SELF.fetch(...)) and env (bindings — env.SESSION_DO.idFromName(...) for direct DO tests; env.HYPERDRIVE for Postgres). Auth tests use the HTTP signup→cookie pattern (shared factories in test/helpers/auth.ts; whole-project nets in test/integration/). There is no D1 — see test/node/no-d1.test.ts.
Postgres-backed tests run against an isolated agent_coordinator_test db. Locally, test/helpers/pg-global-setup.ts creates it by CREATE DATABASE … TEMPLATE from the dev schema (see gotcha 3) and TRUNCATEs it per run, so the suite never pollutes the dev agent_coordinator db. In CI (.github/workflows/ci.yml) the typescript-tests job runs a postgres:18 service (matching pg0) exposing an empty agent_coordinator_test on 127.0.0.1:5440, and TEST_DATABASE_URL points the suite at it; pg-global-setup then replays the drizzle-pg chain from scratch (its deferred-FK loop tolerates 0006's ordering), so CI is self-contained and needs no external Neon branch/secret. The worker isolate disables the pg pool cache via test/helpers/pg-no-pool-cache.ts.
Two vendored Python packages (own uv locks; each has a DEVIATIONS.md for the namespace rename from upstream background-agents-reference):
modal-infra/— Modal app: 6 web endpointsapi_create_sandbox/api_sandbox_lifecycle/api_snapshot_sandbox/api_restore_sandbox/api_validate_environment/api_health, HMAC auth, GitHub-App token issuance, image defs. Deploy withmodal deploy deploy.py(nevermodal deploy src/app.py— onlydeploy.py/-m srcregisters all modules). (api_warm_sandbox/api_build_repo_image/api_delete_provider_imagewere removed — consolidated intoapi_sandbox_lifecycle/ repo-image kill.)sandbox-runtime/— runs inside the sandbox: supervisor entrypoint, bridge, OpenCode integration. Moduleagent_coordinator_sandbox_runtime(renamed from upstreamsandbox_runtime).
Bridge-protocol changes must land in both packages/sandbox-runtime/ and src/do/session-do.ts:processSandboxEvent (see cross-file-sync.md).
wrangler.jsoncvars— non-secret config (BETTER_AUTH_URL,GITHUB_CLIENT_ID,GITHUB_APP_ID,ENCRYPTION_KEY,CONTROL_PLANE_URL,MODAL_API_URL). TheENCRYPTION_KEYhere is a dev placeholder; prod useswrangler secret put ENCRYPTION_KEY.- Secrets —
BETTER_AUTH_SECRET,RESEND_API_KEY,GITHUB_CLIENT_SECRET,GITHUB_APP_PRIVATE_KEY,MODAL_API_SECRET. Dev:.dev.vars. Prod:wrangler secret put …. drizzle.config.ts— Drizzled1-httpdriver; CLI needsCLOUDFLARE_API_TOKEN.tsconfig.json—@/*→./src/*(rare; most code uses relative imports).
- One auth resolver:
resolveContext. Never readmemberdirectly to infer the org. - Audit writes are non-blocking:
audit.log()failures are caught and swallowed — never make the primary operation depend on audit success. session-do.tsis large (~1500+ lines) on purpose — single actor; schema/queue/spawn/event-processing share private state. Add helpers inside the class; don't extract modules without a stable boundary.
Seven project-scoped skills in .claude/skills/. Five encode the design-pack contracts (slices 1–7, §06); two are workflow skills (phase-review gate and session-state orientation). Claude Code auto-loads by keyword; other harnesses use this table — read the skill's SKILL.md first when the work matches. They're thin pointers (checklist + one copy-into-src/ starter artifact, not wired into the build); the design pack is the source of truth and .opencode/rules/*.md still owns the always-on invariants.
| Working on… | Skill | Slice |
|---|---|---|
spawn path, run creation/cancel, API-key minting, credential chain (session-do.ts:doSpawn, src/index.ts) |
governing-agent-runs |
1–3 |
| sandbox config, bridge protocol, LiteLLM gateway / virtual keys | agent-run-contract (+ agent-run-contract.schema.ts) |
6 |
| adding/swapping a sandbox provider, lifecycle/heartbeat/cleanup | provider-adapter-contract (+ sandbox-provider.ts) |
7 |
src/db/pg/*, migrations, run/event data placement (Postgres) |
tenant-data-boundary (+ agent-runs.schema.ts, adr-0009-*.md) |
4 |
| making a repo agent-runnable: env profiles, setup autodiscovery | environment-profiles |
§06 |
| Workflow: iterative review gate after each phase (ask→approve→commit vs iterate) | phase-review |
all |
| Workflow: session bootstrap — read state file, re-orient, track steps, verify | project-orientation (+ session-state.json) |
all |
| Workflow: end-of-session wrap-up — verify, update state, log actions, plan next | task-wrap-up |
all |
| Skill management: use the skill-freezer skill to move skills in/out of cold storage (e.g. "pull in wiki skills", "freeze the ads stuff"). |
Canonical target look = .sisyphus/design-reference/ (port verbatim): Thread.tsx (assistant-ui shadcn shell — warm layout, turn dividers / no assistant bubble, .surface user bubble, action bars, branch picker, .surface-elevated composer), theme.css (brown-red "Vertical sheen": shadcn light+.dark tokens, @theme inline, body glow/grain, .surface/.surface-elevated/.recessed-well/.code-pill), README.md (adoption notes; target shots in .sisyphus/evidence/reference/). The package keeps its custom MessageRenderer (streamdown + @pierre/diffs + 16 cards) wired into the shell; the 16 cards are rethemed to the warm tokens.
Void is used by packages/chat only (SSR SPA, no custom Durable Objects — deploys via npx void deploy). For the control plane the Void entry-switch was ABANDONED 2026-06-12 (WS6): Void's generated entry can't re-export the custom DO classes (SessionDO, OrgEventsDO) that wrangler main requires, so the entry stays src/index.ts. Instead routes/* use the additive-mount pattern — defineHandler files from the lightweight void/handler subpath, imported into the Hono app and mounted via app.get(...) (src/index.ts:29-39 mounts routes/api/runs/*, routes/{index,health,docs,openapi.json}, routes/api/config). void/handler bundles cleanly in Workers; the full void entry (pulls vite/node) does NOT.
Key fact: defineHandler(c) receives a standard Hono Context — hono/*/@hono/* packages, auth resolvers, streaming, and response helpers transfer unchanged; only the export differs (export const GET = defineHandler(...) vs app.get(...)).
import { defineHandler } from "void/handler";
export const GET = defineHandler(async (c) => ({ data: "automatic JSON" }));
export const POST = defineHandler.withValidator({ body: schema })(async (c, { body }) => ({ ok: true }));npm run void:prepare (void prepare) regenerates .void/*.d.ts and is required before tsc (the typecheck script runs it first); keep the generated .void/ dir. There is no void:build for the CP (removed — the entry is src/index.ts, deployed via wrangler). The guard test/node/void-cp-abandoned.test.ts keeps the dead routes/middleware/ scaffold from returning and locks the DO re-export. Rationale: docs/MASTER-FIX-PLAN.md WS6 + the archived .sisyphus/plans/void-migration-plan.md.
Grounded in the code, prioritized. (See .sisyphus/plans/server-modernization-plan.md + docs/design/agent-architecture-review/.)
- fresh-machine
pg:migrate:local(drizzle-kit) still can't replay from scratch — the test/CITEST_DATABASE_URLfrom-scratch path is now FIXED (pg-global-setup.tsdefers the0006FK and retries;eb4af34), but drizzle-kit has no deferral, so a fresh dev machine still needs a cloned/branched pg0. A NEW forward migration is journaled after0006so can't help; durable fix = teachnpm run setupto clone, or route bootstrap through the tolerant migrator instead of drizzle-kit. - Platform events backend EXISTS (previously listed missing) —
OrgEventsDO(src/do/org-events-do.ts) is implemented, exported (src/index.ts), and bound asORG_EVENTS(wrangler.jsonc v2 migration);GET /api/events/wsis live. Real-time / cross-tab updates work with an API key. - Repo-image cron + D1 removed (2026-06-12) — the dead repo-image stack and the vestigial D1 binding/migrations were deleted (merged WS3+WS1 cut); a
test/node/no-d1.test.tstripwire fails the build on any D1 reintroduction. /api/usageEXISTS (previously listed missing) —agent_runsroll-up served fromsrc/index.ts;settings/usage+settings/modelsshow real data with an API key.- OpenAPI/SDK not schema-first — routes are hand-written Hono;
src/openapi.ts(699 lines) is dead/unserved; no Zod→OpenAPI generation (Modernization Phases 1–2). - Void abandoned for the control plane (2026-06-12) — entry stays
src/index.ts(~720 lines);routes/*use additive-mount viavoid/handler. Void's inability to re-export custom DOs blocks the entry-switch (WS6); atest/node/void-cp-abandoned.test.tsguard prevents regression. Chat stays on Void. - GitHub OAuth real-data flow — unblocked by the pg pool fix but unverified; needs a real sign-in at
http://localhost:5174. Until then/account+/reposhave no live data locally. - Design-pack slices (
.claude/skills/: governing-agent-runs, agent-run-contract, provider-adapter-contract, tenant-data-boundary, environment-profiles) are checklists + starter artifacts, not wired into the build. - E2E / browser-test harness is in place.
packages/chathas Playwright E2E (packages/chat/e2e/, mock mode,npm run test:e2e), Vitest-4 browser mode + visual regression (npm run test:browser), and a generated-spec tool attools/test-gen/(npm run test-gen -- smoke|capture|gen ...;npm run test-gen:setupto install). GOTCHA: the app is Void-SSR'd, so a Playwright test must wait for hydration before interacting (packages/chat/e2e/hydration.ts) — a click/type before hydration is a silent no-op (NOT a trusted-event incompatibility). - dev:real prod-leak footguns fixed — the SDK requires an explicit
url(no hardcoded prod default), chat stores resolve viacontrolPlaneUrl(), anddev:local --realpins/warns onVITE_BASE_URL/VITE_API_KEY(see.opencode/rules/browser-testing.md). - Hexagonal port/adapter layer built; S1–S5 landed, S6 ~80% (W1b cutover) — 15 ports across 5 planes under
src/lib/{sandbox,agent,controller,transport,session,record,scm,secrets,gateway,identity,ingress,blob,observability,metering}/*.ts, each with an in-memory Fake + a fault catalog. Proven (315 tests / 33 files green, 2026-06-07):test/integration/walking-skeleton.test.ts(full run lifecycle in-memory, zero infra),test/integration/disruption.test.ts(break each seam → assert fail-closed),test/ports/conformance-registry.test.ts(rename/drop a fake or add a port without fake+faults = RED BUILD). S1–S5 landed 2026-06-05/07:DoAgentControllerin the DO with fakes (S1) →SqlEventSinkdurable (S2,e6dccd8) →DoSqliteSessionState(S3,d2d6877) →DoWebSocketChannel(S4,746176c) →LocalProvider13-state FSM (S5,3823880). S6 (Real CodingAgent) — ~80% done. Three real adapters behind theCodingAgentinterface:OpenCodeAgent(880-line HTTP/SSE,src/lib/agent/opencode-agent.ts),AcpAgent(generic ACP to any agent binary,src/lib/agent/acp-agent.ts), andOpenCodeAcpAgent(stdioopencode acppreset,src/lib/agent/opencode-acp-agent.ts). Proven end-to-end:scripts/opencode-acp-loop.ts(realopencode acp→LocalProvider→DoAgentController→ real model token →execution_complete);scripts/opencode-acp-conformance.ts(contract test, opt-in Node — child processes can't run in the Workers vitest pool).LocalProcessProvider+scripts/local-sandbox-runtime.tsnow spawn real opencode in isolated sandboxes, dial back over a real WebSocket, authenticate, and reachready(three spawn/handshake bugs fixede0e6a7a). Remaining S6: (1)FakeControlPlane.prompt/promptThenDropMidStreamtest-harness methods for the reconnect-resume e2e inscripts/local-process-sandbox-e2e.ts, (2)LocalProcessProviderconformance runner with real agent. Then: S7 (LiteLLM Gateway), S5-cloud (Modal swap), S8 (the flip — delete inline Modal path). TheSANDBOX_RUNTIME=controller|acpflags gate the swap; absent in prod.
Durable project state lives in THIS file (auto-loaded every session, in both OMP and Claude Code) — treat it as the source of truth, not the memory backend. The OMP memory backend (Hindsight, remote https://hindsight.omoios.dev, per-project-tagged) only auto-injects a small generic slice at session start and does not re-query mid-session, so proactively recall/reflect when starting a work segment or shifting topic — don't trust the auto-injected <mental_models>/<memories> block to hold recent specifics. Memory is split-brain: OMP→Hindsight (sessions in ~/.omp/agent/sessions/), Claude Code→claude-mem (raw transcripts in ~/.claude/projects/-Users-kevinhill-Coding-Projects-agent-coordinator/*.jsonl); the two stores don't see each other, so "what did we do recently" means checking both (recall/reflect and mining the JSONL).
docs/decisions/ is the living source of truth for the hexagonal rewrite (the move off
super-coupled/Modal-hardwired toward ports-&-adapters). Read order: docs/decisions/why-cutover.md
(why we're doing this + what NOT to do) → docs/decisions/README.md → current-state.md → the
relevant ADR — before touching code. The non-negotiable rules
(staged in docs/decisions/rules-for-agents.md, graduating here as they harden):
- A port is a bundle, not an interface:
(interface, ≥1 real adapter, 1 fake adapter, 1 contract test, 1 fault catalog). DoD = passing contract + fault tests, never prose. - Never weaken a fail-closed/negative test to go green — esp. cross-tenant reads. After a hard pass, re-introduce a known fault and confirm a test goes red.
- Interface stable, adapter swappable — swap Modal/LiteLLM by writing a new adapter, never by changing the core's shape. Modal is being demoted to one adapter; local-disk adapter first (ADR-0004).
- Repo docs (L1) are authoritative over any memory store; cloud memory is a mirror, never the only copy. Write durable facts the session they're decided (ADR →
decision-log.md, action →action-log.md). - Verify platform facts against live docs, not memory (CF DO observability export, Modal lifecycle, gateway routing).
Source decisions are recovered in docs/design/architecture-retrospective/ (the 7-file pack: system frame, M1–M7 mistakes, principles, 14-port registry, conformance model, action sequence). The load-bearing faults are in docs/decisions/fault-catalog.md.
- Scanning errors / debugging:
docs/runbooks/scanning-errors.md(how to find & review errors) +docs/debugging.md(trace_id correlation diagram + event catalog). Custom trace_id correlation, NOT Sentry/OTel, across two log planes joined by trace_id: control-plane Worker+DO vianpm run logs(scripts/cf-logs.ts, needsCLOUDFLARE_API_TOKEN+CLOUDFLARE_ACCOUNT_ID) ornpx wrangler tail; Modal sandbox/bridge viamodal app logs open-inspect. Durable post-mortem:audit_log,agent_runs/agent_run_events(PG). Gotcha: a failed agent run logs atinfo/warn(execution.completesuccess:false+prompt.provider_error), NOTerror—--level erroralone misses it; chat-SPA errors hit no server log (browser DevTools only). docs/references/01-…05.md— the "why" behind the DO, spawn flow, bridge protocol, event flow.docs/consumer-saas-example.md— downstream consumer pattern.docs/design/background-agent-platform/— target design pack;docs/design/agent-architecture-review/— as-built gap analysis.docs/design/sandboxes-and-snapshots/— the design behind the hexagonal cutover (decoupled-architecture.md= keystone;testability.md= contract test blueprint;port-catalog.md= 15-port inventory with conformance registry).docs/design/coding-agent-seam/current-reality-and-flake-catalog.md— load-bearing for S6: 10 silent-degradation flakes in the OpenCode SSE bridge, each with validated fix direction.docs/cutover-guide/— interactive web dashboard showing sprint status, architecture map, data flow, code map, and failure modes.cd docs/cutover-guide && npm run dev..sisyphus/plans/void-migration-plan.md— ARCHIVED Void migration plan (control plane abandoned; chat-only).packages/*/README.md+packages/*/DEVIATIONS.md— Modal/sandbox specifics + the upstream rename.node_modules/void/docs/— full Void API.