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-pingerDirectives
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
EOFThe 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.
EOFIf 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_KEYor_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 ofENVkeys the operator MUST supply at run time. Surfaced on the image-detail page.org.opencontainers.image.source/.version/.maintainerβ standard OCI labels; ghcr.io usessourceto 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.