galaxy-brain

agent
Guvenlik Denetimi
Uyari
Health Uyari
  • License — License: Apache-2.0
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 5 GitHub stars
Code Gecti
  • Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Gecti
  • Permissions — No dangerous permissions requested

Bu listing icin henuz AI raporu yok.

SUMMARY

The deterministic harness a coding agent should be standing on: code shape, call graph, diff impact and crash state in milliseconds, offline, with no model in the loop. One CLI, 17 languages, zero runtime dependencies.

README.md

galaxy-brain

galaxy-brain

The deterministic harness a coding agent should be standing on.

Facts about your code — what shape it has, who calls what, what a diff moved, and (in Python)
where it died and with what state — delivered in milliseconds, offline, with no model in the loop.
Your agent stops guessing and starts reading.

Take the model out of your architecture diagram. Everything still visible is the harness.
galaxy-brain is that harness. Not a better prompt, not an autonomous loop: the layer underneath
both, which supplies the evidence they need and can be trusted because nothing in it is inferred by
a model.

The graph reads 17 languages — Python with the stdlib ast, plus 16 more through ast-grep,
integrated by reference: JS, TS, TSX, Go, Rust, Java, Kotlin, Swift, Ruby, PHP, Lua, Scala, Elixir,
C#, C and Dart. Every one has a conformance probe in the suite, so "supported" is a claim you can
re-verify, not a list of intentions — and what a language can't do is printed in its own output
rather than hidden. The error console is multi-language too, but only where a runtime offers a
hook that observes without handling — same exit code, same trace
(ADR 0012): Python, JS/TS (locals included, through the
inspector), the JVM, C#, Ruby, PHP, Lua and C natively; Go and Rust through a stderr wrapper
(gb-run.py). Python arms itself with gb on; the rest need one variable in the shell that
launches them (NODE_OPTIONS, JAVA_TOOL_OPTIONS…), which gb on --lenguajes prints — gb cannot
export it for you. gb status says, per language, whether it is armed and what it cannot see.

In a mixed repo, both engines run. Python is parsed with the stdlib ast, the other 16 through
ast-grep, and in a repo that has both, the two graphs are merged into one — a Python backend
and a TypeScript frontend are in the same picture, and a cycle among TypeScript files blocks the
gate exactly like a Python one. Edges between language families exist only where the source
names both ends literally: a Tauri invoke("cmd") to its #[tauri::command], an Electron
ipcRenderer.invoke("channel") to its ipcMain.handle("channel"), a Python import of a pyo3
#[pymodule], and a test that launches another program. What is not done is guess the rest: a
fetch("/users") cannot be tied to a Flask view without resolving the runtime, so the report says
so in not_covered instead of faking a link. Name collisions keep Python's name and suffix the
other (web/app.py stays web.app, web/app.ts becomes web.app:ts). ast-grep remains
optional: without it you get the complete Python report plus a line naming what was skipped.

Until 15 Aug 2026 this was not true — Python excluded the rest, so a mixed repo was
analyzed half-way and said nothing about it. Measured on a 2 .py + 2 .ts
bench: 2 modules of 4, gate green, zero warnings. That was a false green, and
ADR 0010 is the fix and the
autopsy.

One tool, gb. A single Python package, zero model calls on the hot path, zero
dependencies
beyond the standard library. An exception is a fact; the state at the moment of
failure is a fact; the shape of the import graph is a fact. Reporting facts requires no judgment,
which is why it can be instant and cannot fail in expensive ways.

Disambiguation: in the 2026 "harness / loop / graph" vocabulary, graph means agent
orchestration. The graph here is a graph of your code — modules, symbols, call edges, parsed
from the AST.

v0.7.0 · 1159 tests · 19.3k LOC source / 13.2k LOC tests · clean gate · ruff · Python ≥ 3.9 · zero runtime dependencies (ast-grep optional, only for non-Python graphs) · CLI output is Spanish by default — GB_LANG=en switches the console (capture notice, gb show/last, graph anchor) and the verifier (gb tests selection + checkpoint, gb check) to English; floor and map still Spanish, coming next


The map, live: four agents at once.

Four agents at once on the live map

20s excerpt · ▶ full clip with controls (1m46s, no audio)

Four agents editing one repository at the same time — a small demo project, four git worktrees, and
no orchestrator telling the map anything. gb who --html --watch re-derives the whole picture
from disk every few seconds: which symbols each agent touched, where two of them cross on the
same node, each agent's own console, and the verdict ladder it leaves behind when it finishes. When
they were done, gb tests --union --run verified each branch alone and then all four together —
54 tests, every row green.

Every card on that screen is a fact read off the disk. Nothing is declared, nothing is inferred, and
no model was asked.


Table of contents


The thesis

An agent working on your code is limited by one thing: what it knows about the code it cannot
see.
Given a 50-module repo, it opens files to find out what is there, greps to guess who calls
what, and re-runs a crashed program with print statements to learn what already happened. Each of
those is a guess standing in for a fact that a parser could have handed over in 200 ms.

So the bet is not a better prompt or a smarter loop. It is this:

A deterministic ecosystem underneath. The AI as the cherry, not the engine.

Putting a model on the path that always runs makes that path expensive, slow and optional — and
optional things get abandoned. Every fact gb serves comes from the AST, from git, or from the
interpreter's own hooks. No inference, no API key, no network, nothing to be wrong about.

Don't iterate on trust; iterate on evidence. gb is the evidence supplier. The loop and the
orchestration belong to whatever is driving — Claude Code, your CI, you. gb provides; it does not
orchestrate, and SCOPE.md says so as a limit, not as a roadmap.

What it upgrades, concretely

Without it With it
Agent greps for a function, opens 4 files, guesses the call sites gb calls <symbol> --depth 2 — callers and callees with file:line, exact
Agent re-runs the crashed script with print to see the values The values were already captured at death: gb show <id>
Agent starts each session blind to the repo's shape SessionStart hook injects the whole map: ~110 tokens, 162 ms
Agent searches, gets file paths, reads them to learn the signatures Every search carries the matching symbol cards along
"Did my change break something far away?" gb check --staged — the blast wave, before the commit
A fact learned last week, re-learned today gb memory — durable notes, surfaced in any repo
Quality gate that flags style and gets bypassed Only facts gate; proxies inform (rule 11)

The last row is the one that took a rewrite to learn. Gating proxies — coupling churn, smells,
test-shape heuristics — manufactures false positives, false positives train people to type
--no-verify, and after that the gate protects nothing. So the only two things that stop a commit
here are facts you cannot argue with: a new import cycle, or a crossing of a boundary you
declared yourself.

What it is not

  • Not an autonomous loop, and not a bundle of skills or agent prompts. There is no skills/
    directory and there never will be. Prompt scaffolding depends on a model obeying instructions; gb
    is the half that holds when it doesn't.
  • Not a code reviewer. It does not decide whether your code is good. It reports what is there.
  • Not a model wrapper. Zero API calls, zero dependencies beyond the standard library. It works
    on a plane.
  • Not useful for failing tests — pytest catches the exception, so it never reaches
    sys.excepthook. Use pytest -l, which prints the same locals. That negative result is written
    down in docs/pruebas-de-uso.md rather than hidden.
  • Not a server, not an MCP server. SCOPE.md has the reasoning, including the one
    condition that would reopen the MCP question.
  • Not multi-language everywhere. The graph reads 17 languages and so does the gate — import
    cycles and declared boundaries hold in any of them, and a bank of 18 real open-source repos across
    the 17 languages pins symbols, edges, calls and inheritance against code nobody wrote for gb. The
    error console captures where the runtime has an observing hook (see above); Dart and Swift do
    not yet. Narrowing test selection is licensed per language — Python, js, ts, go, c#,
    java, php, lua, rust and ruby today, each earned with a bench of real failures. The rest
    have not failed a measurement: they have no bench yet, so gb tests runs the whole suite and says
    why. A call graph with holes doesn't cost you savings; it costs you a false green — so when the
    caller chain dies on a method the graph cannot see being called (value.method() outside Python),
    gb tests runs everything instead of narrowing.

The two halves

Both are the same discipline — exact answers about your code — pointed in different directions:

The backbone: the graph. Modules and symbols derived from the AST on every look — never
declared, never maintained by hand. Who calls what, what imports what, with file:line.
graph · symbols · calls

Everything else lands on its nodes. Backward, at what already happened: the error console
records an uncaught exception anywhere in your Python environment with the state around it — and
anchors it to its node and its callers, so the failure that happened once while you were not looking
does not have to be reproduced. last · list · show · on · off · status

Forward, at what you are about to touch: the blast wave of a diff and the tests it selects, the
classic errors a change added, the project's missing scaffolding, and the facts that outlive a
session. check · tests · delta · floor · memory

