# PR 2 — Complete local Surface application

> Implement this PR in `juspay/odu` on merged PR 1. This is an implementation handoff, not a request for another plan. Deliver the singleton web service, complete browser, generated CLI, HTTP MCP, stdio bridge, upstream skill, packaging, documentation, and tests together. PR size is not a splitting criterion. Read repository instructions/current code and preserve unrelated work. Do not merge or deploy without authorization.

Plan: https://odu-agent-spec.pages.dev/  
Required foundation: https://odu-agent-spec.pages.dev/phases/1.md  
Design baseline: 2026-09-06. Source links below are audited references, not consumer pins. This brief includes the required semantics so no earlier chat is needed.

## Outcome and prerequisite

```sh
nix run github:juspay/odu -- web
# ensures/reuses singleton, prints http://127.0.0.1:18440, returns
# service stays alive after the agent exits
```

The user sees every registered run across repositories/worktrees in a local browser. Agents start, inspect, wait, read logs, retry, and cancel the same runs through generated CLI or MCP. No consumer-project pin is required.

Before implementation, verify merged PR 1 provides: canonical per-user catalog, immutable attempts/logs, durable attention query, live/finalized retry, request/launch receipt reconciliation, guarded owner recovery, independent coordinator launch ports, preserved native CLI/MCP, and working Nix packages. Read its final exports and tests rather than assuming illustrative names are literal APIs. Start from its merged commit; do not ship runtime paths requiring an unmerged companion branch. If the foundation is absent, report the exact prerequisite instead of building a parallel engine.

## Complete architectural unit

```text
Browser ───────────────┐
Generated CLI ────────┼→ service Surface → coordinator Surface → workers
HTTP MCP / stdio ────┘        ↓                   ↓ single writer
                         request receipts     RunHistory evidence
                         registry projection ← catalog + live state
```

All advertised clients, controls, recovery paths, and lifecycle behavior arrive in this PR. No placeholder dashboard, deferred MCP transport, or later retry implementation. Preserve native compatibility while routing the stdio MCP entry through the service; provide working legacy aliases/adapters for existing public tools/resources where needed, with all execution delegated to the same authority.

## Packages and composition

```text
packages/
  run-client/      PR 1: lean coordinator contract + dial
  run-history/     PR 1: evidence, catalog, attention
  execution/       PR 1: coordinator + workers
  cli/             extend: bootstrap, generated CLI, stdio MCP bridge
  service-client/  NEW: /surface schemas, /verbs metadata, /dial
  service/         NEW: registry, service handlers, request orchestration
  web-ui/          NEW: complete browser app
src/main.ts        binary composition root
src/runner-main.ts worker composition root
website/           public documentation, separate from runtime web-ui

service-client → run-client/surface (portable vocabulary only)
service → service-client + run-client + run-history
web-ui → service-client/surface + framework browser hooks
cli → service-client + run-client + run-history (native compatibility)
root → cli + service + execution
```

Service receives a `RunLauncher` port bound by root to the packaged coordinator; it does not import execution. Service owns cross-run orchestration/request receipts, not scheduling, log verdict, retry closure, or GitHub posting. Reuse PR 1's policies. Client packages have lean manifests and browser-safe exports; Node/platform dials must not leak into browser imports. Test the actual import/package closure.

Root composes daemon control, history reader, service runtime, launch port, HTTP/WS/MCP adapters, and static asset paths. `web-ui` produces assets served by the listener, not server-side UI imports. Worker closure stays free of web/UI/MCP. Ship one release via existing Nix/Bun hydration and one Effect instance; no independent package publication or new flake inputs.

## Use the whole Kolu Surface framework

Inspect actual source from Odu's current npins dependency and existing consumers before implementation. Audited revision: `f3ba6394b4ac667906190f2e5cda3b38ea871e47`. Surface there is Effect RPC; guide references to oRPC can be stale.

| Framework package | Responsibility |
| --- | --- |
| `surface` | Typed cells/collections/streams/procedures, implementation, projection, clients |
| `surface-app` | Static HTTP shell, framework WS route, origin/liveness/reconnect lifecycle |
| `surface/solid` | Browser reactive reads/subscriptions |
| `surface-cli` | Shared verbs/readers, help, JSON and exit discipline, endpoint resolver |
| `surface-mcp` | Shared exposure, tools/resources, injected MCP transport |
| `surface-daemon` | Per-user home, singleton gate, frozen control identity/drain, daemon entry |
| `surface-daemon-supervisor` | Endpoint convergence, identity/reuse/recycle, survivable process spawn |
| `surface-remote` | Existing worker provisioning/sessions; retain execution's use |

