Documentation
¶
Overview ¶
Package spec reads Agentfiles into structured Agentfile values.
Parsing is a two-phase process: a line scanner handles comments, heredocs, and ARG expansion, then each instruction line is parsed by a participle v2 grammar. Semantic validation runs after parsing.
Agentfile (text)
|
v
+-------------+
| Line Scanner | comments, blank lines, # syntax=
+-------------+
|
v (per instruction line)
+-------------+
| Heredoc | extract <<MARKER content
| Extractor |
+-------------+
|
v
+-------------+
| ARG | ${VAR} substitution
| Expansion |
+-------------+
|
v
+-------------+
| Participle | grammar-based instruction parsing
| Parser | FROM, RUNTIME, MODEL, NAME, CONTEXT,
| | CONFIG, BIN, ADD, LABEL, ARG
+-------------+
|
v
+-------------+
| Validation | FROM first, reserved names, required
| | config constraints
+-------------+
|
v
*Agentfile
Index ¶
- Constants
- Variables
- func GenerateAgentMD(af *Agentfile, caps []Capability) string
- func IsQualified(name string) bool
- func Validate(af *Agentfile) error
- type Add
- type Agent
- type Agentfile
- type Bin
- type Capability
- type Config
- type Context
- type Env
- type Mount
- type Override
- type Reference
- type ReferenceWithDigest
Constants ¶
const ( AgentConfigLayerMediatype = "application/vnd.openotters.agent.config.v1+json" ContextLayerMediaType = "application/vnd.openotters.context.v1" AgentArtifactType = "application/vnd.openotters.agent.v1" // AgentfileMediaType marks the layer carrying the raw, // user-authored Agentfile bytes — verbatim, not a // marshal/reconstruct of the parsed spec. Build pipelines push // this alongside the context / add layers; materialisation // extracts it to <agent-root>/etc/Agentfile so the image stays // self-describing in a form an operator can read or re-build // from without a registry round-trip. The Agentfile is the // source of truth for the agent; this mediatype represents that // source as it was written. No version suffix: spec version // belongs in the SYNTAX directive inside the file itself, not // in the wire mediatype. AgentfileMediaType = "application/vnd.openotters.agentfile" // BinArtifactType marks an OCI image as an openotters bin-tool // (single binary per platform, io.openotters.bin.* annotations // for openotters-specific metadata, OCI image-spec keys for // everything covered by the spec). Lets consumers distinguish // tool images from agent images without annotation-sniffing. BinArtifactType = "application/vnd.openotters.bin.v1" OctetStream = "application/octet-stream" Markdown = "text/markdown" // AnnotationBinName is the binary's filename inside the tar // layer — what the puller looks up when extracting the bin // from the rootfs. Distinct from org.opencontainers.image.title, // which is the human-readable display label for the image // ("jq command-line JSON processor") and may differ from the // binary filename ("jq"). Reverse-DNS form per the OCI // image-spec custom-key rule. AnnotationBinName = "io.openotters.bin.name" // AnnotationBinPath is the in-image absolute path the daemon // binds into the agent filesystem when the bin is mounted. No // OCI predefined key covers this — it's an openotters runtime // concept (where the daemon mounts the binary), not image // metadata an external tool would care about. AnnotationBinPath = "io.openotters.bin.path" // AnnotationBinUsage is the in-image path to a markdown file // describing how the model should invoke this bin. Loaded into // the agent's system prompt at run time. Distinct from // org.opencontainers.image.documentation (which is a URL). AnnotationBinUsage = "io.openotters.bin.usage" DefaultBinPath = "/" DefaultUsagePath = "/USAGE.md" )
const DefaultSyntax = "openotters/agentfile:1"
DefaultSyntax is the syntax value assumed when an Agentfile omits the `# syntax=` pragma. Parse stamps this onto Agentfile.Syntax.
const DefaultTag = "latest"
Variables ¶
var SupportedSyntaxes = []string{DefaultSyntax}
SupportedSyntaxes lists every `# syntax=` value the parser accepts.
Functions ¶
func GenerateAgentMD ¶
func GenerateAgentMD(af *Agentfile, caps []Capability) string
GenerateAgentMD generates markdown documentation from an Agentfile. caps is the list of LLM-facing tool functions the runtime image registers (daemon-supplied; the Agentfile itself doesn't declare them today). Each entry's description shows up in the "Capabilities" section so the model can read what each tool does without invoking it.
func IsQualified ¶
IsQualified reports whether name carries a registry-host component. Heuristic matches containerd / docker reference parsers: the first slash-separated segment is a host when it contains "." (a TLD) or ":" (a port), or equals "localhost". Bare names like "foo" or "agents/foo" are unqualified — a caller with a default registry fills the host in via QualifyWithDefault.
Accepts either a bare name ("agents/foo") or a full reference string ("agents/foo:v1") — the trailing tag, if any, is stripped before the host-detection runs so callers don't need to ParseReference first.
func Validate ¶
Validate checks structural invariants on a programmatically constructed Agentfile: FROM is required, context name AGENT is reserved, and required configs cannot carry a default value. Parse already runs Validate; callers who build an Agentfile in code should call Validate themselves.
Types ¶
type Agent ¶
type Agent struct {
From string `json:"from"`
Runtime string `json:"runtime,omitempty"`
Model string `json:"model,omitempty"`
Name string `json:"name,omitempty"`
Contexts []*Context `json:"contexts,omitempty"`
Configs []*Config `json:"configs,omitempty"`
Bins []*Bin `json:"bins,omitempty"`
Adds []*Add `json:"adds,omitempty"`
Exec []string `json:"exec,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Args map[string]string `json:"args,omitempty"`
Envs []*Env `json:"envs,omitempty"`
// RuntimeMounts is a runtime-only side-channel populated by
// spec.WithMounts. Not serialised — Agentfiles do not have a
// MOUNT directive; mounts live with the launch invocation
// (`otters run -v ...`). Both executors read this slice at
// Create time to attach the user's bind mounts to the agent.
RuntimeMounts []*Mount `json:"-"`
}
type Capability ¶
type Capability struct {
Name string `yaml:"name" json:"name"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
}
Capability declares one LLM-facing tool function the runtime image registers (e.g. `job_submit`, `context_show`). NOT part of the Agentfile spec itself — the runtime image owns the list; the daemon supplies it at materialise time and the agentfile library just plumbs it into ResolvedConfig.Capabilities + AGENT.md. Put here so renderers in spec/ can format it without an import cycle on executor/.
type Env ¶
type Env struct {
Key string `yaml:"key" json:"key"`
// Value is the spawn-time value the daemon resolves from the
// Agentfile's default and any operator override. It's never
// persisted to agent.yaml — secrets are kept out of the
// workspace by construction. The daemon hydrates this field
// in-memory at Restore / Start and supplies it via the spawn
// env to the runtime subprocess.
Value string `yaml:"-" json:"value"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
}
Env declares an OS environment variable to be set on the spawned agent process. Unlike Config (a runtime-SDK knob the agent reads via the runtime API) and Arg (build-time substitution), Env values land directly on os/exec's Cmd.Env (system executor) and container.Config.Env (docker executor).
Reserved keys (PATH, HOME, XDG_*, TMPDIR, LANG, OTTERS_AGENT_ROOT, any *_API_KEY / *_API_BASE) are rejected by Validate to keep the locked-down env contract intact.
type Mount ¶
type Mount struct {
// Host is the operator-supplied host path bound at run time
// (`otters run -v HOST:TARGET[:ro]`). It's never persisted to
// agent.yaml — the daemon stores it in its own state and
// re-applies on every Restore / Start. Disk-side, only the
// target + description + read-only flag live in agent.yaml.
Host string `yaml:"-" json:"host,omitempty"`
Target string `yaml:"target" json:"target"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
ReadOnly bool `yaml:"read_only,omitempty" json:"read_only,omitempty"`
}
Mount is the runtime-only spec for a host-path → in-agent binding declared by `otters run -v HOST:TARGET[:DESC][:ro|:rw]`. Mirrors executor.Mount one-to-one; kept in spec/ so spec.Override (Agentfile mutator) can carry it without a circular import on executor.
type Override ¶
type Override func(*Agentfile)
Override mutates a parsed Agentfile. Overrides are applied via Apply and are intended for runtime field replacements (model, runtime image, etc.) coming from CLI flags or daemon config. Kept distinct from spec.Config, which is a data record declared in the Agentfile itself.
func WithExtraEnvs ¶
func WithMounts ¶
WithExtraEnvs appends additional ENV declarations to the parsed Agentfile. Used by the daemon to surface per-run env overrides (`otters run -e KEY=VAL`) through the same plumbing as the Agentfile-declared envs. Validate runs after Apply so reserved keys (PATH, *_API_KEY, etc.) are still rejected before the agent starts; the override is additive — duplicate keys win against the Agentfile-declared value. WithMounts attaches user mounts to the agent. The spec doesn't have a MOUNT directive (mounts live on the run invocation), but the override piggybacks through Agent.RuntimeMounts so the executor backends pick them up at Create time without a separate call-time channel.
type Reference ¶
Reference identifies an OCI image by name and tag. Name can be local ("meteo") or remote ("ghcr.io/openotters/agents/meteo"). Tag defaults to "latest" when not specified.
func ParseReference ¶
ParseReference parses a reference string in the form "name" or "name:tag". The tag is the part after the last colon that follows the last slash. This correctly handles host:port/name:tag references.
func QualifyWithDefault ¶
QualifyWithDefault returns ref with defaultRegistry prepended to its Name iff Name isn't already qualified. defaultRegistry should not include a scheme or trailing slash; an empty defaultRegistry is a no-op (callers without a default fall back to the unmodified ref).
type ReferenceWithDigest ¶
ReferenceWithDigest pairs a Reference with a content-addressed digest from the OCI store.
func (ReferenceWithDigest) String ¶
func (r ReferenceWithDigest) String() string
String returns "name:tag@digest".