A local loopback daemon amortizes doctrine parse and caches deterministic gate verdicts
Filename: 2026-08-13-5-local-daemon-amortizes-doctrine-parse-and-caches-gate-verdicts.md
Status: Proposed (direction-setting; not a prerequisite for the gate work)
Date: 2026-08-13
Deciders: Operator (ATDD)
Technical Story: Records a direction surfaced while designing declarative gates (ADR 2026-08-13-2). Captured now so the gate design is daemon-ready, but scoped as a follow-on.
Post-dialectics revision (2026-08-13) — DECISION HELD OPEN
Per operator: this decision is held open for further discussion; the rest of the gate
architecture proceeds without it. Recorded from the dialectics pass
(work/gate-design-dialectics/04-..., 99-COHERENCE.md), not yet settled:
- The verdict cache keyed on pack content-hash is contested as unsound. The pack hash omits
the working tree, which is the dominant input of both the incumbent
spec-kitty-pre-reviewgate (scoped tests at head) and thedocs-structural-lintexample (lints the tree). Editing a tracked file leaves the pack hash unchanged → a stale PASS could cross a blocking gate. Capturing "everything a subprocess reads" is not feasible without tracing infra that dwarfs the daemon. - Parse-amortization is sound and daemon-independent — the parse is a pure function of pack bytes and can be a content-addressed on-disk cache with no daemon.
- No decision is folded here yet. The daemon-as-warm-API and the verdict-cache question remain open pending further discussion.
Context and Problem Statement
The CLI parses doctrine + DRG (and, with declarative gates, gate definitions) on every invocation. Adding gate execution compounds that per-invocation cost. But gates are deterministic and side-effect-free, which makes their verdicts cacheable.
There is precedent for a resident local process: the dashboard daemon
(.kittify/.dashboard) and the orchestrator-api external surface already exist, and the
project's loopback-only HTTP posture (127.0.0.1, no forced TLS) is the established security
model for such a surface.
Decision Drivers
- Amortize doctrine/DRG/gate parse across invocations.
- Exploit gate determinism: identical inputs ⇒ identical verdict ⇒ cache hit.
- Do not couple the gate design to a daemon that does not exist yet.
Decision Outcome
Direction (not yet a committed build): a local, loopback-only daemon may host doctrine resolution and gate execution behind an API. It holds:
- Parsed doctrine / DRG, loaded once and reused across calls.
- A deterministic gate-verdict cache, keyed on
(gate id, gate/pack version, hash of the inputs the gate reads). The packpack-meta.yamlcontent-hash is the natural invalidation key — the same hash that is signed for trust (ADR 2026-08-13-4). Three concerns converge on one hash: integrity (signature), identity (version), and cache-key.
Gate execution then becomes a warm API call; a cold CLI subprocess remains the fallback and the v1 path. The gate design (ADRs -2/-3/-4) must stand on its own without the daemon — the daemon is an optimization, not a dependency.
Consequences
Positive
- Repeated transitions collapse to cache hits; parse cost is paid once.
- Reuses existing loopback-daemon precedent and security posture.
Negative
- A daemon that executes shipped code is a persistent execution surface with a real lifecycle (start/stop/staleness) and security story — deserves its own ADR + threat model before it is built.
- Cache correctness depends entirely on the input-hash being complete; a gate that reads an un-hashed input would return a stale verdict.
Neutral
- Whether this extends the existing dashboard daemon or is a new process is unresolved.
Open questions (for the dialectics squad)
- Extend the dashboard daemon, or a separate gate/doctrine daemon?
- How is "the inputs the gate reads" captured completely enough to be a safe cache key?
- Is verdict caching even worth it before the daemon exists (cold CLI has no persistent cache)?