Source roots: https://github.com/juspay/kolu/tree/f3ba6394b4ac667906190f2e5cda3b38ea871e47/packages

Concrete inspected seams to recheck: `defineSurface`; `serveSurfaceApp`; `surfaceCommands({surface, expose, verbs, endpoint, ...})`; `serveSurfaceAsMcp({surface, client, expose, tools, transport?})`; `daemonHome`/`daemonMain`/`daemonProcessMain`; supervisor `createEndpoint`/`converge`/`recycle`/`survivableSpawnDriver`.

Do not invent a REST `/api` layer or custom browser SSE protocol. Browser and generated CLI dial the same framework WS Surface, using its actual route helper. HTTP agents use MCP Streamable HTTP at `/mcp`. `surface-mcp` has injectable transport, not a turnkey shared-listener HTTP mount: complete actual request/session/teardown integration with the MCP SDK and service listener in this PR. Test it over HTTP.

`odu mcp` is a stdio connection/projection bridge with no independent run authority. Share one exposure/verb table across CLI and MCP. Existing Olai code demonstrates CLI-to-MCP HTTP in principle; this design uses WS for generated CLI to retain streaming readers. If using a request/response endpoint, declare `streaming:false` and do not advertise unsupported watch/follow. Do not assume MCP resource notifications replace durable waits.

## Singleton and lifetime

- Fixed default origin: `http://127.0.0.1:18440`. One per-user daemon home/control socket, independent of cwd. `odu web` ensures verified readiness, prints URL, and returns. Do not silently select another port.
- Concurrent launchers converge through framework singleton machinery. Readiness includes HTTP bind and matching identity. An unrelated occupied port produces an actionable refusal; do not kill its process.
- Browser, HTTP agents, and CLI use the same service handler authority. Compose lifecycle/control Surface as a framework sibling, not a second identity protocol.
- Reuse compatible running daemon. Expose active build/revision and protocol/storage compatibility. A new invocation from upstream must not silently recycle active CI to match its binary.
- Implement a documented explicit upgrade path using framework capture/drain/reattach. Incompatible daemon yields clear recovery; independent coordinators survive supported service replacement. Reconnect and reconcile journal/catalog/receipts before claiming ready.
- Both daemon and run owners must survive caller exit. Use the framework survivable spawn driver with absolute binary/environment. Detached children inside the web daemon's cgroup are insufficient. Test Linux systemd and non-systemd branches as applicable; retain supported Darwin behavior.
- HTTP disconnect, MCP wait cancellation, and CLI Ctrl-C cancel observation only. Domain `run_cancel` is explicit. Preserve native foreground cancellation semantics from PR 1.
- Bind loopback, use framework origin/Host controls and explicit MCP access policy. Reject unauthorized cross-origin mutations; protect local arbitrary-checkout execution from hostile web origins. Apply one coherent policy without secrets embedded in URLs. Document and test the actual policy.

## State and mutation authority

```text
user-state/odu/runs/RUN_ID/...       coordinator-owned PR 1 catalog/evidence
user-state/odu/service/requests/ID   service-owned durable request receipts
service runs board                 derived projection, not second truth store
client after-cursor                per caller, never destructive global ack
```

Discover native runs created before web starts through the canonical catalog. Reconcile surviving coordinator endpoints and terminal records on startup; link loss is not proof of death. Explicit legacy import remains available. Checkout removal cannot erase retained new evidence.

Persist accepted service request identity before dispatch and reconcile it with execution's launch/retry ID after crashes. Same ID/input returns the same receipt; conflicting input refuses. A crash between spawn and service receipt must not duplicate execution. Use guarded recovery for unfinished/dead owners; never turn unknown state into a pass or a fabricated test failure. Board projections stay bounded/paged and identify stale/disconnected state honestly.

## Service contract and shared verbs

The shape below specifies domain meaning, not copy-paste framework API syntax. Implement concrete typed schemas and generated help.

