CodeLedger
by mikuare
README.md
# CodeLedger
[](https://github.com/mikuare/Code-Ledger/actions/workflows/ci.yml)
[](LICENSE)
[](https://www.python.org/downloads/)
**Give your coding agent a memory of your codebase — what exists, what changed, who changed it, and what breaks if you touch it.**
Coding agents start every session blind. They re-read your repository, rebuild something that already exists, or change a file the task never mentioned. CodeLedger keeps a local index so the agent can ask instead of guess, and so you can see afterwards exactly what it touched.
```bash
codeledger impact authenticateUser # who breaks if I change this?
codeledger why formatName # who changed this, and for what request?
codeledger scope "fix login" --files src/billing/charge.py # is this even in scope?
```
It runs entirely on your machine, stores everything in SQLite, and speaks [MCP](#mcp) so Claude Code, Codex, Cursor, Gemini, and Aider can query it mid-conversation. No source code ever leaves the machine.
Its one design rule: **never assert what it cannot back up.** Unknown authorship stays `unknown`, an unanalysable language reports `shallow` coverage, and an ambiguous task returns `UNKNOWN` rather than a confident wrong answer.
CodeLedger is a local-first, SQLite-backed project memory and change-intelligence CLI for coding agents and human developers. It indexes file hashes and symbols incrementally, preserving deleted symbols as historical evidence instead of inventing authorship or intent.
**New here? Start with [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md)** — first setup, the daily loop, and what to do when WSL, your IDE, or an agent closes unexpectedly.
For the command-level reference, see [docs/SETUP_AND_WORKFLOW.md](docs/SETUP_AND_WORKFLOW.md).
Coordination evidence explained in a sentence rather than a payload — who else is affected, on what route, how current the evidence is, and a message you may choose to send — is `codeledger advise` ([docs/ADVISOR.md](docs/ADVISOR.md)). It is off by default, it decides nothing, and every name the prose uses is checked back against the evidence before you see it.
Running more than one agent on one repository — two Claude Code terminals and a Codex one, say — is [docs/MULTI_AGENT.md](docs/MULTI_AGENT.md): session identity, claiming what you are about to change, conflicts before the edit rather than after, and exactly how far `strict` enforcement does and does not reach.
## The prompt
`codeledger`, on its own, opens an interactive prompt for the project you are in — the same shape as `claude`, `codex` or `gemini`:
```text
$ codeledger
▄▀▀▀ ▄▀▀▄ █▀▀▄ █▀▀▀ █ █▀▀▀ █▀▀▄ ▄▀▀▀ █▀▀▀ █▀▀▄
█ █ █ █ █ █▀▀ █ █▀▀ █ █ █ ▄▄ █▀▀ █▄▄▀ ◈
▀▄▄▄ ▀▄▄▀ █▄▄▀ █▄▄▄ █▄▄▄ █▄▄▄ █▄▄▀ ▀▄▄█ █▄▄▄ █ ▀▄
Project intelligence for AI-assisted development
◈ shop-web v0.4.0
/home/you/src/shop-web
✓ Index 412 files · 3,908 symbols
! Fresh 1 file(s) changed
✓ Analysis tree-sitter
! Agent not connected
– Watcher off
Recommended
refresh --changed
setup-agent claude-code
Type help for commands, exit to leave.
◈ shop-web ❯ chek
✕ Unknown command: chek
Did you mean
check
◈ shop-web ❯ check --json | python -m json.tool
› That looks like a shell command
You are inside the CodeLedger prompt, which runs CodeLedger commands rather
than shell syntax, so '|' has no meaning here.
Run it from your terminal
codeledger check --json | python -m json.tool
Or from here
! codeledger check --json | python -m json.tool
runs it in your shell, in this project's directory.
```
Four questions answered before you type: which project, is the index current, is an agent connected, what should I do. `help` groups the commands by what you are trying to do; `root` shows or switches the directory the session runs against; a typo is answered with the command it probably was, never with an argparse usage dump; and a pipeline is told where it belongs rather than handed to argparse as arguments.
**`◈` is CodeLedger's mark** — a node inside a node, because the subject of this tool is a symbol that other symbols reach. It signs the wordmark once, heads the prompt, and heads each command. The wordmark is drawn from a bitmap font built into the package, so it adapts: 52 columns of half-block letterforms, CODE over LEDGER at 29, letterspaced text below that, and the same in ASCII where the terminal cannot encode the glyphs. No `pyfiglet`, no `rich` — the dependency list stays empty.
It is a front end and nothing else: each line is handed to the same parser and the same dispatch the command line uses, so every command behaves identically and there is no second implementation to drift. `codeledger shell` is the same thing under an explicit name.
### Terminal behaviour
Colour, width and character set are detected once, from the stream being written to, and **colour is never the only signal**: every state carries a symbol and a word as well. `NO_COLOR`, a pipe, a CI log, `TERM=dumb` and a 40-column window all get the same information in a form that survives them.
```text
$ CODELEDGER_ASCII=1 NO_COLOR=1 codeledger
C O D E L E D G E R
Project intelligence for AI-assisted development
<> shop-web v0.4.0
/home/you/src/shop-web
[ok] Index 412 files - 3,908 symbols
[!] Fresh 1 file(s) changed
[ok] Analysis tree-sitter
[!] Agent not connected
[-] Watcher off
```
The ASCII wordmark is letterspaced rather than drawn in `#`: a three-row hash font at this size reads as a failure, and letterspacing reads as a decision.
Every command that renders takes the same three depth flags:
| Flag | Gives |
|---|---|
| *(none)* | The answer, the actions, and a one-line summary of the caveats |
| `--verbose` | Every caveat, threshold and bound, in full |
| `--debug` | Everything `--verbose` shows, plus the raw payload |
| `--json` | The structured answer, undecorated — the formatting layer is bypassed entirely |
## Start here
```bash
cd /path/to/your-project
codeledger setup
```
One command for whatever state the project is in — never indexed, indexed and out of date, or already current. It does the additive, idempotent thing (index, or bring the index up to date), then answers the only question a new user has: *can I use it now?*
```text
◈ SETUP your-project
✓ READY_WITH_STEPS
What just happened
✓ Indexed indexed 412 file(s) and recorded 3,908 symbol(s)
✓ Now holds 412 file(s), 3,908 symbol(s)
Optional, and worth doing
1 no coding agent on this machine is connected to this project
an agent that cannot reach CodeLedger will not use it, however well it is indexed
codeledger setup-agent claude-code
or codex, cursor, gemini, aider — and this edits that agent's config, not your project
Using it from here
codeledger brief "<what you are about to do>" before you start — or ask your agent
codeledger refresh --changed after you or your agent edit code
codeledger done "<what you did>" before you call it finished
```
Run it again any time — after edits it refreshes, when nothing moved it says so. It never connects an agent for you: that edits configuration outside your project, so it is named with the exact command instead.
**How does it know my code changed?** CodeLedger reads an index, so it answers about the code it last looked at. `codeledger refresh --changed` brings it up to date and only reads what actually moved — that is the whole daily loop. `codeledger check` tells you at any moment whether the index is behind. An agent following the protocol file refreshes on its own; `codeledger watch` in a second terminal covers edits nobody reports.
## Is it working?
One command, and it answers the question rather than printing counts:
```bash
codeledger check
```
```text
CODELEDGER CHECK
! STALE
CodeLedger is indexing shop-web and has fallen behind the working tree.
Project
shop-web
/home/you/src/shop-web
Health
✓ Install CodeLedger 0.4.0
✓ Index 412 files · 3,908 symbols
! Freshness 1 file changed since last index
✓ Analysis tree-sitter enabled
✓ Agent protocol CLAUDE.md, AGENTS.md, CODEX.md
✓ Slash command /codeledger available
! MCP registration no agent connected here
– Watcher not running
✓ Activity change #218 by claude-code, 4m ago
! Index needs refresh
1 file changed since CodeLedger last observed this project. Answers
involving that file describe an earlier version of it.
Fix
codeledger refresh --changed
Recommended
1 codeledger refresh --changed
2 codeledger setup-agent claude-code
or codex, cursor, gemini, aider
Then verify
codeledger check --deep
Notes
CodeLedger can verify its own configuration and index, but cannot
observe whether an agent chose to query it.
Use `check --verbose` for the full list.
────────────────────────────────────────────────────────────────────────
2 actions recommended · 2 warnings · 0 problems
```
The verdict is `ACTIVE`, `DEGRADED`, `STALE`, `NOT_WORKING` or `NOT_INITIALIZED`, and the components behind it fail differently on purpose. **Install, index and freshness decide whether an answer can be believed** — an index that has fallen behind the working tree still answers every question, confidently, about code that has moved, so a failure there caps the verdict outright. **The protocol file and the MCP registration decide whether an agent ever asks** — a real failure and not the same one, so it caps at `DEGRADED`; a ledger that answers correctly on the command line is not "not working".
The registration check is the one that catches a working install nobody can reach. It reads the agent config files on this machine for the `codeledger` entry and compares the `--root` it binds to against this project:
```text
[XX] mcp registration BROKEN
registered for this project, but bound to /home/you/src/other-project
rather than /home/you/src/shop-web — it will answer about the wrong repository
```
That registration launches perfectly. It is worse than no registration, because it looks like it works.
**A registration another project owns is not this project's.** Claude keeps one server map per project it has been opened in, so one config file holds an entry for every repository you have worked in. Those are reported as context and never as a verdict about here — a project with no registration is `NOT_REGISTERED`, and the other projects' setups are left alone:
```text
[XX] mcp registration MISSING
no agent config on this machine registers CodeLedger for this project.
CodeLedger is registered for 3 other project(s) (shop-api, billing,
docs-site); that setup is theirs and is left alone
```
A config file that cannot be read is reported `UNKNOWN`, never "not registered".
Add `--deep` to launch the server and complete a real MCP handshake. `codeledger doctor` remains the full diagnosis.
In Claude Code, `/codeledger check` does the same thing — and the same command exists inside Gemini CLI and Cursor, all written into the project by `codeledger init`. For Codex, `codeledger setup-agent codex` installs a global `/codeledger` prompt (`~/.codex/prompts/codeledger.md`) that serves every project. Each routes `check`, `map`, `guard`, `value`, `done <task>`, and treats anything else as the task, passed to `brief` verbatim.
## What to do with a request
Before an agent edits anything, one call:
```bash
codeledger brief "fix the checkout total"
```
It answers `do_next` and gives the exact next command. Six answers, in the order they outrank each other:
| `do_next` | Means |
|---|---|
| `ASK_USER` | The repository cannot settle something the request leaves open. Questions and options are supplied; **ask, do not pick** |
| `RECONSIDER` | Previous attempts changed no symbol, or repeated themselves while verification kept failing |
| `RESUME` | This task has been worked on before — read `next_action` and `failed_attempts` first |
| `CONFIRM` | The target is settled; restate the task and have the restatement checked |
| `PROCEED_WITH_CARE` | Settled, and the footprint is worth reading first |
| `PROCEED` | Settled, and nothing measured argues against starting |
`brief` composes `resume`, `clarify`, `check` and `progress`. Each is still available on its own; the composition exists because the step that is four calls deep is the step that does not happen.
### And one call before saying it is finished
```bash
codeledger done "fix the checkout total" --agent claude-code
```
The mirror of `brief`, for the moment an agent is most inclined to cut corners. It refreshes and claims the work if files moved, then answers with an evidence-ranked verdict — `NOT_DONE`, `BROKE_SOMETHING`, `OUT_OF_SCOPE`, `UNVERIFIED`, or `DONE` — each below `DONE` carrying the exact commands that remain:
```text
◈ DONE add tax calculation
! UNVERIFIED
Why
• real symbols changed and nothing is recorded as verifying them —
"it looks right" is the claim this exists to replace
What remains
1 codeledger verify-run project project TEST -- <your test command>
```
Guard findings are split into what *this task* broke versus what was standing before it — somebody else's damage is context, never your verdict. `DONE` requires verification recorded after the last change, scope inside the task, and nothing witnessed broken; it is the strongest recorded answer, never proof of correctness. It reports, and never blocks.
### Fighting back on a vague request
```bash
codeledger clarify "fix the invoice rendering"
```
```text
CODELEDGER CLARIFY — ASK_USER
Request (unchanged): fix the invoice rendering
1 thing(s) about this request cannot be settled from the repository. Ask the
user before editing; do not pick one.
ASK — 3 areas of this repository match this request — src/admin, src/billing,
src/reporting. The request does not say which of them is intended.
why: 3 areas of this repository match the request and it does not say which is intended
- src/admin
- src/billing
- src/reporting
- all matching areas
- a path the user names instead
```
Two things it will not do.
**It does not rewrite the request.** The user's words come back verbatim, `request_rewritten` is `false`, and every reading of them is labelled a reading. A rewritten prompt hides which parts were the user's and which were inferred — and an inference recorded as a fact is the mechanism by which a wrong target survives every later check.
**It does not answer the question it raises.** Which area was meant is a fact about intent, and intent is not in the index. A blocking gap produces a question with options and never a quiet default. The options are listed in the order they were found, and none is marked as the likely one; naming a favourite would be choosing for the user with the appearance of evidence.
When the request *is* clear, the same command recommends — but only where the evidence prefers something:
```text
CODELEDGER CLARIFY — CONFIRM
Target: TARGET_RESOLVED — src/checkout
Files: src/checkout/total.py, src/checkout/cart.py
RECOMMENDED: extend the existing implementation (calculateTotal)
- 1 indexed symbol(s) already match this request
- calculateTotal in src/checkout/total.py
Options:
* extend the existing implementation (calculateTotal)
1 indexed symbol(s) already match this request
add a new implementation alongside it
correct when the existing code genuinely does something else — say which,
rather than proceeding silently
```
Where nothing measured prefers one option, none is marked and the choice stays the user's.
### Confirming the agent understood
The agent states what it thinks the task is, in its own words. CodeLedger checks that restatement against the request and the index:
```bash
codeledger clarify "fix the checkout total, keep the tax rounding" \
--understanding "I will change calculateTotal in src/checkout/total.py so the discount applies before tax"
```
```text
CODELEDGER CLARIFY — MISMATCH
The restatement does not match the request: 1 point(s) the request made are
absent from the restatement.
Points the request made that the restatement does not carry:
- preservation: keep the tax rounding
```
Three things can be checked and no more: whether the restatement carries every point the request made, whether it resolves to the same target, and whether everything it names exists here. An agent confidently naming a file this project does not have is the clearest sign it has understood a different codebase. `CONFIRMED` means the restatement is *consistent with* the request and supported by the index — never that the reading is right, which only the user can settle.
## The project map, without reading the project
```bash
codeledger map
codeledger map src/checkout
```
```text
CODELEDGER MAP — shop-web (the whole project)
412 file(s), 27 area(s), ~1,650 tokens, 0 files read
checkout
14 file(s), 186 symbol(s), coverage full
key: calculateTotal (9), applyDiscount (4), Cart (3)
src/checkout/total.py, src/checkout/cart.py, src/checkout/discount.py
Entry points: src/main.ts
INFERRED from filename convention (app, bootstrap, cli, entry, index, main,
server, start, __main__). A project naming its entry point anything else
appears here with none, which does not mean it has none.
Most depended on — measured: distinct files holding a recorded calls/uses/imports edge to the symbol, 2 or more:
calculateTotal 9 file(s) src/checkout/total.py
```
This is what an agent otherwise reconstructs by listing directories and opening files — thousands of tokens per session, for a worse answer, because a reader skimming for structure sees names and misses the dependency graph entirely. Key symbols are ordered by how many distinct files depend on them, which is measured. Entry points are a filename convention and say so. Every list is bounded and every bound travels with its total.
## Quick start
```bash
python -m codeledger.cli setup # start here: indexes or updates the project, then confirms it is usable
python -m codeledger.cli status
python -m codeledger.cli context "authentication timeout" --json
python -m codeledger.cli lookup authenticateUser
python -m codeledger.cli impact authenticateUser
python -m codeledger.cli refresh --changed
```
Useful safety/intelligence commands:
```bash
codeledger check # is CodeLedger working on this project?
codeledger map # the project's shape, without reading a file
codeledger brief "Add admin user activity tracking" # one call before starting
codeledger clarify "Add admin user activity tracking" # question it, or recommend an approach
codeledger prompt "Add admin user activity tracking, preserve permissions, and add tests"
codeledger plan "Add admin user activity tracking, preserve permissions, and add tests"
codeledger handshake "Add admin user activity tracking, preserve permissions, and add tests" --ai-plan "Update the admin user view, preserve permissions, and add tests"
codeledger tests --files src/admin/users.tsx --symbols UserList
codeledger features --infer
codeledger git-import
codeledger regressions
```
For large repositories, especially projects under `/mnt/c` in WSL, use the fast metadata pass first:
```bash
cd "/path/to/your-project"
codeledger init --quick --verbose
codeledger refresh --changed --verbose
```
`--quick` discovers source files, records size/mtime/hash metadata, and skips semantic parsing. The following `refresh --changed` parses only files that still need analysis. Normal refreshes use `os.scandir()` with directory pruning, avoid symlinks, skip non-source files and files larger than the configured limit, and reuse size/mtime metadata before hashing. Configure `source_extensions`, `max_file_size`, `ignores`, and `follow_symlinks` in `.ai/codeledger/config.json`.
### What an incremental refresh costs
`refresh` reports its own price, so a slow project can be diagnosed rather than guessed at:
```text
CODELEDGER REFRESH
Discovery 0.331s
Hashing 0.000s
Parsing 0.000s
Database 0.001s
Total 0.334s
Files checked 800 (parallel stat)
Files changed 0
Directories 19 visited, 2 pruned
Traversal full
```
Proving that nothing changed means asking every source file for its size and mtime — a directory's timestamp does not move when a file inside it is edited, so there is no cheaper way to be sure, and CodeLedger will not report "no changes" on a guess. What it avoids is the expensive part: nothing is read, hashed, or parsed unless its metadata moved, and ignored directories are pruned before they are entered.
That one `stat` per file is the whole cost, and its price varies enormously — roughly 2µs on a local Linux volume against 1ms across `/mnt/c`, where every call is a round-trip to the Windows filesystem driver. Those round-trips are issued in parallel when, and only when, a timed sample shows the volume is slow enough to be worth it; on a fast volume the thread pool would cost far more than the work.
Measured on 800 source files plus 4,000 ignored files, cold:
| | `/mnt/c` (WSL) | native Linux volume |
|---|---|---|
| no-op refresh | 0.68s | 0.017s |
| one-file refresh | 0.49s | 0.026s |
| 5,000 files, no-op | 3.8s | 0.15s |
**If your project can live on the Linux filesystem rather than under `/mnt/c`, put it there.** It is roughly 25× faster here, and that is a property of the WSL filesystem boundary, not of CodeLedger.
## Agent workflow
Start a session before an agent edits the project, then refresh afterward. Refresh only reparses changed files and automatically records the agent, session, changed files, and changed symbols.
```bash
SESSION=$(codeledger session start --agent codex --request "Fix login timeout" --json | python3 -c 'import json,sys; print(json.load(sys.stdin)["session_id"])')
codeledger context "login timeout"
# let the agent or developer make changes
codeledger refresh --changed --agent codex --session "$SESSION" --request "Fix login timeout"
codeledger session end --session-id "$SESSION"
codeledger changes
```
Known agents include `codex`, `claude-code`, `gemini`, `aider`, `cursor`, and `human`. Unknown names are retained as generic providers.
## Automatic agent integration
For the most automatic workflow, wrap the local agent command:
```bash
codeledger run --agent codex --request "Fix login timeout" -- codex
codeledger run --agent claude-code --request "Add search pagination" -- claude
```
The wrapper prints context before the agent starts, creates a session, runs the command in the project directory, refreshes changed files afterward, records a change, and returns the agent's exit code.
If an agent cannot be wrapped, use the polling watcher in another terminal:
```bash
codeledger watch --agent codex --interval 2
codeledger watch --once # one scan, then exit — for a git hook or a CI step
```
The watcher records external edits as they happen and attributes them to the selected agent/session. If no agent evidence is available, use `unknown`; CodeLedger never fabricates authorship.
Each poll walks the project tree, so idle polls back off geometrically from `--interval` toward `--max-interval` (default 30s) and reset to `--interval` as soon as a change is recorded. An active session stays responsive while an idle watcher stops re-walking a large tree every two seconds. Pass `--max-interval 0` to poll at a fixed rate.
The installed command is `codeledger` after `pip install -e .`. State lives in `.ai/codeledger/codeledger.db`; generated Markdown exports are derived views, never the source of truth. Secrets and common generated/dependency directories are ignored by default. Git is optional and used only when available.
## Design
The core is deterministic: filesystem inventory, SHA-256 hashes, Python AST parsing, conservative multi-language extraction, SQLite indexes, and optional Git evidence. The adapter boundary is intentionally small so Codex, Claude Code, MCP, CI, and other integrations can record sessions and changes without coupling the storage layer to a provider.
Current source and filesystem state outrank indexed memory. An unchanged file is not reparsed during `refresh --changed`; removed symbols are marked `deleted` and remain queryable. Unknown attribution is represented as `unknown`/`NOT RECORDED`.
File identity is the SHA-256 of the raw bytes, never of a lossily decoded string, so an edit confined to bytes that are not valid UTF-8 still registers as a change.
`impact` answers from the dependency index — call, use, and import edges recorded at parse time — rather than reading the working tree, and reports `"source": "index"`. When the index is known to be incomplete, `--scan` adds a full filesystem pass:
```bash
codeledger impact authenticateUser # indexed edges, bounded work
codeledger impact authenticateUser --scan # also reads every source file
```
See [Language support](#language-support) for what each language's analysis is actually based on.
Because that coverage is uneven, **absence of evidence is never reported as absence of impact**. If the index finds no dependents at all, `impact` reads the working tree before answering and reports `"source": "index + fallback scan"`. A query matching no indexed symbol returns `risk: UNKNOWN` with the reason, rather than a falsely reassuring `LOW`. Pass `fallback=False` through the API to keep a query strictly indexed.
## Language support
Install the grammars for full parse-tree analysis across languages:
```bash
pip install "code-ledger[languages]"
```
This adds `tree-sitter` and a bundled grammar pack (~3 MB, 370+ grammars, prebuilt wheels — no compiler, no network at runtime). Every language then gets the same treatment: real symbol ranges, qualified names, and call graphs.
| Tier | Analysis | Languages |
|---|---|---|
| `full` | parse tree — symbols, scopes, call graph | Python, JavaScript, TypeScript, JSX/TSX, Go, Rust, Zig, Java, Kotlin, Scala, C#, Swift, Dart, Ruby, PHP, Perl, Lua, R, Julia, C, C++, Objective-C, Elixir, Haskell, Bash, Solidity, Protobuf, SQL, Vue, Svelte — every extension in the grammar registry is scanned by default and verified by a per-language test |
| `full` (no install) | Python AST | Python |
| `shallow` | line patterns, imports only | CSS, SCSS and HTML (tracked for change and scope; no grammar exists), and every non-Python language when the grammar pack is absent |
Analysis is optional on purpose. `pip install code-ledger` stays dependency-free and keeps working; it simply reports reduced coverage instead of guessing. Every file records the provider and coverage tier that produced it, so the system can tell *"nothing depends on this"* apart from *"this language is not really analysed"* — and `impact` verifies against the working tree whenever coverage is `shallow`, rather than trusting a partial index.
```bash
codeledger status # includes analysis.shallow_languages and an install hint
```
Coverage is checked in, not asserted: `test_every_supported_language_yields_symbols_and_a_call_graph` builds a real file in nine languages and fails if symbols or the call graph are missing, and `LanguageDiscoveryConsistencyTests` writes one real file for every extension in the grammar registry, runs discovery end to end, and asserts the *specific* definitions each must yield — so a grammar that parses cleanly but returns nothing, or returns a keyword as a symbol, fails the build. A grammar that yields nothing on a file it could not parse degrades to `shallow` rather than reporting empty results as full coverage. Scanning follows the registry: if a grammar ships, its extension is in `source_extensions` by default, and a `config.json` still carrying an older release's default list follows the current one.
Installing or removing grammars upgrades an existing index in place — `files.analysis_version` records the provider, so the next `refresh --changed` reparses only what a different analyser would now read. No re-init, no migration command.
The design and its trade-offs are in [docs/LANGUAGE_SUPPORT.md](docs/LANGUAGE_SUPPORT.md).
## More than one agent on the same project
Claude Code and Codex can share one ledger. Set each up once, then let both query it:
```bash
codeledger setup-agent codex
codeledger setup-agent claude-code
```
The database is WAL-mode SQLite, so several agents and a watcher can read and write concurrently. Every coordination write runs inside `ledger.locked()` — `BEGIN IMMEDIATE`, one commit on exit, rollback on any exception, and a connection that refuses a nested commit or `BEGIN` rather than letting a helper release the lock early; independent processes serialise through SQLite's own write lock and a 30-second `busy_timeout`, and a process killed mid-write leaves the ledger usable with no repair (`docs/MULTI_AGENT.md`, *Transactions and locking*). At the start of a turn an agent asks what happened while it was not looking:
```bash
codeledger since --agent claude-code # since claude-code last recorded anything
codeledger since 42 # since change #42
codeledger since session-f8e17355e4 # since a session started
```
```text
2 change(s) by codex: 1 file(s), 2 symbol(s). 2 were made by another agent.
#8 by codex ['src/auth/session.py'] symbols=['login', 'logout'] effect=symbols-changed
#7 by codex ['src/auth/session.py'] symbols=['login'] effect=symbols-changed
```
Agents reach the same thing through MCP as `codeledger_get_changes_since`.
When two agents are live, CodeLedger warns before they collide. Editing the same *symbol* is a stronger signal than merely touching the same file, and it is graded accordingly:
```text
POTENTIAL CONFLICT: claude-code also changed the same symbol(s): login.
Re-read those before editing so the two agents do not undo each other.
```
### Attribution is graded, not asserted
The filesystem records that a file changed. It does not record which process changed it, and no amount of watching recovers that. So every change stores how well its authorship is actually known:
| Confidence | When | Recorded as |
|---|---|---|
| `HIGH` | The agent called `refresh` itself — it is reporting its own work | that agent |
| `MEDIUM` | A change entered by hand via `record` | the name given |
| `LOW` | The watcher observed an edit | `unknown`, with the live agents named as context |
| `UNKNOWN` | A refresh was recorded with no agent name | `unknown` |
The watcher never credits the name it was launched with, even when that agent is the only one running. `watch --agent codex` says who started the watcher, not who wrote the file — a developer, an editor or a formatter produces an identical filesystem event. If you want per-symbol authorship, have each agent call `refresh` itself; that is what the protocol tells them to do.
For the sharpest record with two agents: run the watcher for continuous safety, and have each agent refresh on its own behalf when it finishes a task.
### When a session dies without saying goodbye
A watcher is an ordinary foreground process. Closing WSL, closing the IDE, a crash or `kill -9` all end it without any chance to clean up — no signal handler can cover the last two. So liveness is decided from evidence rather than from the row still saying `active`: each session records a PID, a host and a heartbeat, and any command that reports who is working reconciles them first.
```bash
codeledger session list
```
```text
ACTIVE:
claude-code session-8ec7d4321e (pid=unrecorded, last activity 2026-08-09T06:15:13+00:00)
CRASHED:
codex session-1a380d901c (pid=999123) — process 999123 is no longer running on this host
```
A dead PID is conclusive. A *live* PID is not, because PIDs are recycled — so a session whose heartbeat has gone quiet past `session_stale_seconds` is retired regardless. An agent may legitimately think for several minutes, so idleness is not death: `IDLE` still counts as live, only `STALE` stops counting. Nothing is ever deleted; a retired session keeps its history and gains a reason. `codeledger doctor` reports any that are left over, and `codeledger session reconcile` retires them on demand.
## Did the change actually do anything?
The most expensive failure in agent-assisted work is the silent loop: you prompt, the agent edits, nothing changes, you prompt again. The agent has no memory of the last attempt, so it re-reads the repository and often tries the same thing — spending tokens to rediscover what already failed.
Every refresh now reports what an edit actually achieved:
| `effect` | Meaning |
|---|---|
| `symbols-changed` | real code changed |
| `text-only` | files changed but no symbol did — formatting, comments, or an edit that missed |
| `none` | nothing changed at all |
A file rewritten with identical content never counts. `codeledger run` says so directly rather than burying it:
```text
[CODELEDGER] NO EFFECT: this attempt changed 1 file(s) but no symbol.
[CODELEDGER] Run `codeledger progress 'Fix the total calculation rounding'` before retrying.
```
Before retrying a task that did not work, ask what previous attempts did — one cheap query instead of re-reading the codebase:
```bash
codeledger progress "Fix the total calculation rounding"
```
```text
status: REPEATING
guidance: 3 attempts have edited calculate_total and verification still fails.
Editing the same symbol again is unlikely to help. Re-read the failure
output, widen the search with `codeledger impact <symbol>`, or ask the
user whether the request describes the real problem.
```
The four verdicts are `NO_EFFECT` (attempts changed no symbol — the edits are not reaching the code that runs), `REPEATING` (same symbols edited repeatedly, verification still failing), `UNVERIFIED` (real changes, no evidence recorded), and `VERIFIED` (verification passed after the last attempt — stop editing). Agents reach it through MCP as `codeledger_get_progress`, and the protocol written into `CLAUDE.md`/`AGENTS.md`/`CODEX.md` tells them to call it before a retry.
Note what this does *not* do: it never claims the user's prompt was wrong. It reports what the attempts changed and whether verification passed, and where the evidence points at the request itself, it says to ask you.
## Attribution
Every file and symbol records the agent and session that last changed it, and `why` links a symbol to the request behind it:
```bash
codeledger why formatName
```
```text
answer: Last recorded request touching this symbol: Uppercase the formatted name
attribution: formatName src/admin/users.tsx last_modified_by=claude-code session=sess-42
```
Credit is assigned only to symbols whose content actually changed. A symbol that merely shifted lines because of an edit elsewhere in the same file keeps its previous author and `updated_at` — a refresh never reassigns authorship for work nobody did. Symbols changed outside a recorded session are attributed to `unknown`, never guessed.
## Issues, decisions, and verification
```bash
codeledger issue AUTH-42 "Refresh token expiry edge case" --severity HIGH
codeledger decision ADR-1 "Keep authentication centralized" --rationale "Avoid duplicate services"
codeledger verify symbol authenticateUser TEST PASSED --evidence "python -m unittest tests/test_auth.py"
codeledger issues
codeledger decisions
```
These records are local SQLite data and are surfaced automatically by `context`.
## MCP
CodeLedger includes a local stdio MCP server. Configure an MCP-capable agent to launch:
```bash
codeledger mcp --root /path/to/project
```
Available tools include context retrieval, symbol lookup, impact analysis, history, issues, decisions, session checkpoints, resume, and incremental refresh. The server never sends source code over the network.
The MCP server starts a session when the agent connects and ends it when the agent disconnects, so checkpoints, heartbeats, and conflict detection work without the user running anything.
## Continuing work across sessions
A conversation is temporary. When it approaches the model's context limit, the first thing compressed away is usually the most expensive to recover: which approaches were already tried and failed. The next session then re-reads the repository, rediscovers the state, and repeats the failed attempt — because nothing recorded that it failed.
A checkpoint moves that knowledge out of the conversation and into the project:
```bash
codeledger resume "Fix authentication timeout"
```
```text
CODELEDGER SESSION RESUME
Previous objective:
Fix authentication timeout
Recorded by:
claude-code (provider anthropic, model UNKNOWN), confidence HIGH
Completed:
- identified the timeout source
Unresolved:
- production verification
Failed approaches:
- raising the client-side timeout did nothing
Recommended next action:
run production verification
Estimated context: 313 tokens
Files avoided: 1,284 of 1,285 — repository-wide scan NOT REQUIRED
```
Three things make this trustworthy rather than merely convenient:
**Selection is by task, not by recency.** A checkpoint about dashboard CSS is not loaded for a payments task. When nothing matches, CodeLedger says `NO_RELEVANT_CHECKPOINT` and lists the open goals instead of promoting an unrelated one — unrelated context is worse than none.
**A checkpoint never outranks the source.** It is an AI summary, the lowest rank in the ordering below. Every file and symbol it names is re-checked against the source at resume time; anything that no longer holds is dropped from the body and reported under `stale_items` with the reason.
**CodeLedger does not write the summary itself.** It cannot see the conversation, so it assembles what it observed — changes, files, symbols, verifications — and the agent supplies the goal, the rationale, the failed attempts, and the next action. A summary invented from a file list would read exactly as confident as one an agent actually wrote. When a session ends without a checkpoint, the mechanical fallback records only what was observed, marks itself LOW confidence, and says `NOT RECORDED` where the next action should be.
Agents following the protocol file do this on their own. The manual commands exist for inspection:
```bash
codeledger checkpoint list # what has been recorded
codeledger checkpoint state # what this session would checkpoint
codeledger checkpoint create --goal "..." --next-action "..."
```
## What worked before must still work after
The most expensive report a user can file is *"something that used to work is gone."* Every refresh that deletes a symbol now answers whether that deletion broke anything the index can witness — at the moment the agent can still cheaply undo it:
```text
✕ helper was removed and active code still references it
2 active reference(s) still name it: caller (b.py), (module level) (b.py)
Fix
codeledger restore-info helper
```
Three kinds of loss, in order of how directly they predict breakage: a removed symbol that live code still references (the dangling callers are **named**, from recorded dependency edges); a removal that invalidated a recorded `PASSED` verification; and a verification that went from `PASSED` to `FAILED`. `codeledger guard` is the whole-index sweep.
What it will not do is cry wolf. A symbol deleted in one file and recreated in another was *moved*, and is never reported. An unreferenced, unverified deletion is ordinary work and raises nothing. And every finding carries options — restore, update the callers, or confirm the removal was meant — rather than a verdict, because which is right is a question about intent. `CLEAN` means no *recorded* evidence of loss, never that the change is safe: a reference built at runtime is invisible to a static index, and the guard says so every time.
## Seeing what it does for you
```bash
codeledger value
```
```text
◈ VALUE shop-web · recording since 3d ago
Memory held for your agents
Files indexed 412
Symbols 3,908 active
Preserved deletions 214
Dependency edges 11,032
Times it said something before it got expensive
Guard findings 7
Conflicts warned 3
Scope warnings 5
No-effect attempts 4
Regressions detected 1
```
Every intervention — a conflict warned, a scope violation flagged, a guard finding raised — is persisted as it happens, so the report is counted from rows rather than asserted. And it closes with what the numbers deliberately do not claim: warnings counted were *raised*, not necessarily heeded, and the most valuable outcomes — the duplicate not built, the wrong-target edit not made — leave no record precisely when the tool works. These numbers are the floor of the value, not the measure of it.
## Before writing new code
`plan` answers two questions the source alone does not: what a change would
reach, and whether the project already does this.
```bash
codeledger plan "Remove the theme color"
```
```text
Risk: HIGH
SHARED DEPENDENCY — a change here reaches 6 file(s) across 6 area(s) [HIGH]
ThemeProvider (src/theme/ThemeProvider.tsx)
areas: Dashboard, Landing, Orders, Payment, Queue, SharedDrawer
- defined under a shared location (src/theme)
- named like shared infrastructure (ThemeProvider)
SCOPE AMBIGUITY
'ThemeProvider' is shared: it affects Dashboard, Landing, Orders, Payment,
Queue, SharedDrawer. The request does not say which of those it applies to.
```
The dependency graph always knew this; `plan` used to report only the file the
symbol was *defined* in. It now reports the blast radius, from indexed queries
only — planning never scans the working tree.
**Scope ambiguity is judged on five signals**, not just "more than one file":
how many areas depend on the symbol, whether it is shared by design (location,
name, and measured spread — conventions can lie, the measurement cannot), the
size of the radius, whether the request already names a scope, and the intent.
A request that names its scope is never questioned. Adding to a shared module
is not ambiguous, because it changes nothing for existing dependents. A plain
helper used by two areas is not ambiguous either — warning there is how a guard
teaches agents to ignore it.
**Absence of evidence is never reported as safety.** If the files behind an
answer are analysed shallowly, confidence drops to `LOW` and a caveat says the
dependency graph is incomplete. A small dependent list is unproven, not proof
that a change is contained.
### Reusing what already exists
`handshake` compares the plan an agent proposes against what the project has:
```bash
codeledger handshake "Make this new button open the same kind of panel" \
--ai-plan "I will create a new CheckoutFlyout with its own slide animation and open/close state."
```
```text
POSSIBLE DUPLICATE IMPLEMENTATION
Plan creates: CheckoutFlyout
Existing implementation to inspect or reuse:
OrderPanel src/components/OrderPanel.tsx (matches the request)
SharedDrawer src/components/SharedDrawer.tsx (used by the existing implementation)
useDrawerState src/state/drawerState.ts (used by the existing implementation)
```
It reads the dependency edges forwards one hop, so it names the whole existing
flow rather than only its entry point — the shared drawer and shared state
underneath are what reuse actually means. It recommends and never rejects: a new
implementation is sometimes correct, and when it is, the agent should say why.
### Agent, provider, and model
These are three separate facts and are recorded separately. An agent name says which program is running, not which model it is driving today, so the model is stored only when the runtime actually reports it and is `UNKNOWN` otherwise — never inferred from the agent name. An agent CodeLedger does not recognise is recorded as itself with a `generic` provider. Nothing in CodeLedger branches on which vendor an agent belongs to.
If a runtime reports its context usage, pass `context_window` and `context_used` and CodeLedger will recommend a checkpoint past a configurable threshold (default 80%). Most runtimes report neither; the feature works identically without them, and CodeLedger never interrupts an agent mid-task.
For Codex, initialize the integration once from the project directory:
```bash
codeledger setup-codex
```
Then start a new Codex session and leave the watcher running in a second terminal:
```bash
codeledger watch --agent codex
```
Codex can query CodeLedger during the same conversation through MCP, while the watcher records edits continuously. This removes the need to exit Codex or manually run `status`/`changes` after every task. Restarting Codex after MCP setup is required because an already-running client does not gain new tools dynamically.
## Scope guard
Every task-aware refresh now produces a conservative scope result:
```text
SAFE changed files fit the known task boundary
WARNING unrelated files or symbols changed; review the diff
UNKNOWN CodeLedger lacks enough context to define a safe boundary
```
The wrapper and watcher display warnings automatically. You can also check a proposed diff directly:
```bash
codeledger scope "Update authentication" --files src/auth/service.py src/auth/session.py --symbols authenticateUser refreshSession
```
Scope warnings are advisory, not destructive blocking. A new file is allowed only in a directory that already contains a task-relevant file — a sibling directory such as `src/billing/` is not covered by a match in `src/auth/` — while ambiguous tasks remain `UNKNOWN` instead of being falsely marked safe.
The boundary is drawn from indexed symbols matching the request and from any paths written into the request itself. When neither exists, request keywords are matched against file paths so that a task whose wording happens not to match a symbol name still gets a judgement rather than `UNKNOWN`. Every result reports `boundary_evidence` naming which of these was used, and keyword matches are labelled weak evidence.
## Safety loop
Use pre-change planning and evidence-backed verification:
```bash
codeledger plan "Add admin user activity cards"
codeledger verify-run project project TYPECHECK -- npm run typecheck
codeledger regressions
```
The plan reports existing implementations, affected files, risk, known issues, decisions, and suggested tests. `verify-run` executes a local command without a shell, stores its output and pass/fail result, and `regressions` identifies subjects that previously passed and later failed. These results are also available to Codex through MCP.
## Prompt understanding
CodeLedger also creates a deterministic task brief before the agent edits code:
```bash
codeledger prompt "Add a secure admin view showing newly registered users, preserve existing permissions, and add tests"
```
The brief extracts intent, likely project areas, paths, constraints, acceptance criteria, risk, and clarifying questions. It does not invent requirements or call an external AI service. The structured brief is included automatically in `context`, `plan`, and MCP responses so agents receive a clearer, project-aware task.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues