https://github.com/cope-labs/cairn
Runtime signal capture and distillation for AI-assisted development. Captures test failures, runtime traces, and structural patterns — distills them into agent context that compounds over time.
https://github.com/cope-labs/cairn
ai-agents ai-coding developer-tools observability python runtime-analysis
Last synced: about 1 month ago
JSON representation
Runtime signal capture and distillation for AI-assisted development. Captures test failures, runtime traces, and structural patterns — distills them into agent context that compounds over time.
- Host: GitHub
- URL: https://github.com/cope-labs/cairn
- Owner: Cope-Labs
- License: agpl-3.0
- Created: 2026-04-08T04:18:13.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-04-08T16:50:19.000Z (about 1 month ago)
- Last Synced: 2026-04-14T03:23:24.452Z (about 1 month ago)
- Topics: ai-agents, ai-coding, developer-tools, observability, python, runtime-analysis
- Language: Python
- Size: 169 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Cairn
[](https://pypi.org/project/cairn-ai/)
[](https://pypi.org/project/cairn-ai/)
[](https://pypi.org/project/cairn-ai/)
[](https://www.gnu.org/licenses/agpl-3.0)
**Writes its own `CLAUDE.md` — from what your code actually does.**
Your AI coding agent starts every session cold. Cairn gives it memory of what this codebase has actually failed on — captured from your runtime, distilled into typed patterns, injected into the agent's context before the next session.
```bash
pip install cairn-ai
pytest # cairn's pytest plugin auto-registers
cairn install # scaffold CLAUDE.md / AGENTS.md / Cursor rules
cairn status # see what was captured
```
`cairn install` never overwrites files you've edited; pass `--force` to
regenerate them. No config, no decorators, no changes to your tests.
Captured records are scrubbed of secrets and PII before they hit disk.
---
## What it captures
Every run, Cairn writes structured signal to a local SQLite store:
- **Tests** — pytest pass/fail, assertion values, local state at failure
- **Execution** — function calls, return values, exceptions, duration (Python 3.12+ `sys.monitoring`; `@cairn.watch` decorator fallback on older)
- **Structure** — AST scan for 9 anti-patterns, untested branches (via `coverage`)
- **Git** — commit → test delta → error delta correlation
Records are idempotent, vector-embedded for semantic search, and decay on a configurable half-life.
`cairn distill` clusters failures, names them (deterministic heuristics or LLM via OpenRouter), and produces:
- **Context snippets** for the agent's system prompt
- **Lint rules** encoded as ruff/flake8 rules so the pattern can't recur silently
- **Test templates** — property-based stubs from observed function behaviour
---
## Success signal
Cairn captures both PASS and FAIL outcomes. Stable modules (high pass rate,
long streak since last failure) are surfaced alongside broken clusters so
agents can tell what's safe to refactor:
```bash
cairn stability # per-module pass/fail/rate/streak table
cairn rollback-check # commits correlated with a failure spike
cairn flaky # tests whose assertion fails with >1 distinct value
```
`build_context` appends a "Stable modules" section automatically when the
store has any PASS records.
---
## Consumed outputs
Distilled signal feeds back into the project rather than sitting in
Markdown:
- **Templates** — `cairn templates` writes pytest-collectable stubs into
`tests/cairn_generated/`. Run them with `pytest tests/cairn_generated`
or the bundled `nox -s cairn_generated` session (installs Hypothesis).
- **Rules** — `cairn rules` reads `[tool.ruff.lint] select` from the
project's ruff config and prints a concrete pyproject patch for the
codes implied by captured AST records.
- **Health policy** — `cairn distill --analyze-health --policy-exit-halt`
exits 3 on any HALT escalation, suitable as a pre-commit / CI gate.
---
## Safety
Captured content (pytest locals, monitor return values, ingested JSONL) is
passed through a secret/PII scrubber before it is written to SQLite. The
scrubber redacts:
- sensitive env-var values (by harvesting env at import time)
- `key=value` / `key: value` pairs whose key matches a sensitive-name heuristic
(`password`, `token`, `api_key`, `authorization`, `cookie`, `session`,
`private_key`, `credential`, …)
- recognised token shapes (AWS, GitHub, Anthropic / OpenAI / OpenRouter, Slack,
JWT, PEM private keys)
- common PII shapes (email local-parts, IPv4, credit-card-shaped digit runs)
Records are content-addressed after scrubbing, so two observations differing
only by a secret collapse into a single row and the hash can never be inverted
to recover the original value.
---
## How it differs
| Tool | What it does | What Cairn adds |
|---|---|---|
| Sentry / Datadog | Production errors | Feeds dev-time agent context |
| pytest-cov | Untested paths | Persists and distills over time |
| CLAUDE.md / AGENTS.md | Manual agent context | Writes itself from runtime |
| Cursor / Cline | Generates from context | Remembers what failed last session |
---
## Architecture
```
cairn/
├── capture/ # sys.monitoring, pytest plugin, AST scanner, git linker
├── store/ # TruthRecord schema, SQLite writer, semantic search
├── distill/ # clustering, naming, decay, health (margin-powered)
├── inject/ # context builder, MCP server (stdio + HTTP/SSE)
└── cli.py
```
---
## TruthRecord
```python
@dataclass
class TruthRecord:
id: str # sha256(source:domain:content)
source: str # "pytest" | "monitor" | "ast" | "git" | "distill"
domain: str # project namespace / module path
content: str # the fact
confidence: float # 0.0–1.0, decays with age unless re-confirmed
occurred_at: datetime
source_file: str | None
source_line: int | None
exception_type: str | None
pattern_cluster: str | None # assigned by distillation
embedding: list[float] # semantic search
```
Re-running the same failure updates the existing record rather than inserting a duplicate.
---
## CLI
The 22 flat commands are also reachable through three groups:
`cairn store {…}` (status, stats, export, prune, sync, ingest, embed, query),
`cairn pattern {…}` (templates, rules, context, flaky, stability, rollback-check),
and `cairn capture {…}` (scan, watch, prime). The flat names continue to work.
| Command | Description |
|---|---|
| `cairn status` | Summary: counts, active vs stale, recent failures, clusters |
| `cairn stats` | Detailed breakdown by source, domain, confidence, age |
| `cairn stability` | Per-module pass/fail/streak — flags safe-to-refactor vs flaky modules |
| `cairn rollback-check` | Commits correlated with a failure spike (rollback candidates) |
| `cairn flaky` | Tests whose assertion fails with >1 distinct value |
| `cairn rules` | Diff cairn-implied ruff codes against the project's ruff config |
| `cairn scan [PATH]` | AST scan, optionally with coverage + git linkage (`--subtree` for monorepos) |
| `cairn distill` | Cluster + name patterns; `--analyze-health`, `--policy-exit-halt`, `--use-llm` |
| `cairn context` | Print Markdown context (respects `--max-tokens`, default 4000) |
| `cairn query Q` | Keyword / semantic search with source + age filters |
| `cairn watch TARGET` | Instrument and run a script or module (3.12+) |
| `cairn serve` | MCP server — stdio or HTTP+SSE |
| `cairn templates` | Generate property-based test stubs into `tests/cairn_generated/` (run via `nox -s cairn_generated`) |
| `cairn ingest [FILE]` | Cross-language JSONL ingest |
| `cairn export` | JSON or CSV |
| `cairn sync SOURCE_DB` | Merge another store into this one (`--reweight`, `--source-tag`) |
| `cairn annotate [PATH]` | Insert / remove inline `# cairn:` comments |
| `cairn prune` | Delete stale or low-confidence records |
| `cairn embed --repair` | Backfill missing embeddings |
| `cairn install` | Scaffold agent instructions (CLAUDE.md, AGENTS.md, Copilot, Cursor, Continue) |
| `cairn install-hook` | Git post-commit hook for auto-distill |
| `cairn config show` | Print resolved config |
Run `cairn --help` for full options.
---
## Configuration
Cairn reads `[tool.cairn]` from the nearest `pyproject.toml`, merging with defaults:
```toml
[tool.cairn]
db = ".cairn/store.db"
domain = "myproject"
[tool.cairn.decay] # half-life in days
pytest = 21.0
monitor = 21.0
ast = 14.0
git = 30.0
distill = 60.0
```
---
## Install extras
```bash
pip install cairn-ai # core
pip install cairn-ai[pytest] # pytest auto-registration (recommended)
pip install cairn-ai[coverage] # untested-branch capture
pip install cairn-ai[health] # margin drift / anomaly / causality
```
---
## Health analysis (margin integration)
With `cairn-ai[health]` installed, distillation gains trajectory-aware analysis from [margin](https://github.com/Cope-Labs/margin):
- **Drift** — each cluster as a weekly time series: STABLE / DRIFTING / ACCELERATING / DECELERATING / REVERTING / OSCILLATING with polarity-normalized direction.
- **Anomaly** — latest week classified against history: EXPECTED / UNUSUAL / ANOMALOUS / NOVEL.
- **Causality** — lag-aware temporal correlations across clusters.
- **Escalation** — HALT / ALERT / LOG based on combined health state.
Context output changes from:
```markdown
## ValueError in config.py (12×) [pytest]
```
to:
```markdown
## HALT — ValueError in config.py (12×) [pytest]
_Health: ABLATED | Drift: ACCELERATING WORSENING | Anomaly: NOVEL_
_Causality: --CORRELATES--> KeyError::validate_request (strength 1.00, lag 2 weeks)_
```
Without margin installed, all health fields are `None` and exponential decay runs unchanged.
---
## MCP server
```bash
cairn serve # stdio (Claude Code, Cline)
cairn serve --transport http --port 8765 # HTTP + SSE
cairn serve --transport http --token "$CAIRN_TOKEN" # with bearer auth
```
Tools exposed: `cairn_status`, `cairn_search`, `cairn_context`, `cairn_recent_failures`, `cairn_health`, `cairn_synthesis`.
Add to your agent's MCP config:
```json
{
"servers": {
"cairn": {
"command": "cairn",
"args": ["serve"],
"cwd": "/path/to/your/project"
}
}
}
```
The HTTP transport also exposes REST endpoints:
| Method | Path | Auth |
|---|---|---|
| `POST` | `/ingest` | bearer if `--token` set |
| `POST` | `/query` | bearer if `--token` set |
| `GET` / `POST` | `/sse` · `/message` | MCP handshake |
| `GET` | `/health` | none |
---
## Cross-language signal
Any language can feed Cairn via JSONL — only `content` is required:
```bash
nasti-js apply src/ | cairn ingest --source nasti-js --domain myapp/src
cairn ingest records.jsonl --source custom --domain myservice
cairn embed --repair # backfill embeddings after direct inserts
```
Records from any source are semantically searchable together via `cairn query` or the MCP server.
---
## Python support
Python 3.10+. `sys.monitoring` capture requires 3.12+; earlier versions fall back to the `@cairn.watch` decorator. All other features work everywhere.
---
## Development
```bash
git clone https://github.com/Cope-Labs/cairn
cd cairn
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest
```
The backtest suite (`tests/test_backtest.py`) is a machine-readable spec of Cairn's core contracts. If you change an algorithm, it tells you what changed.
---
## Name
A cairn is a stack of stones left on a trail by previous travelers. Found by the next one.
---
## License
AGPL-3.0-or-later. Commercial licensing available for closed-source use — open an issue.