// sdk · config
Config, sinks & hygiene
where captures go, how they self-manage
Configure capture two ways: environment variables (the gate and the output root) and the programmatic configure() call (sinks, sampling, dedup, GC). Everything here is fail-open — a config or hygiene fault drops a capture, never the host agent.
## Environment
bash
EVALSHIFT_CAPTURE=1 # gate: on for 1/true/yes/on, off otherwise (default off) EVALSHIFT_DIR=/var/captures # capture root; default is ./.evalshift
- +
EVALSHIFT_CAPTURE— the off-by-default gate. Truthy values:1/true/yes/on. - +
EVALSHIFT_DIR— the capture root. Files land at<base>/captures/<suite>/cap_<id>.json.
## configure()
configure() has merge semantics — only the keyword args you pass change. reset_config() restores defaults (useful in tests).
python
from evalshift import configure, MemorySink
configure(
sink=MemorySink(), # where captures go (default: FileSink)
sample_rate=0.1, # capture 10% of runs
dedup=True, # collapse identical-input captures
max_captures=500, # cap per-suite file count (evict oldest)
capture_ttl=86400, # evict captures older than 1 day (seconds)
)| Option | Effect |
|---|---|
sink | Where captures are written (default FileSink). |
redact | Process-wide redactor — see Redaction. |
sample_rate | Fraction of runs to capture, decided at agent entry. |
dedup | Collapse captures with identical input (per process). |
max_captures | Cap a suite directory by file count; evict oldest by mtime. |
capture_ttl | Evict captures older than this many seconds. |
## Sinks
- +FileSink (default) — one JSON file per capture. The root resolves at write time: an explicit
FileSink(base=...)wins, elseEVALSHIFT_DIR, else.evalshiftrelative to the working directory. No repo-root walk — the SDK never assumes it runs inside a checkout. On a filesystem error (read-only mount, disk full) it drops the capture, logs at debug, and returns — it does not crash. - +MemorySink — keeps captures in memory for ephemeral hosts (Lambda, tests); you drain them yourself.
python
from evalshift import configure, MemorySink sink = MemorySink() configure(sink=sink) # ... run agents (zero disk writes) ... captures = sink.flush() # drain everything captured
## Hygiene
Capture can be a firehose, so it self-manages. The hygiene knobs apply only when set; with none configured the sink is used unchanged.
- +Sampling (
sample_rate) — decided once at agent entry.0.0captures nothing;1.0/ unset captures every run. A fault in the draw fails open (captures). - +Dedup (
dedup) — per-process, best-effort collapse of identical-input captures. - +GC (
max_captures,capture_ttl) — evicts the oldest captures by filesystem mtime once a suite exceeds the cap or ages past the TTL.
+
fail-open is sacred
Every SDK boundary swallows and logs its own faults — a bad sink, redactor, sampler, dedup, or GC step drops at most one capture. The only statement not wrapped is your own function call, whose return value and exceptions always propagate. The SDK can never break the host agent.