```text
cells:       service { identity, build, readiness }
collections: runs[runId], logTails[encoded run/node/attempt key]
streams:     nodes({runId}) → initial snapshot + updates
procedures:  run.start, run.wait, run.retry, run.cancel, log.read
shared verbs: run_start, run_wait, run_retry, run_cancel, log_read
```

Run keys are host-global; resources must not depend on the MCP host's cwd. Expose only intended public operations with default-deny projection; private worker configure/lease/control operations stay private.

| Operation | Required contract |
| --- | --- |
| Start | Explicit checkout, expected SHA, request ID, scoped options → accepted addressed receipt |
| Wait | Run ID, after-cursor, bounded deadline → attention and consistent state |
| Retry | Run ID, selectors, expected attempt, request ID → effective run/attempt/scope receipt |
| Cancel | Explicit run/node/lane scope and idempotent request → addressed result |
| Log read | Encoded key, byte offset/limit → bytes/page, next offset, EOF/completeness |

Validate SHA/config/host pool before acceptance. Reserve identity before slow provisioning; expose queued/provisioning state. Existing checkout conflict returns existing run/SHA with observe or explicit supersede options. Refusals contain valid scoped alternatives, not global config rewrites or shell strings to eval.

## All five agent improvements

1. **Resumable attention.** Use PR 1's journal/query. Default wait is 30 seconds. Deliver actionable failure plus diagnosis before unrelated nodes settle. Failures during disconnect/editing replay; independent agents have independent cursors. Cursor consumption never clears unresolved red. Paginate without advancing past omitted events. Wrong/expired cursor gives resync refusal. Terminal response remains terminal; skill stops waiting on it.
2. **Durable diagnostics.** Run/node/shard/attempt-addressed immutable logs survive teardown/retry. Attention carries exit/signal, placement, log key, completeness and reporter excerpt or labelled tail. Default encoded domain response budget 16 KiB, excerpt maximum 4 KiB; paginate excess. Reuse finalization barrier and explicit truncation. Envelopes are outside that budget; log reads page durable bytes.
3. **Correct recovery.** Live same-SHA retry resets selected node/affected dependants with sibling work preserved. Finalized same-SHA retry creates linked selection run from recorded inputs/partitions/closure and reacquires capacity. New SHA requires explicit new run. Race settlement atomically, refuse stale attempts, deduplicate mutations. Never replace snapshot with current HEAD, rewrite parent outcome, or promote selection pass to full pass. Show GitHub reporting debt separately.
4. **Actionable startup.** Typed identity/config/host/SHA/port refusals and visible provisioning replace blind process polling. Lost replies are reconciled by request ID. Accepted waits survive caller churn without owning run lifetime.
5. **One adoption loop.** Browser, generated CLI and MCP share typed meanings and verb metadata. Skill bootstraps from unpinned upstream, starts once, waits with cursor, diagnoses, retries or starts new SHA, and verifies final full scope/SHA/reporting state.

### Transport result is not CI result

| Surface CLI exit | Meaning |
| --- | --- |
| 0 | Successful call, including an answer reporting CI failure or deadline |
| 1 | Declared refusal |
| 2 | Local usage/input error |
| 3 | Endpoint/transport unavailable |
| 130 | Observer interrupted; run continues |

CI answers carry `reason` (`failure`, `still_running`, `settled`), `settled`, `passed`, SHA, scope, failures, cursor/pagination, and posting debt. MCP red CI is a normal tool result; declared refusal is a tool error. Do not introduce v0.1's custom exit-10 deadline scheme. Native legacy exits remain governed by their existing contract.

## Browser: complete working application

- Board: all registered runs, project/worktree/branch, exact tested SHA, queue/running/terminal state, unresolved attention, scope, and reporting debt; bounded history navigation.
- Create: explicit checkout/SHA and scoped settings, duplicate/existing-run response, provisioning feedback.
- Detail: DAG/node/shard/attempt state, live and historical log pages, completed diagnostic excerpts, retry history/linked runs.
- Controls: real live/finalized retry, new run, and explicit cancellation; show effective scope/identity and typed refusals. No decorative or placeholder buttons.
- Connection lifecycle: initial loading, reconnect, stale state, unavailable service, recovered view. Keyboard access and usable narrow layouts.
- Browser hooks use the shared Surface; no separate browser execution or retry logic.

## CLI, MCP, and skill

