USE CASE · DEVELOPER SECOND BRAIN → AGENT SECURITY REVIEW

ONE COMMAND OVER
THE WHOLE FOLDER.
THEN WE MEASURED IT.

A developer's project is not just source. This one measured 1,794 files and 693 MB — 337 markdown, 211 Rust, 206 YAML, 144 HTML, 118 PDFs, 84 JS, 66 Python, 49 JSONL, plus TOML, DOCX and SQLite. xerj brain <folder> read all of it in 281.3 seconds and produced 17,519 evidence-carrying links, including the ones grep structurally cannot make: a marketing PDF linked to the exact Rust crates it names, a review document pinned to the auth source file it cites. Claude Code then connected to that brain over MCP and ran a whitebox security review — against a grep-only control arm given the same task, the same corpus and a comparable budget. It was close, and the honest result is better than a rigged win. The experiment found three real bugs across the two arms — a one-request node-kill (grep), and a document-extractor memory bomb and a secrets-indexing walker (brain) — and all three are now fixed in the current release. Grep edged it on severity with the node-kill; the brain reached two real bugs in the ingestion crate grep never opened, read 29% less source, and both arms produced zero false positives on the findings that held. One grep "finding" — a "catastrophic" path traversal — did not reproduce against the real binary and was struck; that verification is part of the story.

FOR·DEVELOPERS·AGENT BUILDERS·APPSEC ENGINEERS
THE TWO-ARM SECURITY REVIEW · SAME TASK · SAME CORPUS · COMPARABLE BUDGET · EACH ROW SCALED TO ITS OWN MAXIMUM
ARM A · XERJ BRAIN ARM B · GREP CONTROL
Confirmed real findings 2 2 tie
Verified by live exploitation 1 1 tie
Source lines read ~1,200 29% less ~1,700
False positives 0 0

COUNTS AND SELF-REPORTED EFFORT · NOT LATENCY · LOWER IS BETTER ON THE THIRD AND FOURTH ROWS · GREP EDGED IT ON SEVERITY — ITS CONFIRMED FINDING IS A HIGH (UNAUTH NODE-KILL, EXPLOITED LIVE); THE BRAIN'S TWO ARE MEDIUM · ITS OTHER "CATASTROPHIC" FINDING WAS A FALSE POSITIVE FROM A STALE BINARY, STRUCK ON RE-VERIFICATION · THE FINDING SETS ARE DISJOINT · ALL THREE REAL BUGS NOW FIXED · SOURCES IN docs/usecases/dev-second-brain-security →

WATCH IT HAPPEN.

Two and a half minutes, one take, no cuts — the folder, the one command, the map that opens at helicopter altitude, the agent connecting over MCP, and the two-arm security review. Every number on screen is the real run described below.

↓ DOWNLOAD MP4 · 7 MB 2:32 · ONE BINARY · NO JVM · SPACE TO PLAY · ← / → TO SEEK
LAUNCH IT ON YOUR OWN FOLDER · THREE COMMANDS
# 1 · get xerj — one self-contained binary, no JVM, no services to run
curl -fsSL https://xerj.org/get | sh

# 2 · build a second brain over your project folder (source, PDFs, specs, JSON, docs)
xerj brain ./my-project
#    -> attaches to the local engine and prints a live console URL when it finishes

# 3 · connect Claude Code — drop this in .mcp.json at the folder root, approve once
{
  "mcpServers": {
    "xerj-brain": {
      "type": "stdio",
      "command": "xerj-mcp",
      "env": { "XERJ_URL": "http://localhost:9200" }
    }
  }
}
# then run `claude` and ask it to search the brain, expand a node, or review a file

THE MIXED-FOLDER PROBLEM

WHAT ONE COMMAND GAVE

17,519
LIVE LINKS FROM ONE COMMAND
sequence 13,011 · pathcite 2,774 · same_dir 1,155 · mdlink 238 · cratecite 219 · wikilink 96 · href 26
281.3 s
TO BUILD THE BRAIN
1,794 files · 693 MB · 285 datasets · 160,985 records · 8 workers · one run, one machine — not a benchmark
0
FINDINGS THE TWO ARMS SHARED
completely disjoint sets · each arm caught real bugs the other missed · 0 false positives on either side

THE ONE COMMAND, AND WHAT IT PRINTED.

Real output, elided in the middle only. The staging step above it is a wart, not a flourish: the walker has no ignore rules, so pointed at the live checkout it would have walked 116 GB of build output and indexed a .env holding a real credential. Exclude your artifacts yourself.

$ xerj brain xerj-project --fresh --no-open
brain 'xerj-project': 1794 files (693 MB) under xerj-project
attached to the running xerj server at http://localhost:9200
autoindex: 174 byte-identical duplicate path(s) will reuse canonical content
phase A: sniffing + sampling 1620 files…
phase A: 285 datasets inferred, 198 junk/skipped files
graph: brain 'xerj-project' → .xerj-memory-xerj-project-edges; 1155 structural edges, 0 prior edges invalidated (7 detectors live)
phase B: indexing 1422 files with 8 workers → http://localhost:9200
  [1/1422] gtm/competitors/coveo/coveo-investor-deck-may-2026.pdf (59 records)
  ...
done in 281.0s — 285 datasets, 160985 records live, 174 duplicate aliases, 12 junk records, 198 junk/skipped files
graph: 19167 edges → .xerj-memory-xerj-project-edges (cratecite@1 275, href@2 53, mdlink@2 240,
       pathcite@1 3769, samedir@2 1155, sequence@2 13011, wikilink@2 664);
       37699 unresolved, 1733 ambiguous, 7 self-dropped, 0 invalidated

✓ your second brain is ready — 1794 files, 17519 links, 281.3s
  → http://localhost:9200/_xerj-console/#/second-brain?brain=xerj-project
  agents: XERJ_URL=http://localhost:9200 xerj-mcp
# exit code 3 = ready-with-junk · real 4m41s

Two numbers in that output are the honest ceiling of the whole approach, and they are printed by the tool itself: 37,699 candidate citations went unresolved and 1,733 were ambiguous. They are not links. The graph is a lower bound on the citations in the corpus, never a census of them. The 19,167 emitted against 17,519 live is dedupe at write time — edge ids are deterministic, so re-derived edges converge — and the delta was not fully diagnosed, so the number to quote is 17,519.

WHAT THE SNIFFER ACTUALLY READ · CONTENT CLASSES, NOT EXTENSIONS · RECORDS PER CLASS · SHARE OF THE 160,985 LIVE RECORDS
jsonl · 61 files
134,410
83.5%
txt-lines · 528 files
9,626
6.0%
html · 149 files
8,368
5.2%
pdf · 107 files
2,670
1.7%
txt-prose · 296 files
2,639
1.6%
yaml · 215 files
1,724
1.1%
json · 32 files
410
0.3%
sqlite · 20 files
287
0.2%
docx · 3 files
6
0.0%

THESE ARE THE SNIFFER'S OWN CLASSES (ax_format), NOT FILE EXTENSIONS — THE RUST SOURCES LAND IN TXT-LINES · 107 OF THE 118 PDFS YIELDED EXTRACTABLE TEXT; THE OTHER 11 ARE AMONG THE 198 SKIPPED FILES · THERE IS NO OCR, SO SCANNED PDFS YIELD NOTHING

A PDF THAT KNOWS WHICH CRATE IT DESCRIBES.

This is the part no text search reaches. The detectors relate a document to the code it cites — across file types, with the quote that justified it. Three real edges from this run, evidence verbatim from the edge index.

# 1 · marketing PDF → the crates it names · cratecite@1 · confidence 0.6
landing/resources/xerj-tech-brief.pdf
  → engine/crates/xerj-fts/Cargo.toml
  → engine/crates/xerj-vector/Cargo.toml
evidence.quote  "Search tier BM25 (xerj-fts) │ HNSW (xerj-vector)"
evidence.offset 1738   # byte offset inside the extracted PDF text

# 2 · a review document pinned to the auth source it cites · pathcite@1 · confidence 0.7
demo/playbooks/KIBANA_FEEDBACK_SCORECARD_RC4.md
  → engine/crates/xerj-console-api/src/auth/magic.rs
evidence.quote  "four-role model with viewer (`engine/crates/xerj-console-api/src/auth/magic.rs:41,98-100`);
                 `enforce_write` gates managed/default…"

# 3 · a landing page linked to the algorithm it advertises · pathcite@1
landing/agent-search/index.html
  → engine/crates/xerj-ai/src/local.rs

Edge #2 is the shape a security reviewer wants: prose claims pinned to the auth code that has to satisfy them. And the standing caveat belongs in the same paragraph, not in a footer — detection is structural and lexical, never semantic. There is no LLM anywhere in the indexing pipeline and the embedder is lexical-feature-hash. The graph finds citations you already wrote: paths, crate names, wiki-links, hrefs, adjacency. It does not find "these two documents are about the same thing."

The console renders the graph as THE MAP, and it is bounded by construction rather than by throttling: 19,299 links over 1,550 documents resolve to 13 named groups in 44 ms, and a synthetic 50,000-link budget resolves to 13 groups in 122 ms — byte-identical across runs, at 60 fps. The view summarises; it does not attempt to draw every edge, so it does not degrade as the corpus grows.

ONE FILE, AND THE AGENT HAS IT.

A project-scoped .mcp.json at the folder root. Claude Code prompts once to approve it — that approval is a security default, and it is real friction: until it happens, claude mcp list reports ⏸ Pending approval, and a checked-in enabledMcpjsonServers is ignored until the workspace is trusted.

{
  "mcpServers": {
    "xerj-brain": {
      "type": "stdio",
      "command": "xerj-mcp",
      "args": [],
      "env": { "XERJ_URL": "http://localhost:9200" }
    }
  }
}
# secured server: add "XERJ_AUTH": "ApiKey <key>"  ·  key at <data-dir>/admin.key

A verified call over stdio, against the brain built above — this is real request/response, not a schema illustration:

# tools/call xerj_brain_ego {"brain":"xerj-project","node":"07bbf4a9…"}
# the node is the top out-hub: user-feedback/kibana/INDEX.md
{
  "edge_id": "1a76cb620864ed6557f79b98f67b2c33",
  "src": "07bbf4a9…", "dst": "9c3452ff…",
  "type": "mdlink", "weight": 0.9, "hop": 1, "direction": "out",
  "valid_at": 1782575743696, "invalid_at": null, "created_at": 1785445843823,
  "detector": "mdlink@2", "confidence": 0.9,
  "evidence": {
    "quote": "| [`04-performance-and-load`](categories/04-performance-and-load/README.md) | 2387 | 1867 | 103 | gith=1349 disc=337 stac=333 |",
    "source": "user-feedback/kibana/INDEX.md",
    "offset": 427
  }
}

Three corrections to anything you may have read elsewhere, including our own earlier drafts. The server exposes ten tools, not four — the four xerj_brain_* verbs under contract: xerj-second-brain/1 plus six search and memory tools, and there is no flag to restrict it to the brain surface. The overview response returns "nodes": {"total": 0} because node counting is not wired, and the agent sees that verbatim. And nodes_index comes back as a 9,117-character comma-joined string of every dataset name on every orient call, which is context noise the agent pays for first. xerj-mcp also cannot boot the engine: if the server is down, every call returns a clean MCP error and nothing works until you restart it.

THE EXPERIMENT, AND ITS RULES.

One task — perform a whitebox security review of this codebase; report only findings you have adversarially verified; report honest negatives — given to two arms over the same corpus with a comparable budget. ARM A is Claude Code with the brain over MCP. ARM B is Claude Code with grep/rg and file reads only, and it is a real attempt rather than a strawman: it read more source than ARM A did. Every finding from both arms was then re-verified by a third adversarial pass against a live server, and exploited where exploitation was possible.

One methodology wart, recorded because it touched both arms symmetrically: the intended method document does not exist in this tree, so both arms fell back to the same five-stage instruction/data-boundary structure — ARM A to a draft, ARM B to the published WordPress playbook. The comparison stands, but the run did not execute a pre-registered method.

