agentenv

package
v2.7.0-dev.5 Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package agentenv parses the optional .agents/env.yaml (or .env.json) manifest and produces a Resolver that agent-bundle loaders can use to interpolate ${env:VAR} references in AGENTS.md, skill files, and mcp.json values.

The mechanism has two halves:

  1. A machine-readable manifest declaring which env vars the bundle expects — required vs optional, defaults, sensitive markers, and free-text descriptions. The daemon validates required vars at boot; missing required vars are fail-loud errors, not silent empty-string interpolation.

  2. ${env:VAR} interpolation applied at instruction-file load time. Syntax matches what pkg/mcp/config.go already accepts in mcp.json header values, so operators only learn one substitution convention across the whole .agents/ bundle.

Bundles without a manifest keep working unchanged — no manifest, no interpolation, no validation. Zero regression path for existing deployments.

Index

Constants

View Source
const ManifestFileJSON = "env.json"

ManifestFileJSON is the JSON variant of the manifest. Both files are probed at load time — YAML first, then JSON. Declaring both is a configuration error (see LoadManifest).

View Source
const ManifestFileYAML = "env.yaml"

ManifestFileYAML is the manifest filename with YAML syntax. YAML is the preferred format because multi-line descriptions read cleanly; JSON is supported below for parity with config.json / mcp.json.

View Source
const SchemaVersion = 1

SchemaVersion is the current major version of the env-manifest file format. Bump on breaking changes; older versions get rejected at load time with an explicit upgrade-path error.

Variables

This section is empty.

Functions

func FindReferences

func FindReferences(s string) []string

FindReferences returns the unique set of NAME values referenced in s via the ${env:NAME} syntax. Order is deterministic (sorted) so callers can produce stable log lines and diff output.

Used by the Resolver during construction to (a) compute the set of names referenced anywhere in the bundle for the "undeclared reference" drift warning, and (b) enable "unreferenced declaration" detection on the manifest side.

func InterpolateEnv

func InterpolateEnv(s string) string

InterpolateEnv is the legacy free-function used by pkg/mcp. It looks up each ${env:NAME} directly via os.Getenv with no manifest awareness — no required-var checks, no sensitive tracking, no drift warnings. Kept exported so pkg/mcp callers didn't churn when interpolation moved into this package; new call sites should go through a Resolver instead (see NewResolver).

func InterpolateMap

func InterpolateMap(m map[string]string) map[string]string

InterpolateMap returns a copy of m with each value run through InterpolateEnv. Nil / empty maps pass through as nil (matches the pre-agentenv pkg/mcp semantics for absent headers / env blocks).

Types

type Entry

type Entry struct {
	Name        string   `json:"name"                   yaml:"name"`
	Required    bool     `json:"required,omitempty"     yaml:"required,omitempty"`
	Default     string   `json:"default,omitempty"      yaml:"default,omitempty"`
	Sensitive   bool     `json:"sensitive,omitempty"    yaml:"sensitive,omitempty"`
	Description string   `json:"description,omitempty"  yaml:"description,omitempty"`
	UsedBy      []string `json:"used_by,omitempty"      yaml:"used_by,omitempty"`
}

Entry declares one env variable the bundle expects.

A required entry with no env-var set at daemon boot is a fail-loud error (surfaced via Resolver.Errors). An optional entry falls back to Default when unset; missing default → empty string.

UsedBy is optional documentation — a comma-separated list of files (or free-text hints) that reference this var, letting recipe authors grep for coupling. Nothing in the loader validates the entries.

Sensitive marks values that must not appear in verbose logs, the eventlog, or diagnostic surfaces like /stats. Set true for tokens, passwords, API keys.

type Manifest

type Manifest struct {
	Version int     `json:"version" yaml:"version"`
	Env     []Entry `json:"env" yaml:"env"`
}

Manifest is the on-disk schema for .agents/env.yaml (or .env.json).

Version follows the convention set by pkg/config (SchemaVersion=1) and pkg/mcp (Servers.Version) — top-level version bump on breaking change, unknown fields ignored for forward-compat.

func LoadManifest

func LoadManifest(agentsDir string) (*Manifest, error)

