|
| 1 | +# agent-coordinator |
| 2 | + |
| 3 | +**Control plane for a hosted coding-agent SDK.** Authenticate a user, spin up a per-session |
| 4 | +Cloudflare Durable Object, spawn a sandboxed dev box on Modal running [OpenCode](https://opencode.ai), |
| 5 | +and stream the agent’s work back over WebSocket — multi-tenant, bring-your-own-key, with durable run history. |
| 6 | + |
| 7 | +> **Live demo** — Chat UI · <https://app.omoios.dev> API · <https://agents.omoios.dev> (`/health` → `{"status":"ok"}`) |
| 8 | +
|
| 9 | +--- |
| 10 | + |
| 11 | +## What it does |
| 12 | + |
| 13 | +The full pipeline is proven end-to-end against the deployed stack: |
| 14 | + |
| 15 | +``` |
| 16 | +chat → control plane → Modal sandbox → opencode → model → streamed reply |
| 17 | +``` |
| 18 | + |
| 19 | +The sandbox is a **real dev box**, not a file editor — it clones a repo, installs dependencies, and runs |
| 20 | +code. In one end-to-end run it cloned [`expressjs/express`](https://github.com/expressjs/express), ran |
| 21 | +`npm install`, and executed the project’s **full 1249-test suite** inside the spawned sandbox, streaming |
| 22 | +the result back to the UI. |
| 23 | + |
| 24 | +## Architecture |
| 25 | + |
| 26 | +```mermaid |
| 27 | +flowchart LR |
| 28 | + U["Chat SPA\napp.omoios.dev"] -- "HTTPS + WS" --> W["Cloudflare Worker\nHono router\nagents.omoios.dev"] |
| 29 | + W -- "requireAuth / resolveContext" --> A[("Better Auth + app data\nPostgres via Hyperdrive / Neon")] |
| 30 | + W -- "DO id = org:session" --> DO["SessionDO\nprivate SQLite · WS hub\nFIFO queue · alarm watchdog"] |
| 31 | + DO -- "HMAC-signed spawn" --> M["Modal sandbox\nOpenCode + bridge"] |
| 32 | + M -- "WSS dial-back (token-hash auth)" --> DO |
| 33 | + M -- "model calls (BYOK / gateway)" --> L["LLM provider"] |
| 34 | + DO -- "durable flush on every terminal transition" --> R[("agent_runs / agent_run_events\nPostgres")] |
| 35 | +``` |
| 36 | + |
| 37 | +- **Request → DO routing** (`src/index.ts`): one Hono entry; each session resolves to a Durable Object |
| 38 | + whose id is `${organizationId}:${sessionId}` — tenant isolation *by construction* (one org cannot name |
| 39 | + another’s DO). |
| 40 | +- **SessionDO** (`src/do/session-do.ts`): a single actor per session that hibernates when idle and survives |
| 41 | + it — private SQLite (sessions, messages, events, artifacts), a hibernation-safe WebSocket hub, a FIFO |
| 42 | + message queue, ACK’d critical events (re-sent until acknowledged), and an alarm-driven watchdog. |
| 43 | +- **Modal data plane** (`packages/modal-infra`): HMAC-authenticated sandbox lifecycle, GitHub-App |
| 44 | + installation tokens for clone/push, pre-built composite images. |
| 45 | + |
| 46 | +## Engineering highlights |
| 47 | + |
| 48 | +- **Hexagonal ports & adapters.** 15 ports across 5 planes (`src/lib/ports/registry.ts`). Each port is a |
| 49 | + *bundle*, not just an interface: `(interface, ≥1 real adapter, 1 in-memory fake, 1 contract test, 1 fault |
| 50 | + catalog)`. A `walking-skeleton` test runs the entire run lifecycle in-memory with **zero infrastructure**; |
| 51 | + a `disruption` suite breaks each seam and asserts **fail-closed**; a conformance-registry tripwire turns |
| 52 | + *“rename a fake / add a port without its bundle”* into a **red build**. |
| 53 | +- **Multi-tenant by construction.** A single org resolver (`resolveContext`), DO-id namespacing, and |
| 54 | + per-tenant AES-GCM encryption (HKDF of a master key + `organization_id` — cross-tenant decrypt is |
| 55 | + impossible). |
| 56 | +- **Provider-neutral.** No default provider/model anywhere; `provider = model.split("/")[0]`. BYOK by |
| 57 | + default, with a pluggable model-gateway seam (direct / LiteLLM / Bifrost). |
| 58 | +- **Durable run history.** Every terminal session transition flushes an attributed run record |
| 59 | + (tenant, api key, cost, ordered events) to Postgres, non-blocking. |
| 60 | +- **1183 test cases across 143 files**, run inside a real Cloudflare Workers isolate (miniflare) backed by |
| 61 | + an isolated Postgres database — not mocks. |
| 62 | + |
| 63 | +## Tech stack |
| 64 | + |
| 65 | +Cloudflare Workers · Durable Objects · Hono · Better Auth · Postgres (Hyperdrive / Neon) · Drizzle · |
| 66 | +Modal · OpenCode · TypeScript · Vitest · Vite + Void (chat SPA). |
| 67 | + |
| 68 | +## Quickstart |
| 69 | + |
| 70 | +```bash |
| 71 | +npm run setup # one-time: install deps, verify .dev.vars, start local Postgres, run migrations |
| 72 | +npm run dev:local # control plane (:8789) + chat UI (:5174) together |
| 73 | +npm test # full suite — Workers pool + Postgres |
| 74 | +npm run doctor # read-only health check (Node, ports, migration drift, stale procs) |
| 75 | +``` |
| 76 | + |
| 77 | +Secrets go in `.dev.vars` for local dev (`wrangler secret put …` in prod). See [`AGENTS.md`](./AGENTS.md) |
| 78 | +for the full operator guide and [`CODEBASE_MAP.md`](./CODEBASE_MAP.md) for the layout map. |
| 79 | + |
| 80 | +## Design & decisions |
| 81 | + |
| 82 | +This project was built to exercise a clean **hexagonal cutover** off a Modal-hardwired prototype toward |
| 83 | +swappable ports & adapters. The reasoning is documented, not just the code: |
| 84 | + |
| 85 | +- **ADRs + living state** — [`docs/decisions/`](./docs/decisions/) |
| 86 | +- **Design pack** — [`docs/design/`](./docs/design/) (sandboxes & snapshots, port catalog, testability blueprint) |
| 87 | +- **Subsystem deep-dives** — [`docs/references/`](./docs/references/) |
| 88 | +- **Repository tour** — [`CODEBASE_MAP.md`](./CODEBASE_MAP.md) · [`THE_STORY_OF_THIS_REPO.md`](./THE_STORY_OF_THIS_REPO.md) |
| 89 | + |
| 90 | +## Repository layout |
| 91 | + |
| 92 | +``` |
| 93 | +src/ control-plane worker: index.ts (Hono entry), do/ (SessionDO, OrgEventsDO), |
| 94 | + lib/ (15 ports + adapters + fakes), routes/, db/pg/ (Drizzle schema) |
| 95 | +routes/ additive Void file-route handlers mounted into the Hono app |
| 96 | +packages/ chat/ (Void SSR SPA), modal-infra/ + sandbox-runtime/ (vendored Python data plane) |
| 97 | +test/ 1183 cases: integration, ports (conformance + fault), node, transport, e2e scripts |
| 98 | +docs/ decisions/ (ADRs), design/ (target pack), references/, runbooks/ |
| 99 | +``` |
| 100 | + |
| 101 | +## Known limitations & roadmap |
| 102 | + |
| 103 | +Honest scope boundaries — the core pipeline works; these are deliberate v1 cuts: |
| 104 | + |
| 105 | +- **BYOK key-stripping** (`LITELLM_STRIP_BYOK`) is off by default; production runs BYOK-direct. Flipping |
| 106 | + it on requires the model gateway to serve every model first. |
| 107 | +- **Modal is still the inline sandbox path.** The ports/fakes/conformance harness proves the seam is |
| 108 | + swappable; formally demoting Modal to one adapter (and adding a second provider) is future work. |
| 109 | +- **ACP agent adapters** (`OpenCodeAgent`, `AcpAgent`) are Node-runtime reference adapters proven by the |
| 110 | + `scripts/opencode-acp-*` end-to-end loops; the live worker path uses the lower-level ACP client. Wiring |
| 111 | + the wrapper classes into the worker is future work. |
| 112 | +- A few platform event types (`environment.changed`, `audit.event`) are forward-declared with no producer yet. |
| 113 | + |
| 114 | +--- |
| 115 | + |
| 116 | +_Single-author project by Kevin Hill. Built as a deep exploration of agent-orchestrated development on |
| 117 | +Cloudflare’s edge platform._ |
0 commit comments