Skip to main content
Glama

Cairn

A local MCP server that checkpoints AI coding sessions and — when things go wrong — recovers them with context. Not just a rollback. An informed restart.

See DESIGN.md for the full design document.

Install

pip install cairn-mcp          # heuristic-only
pip install cairn-mcp[ai]      # with LiteLLM for auto-labels, handoffs, embeddings

Related MCP server: KeepGoing MCP Server

Setup

cd your-project
cairn init                     # writes cairn.toml, gitignores .cairn/, installs ~/.cairn
claude mcp add cairn -- cairn start

Then add @~/.cairn/cairn_agent.md to your tool's always-loaded context file (CLAUDE.md, .cursorrules, .windsurfrules, or .roo/rules/cairn.md).

Full requirements and per-tool setup (Claude Code, Codex CLI, OpenCode, Roo Code, and others) are in INSTALL.md.

CLI

Command

What it does

cairn start

Run the MCP server (stdio); a session starts on connect

cairn stop

Mark active sessions for this directory as ended

cairn history [--archived]

List sessions and checkpoints (or purged sessions)

cairn dismiss

Dismiss current drift signals (false positive)

cairn init

Set up cairn.toml, .gitignore, and ~/.cairn

cairn eval run [--judge]

Score drift detection against synthetic scenarios

cairn eval results

List past eval runs

MCP tools

Cairn exposes exactly six tools. Descriptions are terse and responses are compact JSON — the whole Cairn footprint in the model's context is budgeted at under 300 tokens.

Tool

Mode

What it does

Returns

cairn_checkpoint(label?, trigger?)

async, fire-and-forget

Snapshot the working tree onto a shadow branch; trigger is "manual" (user asked) or "ai" (model judged, default)

{"status":"queued","id":"..."}

cairn_rollback(checkpoint_id, inspect?)

sync, atomic

Auto-stash, reset to checkpoint, write handoff + recovery gate (inspect=true creates a cairn/recovery/* branch instead)

{"status":"restored","checkpoint":"...","stash_ref":"stash@{0}","handoff_written":true}

cairn_check_drift()

sync (drains queue first)

Run all drift signals: scope drift, write loops, error recurrence, semantic shift, token pressure

{"drift":false} or {"drift":true,"evidence":[...],"suggested_restore":"...","label":"..."}

cairn_log_usage(tokens, limit)

async

Record reported context usage for the token-pressure signal

{"status":"logged"}

cairn_log_event(type, data)

async

Record a write/error/decision event for drift analysis

{"status":"logged"}

cairn_acknowledge_handoff()

sync

Delete the RECOVERY_ACTIVE gate and archive HANDOFF.md

{"status":"acknowledged","archived":"..."}

How an MCP call flows

flowchart TD
    AI["AI coding tool<br/>(Claude Code / Cursor / Roo / Windsurf)"] -->|MCP protocol| SRV["Cairn MCP server<br/>(session starts on connect)"]

    SRV --> CP["cairn_checkpoint(label?)"]
    SRV --> LOG["cairn_log_event / cairn_log_usage"]
    SRV --> DRIFT["cairn_check_drift()"]
    SRV --> RB["cairn_rollback(checkpoint_id)"]
    SRV --> ACK["cairn_acknowledge_handoff()"]

    %% checkpoint path (async)
    CP -->|"ACK immediately: {status: queued, id}"| AI
    CP -.enqueue.-> Q["operation queue<br/>(worker thread)"]
    Q --> SNAP["git snapshot on shadow branch<br/>cairn/snapshots/{session}/{checkpoint}"]
    SNAP --> ROW["insert checkpoint row in SQLite"]
    ROW -->|"on failure: revert git ref"| Q

    %% logging path
    LOG --> DB[("SQLite<br/>.cairn/cairn.db")]

    %% drift path
    DRIFT --> DET{"drift signals?<br/>scope drift · write loops ·<br/>error recurrence · semantic shift"}
    DET -->|no| CLEAN["{drift: false}"] --> AI
    DET -->|yes| EV["{drift: true, evidence, suggested_restore}"] --> AI
    EV --> USER{"user decides"}
    USER -->|dismiss| DISMISS["cairn dismiss<br/>(signals reset)"]
    USER -->|inspect| INSPECT["rollback with inspect=true<br/>creates cairn/recovery/* branch"]
    USER -->|rollback| RB

    %% rollback path (sync)
    RB --> STASH["git stash push -u<br/>(uncommitted work preserved)"]
    STASH --> RESET["git reset --hard to checkpoint<br/>(user stays on their branch)"]
    RESET --> HANDOFF["generate HANDOFF.md<br/>(LiteLLM or heuristic)"]
    HANDOFF --> GATE["write .cairn/RECOVERY_ACTIVE gate"]
    GATE -->|"{status: restored, stash_ref}"| AI

    %% recovery path (next session)
    GATE -.next session, branch matches.-> ACK
    ACK --> ARCHIVE["delete gate, archive handoff,<br/>AI reads HANDOFF.md and resumes informed"]

Development

pip install -e .[dev]
pytest

Related MCP Connectors

Related MCP Servers