The hot path, drawn

galaxy-brain architecture: observed run, deterministic capture, raw facts, graph/floor, agent

The six boxes across the top are the real hot path and they are exactly what ships: an observed
Python process (gb on), capture through the three native hooks — sys.excepthook,
threading.excepthook, sys.unraisablehook — raw append-only facts in ~/.galaxy-brain, the
derived graph plus the floor, and an agent that reads facts instead of guessing. No model is
consulted anywhere in that row; the budget under it (<1 s per edit, <10 s per commit) is
architecture, not aspiration. The certainty ladder and the fact/proxy/human split are rule 11 in
picture form: only facts gate, patterns inform.

What the diagram draws ahead of the code: of the adaptive learning loop, only the
replay bench ships (bucle/replay.py: it re-runs the recorded runs against the
current verifier — 13/13 reproduce, and blinding the verifier turns it red). Clustering and
hypothesis generation do not exist, and graph generations with rollback never will: versioning
the graph means persisting it as the source of truth, which this project banned — what is learnable
is the ruleset, not the graph. The catalogue of known LLM error patterns as built-in
detectors is likewise design intent; today gb delta reports classic errors a diff introduced, and
the "actas" box belongs to the loop runner in bucle/, deliberately outside
gb (gb provides, it does not orchestrate). Everything else
in the poster is shipped and measured.


Wired into the agent

The facts are useless if someone has to remember to ask for them. The norm goes in the default,
not in the prompt:
a rule that requires typing a flag depends on someone remembering, and sooner
or later it fails. So one hook makes the harness ambient — the agent gets the facts without asking,
and without knowing gb exists.

Hook What it injects Measured
SessionStart The compressed map of the repo, a count of unread captures when there are any, and any broken wiring (a hook calling a gb flag that no longer exists, a pre-commit that is not engaged, gb off the PATH) ~110 tokens, 162 ms

There used to be three. A hook after each edit (only what changed in the shape) and one on every
Grep/Glob (gb calls --hook, the matching symbol cards) were retired on 2026-08-13 with the
measurement in front: informing per action changed nothing (0/6), and a default that changes no
outcome is paid noise. gb calls --hook still exists for anyone who wires it by hand; floor --init
no longer does.

A symbol card is what lets an agent call code it has not read — signature straight from the AST
(args, defaults, *, async, the decorators that change the call), location, first docstring line,
and who depends on it, with sources split from tests:

galaxybrain.store.parse_ts(value) · function · src\galaxybrain\store.py:270 — El `ts` de una entrada…
  le llaman (7 — 6 de src, 1 de tests):

The wiring ships per project — gb floor --init writes it into .claude/settings.json, merging
with each machine's own settings, so a fresh clone gets an aware agent with no global setup. The
format is the cross-tool one (AGENTS.md), read natively by Claude Code, Codex, Cursor, Copilot and
Aider, so the awareness is not tied to one vendor.

The model does not know gb exists; its context does. And because every hook is a CLI command,
none of this is Claude-specific: point any orchestrator at gb <command> --json and you have the
same evidence.


What it saves you

A traceback tells you where. This also tells you with what:

KeyError: 'empresa'
hace 1min · facturacion/precios.py:6 · mi-api

  facturacion/precios.py:6  in precio_total
         4 |
         5 | def precio_total(cliente, cupon=None):
   →     6 |     base = TARIFAS[cliente["plan"]]
         7 |     unidades = cliente["asientos"]
         8 |     return base * unidades

      cliente = {'nombre': 'Beto', 'plan': 'empresa', 'asientos': 12}
      cupon   = None

The step that disappears is relaunching the program with a print in it. The failure happens once,
often while you are not looking; reproduction is the expensive work this removes.


Installation

pip install -e .     # from this repo
pip install git+https://github.com/Llicklair/galaxy-brain.git   # straight from GitHub
gb on                # enables capture in this Python environment
gb status            # verifies it stuck

gb on drops a .pth file into site-packages. From then on there is nothing to remember: every
Python process in that environment is covered, without touching any project's code.

Bringing it to another project is one command, with that project's venv active:

# macOS / Linux — instala.sh (locates itself; python3 by default, PYTHON=... to pick another)
sh <path-to-this-repo>/instala.sh

# Windows — instala.ps1, the same thing
powershell -ExecutionPolicy Bypass -File <path-to-this-repo>\instala.ps1

With a venv active they install locally, into that venv; without one, globally, into the
Python on your PATH. Two things the scripts handle so you don't have to: Homebrew's python3 and
Ubuntu 23.04+ refuse a global pip install (PEP 668, "externally-managed-environment"), so there it
goes to your user site — which that same interpreter loads at startup; and a user-site install can
leave gb off your PATH, so the scripts drive everything through python -m galaxybrain.cli and
tell you where gb landed. Every combination (macOS, Windows, Linux × venv, system Python) is
installed for real in CI, a real exception is raised, and its capture on disk is required
(instalacion.yml).

Coverage is per Python environment, not per repo. Being editable (-e), a git pull here
updates every environment with no reinstall. To remove it: gb off — one line, no residue. Cheap
removal is deliberate (rule 10: abandonment is data, not something to armor against).


Day one

git init my-project && cd my-project
gb floor        # the floor: what is missing before you build, and why each piece matters
gb floor --init # drops the base documents — and the pre-commit hook

On a fresh git repo the session map suggests this path by itself — one line, only in the
unambiguous case (git present, no code, no floor docs), silence otherwise. --init leaves seven
pieces, never overwriting anything:

Piece Why it is there
AGENTS.md Executable context in the cross-tool format read by Claude Code, Codex, Cursor, Copilot and Aider — including the gb usage contract for agents
SCOPE.md What is in, what is out, and the done criterion
ARCHITECTURE.md The design law, so later decisions have something to cite
docs/adr/README.md An ADR folder in MADR form: one file per decision that was expensive to make
docs/evidencia.md The evidence log, so choices cite measured numbers instead of folklore — including the failures, because a project that records only what worked has advertising, not evidence
.githooks/pre-commit The gate wired in ratchet mode: inherited debt does not block, only new debt does. Hook it once with git config core.hooksPath .githooks
.claude/settings.json Wires the agent at project level — session map, edit delta and symbol cards travel with the repo, merging with each machine's own settings

One thing no tool can write for you, and gb says so out loud: the done criterion in SCOPE.md.
You write it before the first line of code, because not knowing when to stop is the number one
cause of over-engineering, and the cure costs one sentence.


Command reference

Seventeen subcommands, and every one belongs to a family. A command that does not fit a family does not
ship — there is no "small exception", because small exceptions are exactly how a monster gets built.

Where it crashed, and with what state

Command What it does
gb last This project's latest failure, with its state
gb last --full The same, with every frame kept
gb list -n 20 The history grouped by signature: what breaks, and how often
gb list --chrono The raw timeline, most recent first
gb list --all Every project, not just this one
gb list --efimeros Include python -c / stdin captures (hidden by default, and said so)
gb show <id> One specific failure — the id comes in the capture notice
gb on / gb off Enable / disable capture in this environment
gb status What is active right now, and how many captures are unread
gb status --cobertura Runs 8 real failure modes and shows which ones leave a record

What shape it has

Command What it does
gb graph <path> Coupling map: imports, cycles, hotspots
gb graph --gate Exit code ≠ 0 on cycles or declared-boundary crossings — for pre-commit
gb graph --gate --since HEAD Ratchet: only new debt fails
gb graph --boundaries FILE Layering rules (defaults to .gb-boundaries at the root)
gb graph --smells Over-engineering proxies — advisory, never blocks
gb graph --self-test Injects known defects and fails if the gate does not see them
gb graph --context The compressed map as a session payload; silent when there is nothing to say
gb symbols <path> Symbol graph: who calls whom, with its resolution coverage
gb symbols --since REF What grew since that ref, marked apart
gb calls <symbol> Callers and callees of a symbol, with file:line
gb calls <symbol> --depth 2 The wave: also who calls the callers
gb calls --hook PreToolUse mode: reads hook JSON from stdin, silent when there is nothing

Shared flags worth knowing: --json on every reporting command for raw output (gb on/gb off flip state and take none). (The HTML canvas was retired
on 2026-08-13 after two tied A/Bs — and came back on 2026-08-14 as gb who --html, renderer only,
writing the project's mapa.html; gb who --watch --html keeps it live by foreground polling. The
self-managed watcher machinery, which was the measured culprit, stays retired — the amended
sentence lives in SCOPE.md.)

What each change did

Command What it does
gb check What a diff did to tests, coupling, and its wave (default range HEAD~1..HEAD)
gb check --staged Reviews the index instead of a range — the only correct thing in a pre-commit
gb check --brief One line when there are no signals, for hooks
gb tests Which tests to run for what changed, derived from the call graph
gb tests --staged The same over the index, for a pre-commit
gb tests --staged --run Also executes them — exit code is pytest's

check informs and never blocks. Its signals are proxies, and proxies that gate manufacture the
false positives that end in --no-verify.

What it is missing at the base

Command What it does
gb floor The minimum scaffolding a project needs before building
gb floor --init Drops the seven base pieces, never overwriting
gb floor --time Times the suite against the DORA threshold — runs the tests, so it is opt-in

What was learned, across repos

Command What it does
gb memory index The compact index, one line per note
gb memory recall <words> Full text of the most relevant notes
gb memory context The session payload (what the SessionStart hook calls)
gb memory add --name x --description "..." --scope always Add or overwrite a note (body via --body or stdin)

The error console

The backward-looking half: the state at the moment of death, kept so nobody has to reproduce it.
An agent handed this does not re-run your program with print statements; it reads what already
happened. That is the single most expensive guess it makes, removed.

With no arguments, gb last and gb list filter by the repo you are standing in. And the failure
card ends in the graph — the crash anchored to the symbol whose body contains the line, with
its blast wave one command away:

en el grafo: lib.base · function · lib.py:5
  le llaman (1): lib.ayuda

The anchor is honest about time: it resolves against today's code, and if the file changed
after the capture it says so, with the exact commit — instead of silently pointing at whatever
occupies that line now.

One failure type, three exit doors. "Uncaught exception" is not a synonym for sys.excepthook:
the interpreter lets failures out through three doors, and all three are covered.

Door When Status
sys.excepthook The exception kills the main thread and the process covered
threading.excepthook It kills a threading thread; the process lives on covered (GB_NO_THREADS=1 opts out)
sys.unraisablehook Python could not propagate it: __del__, weakrefs, GC covered

The third door was the only one that vanished without a trace: the interpreter prints it, the
process does not die, and nothing was left to show. None of the three costs anything while your
program works.

What triggers a capture and what does not — executed, not documented:

gb status --cobertura   # runs 8 real failure modes and shows which leave a record
LO QUE SI deja registro          LO QUE NO (y es correcto)
  + excepcion no capturada          - asyncio: tarea suelta que nadie espera
  + excepcion en un hilo            - sys.exit(1)
  + excepcion en __del__            - KeyboardInterrupt
  + asyncio fuera de run()          - excepcion atrapada por try/except

The boundary is not documented — documents age. It is demonstrated every time you run it.

The history lives in ~/.galaxy-brain, append-only, outside the observed repo: the harness never
dirties the project it is watching (rule 7).


The map

galaxy-brain's own module graph

The forward-looking half: what shape the project has and who calls whom — the questions an agent
answers today by opening files and grepping. All deterministic, zero models, zero dependencies.

Clicking a node answers with facts: its description (taken from the docstring, not from a
model), who calls it, whom it calls, what it imports, whether it sits in a cycle — plus the layers
of history: the error-cycle rings (captured → read → intervened → silent) and a halo on whatever is
in progress, uncommitted, right now. Imports are drawn differently from calls because an
import is exact and a call is inferred
— merging them into one number would mean gating on a
proxy.

Two honest numbers ship with every run: gb symbols declares what it could not resolve
(object.method() calls require type inference, and here nothing is guessed: a false edge gets
believed, a missing one gets noticed), and measured against an inference-based index it scores
93% recall with zero dependencies. Details and the negative results live in
docs/pruebas-de-uso.md.

The graph is always derived, never persisted. A stored map is a map that goes stale and lies;
recomputation is fast enough that caching would buy milliseconds and cost correctness.


What a change did

gb check --staged      # before committing
gb check HEAD~5..HEAD  # what the last five commits moved

It reports what a diff did to the tests, to coupling, and to the blast wave of the symbols it
touched. Its whole output is advisory. The one thing in this area that can stop a commit lives in
graph --gate, and only for two facts: a new import cycle, or a crossing of a boundary you
declared in .gb-boundaries.

A crossing is checked on both ways of depending on a module — importing it and calling into
it. A -/-> B promises "A does not depend on B", and there are languages where you reach another
module without importing it: crate::b::f() in Rust, or the same package in Java, C# and Kotlin.
Checking only imports left that door open, and a real agent walked through it.

The same fact has a second consequence, on the other side of the tool: in those languages the module
graph can legitimately come back empty. Run gb graph on a Java project whose classes share a
package and you get 3 modules, 0 internal edges — not because nothing is coupled, but because
there is no import to derive it from. So gb says which of the two it is:

3 modulos, 0 aristas internas, 0 ciclo(s)

OJO con el 0: hay modulos y ninguna arista, y el motor de este arbol tiene un limite conocido:
  - dos ficheros del mismo paquete se usan SIN import en Java, asi que no dejan arista de
    modulo: 0 aristas aqui NO significa 0 acoplamiento. Las llamadas entre ellos si se ven

Every language declares what its engine cannot see, and gb prints it next to the number it qualifies
— only for the languages actually present in the tree. A zero that means "nothing here" and a zero
that means "not visible from here" used to print identically; that is the failure mode
ADR 0008 exists to prevent.

Dependencies the code never confesses

The graph is derived, never declared — but it can only derive what an import statement writes down. A
repo that talks to its Go service over HTTP, to a worker over subprocess, or to another module
through a CLI has real dependencies that no static analyser can see, and they are missing from the
cycles, the fan-in, the test selection and the map. A => B writes one down by hand:

app.web  =>  svc.payments     # declared: HTTP, subprocess, CLI, IPC — anything static analysis misses
app.web -/-> app.db           # forbidden: A must not depend on B
ENTRY = app.web, app.api      # a group, expandable in both forms above
FUERA = app                   # reserved group: these exact modules are deliberately outside the
                              # "no rule mentions them" warning — the decision prints, the nagging stops

A declared edge is an edge like any other: it is injected before anything is computed, so it
closes cycles, counts in fan-in/out, reaches the test selection and gets drawn on the map — and the
boundaries govern it, so => is no back door around a -/-> you declared.

The token is => and deliberately not -->: you get --> by dropping the slash from -/->, and
that one-character typo would have turned "A must not depend on B" into "A depends on B" — the exact
opposite of the rule you meant to write, passing green. --> stays a malformed line that gets
reported.

Which tests to run for what changed

gb tests --staged          # the selection and why those
gb tests --staged --run    # and run them; exit code is pytest's

The call graph already knows who calls whom, so walking up from the symbols a diff touches reaches
the tests that exercise them. Measured on this repo: 5 symbols, 5 of 5 the same exit code as the
full suite, saving between 20% and 97% of the wall clock (32 s → 1–26 s). The saving collapses
exactly where the symbol is heavily coupled, which is honest information about the design rather
than a failure of the method.

The rule that governs it: when in doubt, everything. A selection that leaves out a test that
would have failed is not "less coverage", it is a false green — so a touched conftest.py, a
change outside every symbol, an unreadable diff, or a symbol no test reaches all return the whole
suite, with the reason stated out loud. Silence would read as a saving that never happened.

Two details that cost a measurement to learn, and that any implementation of this needs: select
files, not test ids (one non-existent id makes pytest exit 4, no tests ran, which in a gate
reads exactly like "everything passed"), and filter on the name starting with test_ rather than
on living under tests/ — a helper called _generar lives in a test file, the graph sees it, and
pytest does not collect it.

This is the one place gb runs code from the observed project, so --run is explicitly opt-in: the
default prints the list and lets you decide.


The floor

gb floor reads a repo and reports what a project needs before it can be worked on reliably —
whether there is a declared test command, a lint or type gate, CI, isolation, an agent context file,
a record of past decisions. It detects what you already use rather than prescribing a stack:
ruff, flake8, pylint, eslint, biome, golangci-lint, mypy, pyright, tsconfig, prettier, black,
rustfmt, GitHub Actions, GitLab CI, CircleCI, Jenkins, Docker, devcontainers. Nothing is hardwired
and nothing is vendored — a project-specific path would be a bug (rule 6), and external tools are
integrated by reference, never bundled.

Then --init writes only what is missing, never overwriting, and the pre-commit it drops is wired in
ratchet mode: inherited debt passes, new debt fails. A gate that fails on day one over debt you
did not create gets disabled on day two.

The one thing it cannot write for you — the done criterion — it tells you to write yourself. Not
knowing when to stop is the number one cause of over-engineering, and the cure costs one sentence.


Cross-repo memory

A fact learned in one repo should not die there. gb memory is a vault of durable markdown notes
(in ~/.claude/memory-global, hand-editable, with [[wikilinks]] so Obsidian opens the graph)
that surface in any project via a SessionStart hook.

Lean by design: session start injects the index of all notes but the full text only of always
notes and this project's; the rest is pulled on demand with recall. The vault is never dumped
whole — context is the scarce resource, and spending it on notes nobody asked for is the failure
mode this design avoids.


How it is verified

Three layers, because tests alone only pin what you already knew how to check.

1. The suite — 1159 tests, ~480 s (measured 2026-09-24). Runs on every commit via the pre-commit
hook, still under the 600 s DORA threshold — but no longer far under it.

2. The gate is verified by breaking it. A gate degrades in silence: it keeps returning zero and
stops looking at anything.

gb graph --self-test        # injects 6 known defects and fails if the gate does NOT see them
gb graph src --self-test    # additionally: relations that must hold on YOUR code

3. Metamorphic relations — "these two ways of asking must agree", evaluated on your real repo:
the same folder spelled c: or C: yields the same shape; a cycle's imports are edges that exist;
graph and symbols see the same modules. That is where most of the real defects lived.

4. Behavior is demonstrated, not asserted. gb status --cobertura executes eight failure modes
and shows which leave a record. A document claiming the same thing would eventually be wrong without
anyone noticing.


Cost, measured

The budget is measured, not estimated (rule 4). Python 3.11, Windows 10, median of 20 cold starts:

Clean interpreter startup 21.2 ms
With the hook installed 27.7 ms
Hook cost 6.4 ms (A/B measured: 5.2 ms)
— of which, import threading 5.2 ms

While your program works, the cost is zero. Not "low": zero. The hooks only run once the
process has already failed. For a tiny CLI that never touches threads: GB_NO_THREADS=1.

Other measured numbers: session map ~110 tokens in 162 ms · symbols 93% recall · search hook
430 ms (this repo) / 330 ms (600 synthetic modules) · gb show with anchor 158 ms · ruff 130 ms ·
the suite in ~30 s.

The budget is architecture, not performance: < 1 s per edit, < 10 s per commit. Exceeding it is
a design violation to be fixed by removing something, not an optimization task for later.


Settings

Everything via environment variables; there is no config file to maintain.

Variable Default What it does
GB_DISABLE off Turns capture off without uninstalling anything
GB_QUIET off Silences the one-line notice printed after the traceback
GB_HOME ~/.galaxy-brain Where the history lives
GB_NO_THREADS off Skip thread exceptions (saves 5.2 ms of startup)
GB_ALL_FRAMES off Also keep locals of library frames
GB_MAX_FRAMES 20 Frames kept (innermost survive)
GB_CONTEXT_LINES 2 Source lines around the failing one
GB_OPEN_CMD browser What opens the map (--open); receives the path as last argument

GB_OPEN_CMD exists because gb knows no editor and will not maintain a list: a hardwired
command is a bug (rule 6 — nothing project-specific, everything detected at runtime). Point the map
wherever you want (GB_OPEN_CMD="firefox --new-window").


Secrets — redaction by name, with an honest residue

The state around a failure is exactly where credentials live. The console redacts by name
(password, token, api_key, secret, auth, credential, session, cookie…). The trigger
is always the name, never the content: guessing whether a string is a secret is expensive and
fallible; the name was written by a human, on purpose.

The residue, said plainly: a secret without a sensitive name next to it reaches disk — a
positional literal (connect("hunter2")), a password inside a URL (user:pass@host), a secret
value under an innocent name. Closing that would require content heuristics, which this project
rejects deliberately because they fail and sell false safety. That is why the golden rule does not
depend on redaction: the history lives in your $HOME in plain text; treat it as sensitive and
upload it nowhere.


