openotters

Agentfile

The declarative grammar for OpenOtters agents.

An Agentfile describes everything an agent needs to run: which model to use, what tools it can call, what memory it has, and what its behavior should be.

A minimal Agentfile

FROM scratch
RUNTIME ghcr.io/openotters/runtime:latest
MODEL  anthropic/claude-haiku-4-5-20251001
NAME   pinger

CONTEXT SOUL <<EOF
You are a connectivity probe. Given a host, call the ping tool
and reply "<host>: reachable" or "<host>: unreachable (<reason>)".
EOF

BIN ping ghcr.io/openotters/tools/ping:latest "TCP-port-80 reachability"

Build it, run it:

otters image build ./Agentfile -t ghcr.io/me/my-pinger:v1
otters run ghcr.io/me/my-pinger:v1 --name my-pinger

Directives

FROM

Base image to inherit from. scratch for a brand-new agent; otherwise an existing agent image to extend.

RUNTIME

The runtime image that supplies the agent loop, tool harness, and persistence layer. Pinned per agent so upgrades stay deliberate.

MODEL

<provider>/<model-name>. The provider must be registered with otters provider add. The model can be overridden at run time β€” either via otters run --model ... or via the GUI's run-from-image dialog.

NAME

Default friendly name when the agent is run without --name.

CONTEXT

A heredoc whose content becomes one section of the agent's system prompt. The common form names the section, giving the LLM a reading order:

CONTEXT SOUL <<EOF
You are a connectivity probe…
EOF

CONTEXT SCENARIOS "Common shapes the user brings" <<EOF
- "is X up?" β†’ ping X
- "round-trip time?" β†’ ping -c 3 X
EOF

The optional quoted description renders as a sub-header above the body. Pick short, capitalised section names (SOUL, SCENARIOS, IDENTITY, POLICIES …) so the model can refer to them.

BIN

A tool the agent can invoke, as a container image:

BIN <name> <image> "<one-line description>"

The description is shown to the model when it picks tools, so write it like a function docstring: what it does, what it expects, what it returns. A longer USAGE block can follow as a heredoc:

BIN curl ghcr.io/openotters/tools/curl:latest "HTTP client" <<EOF
Pass curl(1) flags as args. Common shape:
  -X POST                   method override
  -H "name: value"          header (repeatable)
  -d "<json>"               request body
  -sS                       silent progress, still surface errors
URL is the last positional arg. Wrap in sh -c when chaining with jq.
EOF

If the BIN image itself carries a USAGE.md (baked at build time by otters bin build -u …), the runtime appends it under a ## Usage section in the tool's model-facing description. The vnd.openotters.bin.usage manifest annotation drives this β€” declare your own BINs with a USAGE.md and agents pick the docs up without you re-stating them in the Agentfile.

ENV

OS environment variables set on the spawned agent process. Use this for application-level knobs the agent (or its tools) read via os.Getenv β€” NODE_ENV, LOG_LEVEL, FEATURE_X, third-party SDK config, etc.

ENV <KEY>=<value> "<optional description>"

Keys must be uppercase POSIX-style names (^[A-Z_][A-Z0-9_]*$). The following are reserved and rejected at build time so the locked-down agent env stays intact:

  • PATH, HOME, XDG_CONFIG_HOME, XDG_CACHE_HOME, XDG_DATA_HOME, TMPDIR, LANG, OTTERS_AGENT_ROOT
  • Any key ending in _API_KEY or _API_BASE (those travel through the provider-credential channel β€” declare a provider model instead)

ENV is not the same as CONFIG. CONFIG is a runtime knob the agent program reads via the runtime SDK; ENV lands directly on the spawned process's environment. Use ENV for OS-level integration, CONFIG for behaviour you want surfaced to the LLM.

The operator can override any ENV at run time with:

otters run <ref> -e KEY=VAL -e KEY2=VAL2

…or from the GUI's run-from-image dialog (/agents/new β†’ catalog cards, or /images/<ref> β†’ per-version Run button). The dialog pre-fills every declared ENV row so the operator only edits the values that matter; the image's baked-in defaults travel as the fallback.

Empty ENV values are valid β€” useful for agents that require a value the operator supplies. Pair an empty-default with the io.openotters.required-env label so operators see the requirement on otters agent inspect:

ENV HASS_URL=""    "Home Assistant base URL"
ENV HASS_TOKEN=""  "Long-lived access token"

LABEL io.openotters.required-env="HASS_URL, HASS_TOKEN"

CONFIG

Runtime-SDK knobs the agent reads at start-up. Used for behaviour the LLM should not see in its tool list, but the runtime cares about:

CONFIG max-tokens=2048 "Headroom for richer state dumps + filtered views"
CONFIG max-iterations=10 "Allow fetch -> filter -> service-call chains"

The spec only defines the directive's shape β€” a key/value pair with an optional description. The key vocabulary itself is not fixed by the spec; each runtime image declares which keys it consumes and how they're interpreted. The daemon stamps every CONFIG entry verbatim into etc/agent.yaml's configs: map (kebab-case, no rename); the runtime reads from there.

That makes CONFIG a runtime-extension point: a custom runtime image that, say, exposes a different sampler or memory backend can document its own keys (e.g. temperature, cache-store, safety-mode) and the spec doesn't need to change.

For the stock ghcr.io/openotters/runtime, the supported keys live in the Runtime β†’ Tunables section of the runtime docs. Always consult the runtime image's docs (not the Agentspec) for the authoritative list. Unknown keys are accepted and persisted regardless, so an agent built against a future runtime version can declare keys today's runtime doesn't yet read.

These are not CLI flags β€” the Agentfile (or a daemon-side override) is the only input path.

ADD

Bake a static file into the agent at build time:

ADD ./data/cities.json /data/cities.json "City coordinates lookup table"

The file lands inside the image at the destination path, readable at run time from etc/data/<basename>. The optional description shows up on the agent's image-detail page.

LABEL

Standard OCI image annotations stamped into the manifest. The OpenOtters convention reserves a few keys for operator hints:

  • description β€” short blurb shown in image listings.
  • io.openotters.required-env β€” comma-separated list of ENV keys the operator MUST supply at run time. Surfaced on the image-detail page.
  • org.opencontainers.image.source / .version / .maintainer β€” standard OCI labels; ghcr.io uses source to auto-link the package to its repo.
LABEL description="Home Assistant REST agent"
LABEL maintainer="[email protected]"
LABEL io.openotters.required-env="HASS_URL, HASS_TOKEN"
LABEL org.opencontainers.image.source="https://github.com/me/my-agents"

EXEC

Override the runtime's default entrypoint with a custom argv. Most agents don't need this β€” the runtime is self-driving. Useful when you've forked the runtime and want to point at a custom binary inside the image.

ARG

Build-time substitutions. References to ${NAME} in other directives are expanded from ARG NAME=default declarations and --build-arg overrides at otters image build time.

The full grammar

The complete spec lives at github.com/openotters/agentfile and is frozen at v1.0 once stabilised.