Runtime Architecture
High-Level Shape
Odyssey is a single-process runtime with three main layers:
CLI / TUI / HTTP server / embedded Rust app
|
OdysseyRuntime
|
-------------------------------------------------
BundleStore SessionStore Scheduler ApprovalStore
ToolRegistry host SandboxRuntime restricted SandboxRuntime
The transport surface changes, but the execution engine does not. That is the most important design idea in the repository: the CLI, server, and TUI all drive the same runtime type.
Core Runtime Components
Bundle store
BundleStore owns bundle installation, lookup, export, import, publish, and pull flows. At
execution time it resolves a bundle reference into an installed bundle path plus metadata and the
resolved manifest.
Session store
SessionStore persists sessions as JSON under the configured session root. It also owns the
broadcast senders used for per-session event streams.
The store is designed for runtime continuity:
- session writes are atomic
- corrupt session files are quarantined instead of crashing startup
- turn history is normalized after execution so tool activity can be replayed into memory
Tool registry
The builtin ToolRegistry is process-wide. Each turn selects a subset of those tools based on the
bundle manifest and the agent permission rules, then adapts them into the executor interface.
Approval store
ApprovalStore is in-memory session state for:
- pending approval requests
- session-scoped
allow_alwaysdecisions
It is intentionally ephemeral. Approvals do not survive process restarts.
Sandbox runtimes
OdysseyRuntime keeps two shared sandbox runtimes alive:
- one host runtime for
danger_full_access - one restricted runtime for confined execution
Each SandboxRuntime manages prepared cells and reuses them across turns when the session, agent,
and policy match.
Sessions, Turns, and Ordering
The runtime is concurrent across sessions but serialized within a session.
Two pieces make that work:
- the scheduler limits global concurrency with a semaphore
SessionExecutionGuardshold a per-session async mutex so only one turn or direct operator command runs in a given session at a time
That design avoids races in session history, approvals, and sandbox cell reuse while still allowing different sessions to execute in parallel.
Managed Sandbox Cells
Restricted execution is built around reusable managed cells. A cell is identified by a
SandboxCellKey containing:
- an optional
session_id - the
agent_id - the cell kind
- a component id
The runtime currently uses tooling cells for bundle execution. Each managed cell is laid out like this:
<cell>/
app/ # staged bundle contents
data/
home/ # HOME for sandboxed processes
cache/ # runtime-owned caches
tmp/ # private tmp area
runs/ # per-execution scratch directories
Each execution inside a cell gets its own scratch layout under runs/<uuid>/:
<run>/
inbox/
outbox/
work/
tmp/
This split is the core of Odyssey's sandbox filesystem model:
app/can stay read-only in restricted modes- tools still get writable
HOME, temp, cache, and per-run work areas - cells can be reused without rebuilding global sandbox state every turn
Turn Execution Pipeline
A submitted turn currently follows this path:
- Load the session record from
SessionStore. - Resolve the bundle and default agent from
BundleStore. - Choose the effective sandbox mode from the runtime override or the manifest default.
- Prepare or reuse a sandbox cell for the session and bundle policy.
- Build tool permission rules from
agent.yaml. - Load bundle skills and build the effective system prompt.
- Select the tool subset from the registry.
- Resolve the model provider from the session model or per-turn model override.
- Build memory from prior turns.
- Run the executor.
- Collect session events into normalized chat history.
- Persist the completed turn.
Two points are easy to miss:
- the runtime builds memory after loading session history, not from raw event replay alone
- the executor runs with an execution-local
ToolContext, not direct access to process-global state
Tool Calls Versus Direct Operator Commands
Odyssey has two different command-execution paths.
Agent tool path
When the agent uses Bash, the flow is:
- tool selection decides whether
Bashis available - tool permission rules decide whether the specific call is allowed, denied, or needs approval
- sandbox exec policy decides whether the shell and subcommands are executable
- events are emitted as tool events and, when a process runs, as
ExecCommand*events
Operator command path
When an operator uses run_session_command or a local TUI command such as !ls, the flow is:
- the runtime resolves the session bundle
- it prepares the same session sandbox cell
- it builds an operator-specific command policy
- it runs the command directly, without the bundle
Bashtool wrapper
This path is intentionally separate because it is an operator surface, not an agent decision. It
still emits ExecCommand* events, but it does not append a normal turn to session history.
Scheduler And Execution Status
The scheduler is an in-process queue backed by:
- a Tokio
mpscchannel for pending jobs - a semaphore that enforces
worker_count - an in-memory
turn_id -> ExecutionStatusmap
submit enqueues work and returns an ExecutionHandle. run uses the same scheduler but waits on
a completion channel for the final RunOutput.
There is no distributed worker tier in the current repository. All concurrency control is local to one runtime process.
Persistence And Cleanup Boundaries
Session persistence
Session state is durable. If the runtime restarts, previously completed turns remain available for:
- session inspection
- future memory construction
- TUI or API history views
Approval state
Approval state is not durable. Pending approvals and allow_always decisions live only in memory.
Sandbox cleanup
Deleting a session is a full cleanup operation:
- it waits for in-flight work on that session to finish
- it removes the persisted session file
- it clears approval state
- it shuts down sandbox cells for the session in both sandbox runtimes
That is why delete_session is asynchronous in the runtime API.
Transport Surfaces
Three user-facing surfaces sit on top of the same runtime:
- the CLI constructs
OdysseyRuntimedirectly for local workflows - the HTTP server constructs one runtime and exposes routes plus SSE event streaming
- the TUI constructs
OdysseyRuntimedirectly and renders the session event stream
The HTTP server is not a separate engine. It is a thin transport adapter over the same runtime.
Security Boundary In V1
In the current design, the bundle manifest is trusted configuration authored by the operator. That means:
- mounts
- env passthrough
- system tool exposure
- network mode
- bundle-local executable paths
are all enforced as declared by the bundle author. The runtime is strict about applying that policy, but it does not yet treat the manifest itself as hostile input. That is an explicit v1 boundary, not an accident.