LoadManifest probes agentsDir for env.yaml then env.json and returns whichever it finds. Both present → error (ambiguous; operator should pick one). Neither present → nil manifest, nil error (caller treats as "no interpolation configured"; existing bundles keep working).

agentsDir may be "" — in that case returns (nil, nil) too, matching how config.LoadOrDefault handles the "no .agents/ discovered" case.

func (*Manifest) Validate

func (m *Manifest) Validate() error

Validate reports schema-level errors in the manifest itself (bad version, duplicate names, empty name field). It does NOT check whether required env vars are set at runtime — that check is Resolver's job because it needs an env-lookup fn to run.

type Resolver

type Resolver struct {
	// contains filtered or unexported fields
}

Resolver interpolates ${env:NAME} in strings using an env-var lookup, with awareness of which names are declared in the manifest.

Construction is a two-phase process:

  1. NewResolver(manifest, lookup) — records the manifest, resolves each declared name against lookup, records errors for missing required vars. This is when required-var validation fires.

  2. Interpolation call sites (pkg/instruction, pkg/skills, pkg/mcp) use Interpolate(s) as they load bundle files. Interpolate records every unique NAME it sees to enable the "undeclared reference" drift warning at ReportDrift time.

A nil Resolver is safe — Interpolate is a no-op, Errors returns nil, IsSensitive returns false. Loaders should tolerate the nil case (bundle without a manifest) rather than requiring a stub.

func NewResolver

func NewResolver(manifest *Manifest, lookup func(name string) (string, bool)) *Resolver

NewResolver builds a Resolver from a parsed manifest and an env-var lookup function (usually os.LookupEnv). Passing nil manifest returns nil — matches the "no manifest, no interpolation" backwards-compat path expected by pkg/config.LoadOrDefault callers.

lookup must return (value, true) if the var is set (even to empty string) and ("", false) if unset. os.LookupEnv has this shape directly; tests can pass a map-backed closure.

func (*Resolver) Errors

func (r *Resolver) Errors() []error

Errors returns fatal validation problems from Resolver construction (currently: missing required env vars). Empty slice → boot may proceed; non-empty → boot should log each and exit.

func (*Resolver) Interpolate

func (r *Resolver) Interpolate(s string) string

Interpolate substitutes ${env:NAME} in s using the resolved manifest values. Undeclared NAMEs fall back to the ambient os.Getenv path so a bundle can still reference standard system env vars (HOME, PATH, etc.) without declaring them — those show up as "undeclared reference" warnings via ReportDrift but don't break interpolation.

Every unique NAME seen is recorded so ReportDrift can compute the undeclared-reference set.

func (*Resolver) InterpolateFunc

func (r *Resolver) InterpolateFunc() func(string) string

InterpolateFunc returns a bare closure suitable for passing to loaders that don't want to import agentenv directly (pkg/instruction, pkg/skills). Nil-safe: nil Resolver returns nil, which loaders interpret as "no interpolation."

func (*Resolver) IsSensitive

func (r *Resolver) IsSensitive(name string) bool

IsSensitive reports whether the named var is marked sensitive in the manifest. Used by log-sanitization paths that already redact certain values (mcp.json headers, /stats surfaces) to also redact env-var values marked in the manifest. Nil-safe: nil Resolver → false.

func (*Resolver) ReportDrift

func (r *Resolver) ReportDrift() []string

ReportDrift returns non-fatal warnings the daemon should log at boot:

  • "undeclared reference: BAR referenced in bundle but not in manifest" — the recipe author probably meant to add it.
  • "unreferenced declaration: FOO declared in manifest but not referenced anywhere" — leftover from a refactor.

Both are advisory (per the #322 issue: warn, not error). The daemon keeps running; the recipe author sees the warnings and cleans up on their next iteration.

Callers must invoke ReportDrift AFTER all bundle files have flowed through Interpolate at least once; earlier invocation reports every declaration as unreferenced.

func (*Resolver) SensitiveValues

func (r *Resolver) SensitiveValues() []string

SensitiveValues returns the set of resolved values that should be redacted in logs, sorted for stable output. Empty when no sensitive entries are declared or nothing has been resolved yet.

Callers that need to grep-and-redact a downstream string (like a full log line) can walk this list; callers that already know which VAR they're logging should prefer IsSensitive.

Jump to

Keyboard shortcuts

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