pikopod.yaml from the working directory unless --config says otherwise. --config is accepted by every command. Run pikopod init to scaffold one.
Unknown keys are startup errors, not silent no-ops. A typo’d or removed knob must never let you believe something is configured. If pikopod starts, every key you wrote is a key it understands.
A minimal working file
Top-level keys
upstreams
Each named upstream has a route on the agent and a target it forwards to. The target alone decides posture: a real provider means watch mode, a vendor sandbox means staging mode, the internal sandbox means scenario mode.
Upstream names may only contain letters, digits,
_ and -, because the name becomes a file path component under data_dir.
listen and tokens
0.0.0.0 is a way to lose data, so pikopod refuses to start rather than let it happen. Supply the token via PIKOPOD_TOKEN or token_file, never on the command line. The environment wins over the file.
When a token is set, every request to either server must carry it in the X-Pikopod-Token header. Tokenless agent listeners additionally reject requests carrying a foreign Host header, which blocks DNS rebinding from a browser on the same machine.
tls
data_dir
sampling
rate thins what recordings persist, never what pikopod learns from. Every record still feeds the learner and the differ; the rate only gates the disk write. Error responses, drift-bearing records and pre-warmup traffic are always kept. 0 keeps guaranteed classes only, and is distinguishable from unset (1.0).
retention
0 (default) keeps size-based rotation only.
scenario from-drift and scenario reproduce read the event log, so fingerprints older than the window can no longer be pinned. Every incident row therefore carries reproducible until <time> and an export: hint. See Incident bundles.
slack
Alert behaviour itself is a fixed contract, not a set of keys. See Alerts.
warmup
min_samples responses across at least min_hours, then freezes a reference. Both are overridable for evaluation. Lowering them shortens the blind window and raises false positives. min_hours: 0 is explicitly distinguishable from unset. See Warmup and baselines.
spec_watch
spec_source. Fetches are ETag-gated and the pin never advances on its own; accepting a declared change is an explicit pikopod import <name> --update.
refine
prefer_spec flips type-conflict precedence back to spec-wins; the default is traffic-wins once the sustain gates clear. Inspect the result with pikopod contract <sandbox>. See Contract.
behaviour
llm
provider to the provider the key belongs to (openai or openrouter), and the key in api_key or in PIKOPOD_LLM_KEY. The provider’s own standard key variable is also honoured, so an existing environment keeps working.
When a key is stored in pikopod.yaml, the file must be mode 0600. Environment-provided keys do not make the file secret.
The key is optional. Exactly three things use it: plain-English scenario authoring (scenario create), pikopod fix, and importing from a documentation URL as the last resort after the deterministic rungs fail. Everything else works with no key at all.
quotas
Sandbox resource limits are fixed rather than configured:
Exceeding them returns
413 or 507 rather than degrading silently. Reset a sandbox’s state with pikopod sandbox reset <name>.
Sandboxes
Sandboxes are not apikopod.yaml key. They are registered by pikopod import and stored in <data_dir>/sandboxes.json with their id, seed, spec source, origin, linked upstream, webhook URL and recordings-fallback flag. See Importing.
The init template
pikopod init writes this file with mode 0600 and refuses to overwrite an existing one:
Validation errors
Every configuration error names the key, the reason and the fix, and exits2. The ones you are most likely to meet:
pikopod doctor checks the file, the data directory, the salt, the ports and every upstream’s reachability in one pass. See Troubleshooting.