Skip to Content
Design DecisionsAgent-Served Solution Data

Agent-served solution data

Status: Accepted · Date: 2026-07-02 · Area: Data

Context

Originally both the agent (writer) and the orbit-webapp reader mounted the solution repository and read it from disk — the reader on a read-only mount. That coupled the reader’s correctness to filesystem semantics that are unreliable in both target environments: SMB-backed Azure Files shares and macOS bind mounts drop inotify events, so a filesystem watcher cannot be trusted to tell the reader when the tree changed. It also meant two processes interpreting the same working tree while the agent switches git branches under it.

Decision

  • The agent is the sole owner of the /repo mount. It loads the whole RawSolutionTree into memory (SolutionModel) and serves it over HTTP; the reader webapp has no repo mount at all.
  • The contract: GET /solution/version returns a per-process monotonic version plus a per-boot instance UUID — cache identity is always the (instance, version) pair, never version alone, because the counter restarts with the agent. GET /solution/tree serves the tree (gzip, ETag "<instance>:<version>" with 304 support); POST /solution/reload is the manual escape hatch; GET /sources/* serves the code explorer’s data; and an SSE stream emits solution.updated events.
  • There is deliberately no filesystem watcher. Reloads are triggered at the writer’s well-defined settled moments — startup, per-task commit (so the reader shows live mid-request progress), request merge, request failure, post-crash recovery, manual — serialized and coalescing.
  • The webapp fetches through a server-only client (agent-source.ts; the browser never talks to the agent), rebuilds its rendered model only when (instance, version) moved, and serves stale from cache with a warning if the agent is down. Live refresh reaches the browser via an /api/orbit-events SSE proxy and an in-place RSC refresh.
  • The loader itself moved to the shared package implementation/shared/trajectory-loader (@maxq/trajectory-loader, a file: dependency of both codebases).

Consequences

  • One process interprets the repository; the reader can never observe a half-switched branch, and the agent’s single replica (ADR-004) is what makes the one in-memory model plus event bus safe. The webapp scales 0–3 replicas with per-replica caches, correct by cache keying.
  • On Azure the reader app needs no share mount and no read-only storage link anymore — provisioning gets simpler.
  • Accepted trade-off: out-of-band edits to the solution repo no longer appear in the reader until a reload is triggered (in local dev: curl -X POST http://localhost:3010/solution/reload). Task runs reload automatically.
  • The shared-loader file: dependency widens both Docker build contexts to implementation/ rather than the individual codebase folders.

Evidence

  • implementation/maxq-orbit-agent/codebase/src/solution/SolutionModel.ts — the in-memory model and reload semantics
  • implementation/orbit-webapp/codebase/src/lib/solution/agent-source.ts and src/lib/solution/index.ts — the HTTP client and (instance, version) cache
  • implementation/shared/trajectory-loader/ — the extracted loader
  • designs/agent-served-solution-data.md (§11 decisions, §12 implementation record), memory/agent-served-solution-data.md