spec

package
v1.0.0-alpha.37 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 15, 2026 License: MIT Imports: 11 Imported by: 0

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

View Source
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
	// (vnd.openotters.bin.* annotations, single binary per platform).
	// Lets consumers distinguish tool images from agent images without
	// relying on annotation-sniffing.
	BinArtifactType = "application/vnd.openotters.bin.v1"

	OctetStream = "application/octet-stream"
	Markdown    = "text/markdown"

	AnnotationBinName        = "vnd.openotters.bin.name"
	AnnotationBinPath        = "vnd.openotters.bin.path"
	AnnotationBinDescription = "vnd.openotters.bin.description"
	AnnotationBinUsage       = "vnd.openotters.bin.usage"

	DefaultBinPath   = "/"
	DefaultUsagePath = "/USAGE.md"
)
View Source
const DefaultSyntax = "openotters/agentfile:1"

DefaultSyntax is the syntax value assumed when an Agentfile omits the `# syntax=` pragma. Parse stamps this onto Agentfile.Syntax.

View Source
const DefaultTag = "latest"

Variables

View Source
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

func IsQualified(name string) bool

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

func Validate(af *Agentfile) error

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 Add

type Add struct {
	Src         string `json:"src"`
	Dst         string `json:"dst"`
	Description string `json:"description,omitempty"`
	Content     []byte `json:"-"`
}

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 Agentfile

type Agentfile struct {
	Syntax string `json:"syntax"`
	Agent  *Agent `json:"agent"`
}

func Parse

func Parse(r io.Reader) (*Agentfile, error)

func ParseFile

func ParseFile(path string) (*Agentfile, error)

func (*Agentfile) Apply

func (a *Agentfile) Apply(overrides ...Override) *Agentfile

Apply mutates a in place by running each override. Returns a for chaining.

type Bin

type Bin struct {
	Name        string `json:"name"`
	Image       string `json:"image"`
	Description string `json:"description,omitempty"`
	Usage       string `json:"usage,omitempty"`
}

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 Config

type Config struct {
	Key         string `json:"key"`
	Value       any    `json:"value,omitempty"`
	Description string `json:"description,omitempty"`
	Required    bool   `json:"required,omitempty"`
}

type Context

type Context struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	Content     string `json:"content,omitempty"`
	File        string `json:"file,omitempty"`
}

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 WithExtraEnvs(envs []*Env) Override

func WithModel

func WithModel(model string) Override

WithModel overrides Agent.Model.

func WithMounts

func WithMounts(mounts []*Mount) Override

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.

func WithRuntime

func WithRuntime(runtime string) Override

WithRuntime overrides Agent.Runtime.

type Reference

type Reference struct {
	Name string
	Tag  string
}

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

func ParseReference(s string) Reference

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

func QualifyWithDefault(ref Reference, defaultRegistry string) Reference

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).

func (Reference) String

func (r Reference) String() string

String returns the reference as "name:tag".

func (Reference) Validate

func (r Reference) Validate() error

Validate checks that the reference has at least a name.

type ReferenceWithDigest

type ReferenceWithDigest struct {
	Reference Reference
	Digest    digest.Digest
}

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".

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL