Open source, pre-alpha, for journal-driven services

Kavach records every input, clock read and random read your service makes. A crash becomes a fixture that replays byte for byte, and a fix only counts when it survives every variation of the incident.

ledgerrecordingstopped: crash at seq 32file:events.jsonl @ 8
seq 31{"id":"evt-008","type":"deposit","account":"bob","amount":null}seq 32 crash: amount is null
fixture writtennull-amount.kavach33 records, 8 steps, 3,815 bytes$ kavach replay … still_failing@31
The demo wallet ledger's journal: 33 records over 8 steps. A deposit with a null amount at seq 31 crashes at seq 32, and the recorder writes the fixture null-amount.kavach, which replays as still_failing@31.

  1. Record

    Your handler reads time and randomness, and emits effects, through the env it's given. An in-process flight recorder keeps those records in memory and flushes a fixture when a step crashes, returns an error or breaks an invariant.
    kavach inspect null-amount.kavach
    The handler reads and emits through envexamples/ledger
    env.now()                          clock
    env.read(8)                        rand
    …
    env.emit("ledger.entries", entry)   output

    Every input, clock read, random read and output becomes a record. The recorder keeps a window in memory and writes it to disk only when a step fails.

  2. Replay

    Recorded inputs are folded through your own handler code, with every clock and random read served from the fixture. Outputs are captured and compared, never executed, so replay needs no broker, database or network.
    kavach replay null-amount.kavach --bin ./ledger-old
    Replaying null-amount.kavachno broker, no database, no network
    $ kavach replay null-amount.kavach --bin ./ledger-old
    service   ledger · start genesis · 33 records · 8 steps replayed
    recorded  crash at seq 31: amount is null
    result    still_failing@31
  3. Verify

    Replay the same journal under the old build and the new one. Kavach reports the first output where they diverge, then runs variants of the incident before it calls anything fixed.
    kavach diff null-amount.kavach --old ./ledger-old --new ./ledger-new
    Same journal, two buildssteps 0 to 31
    ledger-oldstill_failing@31
    ledger-newfixed
    first divergence at step seq 31: output 0 of step 31 differs
      old  (none)
      new  ledger.rejections  {"event":"evt-008","reason":"missing amount"}

    Then 44 variants of the incident. The new build must pass every one the old build fails.

    41 of 41 passfixed
The handler reads and emits through envexamples/ledger
env.now()                          clock
env.read(8)                        rand
…
env.emit("ledger.entries", entry)   output

Every input, clock read, random read and output becomes a record. The recorder keeps a window in memory and writes it to disk only when a step fails.

Guarding only the event type from the incident makes the recorded crash go away. It doesn't fix the bug. kavach diff perturbs the incident: fields changed or removed, the failing event moved earlier, earlier events dropped or redelivered, the clock shifted. Switch between two real fixes for the ledger demo and watch it run.

37of 41 reproducing variants pass

Variant 6

failing input (seq 31): "type" "deposit" → "transfer"

The old build fails as in production. This fix still fails: still_failing@22.

  • passes
  • still fails
  • doesn't reproduce, ignored
  • not run yet
$ kavach diff null-amount.kavach --old ./ledger-old --new ./ledger-partial
old       still_failing@31
new       fixed
first divergence at step seq 31: output 0 of step 31 differs
  old  (none)
  new  ledger.rejections  {"event":"evt-008","reason":"missing amount"}
variants  41 of 44 reproduce the incident on the old build · 37 of those pass on the new build
  ✗  6 failing input (seq 31): "type" "deposit" → "transfer"
       still_failing@22: crash: amount is null
  ✗ 29 failing input (seq 31): "type" "deposit" → "withdraw"
       still_failing@22: crash: amount is null
  ✗ 37 failing input (seq 31): "type" "deposit" → "deposit-1"
       still_failing@22: crash: amount is null
  ✗ 41 failing input (seq 31): "type" removed
       still_failing@22: crash: amount is null
verdict   variant_failed(6)@22

Verdict for null check on deposits only: variant_failed(6)@22. 37 of 41 reproducing variants pass.

Coding agents are good at writing fixes and bad at knowing when to stop. kavach mcp hands them three tools that return the CLI's verdicts as JSON, so a fix isn't done until Kavach says fixed.

kavach_list_incidents
Every fixture, with how it failed
kavach_replay
Reproduce it before changing code
kavach_diff
Old build against new, plus variants
claude mcp add kavach -- kavach mcp

Agents follow AGENTS.md: reproduce first, change code and never the fixture, and report the verdict exactly as printed.

Claude Code, with kavach mcpIllustrative session, real verdicts
  1. > Fix the crash in fixtures/null-amount.kavach
  2. kavach_replay (bin: /tmp/old)
    verdict still_failing@31 crash: amount is null
  3. Edit the ledger handler
    + reject deposits whose amount is null
  4. kavach_diff (old: /tmp/old, new: /tmp/new)
    verdict variant_failed(6)@22
    variant 6: "type" "deposit" → "transfer"
  5. The fix only covered deposits. A transfer with a null amount still crashes, so I'll guard every event type instead.
  6. Edit the ledger handler
    - reject deposits whose amount is null
    + reject any event whose amount is null
  7. kavach_diff (old: /tmp/old, new: /tmp/new)
    verdict fixed 41 of 41 reproducing variants pass

Measured 6 October 2026 on a shared 4-vCPU cloud VM. The command behind each number, and what it includes, are in BENCHMARKS.md, so you can rerun any of them.

Recorder overhead per eventMedian of 5 runs
Replay of the demo crash, in processMedian of 5 runs
Fix verification, 87 replays in process2 replays and 85 variants, median of 5 runs
Size of the demo crash fixtureThe fixture file on disk
Replays that came out byte-identicalThe same fixture, replayed 1,000 times
Narrow fixes rejected, ten planted bugsCrashes, returned errors and broken invariants

Kavach is pre-alpha. It covers single-writer, journal-driven services; multi-writer state and network tapes are out of scope for now. Apache 2.0.