Bundle Format
Bundle Files
An Odyssey bundle is defined by a small set of source files:
<project>/
odyssey.bundle.json5 # Bundle manifest
agent.yaml # Agent spec (path configurable via agent_spec)
README.md # Bundle documentation
skills/ # Skill directories referenced by manifest
resources/ # Resource files referenced by manifest
| File | Purpose |
|---|---|
odyssey.bundle.json5 | Runtime policy: bundle identity, executor, memory, tool list, sandbox settings, mounts, env passthrough, and resource limits |
agent.yaml | Agent identity, prompt, model selection, and tool permission rules |
README.md | Human-facing bundle documentation copied into the built artifact |
skills/ | Skill directories that can be surfaced through the Skill tool |
resources/ | Bundle-local assets copied into the built artifact when present |
odyssey.bundle.json5 points to the agent spec through agent_spec, so the agent file does not
have to be named agent.yaml, but the generated starter project uses that name.
Starter Template Versus Schema Defaults
There are two related but different things to keep in mind:
odyssey-rs initwrites an opinionated starter templateBundleManifestalso has schema defaults when some fields are omitted
Today the starter template generated by odyssey-rs init chooses these values:
| Field | Starter template value |
|---|---|
executor.id | react |
memory.id | sliding_window |
tools | All builtin tools are listed explicitly |
sandbox.mode | read_only |
sandbox.permissions.network | ["*"] |
sandbox.system_tools_mode | standard |
sandbox.system_tools | [] |
sandbox.env | {} |
By contrast, if you omit fields from a manifest and rely on schema defaults, the manifest layer currently fills in:
| Field | Schema default |
|---|---|
memory | { type: "prebuilt", id: "sliding_window", config: null } |
skills | [] |
tools | [] |
sandbox.mode | workspace_write |
sandbox.permissions.network | [] |
sandbox.system_tools_mode | explicit |
sandbox.system_tools | [] |
sandbox.resources | { cpu: 1, memory_mb: 512 } |
Manifest Schema
The bundle manifest (odyssey.bundle.json5) deserializes into BundleManifest. A compact manifest
that relies on schema defaults can look like this:
{
id: 'my-bundle',
version: '0.1.0',
manifest_version: 'odyssey.bundle/v1',
readme: 'README.md',
agent_spec: 'agent.yaml',
executor: { type: 'prebuilt', id: 'react' },
memory: { type: 'prebuilt', id: 'sliding_window' }
}
Top-level fields
| Field | Type | Description |
|---|---|---|
id | string | Unique bundle identifier (required) |
version | string | Semantic version (required) |
manifest_version | string | Bundle manifest schema identifier (required) |
readme | string | Relative path to the bundle README file (required) |
agent_spec | string | Relative path to the agent YAML file (required) |
executor | object | Executor configuration (required) |
memory | object | Memory provider configuration |
skills | array | Skill entries ({ name, path }) |
tools | array | Tool entries ({ name, source }) |
sandbox | object | Sandbox execution policies |
Executor and memory
In the current runtime:
executor.typemust beprebuiltexecutor.idmust currently bereactmemory.typemust beprebuiltmemory.idmust currently besliding_window
Sandbox fields
| Field | Type | Default | Description |
|---|---|---|---|
mode | string | "workspace_write" | Isolation mode: read_only, workspace_write, or danger_full_access |
env | object | {} | Map of sandbox env var name to host env var name to read at runtime |
permissions.filesystem.exec | string[] | [] | Executable paths under the staged bundle root |
permissions.filesystem.mounts.read | string[] | [] | Read-only host mount paths; absolute paths are allowed, and . resolves to the current working directory |
permissions.filesystem.mounts.write | string[] | [] | Read-write host mount paths; absolute paths are allowed, and . resolves to the current working directory |
permissions.network | string[] | [] | [] disables network; ["*"] enables unrestricted outbound network |
system_tools_mode | string | "explicit" | Host executable policy: explicit, standard, or all |
system_tools | string[] | [] | Host executable names or paths that process-execution tools may run |
resources.cpu | number | 1 | CPU limit |
resources.memory_mb | number | 512 | Memory limit in MB |
Manifest example with an explicit sandbox
{
id: 'repo-auditor',
version: '0.1.0',
manifest_version: 'odyssey.bundle/v1',
readme: 'README.md',
agent_spec: 'agent.yaml',
executor: { type: 'prebuilt', id: 'react' },
memory: { type: 'prebuilt', id: 'sliding_window', config: { max_window: 100 } },
tools: [
{ name: 'Read', source: 'builtin' },
{ name: 'Glob', source: 'builtin' },
{ name: 'Grep', source: 'builtin' },
{ name: 'Bash', source: 'builtin' }
],
sandbox: {
mode: 'workspace_write',
env: {
CARGO_HOME: 'CARGO_HOME'
},
permissions: {
filesystem: {
exec: [],
mounts: {
read: ['.'],
write: []
}
},
network: []
},
system_tools_mode: 'explicit',
system_tools: ['sh', 'cargo'],
resources: { cpu: 2, memory_mb: 1024 }
}
}
Validation rules
executor.typemust beprebuiltmemory.typemust beprebuiltexecutor.idandmemory.idmust be non-emptyreadmemust point to an existing file inside the projectagent_specmust point to an existing file inside the project- Each skill path must exist inside the project
sandbox.permissions.filesystem.execentries must point to existing paths inside the project; absolute paths,.., and symlink escapes are rejected- Host mount paths in
sandbox.permissions.filesystem.mounts.readand.writemust be absolute, except.which resolves to the current working directory sandbox.envkeys and values must be valid environment variable names using ASCII letters, digits, and underscores, and must not start with a digitsandbox.permissions.networkonly supports[]or["*"]in v1sandbox.system_tools_modemust be one ofexplicit,standard, orall- Agent tool permission entries such as
Bash(cargo test:*)must parse successfully; malformed entries are rejected - Tool entries must use
source: "builtin"
Current implementation limits
executor.id = "react"is the only executor implemented by the runtimememory.id = "sliding_window"is the only memory provider implemented- Only
source: "builtin"tools are supported (no custom tools in v1) - Network hostname allowlists are not implemented in v1; network policy is either disabled or unrestricted
sandbox.envreads host variables at runtime and injects them under the configured sandbox variable names; missing host variables are silently omittedsystem_tools_mode: "explicit"only mounts declared bundle exec paths and namedsystem_toolsinto confined Linux sandboxes, so complex host tools that spawn helper executables may require additional explicit entries orstandardsystem_tools_mode: "all"allows sandboxed process execution to resolve any executable path the sandbox can already see, so it should only be used for trusted workspace agents- v1 treats bundle manifests as trusted configuration. Mounts, system tools, env passthrough, and bundle-local exec paths are enforced as declared by the bundle author. v2 is expected to treat bundles as untrusted input and add stricter approval or policy layers around those capabilities.
Agent Spec
The agent spec YAML (agent.yaml) maps to AgentSpec. Default template:
id: my-bundle
description: my-bundle agent using Odyssey runtime
prompt: |
You are a helpful assistant Tess, Created by LiquidOS.
model:
provider: openai
name: gpt-4.1-mini
tools:
allow: []
ask: []
deny: []
Fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Agent identifier |
description | string | no | Human-readable description |
prompt | string | yes | System prompt |
model | object | yes | LLM configuration |
model.provider | string | yes | Provider name (for example openai, anthropic) |
model.name | string | yes | Model name (for example gpt-4.1-mini) |
model.config | object | no | Provider-specific configuration |
tools.allow | string[] | no | Tool permissions granted without approval; entries may be coarse like Read or granular like Bash(curl:*) |
tools.ask | string[] | no | Tool permissions that require approval before execution |
tools.deny | string[] | no | Tool permissions that are always denied |
model can be overridden at session creation and again per-turn through TurnContextOverride.model.
Agent tool permission entries may be coarse (Read) or granular (Bash(cargo test:*)), and
granular entries must use a single outer (...) matcher suffix.
Important behavior detail:
- an empty
tools.allowlist does not mean "no tools" - the runtime first selects tools from
manifest.toolsor the builtin registry allow,ask, anddenythen refine availability and permission behavior
That means the default starter agent with empty allow, ask, and deny still has access to the
tools declared by the manifest.
Build Output Layout
odyssey-rs-bundle materializes an OCI bundle layout when building directly:
<bundle-layout-root>/
agent.yaml
bundle.json
index.json
oci-layout
blobs/
resources/
skills/
When a bundle is installed into the local store, the runtime-visible files stay at install root and
the OCI metadata moves under .odyssey/:
<install-root>/
agent.yaml
resources/
skills/
.odyssey/
bundle.json
index.json
oci-layout
blobs/
Build behavior that matters when authoring bundles:
- The agent spec file is copied into the built layout as
agent.yaml - The README file is copied into the built layout at the manifest-defined
readmepath - Each skill entry is copied into
skills/<skill.name> - The
resources/directory, when present, is copied into the built layout asresources/ - Host mounts are also exposed inside the staged app workspace under
mount/read/...andmount/write/...so the agent can reference them with workspace-relative paths - Installed bundles keep OCI metadata and blobs under
.odyssey/so the install root stays focused on runtime-visible files - The manifest (
odyssey.bundle.json5) is not included in the payload layer
Bundle References
You will see the same bundle reference forms across the CLI, HTTP APIs, and runtime:
hello-worldorhello-world@latestresolves the latest installed local bundlelocal/hello-world:0.1.0resolves a specific namespaced installteam/demo:0.2.0andteam/demo@0.2.0resolve namespaced referencesteam/demo@sha256:<digest>resolves by digest./dist/my-bundleresolves a bundle layout directory directly.odysseyarchives must be imported before use; they are not executed directly