FINDING · CONFIRMED BY ADVERSARIAL RE-VERIFICATION
ARM
SEVERITY
EXPLOITED
Negative number_of_shards → allocator panic → abort · FIXED
B · grep
HIGH
YES
PUT with number_of_shards:-1 then a profile:true search → capacity overflow in raw_vec, process Aborted (core dumped), exit 134, port dead · amplified by panic = "abort" · a clamped sibling exists at es_compat.rs:24767 (.max(1)); the profile path at 11446 was the unclamped twin · one unauthenticated request killed the node · re-verified live against the release binary, then fixed (#77): the shard count is now clamped and the same payload returns 200
Unbounded paragraph accumulation in the DOCX extractor · FIXED
A · brain
MEDIUM
YES
docx.rs caps body at 64 MB but para was only flushed at </w:p>, and a single giant <w:t> run was materialized whole · measured: an 815 KB .docx inflating to hundreds of MB inside one <w:p> drove peak RSS to 1.68 GB · requires planting a file in the corpus · fixed (#78): the decompressed read is now capped, and the same bomb peaks at 187 MB, bounded by the cap rather than the file
Walker indexes secrets · no per-brain authorization · PARTLY FIXED
A · brain
MEDIUM
walk.rs filtered on is_file only — no dotfile rule — so a .env in the folder became a searchable record · the same wart that forced the staging step further up the page · fixed (#79): the walker now skips hidden files and directories · the per-brain-authorization gap it pairs with is a larger design item, tracked separately
Index-name path traversal → data destruction · REFUTED on the real binary
B · grep
struck
NO
grep's other headline — that DELETE /.. recursively deletes the parent of the data dir — did not reproduce against the shipped release. Re-checked with filesystem canaries: PUT /.. → 400, DELETE /.. → 404, bulk / doc-index / embedded all reject .., snapshot-restore validates before deleting, canaries all survived. The agent had tested a stale binary. A false positive that reads as catastrophic is exactly why every finding is re-verified against a fresh build (#80 tracks a defense-in-depth hardening of the handler boundary).
Unauthenticated binary protocol, pre-read allocation
B · grep
LOW
confirmed as accurately scored: serve_binary_protocol has zero real callers — the only references are its own doc-comment example · the arm's self-downgrade to dead code is correct

Both arms also wrote honest negatives, and all of them were re-checked and held: SQLite identifier quoting uses ""-escaping; the PDF path checks the literal __extract-pdf before flag parsing and passes the path as argv; the two validate_brain implementations are byte-identical; MAX_QUERY_DEPTH = 64 with a DepthGuard is still in place; credential comparison uses constant_time_eq. Every confirmed finding was then re-verified against the freshly built release binary — which is what caught grep's "catastrophic" traversal as a false positive from stale code, and confirmed the three real bugs, all now fixed.

THE VERDICT: CLOSE, AND HONEST.

The thesis — that the brain finds more — is not cleanly supported by this run, and the honest result is more useful than a rigged one. Across the two arms the experiment found three real bugs: a single-request node-kill (grep) and, in the ingestion crate grep never opened, a document-extractor memory bomb and a walker that indexed .env secrets (brain). Grep edged it on severity — its node-kill was a High — while the brain's two were Mediums; the finding sets were completely disjoint, each arm catching real bugs the other missed. And grep's other headline, a "catastrophic" path traversal, evaporated on re-verification against the real binary — a false positive from a stale build. The outcome that matters: all three confirmed bugs are now fixed (#77, #78, #79). Running this experiment on our own repo hardened it.

The brain's one measurable win was ergonomic: phrase search plus an aggregation over ax_path let ARM A triage sinks by file without a thousand-line sweep, and it reached its findings on ~1,200 lines read against the control's ~1,700. That is a real advantage and it is worth something. It did not convert into better recall or better precision on the findings that mattered, and saying otherwise would be the only genuinely unacceptable outcome here.

01
THE BUGS WERE ABSENCES.
Both of the control's serious findings came from whole-tree cross-file comparison. One from grepping \.\. across the tree and noticing that four sibling validators reject it while IndexName does not; the other from rg with_capacity filtered to non-constant sizes, which surfaced a clamped/unclamped pair. Grep does this natively, because its raw output is the cross-file comparison. An index that answers "where is this densest" is answering a different question.
02
THE BRAIN STEERED THE AGENT AWAY FROM THEM.
ARM A's decisive move was an aggregation that ruled out the giant files by count — and the file it pruned, es_compat.rs at 990 chunks, is exactly where both serious bugs live. An aggregation ranks presence: where is this sink densest. It cannot rank an absence: which of these N validators is missing the check. The tool optimised the wrong quantity for this bug class, and its ranking-by-count affordance nudged the operator to scope the vulnerable crate out entirely. This is the same lesson our WordPress audit recorded from the other direction: a presence-of-a-check model cannot represent a missing sibling guard, because every predicate it evaluates returns true.
03
THE LOSS WAS NOT RIGGED.
Same corpus, comparable effort, symmetric methodology fallback, zero false positives on both sides, honest negatives on both sides — and the control read more source, not less. The scoping that cost ARM A was partly an operator choice; a brain-armed agent could have queried es_compat. But it was a choice the tool's design encouraged, and the single axis where the brain genuinely helped — triage by file density — is the same axis that produced its recall miss. Publishing the result unedited is the only version of this page worth reading.
04
WHAT WE WOULD CHANGE.
Hypotheses, none of them measured yet. Add an explicit absence query to the agent recipe: enumerate the sibling implementations of a guard, then diff them — the brain can serve that today, but nothing in the recipe tells the agent to ask. Stop letting count-based ranking gate coverage; density is a reading-order heuristic, not a scope decision. And fix the two things this review found in the tool itself — the DOCX accumulation bug and the walker's missing ignore rules — before recommending it for audits over untrusted corpora.

WHAT THIS DOES NOT DO.

Every item below was hit during this run. None of it is hypothetical.

Detection is structural and lexical, not semantic. No LLM anywhere in the indexing pipeline; the embedder is lexical-feature-hash. It finds citations you already wrote, not topical similarity. XERJ is not a graph database. No query language, no shortest path, no PageRank, no unbounded traversal — edges are ordinary documents and expansion is a bounded, batched read, at most 2 hops per call, by design. The graph is a lower bound on citations: 37,699 candidates went unresolved and 1,733 were ambiguous on this run, and none of them became edges. The brain does not find bugs — no index does; it narrows the search and puts related documents next to each other, and on this run that narrowing pointed away from the two worst bugs.

The walker has no ignore rules and will index target/, node_modules/ and .env; stage your folder. The brain has no per-brain authorization, so anything indexed is readable by anyone who can reach the server. The MCP server exposes ten tools, not four, with no flag to restrict it. nodes.total is always 0 in the overview. MCP approval is interactive, so CI agents need the user-level settings path, and xerj-mcp cannot boot the engine. PDF extraction is partial — 107 of 118 files yielded text and there is no OCR. And 281.3 seconds for 693 MB is not a benchmark: it is one run, on one machine, with 8 workers, offered with no comparison to any other tool and none to be inferred.

READ THE WHOLE THING.

The full use case is published: the exact commands, the real terminal output, the real JSON, the MCP transcripts, both arms' findings with their file-and-line evidence, the honest negatives, the effort accounting, and the verdict as written. Setup is one command over your folder plus one config file.

OPEN THE USE CASE OPEN THE AGENT RECIPE
READY?·REQUEST ACCESS

RUN IT ON
YOUR FOLDER.

Tell us what is in your project folder — the specs, the PDFs, the configs, the code — and what you want your agent to be able to ask about it. We'll come back with the command, the MCP config, and an honest read on whether the brain beats grep for your question.

We only use this email to follow up about XERJ — nothing else. No spam, no reselling. ✓ THANKS. WE'LL FOLLOW UP WITHIN ONE BUSINESS DAY.