openotters

Workspaces

The per-agent FHS root, the model's CWD, and the context files that tell the agent what it can do.

Every agent gets its own workspace โ€” a directory it can read and write, plus a set of auto-generated context files the runtime injects into the model's system prompt at start-up. The context files are how the model learns about its tools, its environment, and what it's allowed to do, without you re-stating any of it in the Agentfile.

On-disk layout

Under the system executor, the workspace lives at ~/.otters/agents/<agent-uuid>/ on the host โ€” the runtime runs as a plain subprocess and sees those host paths directly. Under the docker executor, each top-level subtree of that host directory bind-mounts onto the matching standard Linux path inside the container, so the agent sees a normal FHS rooted at / โ€” no /agent prefix.

<agent-root>/                    โ†’   in-container (docker)
โ”œโ”€โ”€ workspace/                   โ†’   /workspace        # CWD; the model's writable scratch
โ”œโ”€โ”€ home/                        โ†’   /home             # HOME โ€” agent dotfiles, never host's
โ”œโ”€โ”€ tmp/                         โ†’   /tmp              # TMPDIR โ€” wiped on restart
โ”œโ”€โ”€ var/lib/                     โ†’   /var/lib          # persistent state (memory.db, etc.)
โ”œโ”€โ”€ opt/bins/                    โ†’   /opt/bins         # flat dir of symlinks โ†’ /opt/bin-images/<name>/<name>
โ””โ”€โ”€ etc/
    โ”œโ”€โ”€ context/                 โ†’   /etc/context      # auto-generated context files (see below)
    โ”œโ”€โ”€ data/                    โ†’   /etc/data         # static files from ADD directives
    โ”œโ”€โ”€ Agentfile                โ†’   /etc/Agentfile    # verbatim DSL source โ€” what the operator wrote
    โ””โ”€โ”€ agent.yaml               โ†’   /etc/agent.yaml   # slim runtime config (see "agent.yaml" below)

BIN tool binaries on docker come from per-tool OCI image mounts at /opt/bin-images/<name>/<name> (hidden from the model); the symlinks in /opt/bins/ are what the runtime puts on PATH. On system, BIN binaries are materialised as plain files under usr/bin/<name>.

The model is launched with CWD = /workspace. Any file the agent creates lands there. The other directories exist so HOME, TMPDIR, and PATH resolve to agent-local paths instead of the host's, but the model doesn't have to know about them โ€” its mental model is "I write to my workspace".

agent.yaml

etc/agent.yaml is the runtime's slim config file. The daemon writes it at materialise time; the runtime reads it at start-up.

id: 86106816-โ€ฆ
name: Homelab
model: anthropic-local/claude-opus-4-7
workspace: /workspace

# OCI provenance โ€” what bytes the agent was materialised from.
image:   { ref: kubectl:latest,                    digest: sha256:3b97b227โ€ฆ }
runtime: { ref: ghcr.io/openotters/runtime:latest, digest: sha256:1ab55f94โ€ฆ }

# Runtime tunables (the CONFIG directive's flat map, kebab-case
# keys mirroring the Agentfile). Consumed by the runtime โ€”
# max-tokens / max-iterations / memory-strategy / memory-max-*.
configs:
  max-tokens: 2048
  max-iterations: 10

# Every env key this agent expects. KEYS ONLY โ€” values live in
# daemon.db (operator overrides) and the Agentfile (spec defaults),
# and get merged into the spawn env at start time. No secrets on
# disk in this file by construction.
envs:
  - key: KUBECONFIG
    description: Kubeconfig path inside the agent.

# Spec-declared mounts. Operator -v host paths live in daemon.db;
# only the target / description / read-only flag persist here.
mounts:
  - target: /kubeconfig.yaml
    description: Kubeconfig for the cluster the agent should operate on.
    read_only: true

# Ordered context files. Absolute paths from the agent root.
# Replaces the older dir-scan of etc/context/ โ€” the file is the
# authoritative declaration of what loads into the system prompt.
context:
  - /etc/context/SOUL.md
  - /etc/context/SCENARIOS.md

tools:
  - name: kubectl
    description: kubectl CLI.
    binary: /opt/bins/kubectl
    ref: ghcr.io/openotters/tools/kubectl:latest
    digest: sha256:efghโ€ฆ
    usage: /etc/data/bins/kubectl/USAGE.md

The original Agentfile (verbatim DSL, with comments and formatting intact) rides along the image as its own layer and lands beside this file at /etc/Agentfile. Reading the two together gives you what the operator wrote and the daemon's resolved view.

Context files

At start-up the runtime auto-generates a small set of Markdown files in the workspace root and pulls them into the agent's system prompt. The model reads them like any other context.

AGENT.md

The agent's identity card. Describes:

  • Declared bins โ€” every BIN the agent can call, by name, with the one-line description from the Agentfile.
  • Environment โ€” every ENV key the agent was started with, with the description and the operator-supplied value (redacted for obvious secret-shaped keys).
  • Capabilities โ€” a positive section listing what the runtime can do for the agent (network, persistent memory, async jobs, persistent workspace). Stops the model from hallucinating that it can't do things it can.
  • Binaries allowlist โ€” a strict directive that the agent can only call the listed BINs. Stops the model from hallucinating tools it doesn't have.

Generated fresh on every start, so an -e override or a --model swap shows up immediately.

WORKSPACE.md

The FHS-style layout of the workspace tree, with absolute paths the model uses verbatim in tool calls. The model copies paths out of this file when constructing sh -c "cat /workspace/..." style invocations โ€” no guessing, no hallucinated paths.

MOUNTS.md

Present only when the agent was started with otters run -v mounts. Lists every host bind-mount the operator declared, with:

  • The target path inside the workspace.
  • The optional description from the -v declaration.
  • Whether the mount is read-only or read-write.

Without this file the model has no way to learn about ad-hoc operator mounts โ€” they're not in the Agentfile, they don't show up in env vars, the path just exists. MOUNTS.md makes them explicit.

One file per CONTEXT heredoc

Every CONTEXT NAME <<EOF โ€ฆ EOF block in the Agentfile becomes its own file (SOUL.md, SCENARIOS.md, IDENTITY.md, โ€ฆ). The optional description on the directive renders as a sub-header above the body.

The convention is to give each section a short, capitalised name so the model can refer to it ("per SOUL.md, you โ€ฆ"). The runtime feeds all CONTEXT files in declaration order, so order in the Agentfile controls the reading order in the prompt.

Lifecycle

  • Created on the first otters run for a given agent name. The directory is materialised from the agent image.
  • Persistent across otters stop / otters start. The model's workspace files survive restarts.
  • Wiped on otters rm. Removing an agent removes its workspace.

The runtime never garbage-collects workspace contents on its own โ€” files the agent wrote stay until the operator removes the agent or empties the dir manually.

Operator-supplied bind-mounts

otters run -v HOST:TARGET[:DESC][:ro|rw] exposes a host path as a workspace entry. Use this to give the agent access to project sources, data files, or a credentials file the Agentfile shouldn't carry.

The mount is recorded in MOUNTS.md so the model sees it. :ro makes the mount read-only at the executor boundary โ€” the model can read but not write, regardless of what its tools try.

otters run my-coder -v "$PWD:/workspace/src:Project source"
otters run my-data -v "/data/customers.csv:/workspace/data/customers.csv:ro"

See also

  • Agentfile CONTEXT โ€” declaring context heredocs.
  • Agentfile ENV โ€” environment vars surfaced in AGENT.md.
  • Executors โ€” where workspace bytes actually live (host filesystem vs container bind-mount).