Configuration
Daimon is configured entirely through environment variables. Every variable
resolves in the same order: the process environment wins, and anything not
set there falls back to the env file at ~/.daimon/env. Override the file's
location with DAIMON_ENV_FILE.
The env file exists because hooks run in whatever environment the host process
happened to inherit — a GUI-launched agent has no shell profile, so shell
exports are not a reliable channel. Its format is KEY=VALUE lines; a leading
export , surrounding quotes, blank lines, and # comments are tolerated.
Keep it chmod 600 — it can hold API keys.
daimon configure manages the LLM backend knobs (see LLM backend)
and writes them to ~/.daimon/env. Everything else you set by editing that file
or exporting the variable.
Boolean variables accept 1, true, yes, or on as truthy (matched
case-insensitively where noted). A handful use different conventions — kill
switches that are on unless set to 0, or presence-based flags — and those are
called out in the "What it does" column.
Core
| Variable | Default | What it does |
|---|---|---|
DAIMON_DISABLE | off | Kill switch. When truthy, every hook becomes a no-op — no capture, no briefing. |
DAIMON_ENV_FILE | ~/.daimon/env | Path to the env file that backs every other variable. Read from the process env only (it names the file, so it can't live inside it). |
DAIMON_PROJECT_DIR | unset | Working directory of the session being briefed or serialized, used to route per-project checkpoints. Hooks pass the host's cwd through it; unset means the project is unknown and daimon falls back to the global pointer. |
DAIMON_CAPTURE_HOST | unset | Host-set hint naming which agent produced a capture (claude-code, codex, windsurf, gemini, hermes, manual), forwarded by installed capture hooks (#594). Not something you set by hand — a hook wires it into the environment it hands to the CLI, for the cases a bare transcript path can't resolve on its own. |
DAIMON_MIN_MESSAGES | 10 | Minimum message count before a session is worth serializing. Shorter sessions are skipped. |
DAIMON_TIMEOUT | 420 | Total serialize budget in seconds, shared across retry attempts (per-attempt socket timeouts are capped to the remaining budget). Real serialize/merge calls on gateway and CLI backends run 74s–25min; keep ≥420 or slow calls and retries cannot fit. |
DAIMON_HUNG_AFTER | 1800 | Seconds past which a serialize spawn that produced no result line is treated as hung/killed rather than still running. Default 30 min sits safely beyond a slow run (production serializes take 4–25 min). |
Checkpoint store & GC
| Variable | Default | What it does |
|---|---|---|
DAIMON_CHECKPOINT_DIR | ~/.daimon/checkpoints | Root of the per-session checkpoint store. |
DAIMON_CHECKPOINT_KEEP | 100 | How many per-session checkpoint files to retain (newest-N). Older files are garbage-collected after a successful write. 0 disables GC entirely (keep forever). |
DAIMON_CHECKPOINT_HISTORY | 3 | How many checkpoint pointers to retain per directory (latest.json plus prev-1 … prev-(N-1)), so a failed serialize can fall back to a prior pointer. Minimum 1 (latest only). |
DAIMON_GC_PIN_IMPORTANCE | 9 | Item-importance threshold that pins a checkpoint file against GC: a file whose max item importance reaches this survives outside the newest-N window. 0 disables pinning (pure recency window); values above 10 are clamped to 10. |
Upgrading across 0.42.0
Before 0.42.0 the library named a project's bucket from the literal path it was handed. Since 0.42.0 every entry point resolves the path first: made absolute, symlinks collapsed, then normalized to the git toplevel. Two path shapes therefore name a different bucket than they used to:
- a path with a symlink anywhere in it — on macOS that includes anything
reached through
/tmp, and any mount point that is itself a link; - a path below a git toplevel, when the write came through the library rather than the command line.
The CLI already resolved both of these before 0.42.0, so a bucket written by
running daimon in a terminal is unaffected. A bucket written by a host
process that called the library from such a path sits under the old name, and
nothing reads it.
daimon status says so. Whenever a bucket written under the old rule exists
for the path you are standing in, the health block carries a legacy: line
naming it. Move it once:
daimon bucket migrate # this project
daimon bucket migrate --project /path/to/project
daimon bucket migrate --project /path/to/project --dry-run
The move renames the old directory when the new one does not exist yet, and
merges otherwise. A merge appends every ledger line the new bucket does not
already hold, and adds the old bucket's pointers to the new bucket's chain.
Pointers already in the new bucket are never removed and never displaced: they
belong to the live project. Old pointers go into whatever slots
DAIMON_CHECKPOINT_HISTORY leaves free, newest first, except that an old copy
of a session the new bucket already points at simply replaces that one slot
when it is the newer of the two. Anything else, an old pointer with no free
slot or a file daimon does not write, is left exactly where it is and named in
the report with what to do about it.
Running it twice is safe: a second run over an unchanged state writes nothing
new and returns the code that matches the state it finds. --dry-run prints
the same plan and writes nothing.
Exit 0 means the move finished and the old bucket is gone. Exit 1 means it did
not, and stdout names each thing left with its own remedy: raise
DAIMON_CHECKPOINT_HISTORY to the number it names and run again for pointers
with no free slot; fix or move a file that could not be read; move a file
daimon does not write out of the old bucket. A partial move does not count as
migrated, so the old bucket's rows are not yet read as this project's history.
If you finish the job yourself by clearing the old directory, the next run
records that and the migration is complete.
Exit 2 is a refusal. A --project containing a .. component is refused
because the old rule collapses .. before following a symlink and the current
one follows the symlink first, so the two name different directories; a bucket
written before 0.42.0 was written under the literal slug of the collapsed path,
so pass that path. On a tenant-scoped home (DAIMON_TENANT_SCOPED) an explicit
--project is refused outright and the project comes from
DAIMON_PROJECT_DIR, else the working directory: a migration writes a lasting
alias between two buckets, and choosing which two is a scope choice.
Every completed move appends one line to
~/.daimon/checkpoints/migrations.jsonl. That file is what keeps daimon recall and the request inbox finding history under the old bucket name once
the directory is gone: per-session checkpoint files keep the name they were
stamped with, because their receipts bind their exact bytes. Once a bucket
has absorbed another, daimon status shows a migrated: from <old bucket> on <date> line in place of the warning.
Carry
Deterministic cross-session carry-over of unresolved items.
| Variable | Default | What it does |
|---|---|---|
DAIMON_CARRY | on | Master switch for carry. On unless set to exactly 0 (any other value keeps it on). |
DAIMON_CARRY_FLOOR | 0.05 | Minimum effective weight for a carried item to keep carrying. At the default, decisions expire in ~5–6 weeks (importance-graded) and escalated open questions live ~3–4 months. |
DAIMON_CARRY_MAX | 24 | Cap on carried items per kind (native items never count against it or drop). Minimum 1. |
Briefing
| Variable | Default | What it does |
|---|---|---|
DAIMON_BRIEF_MAX_TOKENS | 3000 | Token budget for the injected briefing, estimated at 4 bytes per token (no tokenizer dependency). It is folded into one byte budget, min(DAIMON_BRIEF_MAX_BYTES, DAIMON_BRIEF_MAX_TOKENS * 4), that every block of daimon brief shares. 0 = unbounded. |
DAIMON_BRIEF_MAX_BYTES | 11264 | The byte budget for the WHOLE daimon brief output, in UTF-8 bytes (what a host's own spill threshold measures): the handoff baton, the rulings, the request and decision panels, the cognitive body, the code-drift block, the teammates section and the withheld-items note all count against it. When it runs short the briefing drops whole items in one fixed order (stale carried items first, background sections before actionable ones, carried before this session's own) and each section says what it hid. The greeting, standing rulings, the handoff and the newest decisions are never dropped; if those alone exceed the budget the brief says by how much. 0 = unbounded. |
DAIMON_MAX_BRIEFING_DECISIONS | 10 | Cap on decisions shown in the briefing (render-time view only, the checkpoint keeps all of them). The Decisions note counts what the cap cut. The newest min(3, cap) decisions from the last session are always shown. 0 = unbounded. |
DAIMON_BRIEF_GLOBAL_FALLBACK | header-only | Controls the cross-project global-pointer fallback when a project has no checkpoint of its own. Default shows a header only; set to full (or 1) to inject the full foreign body. |
DAIMON_STALE_DAYS | 7.0 | Age threshold (days) past which a carried item's effective last-verified stamp (its last_verified, else the latest resolutions.jsonl event, else first_seen) is stale enough for brief to warn about it. 0 warns on every carried item. |
DAIMON_PLAIN | off | When truthy (case-insensitive), forces plain-text output — disables the rich tables/panels in status, brief, and --help. |
NO_COLOR | unset | Presence-based, per the NO_COLOR convention: if the variable is set to any value (even empty), rich output is disabled. |
Worldcheck
Opt-in (#365): a brief-time spot-check of carried PR/issue-state claims
against reality, via read-only gh probes. Default off — daimon brief runs
on the SessionStart hook's latency-critical injection path (an 8s subprocess
budget inside a 10s hook budget), so even budget-bounded network probes there
must be deliberately opted into. Flag off, the render path never touches the
worldcheck module at all — output stays byte-identical to a build without it.
| Variable | Default | What it does |
|---|---|---|
DAIMON_WORLDCHECK | off | When truthy, spot-check carried PR/issue-state claims against reality via read-only gh probes at brief time. |
With worldcheck enabled, contradictions from file-existence, branch-existence, and PR/issue-state probes also demote the item in recall search and suggestions. The item stays visible. A later confirmation clears only the same claim's contradiction; a valid receipt cannot clear a missing-file finding. Skipped or unavailable probes neither invalidate nor clear anything. Dependency-version checks still annotate the briefing only. Evidence is recorded by the shared briefing CLI used by host integrations, not by the renderer.
| Surface | World-evidence collection with DAIMON_WORLDCHECK=1 |
|---|---|
| Claude Code, Codex, Gemini CLI, Kimi Code | Supported through each host's existing daimon brief hook. |
| Windsurf | Supported when the agent runs daimon brief --team; no automatic session-start injection. |
MCP daimon_brief, Hermes in-process briefing hook | Unsupported: these render paths do not run worldcheck. Use the CLI to collect evidence. |
| CLI and MCP recall, proactive suggestions | Consume recorded evidence through the shared recall index. |
Recall
| Variable | Default | What it does |
|---|---|---|
DAIMON_RECALL_DB | ~/.daimon/recall.db | Location of the derived recall index (SQLite FTS). Never a source of truth — safe to delete at any time; recall rebuilds it by scanning the checkpoint and team dirs. |
DAIMON_RECALL_SEEN_DIR | ~/.daimon/recall_seen | Per-session suggestion-cooldown state so a repeated topic never re-injects. Disposable — deleting it only resets cooldowns. |
Team memory
Opt-in shared-memory mirror. See docs/team.md for the full workflow.
| Variable | Default | What it does |
|---|---|---|
DAIMON_TEAM | off | When truthy, mirror each checkpoint into the shared team dir so brief --team can surface teammates. Gates writes only — reads of the team dir are always allowed. A synced remote additionally requires the project to be in its scope allowlist (see team.md); out-of-scope projects mirror to the local dir only. |
DAIMON_AUTHOR | git user.name, then OS user | Team author identity used to namespace your checkpoints. Falls back to git config user.name, then the OS username, then unknown. |
DAIMON_TEAM_DIR | ~/.daimon/team | Root of the shared team-memory mirror. |
DAIMON_TEAM_PROJECT | unset | Explicit logical project path for this machine's sessions (relative, e.g. core/api-gateway). Overrides the sidecar's daimon-team.toml mapping and the origin-derived fallback when routing checkpoints under projects/. |
DAIMON_TEAM_RETENTION_DAYS | 365 | Read-time age window: teammates' checkpoints older than this many days are skipped when reading. 0 = keep all. Never physically deletes from the shared append-only branch. |
DAIMON_TEAM_APPLY_FORGET | off | Standing consent for a TEAMMATE's published forget tombstone to rewrite this machine's own checkpoints. NOT sufficient alone — the apply also requires the typed daimon team sync --apply-forget, because a bare daimon team sync is spawned detached at SessionStart. Default off: a foreign tombstone always suppresses the value on read and in the index, but deleting local belief state from someone else's hash is a decision to make knowingly — the shared branch is append-only, so there is no undo. Without it, daimon team sync --apply-forget refuses at exit code 2 before syncing anything. When set, daimon team status and the team line of daimon status report it as armed. |
DAIMON_LIVE_DELIVERY | off | When truthy, an undecided request addressed to this project is delivered to a session that was already running when it arrived, at that session's next turn boundary, instead of waiting for its next SessionStart briefing. Render surface only: the ledger stays store-and-forward, the delivered nudge is the same pull-only record the next briefing would have shown, and the verbs that decide stay human-only. Once per session per revision; a request revise re-delivers the sharpened ask. Same daimon home only. Default off — briefing-only is the right posture for short sessions, so an always-on consumer turns this on deliberately. |
Receipts
Opt-in signed provenance receipts (#204). When enabled, each checkpoint is
paired with a vitni local-binding
receipt: an Ed25519-signed statement that binds the checkpoint's exact on-disk
bytes (outputs_hash) to its source transcript (inputs_hash), written to a
<session>.receipt sidecar. This makes a post-hoc edit to a checkpoint file
detectable. Receipts are fully valid offline — nothing leaves the machine.
Every step is fail-open: a missing CLI, missing openssl, timeout, or bad
output logs one line to serialize.log and proceeds without a receipt — a
receipts failure never blocks or fails a serialize or a briefing. Verify a
checkpoint on demand with daimon verify-receipt [session]; at briefing time a
receipt-era checkpoint whose receipt is missing or no longer matches its bytes
has its ✓ verbatim labels degraded with a visible note.
Public-key derivation prefers the vitni CLI's keygen command (vitni 0.5.0+) and
falls back to openssl on older CLIs or a failed probe — so on macOS, where Apple's
LibreSSL has no Ed25519 in openssl pkey, receipts work once vitni ≥ 0.5.0 is
installed, with no openssl-with-Ed25519 required.
| Variable | Default | What it does |
|---|---|---|
DAIMON_RECEIPTS | off | When truthy, mint a signed receipt beside each checkpoint. Default off — a new subprocess per serialize is opt-in. |
DAIMON_VITNI_CLI | vitni-verify (on PATH) | The vitni verifier CLI used to sign/verify. A path or a name resolved on PATH. Contract: <cli> <command> with one JSON object on stdin and one JSON line on stdout. |
DAIMON_KEYS_DIR | ~/.daimon/keys | Where the Ed25519 signing seed (signing.seed, mode 0600, auto-created on first mint) and cached public key (signing.pub.json) live. |
Host hooks
Serialize-throttle knobs for hosts that lack a clean session-end event. See docs/hosts/ for per-host setup.
| Variable | Default | What it does |
|---|---|---|
DAIMON_CODEX_SERIALIZE_ON_STOP | on | Whether the Codex Stop hook serializes at all. On unless set to 0, false, no, or off (case-insensitive). |
DAIMON_CODEX_SERIALIZE_ON_SESSION_END | on | Whether the Codex SessionEnd hook serializes at all — the real end of a session, fired once on graceful teardown with the complete transcript, deliberately unthrottled. On unless set to 0, false, no, or off (case-insensitive). Does not replace Stop, which stays as crash insurance for a SIGKILL or a closed terminal that never reaches SessionEnd. |
DAIMON_CODEX_MIN_SERIALIZE_INTERVAL | 300 | Minimum seconds between Codex serialize spawns. 0 serializes on every Stop. |
DAIMON_WINDSURF_MIN_SERIALIZE_INTERVAL | 300 | Minimum seconds between Windsurf serialize spawns (Windsurf has no session-end event, so capture runs on this throttle). 0 serializes every turn. |
DAIMON_WINDSURF_FINALIZER_QUIET_SECONDS | 600 | Quiet period after the last Windsurf activity before a debounced finalizer serializes the trajectory's final transcript state — covers sessions whose last turns land inside the throttle window. Fractional values accepted; 0 disables the finalizer. |
DAIMON_WINDSURF_DIR | ~/.daimon/windsurf | Where the Windsurf adapter keeps the transcripts it accumulates. Read by both the hook that writes them and the forget/heal paths that delete them — change it in one place only, or the writer and the deleter stop agreeing. |
DAIMON_WINDSURF_STATE_DAYS | 7 | Age window for daimon-authored Windsurf transcripts and unparsed payload dumps, reaped by daimon heal. A privacy bound: a forgotten value cannot be located inside prose, so this limits how long the source conversation lingers between forget runs. Clamped to at least 1 — unlike the other Windsurf knobs, 0 does not disable it. |
Heal escalation
Opt-in (#360): daimon heal's default retry re-runs the same extraction
shape that already failed. Escalation re-serializes from several distinct
perspectives instead, merged by the ordinary merge pass — multiplying the LLM
token cost roughly by the perspective count on your own backend, which is why
it ships off by default. Gates the heal path only: the session-end default
serialize never escalates, flag or no flag.
| Variable | Default | What it does |
|---|---|---|
DAIMON_HEAL_ESCALATION | off | When truthy, daimon heal re-serializes from multiple perspectives instead of retrying the same extraction shape. Heal-path only. |
Capture while a ledger cannot be read
When a project's events.jsonl cannot be read (a conflict marker, a bad byte,
an OS error), daimon will not write a checkpoint for it: the forget gate would
have to guess, and a forgotten value could come back. The session is refused
before the model is called, which costs nothing. The refusal is an ordinary
failed serialize. daimon status counts it from the whole log and names the
cure, the project's next briefing says how many sessions are waiting, and
daimon heal retries them one per session start once the ledger reads again,
without spending the single retry. A refusal is lost only when its transcript
is gone.
| Host | Refused capture |
|---|---|
| Claude Code, Codex, Gemini CLI, Kimi Code | Supported: counted, briefed and retried by daimon heal while the host keeps the transcript. |
| Windsurf | Supported, with one limit: daimon's own 7-day reap of Windsurf transcripts (DAIMON_WINDSURF_STATE_DAYS) still applies, so a session refused for longer than that is lost and counted as unrecoverable. |
| Hermes in-process capture | Supported only when the host passes a transcript path that exists. Without one it is counted as unrecoverable and cannot be retried. |
| anamnesis pods | Supported, through the same CLI. |
Scene traces
Opt-in experiment (#317): the serializer asks for a per-item scene, one to
two sentences of episodic context, alongside the rest of the extraction.
Gated on the LongMemEval harness clearing it as a net win before it can
become default.
| Variable | Default | What it does |
|---|---|---|
DAIMON_SCENE_TRACES | off | When truthy, ask the serializer to also produce each item's scene field. |
Ops & diagnostics
| Variable | Default | What it does |
|---|---|---|
DAIMON_LOG_DIR | ~/.daimon/logs | Log directory. serialize-crash.log honors this on both sides — the hooks that spawn the serialize child read it (process env and this file) exactly as the CLI does, because daimon forget deletes that file and writer and deleter must agree on where it is. serialize.log is the exception: the hooks still write it to ~/.daimon/logs unconditionally, and this override only moves where the CLI (and tests) look for it. |
DAIMON_CLAUDE_PROJECTS_DIR | ~/.claude/projects | Where host transcripts live (<slug>/<session>.jsonl). Read-only — the quote-reverification audit reads them to re-check stored quotes against their source. |
DAIMON_SCAR_HARVEST | off | When truthy, draft scar (negative-knowledge) candidates from the transcript at session-end. |
DAIMON_LOG_STDOUT | off | When truthy, serialize result lines also reach stdout, the stream a container runtime collects. The session-end hook lets the detached serialize child inherit stdout instead of discarding it, and the CLI mirrors its error: lines there too, so success, skip and error all land on one surface. Intended for container hosts, where a line written only to serialize.log on a volume is invisible to the platform. Off by default: on a terminal host these lines arrive in your shell minutes after the session ended. It does not touch stderr, which stays reserved for serialize-crash.log. |
LLM backend
Serialization needs an LLM endpoint. daimon configure is the intended way to
set these. The URL, key, and model each fall back to a LITELLM_* variable if
the DAIMON_* form is unset. The litellm backend needs both
DAIMON_LLM_MODEL and DAIMON_LLM_API_KEY; while either is missing,
daimon configure reports backend: litellm — missing: ... naming exactly
what is absent — that line means the config is incomplete, not that the
endpoint is down.
| Variable | Default | What it does |
|---|---|---|
DAIMON_LLM_BACKEND | auto | Transport: auto (litellm if credentials exist, else a named command CLI if one resolves), litellm, command, or claude-cli. claude-cli is the zero-config option: it runs a claude found on PATH with a built-in preset and needs nothing else set. command requires DAIMON_LLM_COMMAND. |
DAIMON_LLM_BASE_URL | http://localhost:4000 | OpenAI-compatible endpoint URL (trailing slash trimmed). Falls back to LITELLM_BASE_URL. |
DAIMON_LLM_API_KEY | unset | API key for the endpoint. Required for the litellm backend. Falls back to LITELLM_API_KEY. |
DAIMON_LLM_MODEL | unset | Model name to send. Required for the litellm backend. Falls back to LITELLM_MODEL. |
DAIMON_LLM_TEMPERATURE | 0.0 | Sampling temperature for every chat call. 0.0 for deterministic extraction; some upstreams reject anything but a fixed value. |
DAIMON_LLM_FALLBACK | on | When the primary backend fails, auto-fall-back to the rescue command (DAIMON_LLM_COMMAND_FALLBACK). Applies to both a litellm and a command primary. Set to 0 to disable. |
DAIMON_FALLBACK_MIN_SECONDS | DAIMON_TIMEOUT | Minimum budget the rescue command is guaranteed on entry. The primary may have drained the shared serialize deadline retrying the very failure the rescue exists to fix, which would kill the rescue on arrival; a healthy remaining budget is never shrunk. |
DAIMON_RESAMPLE_MIN_SECONDS | DAIMON_TIMEOUT | #553: minimum budget the validation resample is guaranteed on entry — the same failure #341 fixed for the command fallback, one path over. The shared deadline can be drained by the attempt whose output just got rejected, which would hand the resample nothing to work with. Defaults to timeout_seconds() for the same reason DAIMON_FALLBACK_MIN_SECONDS does. |
DAIMON_LLM_STREAM | on | Stream the litellm response so the socket timeout bounds the inter-frame gap rather than the whole completion. Without it, a long completion trips the timeout and retries from scratch. Set to 0 to disable. |
DAIMON_LLM_NO_CACHE | off | When truthy, bypass gateway response caching per request — needed when a cached bad response pins a failure or runs must be statistically independent. |
DAIMON_LLM_BRIEFING | off | When truthy, render the briefing via the LLM instead of the deterministic template. |
DAIMON_LLM_COMMAND | unset | Full CLI invocation for the command backend (binary + model + flags). Required for command, and required for a claude on PATH to be used by any backend other than claude-cli. |
DAIMON_LLM_COMMAND_OUTPUT | unset | How to extract assistant text from the command's stdout: text (raw stdout) or json:<key> (parse JSON, read <key>). |
DAIMON_LLM_COMMAND_INPUT | stdin | How the prompt reaches the command backend: stdin (piped), arg (appended as the final argv element), or file:<flag> (written to a tempfile, then <flag> <path> appended). An unrecognized value logs a warning and falls back to stdin. |
DAIMON_LLM_COMMAND_FALLBACK | unset | The one rescue CLI, used when the primary backend fails. Works for both a litellm primary and a command primary, which previously had no rescue direction at all. One fallback, never a chain: if the primary and this both fail the cause is almost always environmental, and a third CLI spends budget reaching the same error while making the install look better protected than it is. When unset, a litellm primary still falls back to DAIMON_LLM_COMMAND as before. |
DAIMON_LLM_COMMAND_FALLBACK_OUTPUT | unset | Output spec for the rescue CLI, same grammar as DAIMON_LLM_COMMAND_OUTPUT. Carried separately because the rescue is a different binary. |
DAIMON_LLM_COMMAND_FALLBACK_INPUT | stdin | Input spec for the rescue CLI, same grammar as DAIMON_LLM_COMMAND_INPUT. |
A claude binary merely present on PATH is not adopted automatically. Serializing sends the full session transcript to whichever CLI is configured, so that CLI has to be named: either DAIMON_LLM_COMMAND, or DAIMON_LLM_BACKEND=claude-cli to opt into the built-in preset.
Previously an unset DAIMON_LLM_COMMAND plus a claude anywhere on PATH was enough for auto installs and for the litellm rescue path. If you relied on that, set one of the two variables above. daimon configure names the resolved binary and its path so you can see exactly which one is in use.
The claude-cli preset runs its claude -p child with --strict-mcp-config, so it loads no MCP servers. Without that flag, the child's own launcher exits early under the isolation env the serializer sets, and Claude Code caches that as a connection failure, so every new session on the machine skips daimon's MCP server for the next 15 minutes (#1077). If you point a DAIMON_LLM_COMMAND at claude yourself, add --strict-mcp-config to it too: the serializer only turns text into JSON and never needs an MCP tool.
Serializer chunking
Long sessions are serialized in overlapping chunks whose partial checkpoints are merged hierarchically. The defaults come from field measurements; they only matter if your sessions routinely run very long.
| Variable | Default | What it does |
|---|---|---|
DAIMON_CHUNK_LINES | 1200 | Rendered-transcript line count above which serialization switches to chunked mode. |
DAIMON_CHUNK_OVERLAP | 100 | Lines of overlap between adjacent chunks, so an item straddling a boundary is seen whole by at least one chunk. |
DAIMON_CHUNK_CONCURRENCY | 4 | Parallel chunk-serialize LLM calls. Minimum 1 (sequential). |
DAIMON_MERGE_GROUP_SIZE | 3 | Max partial checkpoints merged per hierarchical merge call. Minimum 2. Lower to 2 if merge calls die on a gateway with a server-side request ceiling (reasoning models generating 3-way merges can exceed it; raising DAIMON_TIMEOUT won't help — the kill is server-side). |
DAIMON_CHUNK_CACHE | on | #48: content-addressed cache of chunk-extraction output, keyed on the chunk's own bytes, so a re-run of a heal or retry re-pays a call only when the chunk source actually changed. Kill switch — on unless set to 0. |
DAIMON_CHUNK_CACHE_DAYS | 3 | Rotation window for the chunk cache, in days. Cached output is pre-redaction (the #125 quote-verification ordering forces this), so the window is a privacy bound as much as a disk bound — 3 days covers the heal/retry window while keeping exposure short. |