Agent Service Architecture: Session, Harness, Sandbox — a Minimal-Migration Design for Codex/OpenCode Users
Research date: Aug 5, 2026. This is a practical design document built on the primary sources reviewed in the companion article durable-agent-platforms.md (Sierra Pinecone, Shopify River/Aquifer, Anthropic Managed Agents). Every fact is cited inline to a real URL; the design sections are the author's synthesis, not claims made by the sources. Companion article in this workspace: durable-agent-platforms.md.
TL;DR
Shopify's River and Anthropic's Managed Agents both converged on the same agent-service decomposition:
Session (durable, append-only event log) · Harness (the agent loop, stateless and disposable) · Sandbox (where code runs, disposable). The harness never lives inside the sandbox.
For someone who already works with a coding CLI (Codex, OpenCode), the minimal-migration path is short because OpenCode already implements most of this architecture — a headless HTTP server with a full session API, an SSE event stream, and a web/TUI/desktop client. The practical build is:
- Keep using the CLI as the harness — unchanged.
- Add an append-only event log (Postgres) as your canonical session store, fed from the CLI's own session API.
- Run the CLI inside a container sandbox (Docker / E2B / gVisor) instead of bare metal.
- Add post-processing workers on the event log (summarize, diff snapshot, pattern mining → skills).
- Add a chat gateway (Slack via Bolt, or web) last, as a thin profile over the same sessions.
A Slack app is the surface Shopify chose and validated, but it is not the architecture — for a solo developer the terminal or web UI is the cheapest gateway, and Slack is an optional later profile.
1. Naming: Sierra and River are two different things
The search terms "Sierra River" conflate two unrelated products:
- Sierra — https://sierra.ai/ — Bret Taylor's enterprise agent company (Agent SDK, Horizon, Agent Studio). Commercial SaaS; not open source. Its SDK page (https://sierra.ai/product/agent-sdk) emphasizes orchestration, guardrails, composable skills, and omnichannel deployment.
- River — Shopify's Slack-native coding agent, built on Shopify's internal Aquifer platform, documented in Under the River (https://shopify.engineering/under-the-river). River is the visible product; Aquifer is the substrate (session, harness, sandbox, gateway, durable event log, credentials proxy, observability).
The architecture worth copying is Aquifer's, not Sierra's SDK, because the only public engineering write-ups describing the session/harness/sandbox decomposition are Shopify's Under the River (https://shopify.engineering/under-the-river) and Anthropic's Scaling Managed Agents: Decoupling the brain from the hands (https://www.anthropic.com/engineering/managed-agents).
2. The reference architecture
2.1 Three primitives
Anthropic (https://www.anthropic.com/engineering/managed-agents) explicitly frames the design as virtualizing agent components the way operating systems virtualized hardware ("process, file"):
| Component | Role | Durability |
|---|---|---|
| Session | "The append-only log of everything that happened"; durable context object that lives outside the model's context window | Survives everything |
| Harness | "The loop that calls Claude and routes Claude's tool calls to the relevant infrastructure" | Stateless, disposable |
| Sandbox | "An execution environment where Claude can run code and edit files" | Disposable; provisioned on demand |
Shopify (https://shopify.engineering/under-the-river) uses nearly identical vocabulary: Session = "durable identity. Append-only event log. Postgres-backed. The canonical truth about what's happened so far"; Harness = "the agent loop. Reads history, calls the model, emits tool intents. Cheap to recreate, disposable"; Sandbox = "where the code runs. Filesystem, shell, the repo... Disposable. Sometimes warm, often fresh." The design constraint: "Cells die, sandboxes die, machines die. The conversation doesn't."
2.2 Interfaces
Anthropic's essay names the stable interface signatures (https://www.anthropic.com/engineering/managed-agents):
- Session:
emitEvent(id, event)to record,getSession(id)/getEvents(id, slice)to read positional slices (used to resume, rewind, or reread lead-up context). - Harness recovery:
wake(sessionId)reboots a fresh harness from the last event; nothing in the harness survives a crash. - Sandbox:
provision({resources})thenexecute(name, input) → string. The harness calls the sandbox exactly like any other tool. "The harness doesn't know whether the sandbox is a container, a phone, or a Pokémon emulator."
2.3 Why the harness must leave the sandbox
Shopify derives three properties, "and you can't get any of them otherwise" (https://shopify.engineering/under-the-river):
- Safety — "the agent loop is not in the same blast radius as
rm -rf" - Replaceability — swap models, runtimes, even languages on the harness side without disturbing the sandbox
- Observability — the entire decision stream is on the harness side, visible to one place
Anthropic documents the failure mode it escaped (https://www.anthropic.com/engineering/managed-agents): a single-container design ("pets, not cattle") where a container failure lost the session, failures were undebuggable through the WebSocket event stream alone, and the harness's assumption that "whatever Claude worked on lived in the container with it" forced VPC peering for customer environments. After decoupling, p50 time-to-first-token dropped roughly 60% and p95 over 90%, because containers are only provisioned when a tool call actually needs them.
2.4 Profiles and gateways (Shopify)
River is one profile on Aquifer; PR review is another; a headless agent is another. A profile is data (https://shopify.engineering/under-the-river): "a system prompt, a set of skills, a set of extensions, a sandbox policy, model defaults." Adding a new agent product means adding a bundle, not building a new platform. The same substrate serves three consumers:
- Interactive (River) — durable session, live human
- Automation (PR review) — durable session, woken by an external system
- Job (CI/batch) — ephemeral; session log optional
The gateway is the chat surface (Slack for River). The point the secondary analysis makes well (https://adaptordie.io/blog/shopify-river-agent-stack): "The chat surface is an entry point into the same durable session model."
3. The session is your post-processing and analysis plane
The reason the session is an event log and not a chat buffer is that it makes the work queryable and replayable. Two mechanisms matter:
- Context outside the context window. Anthropic treats the session as "a context object that lives outside Claude's context window" (https://www.anthropic.com/engineering/managed-agents). The brain can interrogate it with
getEvents()— pick up where it stopped, rewind a few events, reread context before an action — and context transformation (compaction, trimming, prompt-cache-friendly organization) is pushed into the harness, keeping the raw log lossless. - The corpus flywheel. Shopify mines the session corpus and feeds patterns back into skills, prompts, and defaults: "The agent gets smarter without requiring model retraining" (https://shopify.engineering/under-the-river). It writes its own telemetry — usage numbers in Under the River "come from the
river_sessionsdomain table that River writes to itself, every session." Tobi Lütke frames the compounding loop: "If every interaction with an agent happens in a private window, the only person who learns anything is the person at the keyboard" (https://x.com/tobi/status/2053121182044451016, summarized at https://simonwillison.net/2026/May/11/learning-on-the-shop-floor/).
Concretely, "post-processing and analysis" = read the event log after (or during) a session, summarize it, snapshot the diff, extract metrics, and mine repeated patterns into new skills. That is the analysis pipeline; it is only possible because the session log is durable and canonical.
4. Is a Slack app the best way to implement it?
4.1 The case for Slack
Shopify validated Slack as a surface at scale: in a recent 30-day window, 59,918 sessions in 5,170 channels touching 7,000+ people, with 3,536 River-coauthored PRs merged; 1 in 8 merged PRs at Shopify is coauthored by River; median session 19 minutes and 50 tool calls (https://shopify.engineering/under-the-river). The design decision that makes it work: "River only works in the open. No direct messages." Every conversation is a public, searchable Slack transcript, so a useful debugging path becomes a thread, a thread becomes a runbook, a repeated runbook becomes a skill (https://shopify.engineering/under-the-river). Simon Willison notes the precedent: Midjourney's public-Discord launch had the same "watch and learn" effect (https://simonwillison.net/2026/May/11/learning-on-the-shop-floor/).
Open source implementation: Bolt for Python (https://github.com/slackapi/bolt-python) — the official Slack framework — supports events, slash commands, Block Kit interactivity, and Socket Mode (no public webhook needed).
4.2 Other gateway options
| Surface | Example / tooling | Trade-off |
|---|---|---|
| Terminal (current setup) | Codex CLI, OpenCode TUI | Zero migration; private by default = no shared corpus |
| Web chat | OpenCode web UI (part of the same server) | Public or shareable; same session model; no extra infra |
| Slack | Bolt (https://github.com/slackapi/bolt-python) | Best for teams; threads as durable UI; public-by-default corpus |
| Discord | Discord bot API | Midjourney precedent; public channels, but consumer-grade |
| Mattermost / Matrix / Teams | Self-hosted chat | Data ownership; enterprise access controls |
| GitHub Issues / PRs | GitHub App webhooks | Best for "automation" mode (PR-review agents); no chat at all |
Verdict: Slack is the best fit when the goal is organizational learning (the "Lehrwerkstatt" effect). For a solo developer or a small team the marginal value of Slack is low — the terminal or web UI is the cheaper gateway, and Slack can be added later as one more profile on the same session model. Build the substrate first; the chat surface is swappable, the event log is not.
5. Open-source components per layer
| Layer | Open-source options | Notes |
|---|---|---|
| Harness (agent loop) | Claude Agent SDK (https://github.com/anthropics/claude-agent-sdk-typescript), OpenAI Agents SDK (https://github.com/openai/openai-agents-python), OpenCode (https://github.com/anomalyco/opencode), Codex CLI (https://github.com/openai/codex) | Claude Agent SDK is MIT, wraps the Claude Code loop. OpenAI Agents SDK is MIT, has SandboxAgent, Sessions, and a Redis session store. OpenCode is MIT. Codex CLI is Apache-2.0 with a non-interactive codex exec mode. |
| Session (store) | OpenCode server sessions API (https://opencode.ai/docs/server/), OpenAI Agents SDK sessions (Redis), or a plain Postgres append-only log | Postgres is Shopify's own choice (https://shopify.engineering/under-the-river) and the most queryable for analysis. |
| Sandbox (runtime) | Docker, E2B (https://e2b.dev/), Daytona (https://www.daytona.io/), gVisor (https://gvisor.dev/), Modal (https://modal.com/) | Docker is the simplest start; E2B uses Firecracker microVMs with hardware-level isolation and session-scoped sandboxes; gVisor intercepts syscalls in user space. |
| Gateway / UI | Bolt (https://github.com/slackapi/bolt-python) for Slack; OpenCode TUI/web/desktop for everything else | Bolt handles events, commands, Block Kit, Socket Mode. |
| Knowledge | AGENTS.md convention (shared by OpenCode and Claude Code); Shopify's skills-as-files pattern; Shopify/agent-skills repo (https://github.com/Shopify/agent-skills) |
Skills are "written-down knowledge as files, loaded into agent sessions on demand" (https://shopify.engineering/under-the-river). |
Note on licenses for the harness layer: the Claude Agent SDK package is MIT but its use is governed by Anthropic's Commercial Terms and it is Claude-only (https://www.open-source-ai.tech/projects/claude-agent-sdk). OpenCode and the OpenAI Agents SDK are MIT; Codex CLI is Apache-2.0.
6. Proposed minimal-migration design for a Codex/OpenCode user
6.1 The key finding: OpenCode is already most of the architecture
OpenCode's architecture is a headless HTTP server with a TUI as a client (https://opencode.ai/docs/server/). Running opencode serve exposes an OpenAPI 3.1 endpoint (/doc) with exactly the session primitives you need:
POST /session·GET /session/:id·PATCH /session/:id·DELETE /session/:idPOST /session/:id/message(sync) andPOST /session/:id/prompt_async(fire-and-forget)POST /session/:id/abort·POST /session/:id/summarize·POST /session/:id/forkGET /session/:id/diff·GET /session/:id/todo·GET /session/:id/messageGET /event— server-sent events stream;GET /global/event— global SSEPOST /session/:id/init— "Analyze app and createAGENTS.md"
So sessions, UI/UX, and the agent runtime can be copied from the framework rather than built.
6.2 The target shape
[Gateway] Terminal / Web UI / Slack Bolt app (thread <-> session_id)
|
v
[Session Service] --> Postgres append-only event log (canonical truth)
| ^
| | post-processing workers:
| | summarize, diff-snapshot, pattern mining -> skills/AGENTS.md
v
[Harness] opencode serve (or codex exec) -- stateless, respawn on wake()
| execute(name, input) -> string
v
[Sandbox] Docker / E2B (Firecracker) / gVisor -- per-session workspace, disposable
6.3 Migration steps (each one optional and incremental)
- Keep the CLI as the harness. No rewrite. OpenCode and Codex both already implement the agent loop, tools, permissions, LSP, and MCP (https://github.com/anomalyco/opencode, https://github.com/openai/codex). Migration cost: zero.
- Add the event-log sink. After each turn, read
GET /session/:id/messageandGET /session/:id/diff, and append an immutable row (session_id, message, parts, diff, timestamp) to Postgres. This is your session store and your analysis corpus. OpenCode already persists sessions locally; the sink makes them queryable and durable across machines. - Wrap the sandbox. Run the CLI inside a container (
docker run) or an E2B sandbox instead of bare metal. This buys the three properties — safety, replaceability, observability — that require the harness outside the sandbox (https://shopify.engineering/under-the-river). Keep credentials out of the sandbox: clone the repo with a bundled token wired into the local git remote, and proxy MCP/OAuth tokens from a vault (Anthropic's two patterns, https://www.anthropic.com/engineering/managed-agents). - Add post-processing workers. On the event log: summarize the session, snapshot the diff, compute metrics, and mine repeated patterns into skills or
AGENTS.mdupdates. This is theriver_sessions-style flywheel (https://shopify.engineering/under-the-river). - Add a chat gateway last. A Bolt app that maps
channel + thread → session_idand proxies to the OpenCode HTTP API. One more profile, not a new platform.
6.4 What to copy from a framework, vs. build
- Copy (open source, MIT): session management and UI/UX from OpenCode (server + TUI/web/desktop, https://opencode.ai/docs/server/, https://github.com/anomalyco/opencode); the harness loop from Claude Agent SDK / OpenAI Agents SDK; the Slack UI from Bolt (https://github.com/slackapi/bolt-python); the sandbox runtime from Docker/E2B.
- Build (the only bespoke parts): your event schema (what one row of "truth" means for your domain), the post-processing/analysis logic, and the domain knowledge files.
7. Minimal domain-specific harness generation
The cheapest way to get a domain-specific harness is not to generate code but to write knowledge as files and let the agent load them:
AGENTS.mdper repo or zone. Both OpenCode and Claude Code read these; OpenCode can even generate one viaPOST /session/:id/init(https://opencode.ai/docs/server/). Shopify calls written-down conventions, runbooks, intent documents, and zone knowledge part of the repo's "intelligence layer" (https://shopify.engineering/under-the-river).- Skills as files, loaded on demand. Shopify's model: "Skills: written-down knowledge as files, loaded into agent sessions on demand" (https://shopify.engineering/under-the-river). The public Shopify/agent-skills repo (https://github.com/Shopify/agent-skills) shows the pattern: a collection of agent skills installable via an
npx skillCLI. - A profile is just a bundle. system prompt + skills dir + model config + sandbox policy + permissions (https://shopify.engineering/under-the-river). Making a domain-specific agent = adding a bundle.
8. Implementation reference: build order (synthesis)
This section is the author's synthesis of the sources, not a claim made by any of them.
- Session log first. Postgres append-only event store (R1) with
emitEvent/getSession/getEvents-style APIs (A1) and checkpoints embedded in the stream (Agency pattern, https://sierra.ai/blog/agency-secure-scalable-sandboxes-for-agents). - Stateless harness. Wrap an existing harness (Codex/Claude Code/OpenCode) behind a small adapter; rebuild via
wake(sessionId)(A1). - Sandbox plane. Hardened containers (non-root, dropped capabilities, read-only root filesystem), a filtering egress proxy, and an LLM/credentials proxy outside the sandbox (A1, and Sierra's Agency deep dive https://sierra.ai/blog/agency-secure-scalable-sandboxes-for-agents).
- Telemetry as flywheel input. A session-level domain table written by the system itself — the
river_sessionspattern (https://shopify.engineering/under-the-river). - Gateways last. Slack, web, or issues/PRs as thin profiles over the same sessions.
9. Caveats and open items
- Neither Aquifer (Shopify) nor Agency (Sierra) is open source. The only public primary references to the session/harness/sandbox decomposition are Anthropic's Managed Agents essay (https://www.anthropic.com/engineering/managed-agents) and Shopify's Under the River (https://shopify.engineering/under-the-river).
- Sierra's Agent SDK (https://sierra.ai/product/agent-sdk) is commercial; it is not the open-source substrate described here.
- Usage numbers from Shopify are self-reported and "already wrong, in the upward direction" at publication (https://shopify.engineering/under-the-river).
- This post could not verify primary URLs for first-party Slack apps from the major model vendors at research time (search engines were rate-limited); the Slack argument therefore rests on the Shopify River and Midjourney precedents.
References (all fetched and verified on Aug 5, 2026)
- Shopify — Under the River (Burke Libbey, Javier Moreno, River, 2026-05-28). https://shopify.engineering/under-the-river
- Anthropic — Scaling Managed Agents: Decoupling the brain from the hands (Lance Martin, Gabe Cemaj, Michael Cohen, 2026-04-08). https://www.anthropic.com/engineering/managed-agents
- Tobias Lütke — Learning on the Shop floor (2026-05). https://x.com/tobi/status/2053121182044451016
- Simon Willison — Learning on the Shop floor link post (2026-05-11). https://simonwillison.net/2026/May/11/learning-on-the-shop-floor/
- adaptordie.io — Shopify Just Showed the Agent Stack (2026-05-29). https://adaptordie.io/blog/shopify-river-agent-stack
- Sierra — Agency: Secure, scalable sandboxes for agents (Rohith Ravi, 2026-07-29). https://sierra.ai/blog/agency-secure-scalable-sandboxes-for-agents
- Sierra — Agent SDK product page. https://sierra.ai/product/agent-sdk
- OpenCode — docs: Server. https://opencode.ai/docs/server/
- OpenCode — source (MIT). https://github.com/anomalyco/opencode
- OpenAI — Codex CLI (Apache-2.0). https://github.com/openai/codex
- OpenAI — Agents SDK (MIT). https://github.com/openai/openai-agents-python
- Anthropic — Claude Agent SDK (TypeScript) (MIT). https://github.com/anthropics/claude-agent-sdk-typescript
- Slack — Bolt for Python (MIT). https://github.com/slackapi/bolt-python
- Shopify — agent-skills. https://github.com/Shopify/agent-skills
- E2B — sandbox infrastructure. https://e2b.dev/
- Daytona — sandbox/workspace infrastructure. https://www.daytona.io/
- gVisor — application kernel sandbox. https://gvisor.dev/
- Modal — compute platform. https://modal.com/