Mount generated commands under `odu surface` to avoid existing command/reserved reader collisions. `get/keys/watch/list` grammar comes from declared schemas and generated help; agents must not guess composite resource URIs.

```sh
nix run github:juspay/odu -- web
nix run github:juspay/odu -- surface run_start \
  --input '{"checkout":"/code/app","expectedSha":"ACTUAL_SHA","requestId":"UNIQUE_ID"}' --json
nix run github:juspay/odu -- surface run_wait \
  --input '{"runId":"RETURNED_RUN","after":"RETURNED_CURSOR"}' --json
nix run github:juspay/odu -- mcp
```

Examples specify intended UX; ensure final generated parser/help actually accepts them. HTTP MCP clients use normal initialize/tools/call/resources/read lifecycle at `/mcp`, with the same verbs/data. Stdio bridge connects/ensures the daemon and forwards; keep stdout protocol-clean.

Update authored skill sources (baseline `.apm/skills/odu/SKILL.md`, `.apm/skills/odu-mcp/SKILL.md`, launcher/config as needed), then regenerate managed output by the repository convention. Do not only edit generated `.claude` copies. Teach finite waits, early diagnosis, cursor replay, terminal handling, scoped retry, and transport reconciliation. Use `nix run github:juspay/odu` without a consumer pin. Odu's internal reproducible dependency pins and local PR testing are separate concerns.

## Build order inside this PR

1. Verify PR 1 foundation and compose service-client schemas/verbs with handler ports and catalog projection.
2. Complete singleton, service persistence/recovery, and actual HTTP/WS/MCP lifecycle.
3. Complete browser, generated CLI, stdio bridge, compatibility adapters, and skill.
4. Finish Nix assets/entrypoints/hydration, docs, and all acceptance gates before release.

Framework extension, if needed, must have a working integrated consumer; it cannot remain a deferred prerequisite. Resolve integration in this release or explicitly revisit the architectural boundary.

## Acceptance gates

| Fixture | Required proof |
| --- | --- |
| Start → wait → logs → retry → cancel via all faces | Same addressed state/transitions and declared refusals; real browser, CLI, HTTP MCP and stdio |
| Early failure with slow sibling | Finalized diagnosis returned before sibling completion; board agrees |
| Disconnect/second failure/two observers/bounded pages | No lost events or destructive shared acknowledgement |
| Deadline, CI red, settled green, bad input, wrong run | Domain/transport outcomes and exact framework exits remain distinct |
| Concurrent bootstrap and unrelated fixed-port occupant | One verified singleton or actionable refusal, never random fallback |
| Client exit, service kill/upgrade, surviving native coordinator | Runs continue; restored board/receipts; exact build identity visible |
| Native run started before daemon | Discovered from catalog without arbitrary filesystem scan |
| Crash before/after dispatch or receipt persistence | One execution/action per request ID, reconciliation after restart |
| Live/finalized retry, settlement race, shards, missing snapshot | Correct attempts/closure, preserved evidence, honest partial scope |
| HTTP initialize/session, long wait, disconnect/cancel, resource read | Actual MCP transport works and tears down without canceling CI |
| Cross-origin mutation/invalid Host and allowed local clients | Explicit access policy enforced consistently |
| Browser controls, reconnect, keyboard, narrow viewport | Complete usable paths and honest stale/fault presentation |
| Package/binary closures, static assets, docs/skill examples | Installed Nix application and generated CLI work outside repository cwd |

Run repository typecheck/unit/e2e/CI checks and real `nix build .#odu .#odu-runner`; inspect current recipes (`just typecheck`, `just test`, `just e2e`, Nix format and lock regeneration when changed). Extend black-box fixtures, use isolated per-user state for tests, and serialize fixed-port cases. Do not kill a user's running daemon to make tests pass. Exercise packaged assets and fresh-agent-style commands against the local built PR artifact; upstream will not contain unmerged changes. Report platform-specific lifecycle evidence honestly.

## Completion handoff

Produce the complete implementation and a PR-ready description with before/after behavior, package composition, lifecycle and compatibility decisions, final command/transport examples, and validation evidence. Include browser screenshots and actual cross-face smoke results. Describe any material design adjustment; do not mark completion with missing advertised interfaces or untested claims. No implementation beyond this PR is required for the proposed web experience.
