Skip to content

Latest commit

 

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CodeBrain

Repositories hold code. They don't hold understanding.

Every AI coding agent that touches a repo starts amnesiac: it greps, opens a few files, builds a fragile mental model in its context window, acts on it, and throws it away. The next agent pays the full cost again and reaches a slightly different conclusion. Understanding gets re-derived thousands of times, inconsistently, and never accumulates anywhere.

CodeBrain compiles a repository into a Brain — a durable, versioned, provenance-carrying model of one software system, stored beside the code and kept true on every commit — so any agent or engineer starting any lifecycle task begins informed instead of blind.

Full blueprint — vision, conceptual design, Claude Code integration, roadmap: docs/vision.html


Status

P6 — Cortex. Federation across repos: compose several already-built Brains at query time — composed, never re-extracted — and answer cross-repo impact queries with evidence. The vision's long-run gate for this phase (type-change propagation) is not met; what shipped is scoped honestly below.

Phase Scope State
P0 Schema, provenance envelope, store, diff, plugin contract ✅ done
P1 Deterministic core — L0/L1/L4/L5 extractors, Atlas ✅ done
P2 Context packs, MCP server, hooks, thin L6, eval harness ⚠️ shipped; retrieval gate mostly parity (2/3 repos), one clear outlier
P3 Verification by execution, carry-forward, sync, drift gate ✅ done
P4 L2 behavior, L3 semantics, full L6, rigorous eval ✅ done
P5 L7 memory, write-back, disputes, decay ✅ done; gate met on a small sample
P5.5 Session instrumentation — trials, arms, scoring ✅ built; mechanics proven on one real session (n=1)
P6 Cortex — federation, cross-repo impact ✅ shipped; type-change propagation not attempted

Measured, not asserted

Cold build. 331,000 lines of Python (the CPython standard library) → 234,268 records in 24.5s, about 7.4s per 100k LOC against a 60s gate.

Retrieval — parity is the norm, with one outlier. codebrain eval generates its own benchmark from git history: each past commit is a task whose correct answer is the files it changed. Packs are compared against keyword search over the same repository, at identical k.

Run leak-free, with a Brain per case built from that commit's parent so nothing in it can know the answer:

Repository Files Cases Pack recall@k Search recall@k Delta Better / same / worse
django 2,928 .py 20 35.0% 35.0% ±0.0% 0 / 20 / 0
a private TypeScript/Next.js app (1) ~900 .ts/.tsx 30 29.8% 29.0% +0.8% 2 / 27 / 1
a private TypeScript/Next.js app (2) ~490 .ts/.tsx 30 51.8% 49.2% +2.6% 4 / 23 / 3
a private multi-language desktop app ~190, mixed Rust/JS/Python 30 90.1% 82.8% +7.4% 7 / 22 / 1

All four runs are leak-free, and all four are genuinely unfamiliar repositories the pack has no prior exposure to. Three of four land within a few points of parity — none of those deltas clear enough wins/losses at n=30 to call them more than noise around zero. Only one repository shows a real, repeatable lift. The read stands as it did at three repos: parity is the norm on file retrieval, and one repository so far is a genuine outlier — not a pattern this project can yet explain, and not a claim it makes about which repos will land where.

The fast mode — one Brain built from HEAD — reports +7.3% on django, and most of that gap is leakage. The mechanism is specific: L4 co-change coupling is computed over history that includes the commit being tested, so the precedent facet learns "these files change together" from the very commit it is being asked about. Both arms read the post-change tree, but only the pack gets that extra hint.

What the benchmark does not measure, on either repo, is everything the pack carries beyond its anchors: the contracts, the constraints, the verified runbook, the blast radius, the declared unknowns. Those are five of the six facets and they are the reason the pack exists — but "did it name the right files" cannot see them. Even at parity on file retrieval, those facets plus a budget-bounded pack may be worth it; this harness cannot demonstrate that, and pretending otherwise would be exactly the kind of confident unverified claim the whole project is built to avoid.

Measuring the other facets needs agents completing real tasks.

Write-back. codebrain eval --memory measures the same Brain with and without a previous session in memory, so the delta is the value of write-back and nothing else:

Repository Cases With memory Without Delta Better / same / worse
django 6 38.9% 16.7% +22.2% 4 / 2 / 0
requests 13 84.6% 84.6% ±0.0% 0 / 13 / 0
a private TypeScript/Next.js app 10 70.4% 28.7% +41.7% 9 / 1 / 0

Memory pays where retrieval is hard and does nothing where it is already easy. On requests cold recall is already 84.6%, at its ceiling, and no mechanism could move it. Where cold recall is lower — django at 16.7%, the third repo at 28.7% — memory shows a real lift, and the largest effect landed on the repo with the most disambiguation to offer help with: a Next.js App Router codebase where 241 different files are all literally named route.ts, distinguished only by their full path. A prior session recording which exact route.ts it touched is information keyword search structurally cannot replicate — checked directly on one case: the earlier session and the later task touched the exact same two files, and memory correctly re-surfaced the ambiguous one that plain matching could not tell apart from 240 others sharing its name.

Three caveats, stated because the headline numbers are favourable:

Small samples. Six, thirteen, and ten cases. Suggestive, not conclusive.

The harness selects for the favourable case. It only pairs commits whose changed files overlap, so memory always points at files that are part of the answer. Real prior sessions frequently touch unrelated code. Read these deltas as an upper bound on a related task, not as an average over sessions — and this caveat bites hardest on the third repo, whose fast, iterative commit history produced more closely-related consecutive pairs than either of the other two.

What would settle it is traces of two agents working the same problem in sequence — a commit is the end of a session, not a handoff mid-task, so git history cannot supply them. That needs real sessions instrumented.

The knob that would inflate this further — loosening the task-overlap function until unrelated commits boost each other — is the thing not to do.

codebrain eval --cases 60              # fast, leaky, good for iteration
codebrain eval --cases 20 --rigorous   # slow, leak-free, the number to quote

Federation — Cortex

A Brain answers questions about one repository. Real systems are systems of systems, and no single Brain can answer "which other services call this route?" The vision's long-run target for this phase is full type-change propagation — "this field's type changed, enumerate every consumer across every repo" — and that needs schema/type extraction this project does not have. This phase does not claim to meet it.

What is buildable without inventing facts: compose several already-built Brains at query time and search each member's own source for evidence of a cross-repo reference.

# .codebrain-cortex.toml
[[member]]
name = "payments-api"
root = "../payments-api"

[[member]]
name = "billing-worker"
root = "../billing-worker"
codebrain cortex roster
codebrain cortex impact "POST /v1/charges"
POST /v1/charges  -  defined in payments-api (route)

billing-worker - 1 reference(s)
  worker.py:41  requests.post(BASE + '/v1/charges', json={"amount": amount})

Scanned: billing-worker

This is textual matching over each member's own source, not a
resolved import graph or a type-checked call -- a hit is evidence
to go look at, not proof of a real dependency.

Composed, never re-extracted is the rule the whole phase turns on. Cortex never writes to a member Brain and never merges them into a new on-disk artifact — it reads whatever codebrain build last left in each .brain/ directory. A member that has drifted is caught by its own drift gate; Cortex is not a second copy of that job. A member Brain that fails to load is kept in the roster with its error rather than silently dropped, because a Cortex that quietly ignores a broken member gives a confidently incomplete answer — worse than one that admits the gap.

Measuring what the git benchmark cannot see

Both results above come from one metric — did it name the right files — which is the single facet keyword search also covers, and which saturates on small repositories. It cannot see whether the constraints stopped a bad edit, whether the verified runbook saved a guess, or whether the blast radius prevented a regression. Those only appear when an agent actually does the work.

codebrain trial records real sessions and scores them:

codebrain trial start --task "add rate limiting" --verify   # assigns an arm
# ... the agent works; hooks record edits and commands ...
codebrain trial end --verify                                # decides the outcome
codebrain trial report
Trial pack-vs-control — 2 recorded session(s)

                           with pack     control
  sessions                         1           1
  task success                  100%          0%
  broke the build                 0%          0%
  mean files edited              1.0         2.0
  mean pack tokens              1023           —

  NOT YET A RESULT — fewer than 10 decided sessions per arm.
  The rates above are printed for inspection, not for quoting.

Three rules it is built on:

CodeBrain does not judge its own trial. Success is the repository's own test command, run before and after the session. A harness scored by the system under test will always find that the system under test is doing well.

Arms are assigned before the work, deterministically — a hash of trial name and session id, fixed at start. Deciding afterwards which sessions count is how honest people produce dishonest numbers.

A delta without an n is not a result. Below ten decided sessions per arm the report says so and declines to present the comparison as a finding. A session whose verification never ran is unknown and is excluded from every rate rather than being quietly counted as a failure.

This collects; it produces nothing until real sessions run through it. That is the honest cost of measuring the thing that actually matters.


Install

Requires Python 3.11+. No third-party dependencies — the deterministic core must build a Brain offline, on a locked-down machine, with no API key.

pip install -e .

Quickstart

codebrain build && codebrain status
Brain built at .brain
  1656 records · 457 nodes · 1071 edges · 128 facts
  atlas:     .brain\ATLAS.md
  providers: census, gitmeta, history, structure-py, operations

  layer                         records
    L0 corpus       █·····················     40
    L1 structure    ████████████████████··   1495
    L4 intent       ██····················    118
    L5 operations   █·····················      3

  method        DERIVED 304   EXTRACTED 1352
  status        fresh 1655   unverified 1

Other commands:

codebrain atlas --out -                  # the human-readable Atlas, to stdout
codebrain providers                      # what can extract from this repo
codebrain validate                       # structural problems, non-zero on failure
codebrain diff old-brain new-brain --check   # compare two Brains directly

Every command takes the Brain path positionally or as --brain.

What it extracts today

Provider Layer Reads Produces
census L0 filesystem files, sizes, language mix, primary language
gitmeta L0 · L4 git head, branch, remote, authors, commit count
history L4 git log --numstat churn, hotspots, ownership, co-change coupling
structure-py L1 Python AST modules, symbols, imports, resolved call graph
structure-ts L1 TS/JS scanner modules, declarations, import graph, same-file call graph
structure-rs L1 Rust scanner modules, symbols (fn/struct/enum/trait/impl), mod graph, same-file call graph
operations L5 manifests, Cargo.toml, Make, CI, Docker build/test/run commands, pipelines, CODEOWNERS
behavior L2 AST decorators/urlpatterns, file-based routes, imports routes (Flask/FastAPI, Django, Next.js App Router, express), entrypoints, jobs, env vars, data stores
semantics L3 the Brain itself bounded-context candidates, ubiquitous language, entities
constraints L6 the Brain + .codebrain.toml reviewers, danger zones, public contracts, untested churn, policy zones

Two extractors overlap on purpose. census guesses the repo name from the directory (DERIVED 0.5); gitmeta reads it from the remote (EXTRACTED 0.98). The envelope settles it — a real disagreement resolved by evidence quality rather than by whichever provider ran last.

Using it from an agent

codebrain pack "add rate limiting to the payments API"
CONTEXT PACK · task: add rate limiting to the payments API · 1653/6000 tokens · brain @f8ed2b1b

ANCHORS       payments/api.py:44  charge_endpoint (function)
BLAST RADIUS  payments/middleware.py:12  chain  (direct) [EXTRACTED]
              + 43 test symbol(s) across 4 test file(s) also depend on this
CONTRACTS     payments/api.py:44  charge_endpoint — 4 external caller(s)
PRECEDENT     payments/settle.py changes with payments/api.py (7 shared commits, 70%)
CONSTRAINTS   payments/api.py needs review from @risk-eng [DERIVED 0.80]
RUNBOOK       test   make test  [DERIVED 0.80]  (never executed)
UNKNOWNS      TS/JS has no call graph — blast radius across TS call sites is incomplete

Six facets, because six kinds of ignorance cause six kinds of failure: touching the wrong code, breaking callers you did not know existed, changing a shape someone depends on, inventing a pattern the repo already has, violating a constraint, being unable to check your own work, and guessing confidently.

Claude Code

.mcp.json exposes the Brain as an MCP server with brain_pack, brain_locate, brain_explain, brain_impact, brain_runbook and brain_constraints. Five hooks push context in without the model having to ask:

Hook Command Does
SessionStart codebrain brief ~400-token orientation
UserPromptSubmit codebrain pack --stdin compiles a pack from the task, before the first action
PreToolUse codebrain guard checks the pending edit against L6
PostToolUse codebrain touch marks the edited neighbourhood stale
Stop codebrain learn (P5)

Every hook fails open. If .brain/ is missing, stale or corrupt, hooks print nothing, exit 0, and the session behaves exactly as it would without CodeBrain. A Brain that can break someone's session gets uninstalled the first time it does — so this is a tested contract, not an aspiration.

guard warns by default and never blocks. Denying an edit on inferred evidence would get the hook removed; hard gates wait for real compliance zones in P4, and are opt-in via --deny-guarded until then.

Semantics without a language model

L3 is the layer people expect an LLM to write, and the temptation is to have one narrate the domain and call the result knowledge. That would be expensive on every build and would produce confident prose nobody can check.

So the part that is derivable is derived. Bounded contexts come from import cohesion — a directory whose modules import each other far more than they import outward is a boundary, whatever anyone calls it. Ubiquitous language comes from the vocabulary the code actually uses, weighted by how widely each term is shared. On this repository that yields extract, provider, applies, brain, pack, report, constraints — which is, in fact, what this codebase is about.

What is left — business rules, entity relationships, why a boundary sits where it does — needs a model, and the provider says so in a semantics_coverage_gap fact rather than leaving the absence to be mistaken for "this repository has no domain". An LLM-backed provider can register alongside it; its claims will be INFERRED, ranked below these, and overridden by any human who disagrees.

Declared policy

Everything in L6 is inferred from evidence except one thing. A team can state a constraint outright in .codebrain.toml:

[[zone]]
name = "pci"
paths = ["payments/", "src/billing/"]
reason = "PCI scope — cardholder data"
requires = ["@risk-eng"]
block_agents = true

These are ASSERTED, so they outrank anything the machinery derives, and they are the only constraint permitted to stop an agent. Inferred constraints — churn, bus factor, missing tests — warn and never block, because denying an edit on a guess gets the hook uninstalled by lunchtime.

Memory — the only layer that accumulates

Everything below L7 is re-derived from the code on every build. L7 is what a session leaves behind.

codebrain remember "the limiter store is Redis, not in-process" --about payments/api.py
codebrain learn --session s1 --task "add rate limiting" --lesson "..." --outcome success

It comes back in the next relevant pack:

MEMORY
  a previous session on "fix carry-forward of verified claims" succeeded,
  touching codebrain/verify.py  [EXTRACTED]
  learned: carry_forward must match refuted claims on status, not method  [INFERRED 0.60]
  learned: the envelope must never be mutated during verification  [ASSERTED]

Three rules keep it honest:

An agent's claim is not a human's claim. What an agent did — the files it edited — is EXTRACTED, a fact about the session. What it concluded is INFERRED, a reading. Only a person gets ASSERTED.

An agent may dispute, not overrule. If extraction says one thing and an agent says another, the agent files a dispute that packs surface under UNKNOWNS; the claim itself is untouched. A model that can demote an AST fact by asserting harder can poison every session downstream, and it will do so confidently. A human can overrule, with --human.

Memory ages. A lesson from four hundred commits ago is about a different codebase, so it fades on a half-life — computed at read time from the distance between then and now, never by mutating stored confidence, which would make two reads of the same Brain disagree.

Verification — claims are executable

"The test command is make test" is a hypothesis until something runs it.

codebrain verify

That is a dry run. It prints exactly what it would execute and stops:

Dry run — nothing was executed. These commands came from this repository's
own manifests and would run as-is:

  test   python -m unittest discover -s tests -t .   (from pyproject.toml)

Read them, then re-run with --yes to execute and settle the claims.
codebrain verify --yes
  ok   test   python -m unittest discover -s tests -t .
       passed in 47.4s

  1 claim(s) promoted to OBSERVED · 0 refuted

The claim is now OBSERVED with the exit code, duration and command recorded as evidence. A failing command is refuted, not deleted — it keeps its place with the reason attached, scores 0.0 confidence, can never reach a context pack, and packs say "EXECUTED AND FAILED — do not rely on this" rather than sending the next agent to run something already known to be broken.

