Most teams have one: a project that works, that people depend on, and that no one has examined in a single systematic pass. This one is 379 files — a Python generation pipeline, a TypeScript runtime, a browser extension, two service layers — still in development, read in pieces by the people building it. Checking a project like that periodically is ordinary. What follows is one such check, written down.
The usual move is to point an AI at it and ask what it does. You get a fluent answer in about thirty seconds. What you don't get is any way to tell which parts of that answer were verified and which were inferred from directory names, a record that still exists next week, or a way to make your second question build on your first.
This article is one complete run of the alternative, end to end: twenty-odd commands, two AI assistants that never saw each other's work, and twenty-seven recorded observations — each citing the files it rests on, at the exact versions those files had when they were read. We picked a moderate-sized project on purpose, so the numbers stay readable; nothing was carved out of it beyond logs/ and tmp/.
Paladin isn't a code tool, incidentally — it examines any body of work from declared perspectives. A codebase and a few programmatic viewpoints simply make the example easiest to check against your own experience.
Every command below is as typed, and every number as returned.
Setup, and one decision only you can make
paladin-next init
Paladin Next notebook created.
State directory: .cheetahlabs\paladin-next
Notebook id: 01KZNFSA4XPSTK8TC9XFAD2PCC (stable across moves — identify the notebook by this, not by path)
11 directories, 3 records written.
Harness guide: .cheetahlabs\paladin-next\PALADIN_NEXT_AI_GUIDE.md
Seeded with one adapter: 'repo' [repo] over the whole project.
Note what it did not ask for: this project has no git repository. Paladin doesn't need one — the notebook is its own record.
Then the first decision that is genuinely yours. Paladin will index everything under the project root, and this project has logs/ and tmp/ directories full of runtime output. That is not project material. Nobody should be reading it, and it should not sit in your coverage numbers forever, quietly counted as "not examined yet."
paladin-next adapters set-exclude repo --exclude logs --exclude tmp
Adapter 'repo' now excludes 2 pattern(s): logs, tmp
Exclude patterns for 'repo' take effect on the next scan; nothing is re-inventoried now.
After that scan, subjects under an excluded path DROP OUT of the inventory, and existing
citations to them render as UNRESOLVABLE — staleness semantics, not data loss: no record
is deleted or rewritten, and every judgment, decision, and receipt stands.
That paragraph is the house voice. Paladin tells you the consequence of your decision before you feel it — including the one that will bite in three weeks.
paladin-next scan
350 subjects, in 676 milliseconds. Twenty-nine files excluded, each with a reason on the record: 8 binary, 16 gitignored, 4 by your new patterns, 1 oversize. Exclusions are facts here, not silence.
The notebook now: 350 indexed · 0 examined · 0 observations · 0 invitations Added by this step: an inventory. Paladin knows what exists. It has read none of it.
The question you can't answer yet: what should I even ask?
This is where most attempts to point an AI at an unfamiliar codebase quietly fail. You're expected to know what to ask — and knowing what to ask is the expertise you're missing. "Explain this codebase" gets you a confident essay you cannot check.
paladin-next viewpoints
On an untouched project, Paladin offers exactly one thing:
Everything else tells you why not — and the reasons are counted out of your project, not printed from a template:
- How failures surface — "Available once at least one area has been mapped."
- Trust boundaries — "Available after the first investigation."
You are not writing an expert prompt. You are picking from a short menu that Paladin justifies from its own state. Supplying the question is the point.
None of this is beyond an experienced engineer with an AI assistant and some patience — you could drive the same sequence by hand. What is hard is doing it the same way every time: remembering what last month's pass already covered, not re-reading the comfortable files, not quietly skipping the step you always skip. That discipline is what the tooling is actually carrying. And one part doesn't transfer to prompting at all: the starting points are seeded, so each round deliberately puts material in front of the investigator that nobody thought to ask about — which is precisely what someone steering by intuition will never do.
Handing the reading to a machine
Two commands turn that choice into a dispatch.
paladin-next brief --viewpoint structural-map
A brief is a seeded, reproducible set of starting points — six here — each with a mechanical reason attached:
repo:runtime/package-lock.json why: ground subject never examined (infinite attention debt)
repo:saas/app/services/seed.py why: ground subject never examined (infinite attention debt)
That why is deliberately dumb. Paladin doesn't claim the starting point is meaningful — only that it was honestly chosen. Meaning is the investigator's job, and pretending otherwise would be the first lie.
paladin-next handoff --brief br_01KZNH0AEDTHH612JHBTCN5MC1
This produces the dispatch packet — guide, schema, brief, and a prompt — in one of two delivery modes, and the choice depends on what kind of assistant you're pointing at your code:
cli — the assistant records for itself:
record-run --check --file <bundle> # rehearse: validate, write nothing
revision # get the current state token
record-run --file <bundle> --expected-revision <token>
file — the assistant is a courier. It writes exactly one file to a path Paladin names, runs nothing, and a trusted host records it after the assistant exits. If you're running an agent against your own repository with no execute rights — which you probably should be — this is your mode.
The prompt Paladin writes for you opens like this:
"You are investigating on behalf of Paladin Next, which keeps a durable, hash-chained record of what this project has actually been examined for — and its whole value is that the record says what was really done, not what could have been done. […] treat its starting points as bounded starting points and not as a cage…"
Paste that at Claude, Codex, a local model, whatever you have. It reads.
The line that matters: supplied 6, examined 40
The assistant rehearses before it commits:
record-run --check: bundle is VALID. It would write (nothing was written):
Would write: 15 item(s), 2 judgment(s), 8 relation(s), 1 receipt.
Receipt: supplied 6, accessed 53, cited 33, examined 40, widened 47, abandoned 0, excluded 6.
Recorded run rn_01KZNJ59M7S7328GHTHTZ79CXM.
Written: 15 item(s), 2 judgment(s), 8 relation(s), 1 receipt.
Read the receipt line again. The brief handed the assistant 6 files. It opened 53, read 40 carefully enough to cite or rule out, and went and found 47 of those on its own initiative.
Every one of those is a separate column because they mean separate things. "I was given this file" and "I read this file" are not the same claim, and a system that blurs them will eventually tell you a directory is fine because something once glanced at its name. Here, an investigation that wandered far beyond its brief isn't hidden — it's measured, and the wandering turns out to be where the value was.
What it found
paladin-next view know
saas/— a second, independent multi-tenant service that re-implements the platform around the engine but is not yet wired to it
generator/gen/run_parser.js— the 57-line subprocess bridge that is the single joint between the Python pipeline and the TypeScript runtime
runtime/— the TypeScript engine that executes a parser definition against a DOM, and the only part shipped to users
Fifteen observations in all; three shown. Each is a claim with its evidence attached rather than a summary. The saas/ observation, for example, cites the factory function that currently returns a mock and the two backends that raise NotImplementedError, at the versions those files had when they were read — a component mid-build, now written down with its state instead of rediscovered later. The run_parser.js observation records that the joint between the Python and TypeScript halves is a single file coupled by relative path to built output: worth knowing, and not declared anywhere.
paladin-next coverage
Coverage — where attention has and has not gone
Sources indexed: 350
Ever examined: 40
Never examined: 310
By top-level directory (12):
api/ 39 indexed 6 examined 33 never
generator/ 80 indexed 8 examined 72 never
runtime/ 35 indexed 7 examined 28 never
The header is load-bearing. This is attention, not correctness. "Examined" means somebody actually read it — never that it's fine. The moment a tool lets those two blur, its numbers become decoration.
This is also the column a linter or a pull-request review can't give you. Those look at what changed, or at what a fixed rule matches. Neither accumulates a picture of the whole, and neither can tell you what nobody has looked at yet — which, on an unfamiliar project, is the number that decides where to go next.
The notebook now: 350 indexed · 40 examined · 15 observations · 15 invitations Added by this step: 15 observations, 8 relations between them, 1 receipt, 1 run — all under
rn_01KZNJ59M7…, all citing files at exact versions.
Your turn — and nothing is waiting for you
paladin-next view needs-you
What needs you? (the decision queue, most urgent first)
Reviewed — awaiting your decision:
- api/ — the single-tenant FastAPI server that fronts capture, generation and execution
This open item has independent review and no decision yet; a project decision is invited.
- chrome-plugin/ — the MV3 browser extension that captures a live page and drives the round trip
This open item has independent review and no decision yet; a project decision is invited.
The word in every reason line is invited, never required. Nothing in Paladin blocks on a human. Investigations don't queue behind your attention; an invitation you ignore for a month is still there, still true, and still honest about how long it has waited.
A second opinion, from a mind that never saw the first
Here's the move that's tedious by hand and nearly free here.
You have fifteen claims from one assistant. Point a different assistant at the same claims — one that can read the project and the records but not the first one's reasoning — and have it judge them. Same mechanism as before: it writes a bundle, you record it. The only thing that changes is who signs it.
In this run, the second opinion came from a different model family altogether, and recorded itself accordingly:
{ "kind": "llm", "name": "OpenAI Codex", "id": "openai-codex-reviewer",
"model": "gpt-5", "harness": "codex",
"roleProfile": "reviewer:external-second-opinion" }
It examined all fifteen, re-checked all forty-one citation hashes against the files on disk, and returned twelve supports, two corrections, one extension — and zero disagreements.
That zero is deliberate. It was told explicitly that finding nothing wrong was a valid outcome. A reviewer required to disagree is a reviewer manufacturing disagreement, and you'd never be able to tell which of its objections were real.
The two corrections were real:
"
profile.jsonis explicitly mutable and is written outside the lock, so concurrent stores of the same parser hash with different profiles are last-writer-wins rather than automatically safe by content addressing. Also the specification's logical identity includesschema_version, while the implemented index keys onlyurl_patternand normalized intent."
"The code proves there are two distinct preprocessing implementations with overlapping names and different algorithms. It does not, without version history or an explicit provenance note, prove that
experiments/preprocess_strategies.pyis a fork or that it is older."
Both actors are on the record as llm, with their model, harness, and role. Paladin never scores whether two reviewers were truly independent — it records what each one was and shows you. Independence is a judgment about circumstances; circumstances are exactly what a tool can record and exactly what it cannot evaluate.
Nothing here is fixed at two, or at "one investigates, one checks." That was just this run. Contributions accumulate in whatever order they arrive: one assistant maps a few areas today, a different one adds to the same record next month, a third takes a slice nobody has touched, and any of them may judge what an earlier one recorded. Each contribution is signed by whoever made it, which is what lets them add up instead of overwrite each other. People contribute at a far smaller number of points — mostly the decisions.
Deciding — and deferring
Now the queue means something, because the corrections landed before the decisions.
paladin-next decide it_01KZNJ59K4MGR8GN2RT0A8EEH5 --decision accepted \
--actor-kind human --actor-name kim \
--note "Verified by two independent actors; cited at current hashes."
Decided it_01KZNJ59K4MGR8GN2RT0A8EEH5: accepted
(run rn_01KZNKNX5NZGZKQPFSNAA0P532, judgment jd_01KZNKNX658VKG5NPDM5VXW067).
And where the second opinion changed the answer:
paladin-next decide it_01KZNJ59K4MGR8GN2RT0A8EEHA --decision deferred \
--actor-kind human --actor-name kim \
--note "Second opinion corrected two aspects. Deferred until the claim is revised."
Five accepted, two deferred, none rejected — nothing warranted rejection, and recording one to look rigorous would have been a lie in a ledger whose only job is not to contain lies.
Deferring is a real answer, not a snooze button. The item leaves every bucket of your queue immediately. It comes back only when something actually changes — which, later in this story, it does.
One detail worth noticing in those flags: --actor-kind is explicit for a reason. An AI acting on your behalf must record itself as llm with its model, never as human, however human-equivalent the judgment was. Delegation rides on --actor-role (delegate:product-owner), not on lying about what you are.
The notebook now: 350 indexed · 45 examined · 15 observations · 8 invitations Added by this step: 15 independent judgments and 7 decisions. The queue shrank because questions were answered, not because they were hidden.
Round two: the part a chat cannot do
Run the chooser again. It has changed underneath you:
| viewpoint | reason |
|---|---|
| How failures surface | "Applies across the 12 areas mapped so far." |
| Trust boundaries | "Applies across the whole project — 305 of 350 files have never been examined." |
| Documentation vs. reality | "76 documentation files indexed; 67 have never been examined." |
| Contracts between parts | "12 areas mapped, with 8 recorded connections between them." |
| A second look at what we believe | "15 observations from 9 investigations have never been compared with one another." |
| Map what's here | retired — "The project has already been mapped; revisit only if its structure changed substantially." |
Every number in that table came out of round one. Seven lenses are available that weren't available before the first round, each explaining itself with a count you can verify. And Map what's here has retired itself — it knows it's done.
We took Documentation vs. reality. The brief looks different this time, too: where round one drew all six starting points from "never examined," this one drew three from purposeful (matched on the concern), three from neglect, and one from existing knowledge — pointing at a recorded item rather than a file, because now there is knowledge worth revisiting.
Nine drifts came back. Three of them:
"Three separate documents state the runtime suite has 91 tests; the committed suite defines 114"
"Both
.env.exampletemplates offer a three-provider LLM choice that no code in the tree reads"
"Task 030's documentation alignment pass is recorded Done, yet three of the four categories it names still have live counterexamples in one file"
Each claim cites the documentation and the source that contradicts it, both at content hashes:
repo:README.md "Run Tests block, line 53 — '# Runtime (91 TypeScript tests)'"
repo:runtime/run_tests.js "readdirSync(dist/cjs).filter(f => f.endsWith('.test.js')) …
'npm test' runs every compiled test file, none excluded"
The investigator also killed three of its own candidate findings after checking them, and said so in the record. Negative results are results.
Here is the part worth the whole article. Asked what the standing notebook actually changed about its work, the investigator wrote:
"Three of nine claims would have been very unlikely cold. […] Cold, the port 8000, the Anthropic block, the model name and the test count are four unrelated nitpicks; with the record and task 030 in view they are one failure with a name — a manual sweep, marked complete, over files nobody enumerated."
That is the difference between an answer and a body of knowledge. Round two wasn't a better prompt. It was a round that could see round one.
The notebook now: 350 indexed · 71 examined · 27 observations · 20 invitations Added by this step: 9 drift claims, 11 relations tying them to the areas mapped in round one, 2 new questions.
Keeping a good question
That documentation pass was worth having again. Save it, and it joins the menu beside the built-in nine:
"governance and setup surfaces — task-file status claims, setup instructions, and environment templates versus the code they describe"
paladin-next brief --saved-viewpoint it_01KZNNFW9FCK04RNHBGCGCQ77B
Paladin reads the question off the record itself — you never retype it — and stamps that lens's identity onto the run. Months later, show-run still says which lens produced which knowledge.
Then the part that usually goes wrong in tools like this: you improve the wording. Nothing forks. The new wording supersedes the old, the old one closes, your menu still shows exactly one entry, and both runs stay grouped under one logical lens — each remembering the exact wording it ran under.
Refining a question doesn't fragment its history. Which is the only reason anyone will actually refine one.
When the code moves underneath you
Weeks pass. The project changes: files edited, two deleted, a directory renamed, one Markdown file overwritten with binary content.
paladin-next scan
subjects 350 → 346
diff: added 41, modified 32, deleted 45
{"locator":"runtime/README.md","reason":"binary","detail":"null byte"}
It caught that last one by content, not extension — the path still ends in .md.
Now watch what happens to the knowledge:
| what changed | what the record says |
|---|---|
| a cited file was edited | citation rotted; the item carrying it is stale |
| a cited file was deleted | unresolvable — currentVersion: null |
| Markdown became binary | unresolvable — not "stale," and no invented hash for bytes it won't treat as text |
| edited, then restored byte-for-byte | current again — with no fabricated re-verification |
That last row is the one I'd watch for in any tool making these promises. The file came back to its exact original bytes, so the citation is honestly current again — and nothing pretends a human re-checked it.
And the deferred item — the one the second opinion corrected, that you set aside weeks ago — comes back on its own:
{ "bucket": "accepted-evidence-rotted",
"status": "deferred",
"affordance": { "reasonCode": "evidence-rotted-standing",
"availability": "required-for-honesty" },
"citationStates": [{ "source": "repo:api/services/parser_store.py",
"state": "rotted" }] }
It's back because the evidence your deferral rested on changed. Not because a timer expired. The chooser agrees, without being asked:
Recheck what changed — "2 accepted and 2 deferred records rest on files that changed since."
Re-verification is the same loop: an assistant re-reads the changed source and records a judgment citing it at its current hash. And it refuses to round up. One item here had two rotted citations; after re-verifying one, it was still stale. Only when both were re-examined did the flag clear.
No partial credit for partial checking.
What it costs
Paladin's own overhead is small. On this project — 350 files, 44 MB, a notebook of twenty-eight records — every command is sub-second:
| command | median |
|---|---|
scan | 659 ms |
project (all ten views) | 829 ms |
coverage | 533 ms |
validate --json | 734 ms |
record-run (30 items) | 750 ms |
The expensive part is an AI reading your code — which is the part you wanted done anyway, and whose cost depends entirely on which model you point at it and how much you run in parallel.
What it refuses to do
It is not a correctness oracle. "Examined" means read, never verified-good. accepted means a person with authority decided — and Paladin records who decided, while having no idea whether they were entitled to.
It won't tell you your two reviewers were really independent. It records what each one was — model, harness, role — and shows you. That's a fact. The judgment is yours.
It won't wait for you. If you never open the queue, investigations keep running and invitations keep accumulating, each honestly labelled with how long it has waited.
The pitch isn't that an AI read your codebase. It's that a year from now, you'll still know which parts of it anyone actually looked at.
Provenance: this walkthrough is one real run against a real project, drawn from a larger test campaign; commands and outputs are verbatim. Three snippets — the invitation queue and the two counts beside it — were regenerated after a defect that same campaign found (a silently capped queue), so what you see is the corrected behaviour rather than the behaviour we shipped into the test.