Known limits, stated up front

  • Uncaught exceptions only. An except: pass that swallows the failure is invisible here — and
    rightly so: a handled exception is, by definition, one its author decided was not a failure.
  • Failing tests are not covered. pytest (and jest, vitest…) catches the exception, so no hook
    ever sees it. Use pytest -l.
  • Main thread, threading threads, and finalizers (__del__, weakrefs, GC). asyncio with
    stray tasks
    nobody awaits stays out, as does multiprocessing; if the exception propagates out
    of asyncio.run(), it is captured.
  • No source file, no code context. python -c, exec() and the REPL keep type, message,
    frames and state, but not the surrounding lines (gb list sets them apart as ephemeral).
  • The state is the state at death, not a time-travel debugger.
  • Unrepresentable objects are described, not reconstructed. A __repr__ that blows up leaves
    <Type: repr() failed with X> and does not take the rest of the frame down with it.
  • Non-Python deaths — a segfault, an OOM kill, a kill -9 — raise no exception, so no hook
    ever sees them.
  • Not in Ubuntu's system Python. Its sitecustomize.py installs apport's excepthook after
    every .pth, replacing gb's; gb on names it and refuses instead of pretending. Use a venv
    there, where apport is not visible. macOS and Windows, venv or global, are covered.
  • Local only. Code running in a browser is out of reach of the console. No CI, no UI, no
    server, no MCP server (see SCOPE.md for
    why, and for the single condition that would reopen it).
  • Call edges are inferred. object.method() needs type inference; unresolved calls are
    declared, not guessed. Python resolves more of them than the other 16 languages, which is why
    test selection saves less there (46–62% on the JS, C# and Rust benches) — safely, not falsely.
  • Adoption is the one thing not measured. Latency, overhead, recall and coverage all have
    numbers behind them. Whether people keep using it does not — and by rule 10, if you stop, that
    gets investigated, never blocked with a hook that forces you back.

The design law

Eleven rules, in ARCHITECTURE.md; a change that violates one is rejected in
review rather than argued about. The load-bearing ones:

  1. Zero models on the hot path. Capturing, storing, showing and analyzing consult nobody.
  2. Return, don't rule. Every run ends by handing over something the user wanted. A function
    whose only output is a verdict is misplaced — anything that only says no is a tax.
  3. Latency budget, non-negotiable. < 1 s per edit, < 10 s per commit.
  4. Overhead on the observed process measured, not estimated.
  5. One runtime, one failure type — multi-language in both halves. Local execution, uncaught
    exceptions only; a language joins the graph with its conformance probe, and the console only
    through a hook that observes without handling.
  6. Facts stored raw. Exception, trace and state persist exactly as captured.
  7. History local, append-only, outside the observed repo.
  8. AI only after the fact, explicit and optional.
  9. Fail silently toward the safe side. If capture breaks, the observed program continues as if
    gb were not installed.
  10. Abandonment is data, not a bug to armor against.
  11. Proxies inform, they don't block. Only facts gate.

Two working principles sit above all of them: subtract before you polish (maintenance cost does
not grow with size, it grows worse — every part rubs against every other, so the first question
about a problem is what to remove), and evidence over folklore (a decision cites a measured
fact; "other frameworks do it" is not a reason).


Development

python -m pytest tests/ -q          # the suite — 1159 tests, ~480 s
python -m ruff check src tests      # lint (catches defects, holds no style opinions)
gb graph src --gate                 # the gate, clean

The pre-commit (.githooks/pre-commit) runs lint + suite + gate + gb check --staged — ~8.5 min measured on 2026-09-06: the suite dominates and the gate's ast-grep sweep adds
~11 s (was ~18 s before lenguajes.analyze learned to not re-derive an unchanged tree twice in one
invocation), so rule 3's < 10 s commit budget is currently exceeded here, and that is a known open
debt, not a rounding error. Hook it once with git config core.hooksPath .githooks. git commit --no-verify skips it — and that skip is a datum, not a rule.

The console's layering rules live in src/.gb-boundaries: the core (capture,
store, analysis) does not import the presentation (cli, render). A new crossing stops
the commit; a test-softening signal only informs.

Commits are type: short description (feat, fix, refactor, docs, chore), one logical
change each, with behavior changes and documentation changes kept apart.


Documents

Document What it holds
ARCHITECTURE.md The design law: the thesis, the command families, the eleven rules
SCOPE.md What is in, what is deliberately out, the done criteria per family, and the failure criteria written while they do not hurt yet
docs/research-report.md The measured evidence decisions cite
docs/pruebas-de-uso.md The usage notebook, including the negative results
CLAUDE.md The contract for agents working in this repo

Decision documents are in Spanish for coherence; anything published is in English.

License

Apache License 2.0 — Copyright 2026 Marcos Recio. You may use, modify, fork and
distribute this, including commercially, provided you keep the notice and state your changes; it
also grants you the contributors' patent rights. See NOTICE.

ast-grep, used by reference for the non-Python graph, is never bundled and stays under its own
license.


galaxy-brain

Facts first. Reasoning after.
We do not make the model smarter. We make the system less vulnerable to its mistakes.

Yorumlar (0)

Sonuc bulunamadi