Safety. verify executes commands that came out of a repository, which on an untrusted repo is arbitrary code execution. So it never runs from a hook or from build; it is a dry run by default and needs an explicit --yes with the command list already on screen; only commands CodeBrain itself extracted are candidates; servers are excluded by construction (npm start never returns, and a verifier that hangs is worse than one that never ran); and every run is bounded by a timeout.

Staying true — sync and the drift gate

codebrain sync

Rebuilds when something moved, and carries verified and asserted claims forward. This is load-bearing: extraction is deterministic, so a rebuild reproduces every EXTRACTED and DERIVED claim exactly — but it would also overwrite the two kinds it cannot regenerate, what execution proved and what a human stated. Without carry-forward, every build silently erases every verification and P3 would be pointless by the next commit.

A verification only travels if the claim it proved is unchanged. Verified make test, and the command is now pytest? The evidence is about a different claim and is invalidated rather than carried — that would be the Brain lying with a real receipt attached.

codebrain drift --check

Rebuilds into memory, compares against the committed Brain, writes nothing, and exits non-zero when they disagree. That is the CI gate. A stale Brain misleads every agent downstream, which is worse than having no Brain at all.

DRIFT: the committed Brain no longer describes the code.
  +2 -0 ~3 records

  + L1:symbol:codebrain/gitutil.py#drift_probe
  ~ L1:fact:|python_summary  (value)

  Run `codebrain sync` and commit the result.

The Atlas

codebrain build also writes .brain/ATLAS.md: the onboarding document that is always true because it is generated, not maintained. What this repo is, how to run it, the most depended-upon modules, where the risk sits (hotspots, single-author files, coupled files), who to ask — and a closing section stating what the Brain does not know, because a document that hides its gaps gets trusted exactly where it is weakest.


The model

A Brain is eight layers of three record types, each carrying one envelope.

Layer Answers
L0 corpus what is this thing, and what is in it
L1 structure what exists and how is it wired
L2 behavior what it does when it runs
L3 semantics what it means in the business
L4 intent why it is like this
L5 operations how to build, test, run, ship it
L6 constraints what must not break
L7 memory what we've learned since

Nodes are things that exist. Edges are typed relationships. Facts are claims that aren't graph-shaped — and are the unit of verification: "the test command is make test" is a hypothesis until CodeBrain runs it.

Every record carries a provenance envelope:

Envelope.make(
    Method.EXTRACTED,          # EXTRACTED | DERIVED | INFERRED | OBSERVED | ASSERTED
    source="census",           # which provider said so
    as_of="d3f91d2",           # the commit it was true at
    evidence=(Evidence(path="src/api.py", start_line=44),),
)

This is the honesty model, and it is load-bearing. An agent can demand EXTRACTED-only facts before a risky refactor. Confidence decays with status — a refuted claim scores 0.0 and can never reach a context pack. A Brain that confabulates silently is worse than no Brain.

Conflicts between providers are settled by the envelope, not by whoever ran last, and the ordering is total — so two builds of the same inputs produce byte-identical Brains, and the drift gate never fires on noise.

On-disk format

.brain/
  manifest.json        schema version, as_of commit, providers that ran
  layers/l0.jsonl …    one record per line, sorted by id
  .gitignore           derived indexes only — never the JSONL

JSONL sorted by readable id, because the Brain is committed to the repository it describes and reviewed on pull requests. Ids look like L1:symbol:payments/api.py#charge_endpoint, not a3f9c2e1 — a reviewer has to be able to read the diff.

Writing an extractor

from codebrain.providers import Provider, register
from codebrain.model import Fact, Layer, REPO

class MyProvider(Provider):
    id = "mine"
    layers = (Layer.L5,)

    def applies(self, ctx):
        return (ctx.root / "Makefile").is_file()

    def extract(self, ctx):
        yield Fact(layer=Layer.L5, subject=REPO, predicate="test_command",
                   value="make test", env=...)

register(MyProvider())

Two rules: declare the layers you write, and return False from applies when you can't run. A provider that can't run says so — it does not emit low-confidence guesses to look busy. A provider that raises is recorded and skipped, never fatal: a partial Brain beats no Brain.

Development

python -m unittest discover -s tests -t .

556 tests, no external test runner required.


License

MIT © EnterpriseX Platform

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages