Your docs pass review because the reviewer already knows the answer.
agentprobe sends an AI agent through them cold — in a Docker sandbox, with no prior knowledge — and reports every place it got stuck, with file:line and a suggested fix.
Open-source · local-first · BYOK · MIT
We pointed agentprobe at three of the most-complained-about docs in open source — LangChain, boto3, and FFmpeg — using nothing but the generic built-in tasks (quickstart, auth-setup, error-handling). No hand-tuning, no per-SDK config, about 45 minutes of wall time. All three came back 0/3 on pass rate.
LangChain — three READMEs fighting each other. The root README.md presents itself as primary. libs/langchain/README.md calls itself "langchain-classic" and says it's legacy. libs/langchain_v1/README.md also calls itself "LangChain". All three tell you to pip install langchain, and no page explains the relationship. This is the v0 / v1 / classic confusion the community has been complaining about for over a year — surfaced cold, in one run, by an agent that had never read the complaints.
boto3 — docs/source/guide/quickstart.rst:112. The "where to obtain credentials" section bottoms out in external IAM Console links and stops: no aws configure, no ~/.aws/credentials sample, no export AWS_ACCESS_KEY_ID=…. The agent's notes truncate mid-sentence, because there was nothing left in the SDK to read.
FFmpeg — doc/protocols.texi:1. 2347 lines of accurate protocol reference, with authentication scattered across the AMQP (line 66), FTP (376), and HTTP (512) sections and no index or entry point. The agent read top-to-bottom trying to synthesize a unified guide and hit its output limit at "AMQP with Au". A human ctrl-Fs their protocol in 30 seconds; an agent without that meta-knowledge drowns.
Three unrelated repos, three languages, one shared failure mode — the example ends one line too early:
| Repo | Location | What's missing |
|---|---|---|
| LangChain | README.md:37 |
no print() after model.invoke() |
| boto3 | README.rst:88 |
no expected output after the S3 list-buckets call |
| FFmpeg | doc/examples/show_metadata.c:24 |
no sample key=value output |
Humans skim past this because they already know what model.invoke / list_buckets / metadata extraction should print. An agent has no such intuition: it writes the code, runs it, and has nothing to compare the result against. The line between "the code" and "did it work" is where docs reliably die — and it's the line agentprobe is built to find.
Honest accounting: across those three runs the judge emitted 15 issues and 7 survived our own triage. The discards were mostly duplicates of a pattern already reported against another repo, plus one premise mismatch — the auth-setup task assumes account-level authentication, which FFmpeg simply doesn't have. Expect to skim a shortlist, not to merge a patch series unread. See Limitations.
You point it at a repo and pick the tasks you want exercised:
agentprobe scan --repo https://github.com/stripe/stripe-node --tasks quickstart,auth-setupThe sandbox boots, Claude Code runs each task, the judge reads the resulting trace, and you get a markdown report:
## stripe-node — auth-setup (partial)
### Issue 1 — missing_step (confidence 0.85, Critical)
**README.md:42** — also: README.md:50
The agent answered "Partial" on the validation question because no
section describes how to verify a freshly-set API key. The setup section
ends at the `customer.create` example with no "if this prints a cus_…
ID, auth works" line.
**Suggested fix**: insert after line 51:
> ### Verifying your authentication
> Run `await stripe.balance.retrieve()` …
Each issue carries a category (missing_step, ambiguous_choice, naming_inconsistency, stale_doc, unclear_error, implicit_assumption), a primary file:line, optional also_consider locations, and a one-paragraph suggested fix grounded in real lines of your file.
Most "AI for docs" tools either grade your docs in the abstract or rewrite them with an LLM and call it a day. agentprobe does the opposite:
- Real agent, real sandbox — spawns Claude Code in a Docker container, points it at your SDK, and gives it a task (quickstart, auth setup, error handling, etc.). The trace records every Read / Bash / Grep / Edit it makes while wrestling with your docs
- Three-stage judge, not one prompt — Haiku classifies the trace and locates friction events, Sonnet pinpoints the root cause per friction (file:line, category), Sonnet writes the suggested fix. Each stage has a narrow job; structured-output tool_use keeps the schema clean
- Reproducible eval set — 15 frozen traces under
eval/traces/with hand-labeled ground truth issues undereval/cases/.agentprobe evalre-runs the judge on those traces and scores precision/recall against GT. Lets you iterate the judge prompts without paying for fresh sandbox runs - BYOK LLM — set
ANTHROPIC_API_KEYand pay Anthropic directly. No proxy, no relay - MIT-licensed, no telemetry — fork it, audit it, run it on internal SDKs
Requires Node 20+, Docker, and an Anthropic API key. Get a key at https://console.anthropic.com/settings/keys (account + billing required).
git clone https://github.com/agentprobe2026/agentprobe.git agentprobe
cd agentprobe
npm install
npm run buildThen either run via the local dist/cli.js or npm link to get the agentprobe binary on your PATH.
# 1. Set your key (or copy .env.example to .env and fill it in)
export ANTHROPIC_API_KEY=sk-ant-...
# 2. See what tasks are available
agentprobe list-tasks
# ↑ if this prints task names without an auth error, your key is valid
# 3. Run a scan against a public repo
agentprobe scan \
--repo https://github.com/frida/frida-python \
--tasks frida-hook-open \
--out ./runs
# 4. Open the markdown report
$ cat runs/<timestamp>/report.md
# ↑ a report with Critical / Warning issues means the scan worked —
# that's the tool's job. An empty issues list means the agent finished
# the task without friction. Exit code 0 = scan completed; non-zero
# = sandbox or judge failure (see Troubleshooting).Each scan run writes its sandbox workdir, raw trace JSONL, and the rendered markdown report under runs/<timestamp>/. Pass --thorough to run the judge twice and union results (~2× cost, +5–10pp recall, smooths model variance).
# Re-score the judge on every labeled eval case (no sandbox cost — uses frozen traces)
agentprobe eval
# Or just a few cases while tuning a prompt
agentprobe eval --only 005-stripe-auth,009-langchain-errorsYou get per-case TP/FP/FN, per-category precision/recall, and per-case judge outputs under runs/eval-out/ for inspection.
- Four built-in tasks — three generic ones run by default (
quickstart,auth-setup,error-handling) plus one SDK-specific (frida-hook-open, a category-coverage smoke test) that you opt into via--tasks frida-hook-open. Each is a YAML undersrc/tasks/builtin/and you can add your own - Six docs-defect categories —
naming_inconsistency,missing_step,ambiguous_choice,stale_doc,unclear_error,implicit_assumption. The judge picks one per issue and is rewarded for category-accurate matches by the scorer - Confidence binning — every issue lands in Critical (≥ 0.8), Warning (0.6–0.8), or Drop (< 0.6). The report only shows Critical + Warning by default
- Pipeline-level rescue — when stage 1 (Haiku) under-emits frictions because the agent declared
Validation per docs = Partial / Noor wrote a non-empty## Gapssection, the pipeline scans the agent's own output and injects synthetic frictions so stage 2 sees them - Category hints from regex — paragraphs in the agent's output matching telltale
ambiguous_choice/implicit_assumptionwording get a[CATEGORY HINT: …]tag that pushes stage 2 off its missing_step default - Location hints + fallback synthesis — file:line citations extracted from gap paragraphs are forwarded to stage 2 as
[LOCATION HINT: …], and if stage 2 still returns null on a hinted friction the pipeline synthesizes the issue directly from the hint to preserve recall
agentprobe is a CLI; configuration is per-invocation rather than a settings file. The pieces that change behavior:
| Flag / env | Default | Notes |
|---|---|---|
ANTHROPIC_API_KEY |
required | Sent only to Anthropic. Loaded from .env if present |
--repo <spec> |
required for scan / collect |
Git URL or local path. Local paths are mounted into the sandbox read-write |
--tasks <ids> |
all generic builtins | Comma-separated, e.g. quickstart,auth-setup. SDK-specific tasks (currently frida-hook-open) are skipped by default; pass them explicitly to opt in |
--out <dir> |
./runs |
Where sandbox workdirs, traces, and reports land |
--thorough |
off | For scan: run the judge pipeline twice and union issues. Doubles API cost but recovers ~5–10pp recall lost to temp=0 model variance |
--only <case-ids> |
all eval cases | Comma-separated case ids for agentprobe eval |
LINE_TOL (in src/eval/score.ts) |
8 | Match tolerance between judge line and GT line. Tightening makes the eval stricter |
Models are pinned in src/judge/pipeline.ts (claude-haiku-4-5-20251001 for stage 1, claude-sonnet-4-5 for stage 2/3). Prompts live under src/judge/prompts/*.md and are hot-reloaded on every run — edit and re-run agentprobe eval without recompiling.
┌────────────────────────────────────────────────────────────┐
│ agentprobe CLI │
│ scan · collect · eval · list-tasks │
└──────────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────┴───────────────────────────┐
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ tasks │ │ runner │ │ judge │ │
│ └──────────┘ └────┬─────┘ └────────┬─────────┘ │
│ │ │ │
│ ┌───────┴──────┐ ┌───────┴──────────┐ │
│ │ sandbox │ │ 3-stage │ │
│ │ (dockerode │ │ pipeline │ │
│ │ + Claude │ │ (Haiku→Sonnet │ │
│ │ Agent SDK) │ │ →Sonnet) │ │
│ └──────┬───────┘ └────────┬─────────┘ │
│ │ │ │
│ ┌──────┴──────────┐ ┌─────┴────────┐ │
│ │ trace.jsonl │──→│ report.md │ │
│ └─────────────────┘ └──────────────┘ │
└───────────────────────────────────────────────────────┘
src/cli.ts—commander-based CLI entry point withscan,collect,eval,list-taskssubcommandssrc/runner/sandbox.ts— boots a Docker container, mounts the target repo, runs Claude Code via the Claude Agent SDK, streams events intotrace.jsonlsrc/judge/pipeline.ts— three-stage judge orchestrator. Stage 1 classifies + locates friction with Haiku; stage 2 produces one root-cause issue per friction with Sonnet; stage 3 writes the suggested fixsrc/judge/prompts/*.md— system prompts for each stage, loaded fresh on every run for fast iterationsrc/eval/score.ts— greedy bipartite matcher between judge output and ground truth issues; computes per-category precision/recall and confidence-bin agreementsrc/report/markdown.ts— renders the per-task issue list into the markdown the user reads
- Stage 1 (Haiku) classifies the trace as
success/partial/failureand lists up to 8 friction event indices with one-line summaries. Cheap and fast. - Pipeline rescue — if the agent's output file declared
## Validation per docs = Partial / Noor has a non-empty## Gapssection but stage 1 emitted zero frictions, the pipeline scans the agent's output and injects synthetic frictions so the rest of the pipeline isn't starved. - Pipeline category hints — paragraphs whose wording smells like
ambiguous_choice(e.g. "two different", "vs", "third option") orimplicit_assumption(e.g. "format not documented", "implicit") get a[CATEGORY HINT: …]prepended; file:line citations get a[LOCATION HINT: …]. These steer stage 2 off its missing_step default. - Stage 2 (Sonnet) produces one issue per friction — picks
category,primary_locationwith a concrete file:line (Rule 1 insrc/judge/prompts/root-cause.mdis strict about noline: 1fallbacks except when an entire section is structurally absent), up to threealso_considerlocations, and confidence. Returnsnullif no verifiable defect. - Hint fallback synthesis — when stage 2 returns null on a friction that already carries both CATEGORY and LOCATION HINTs, the pipeline builds the issue directly from the hint so recall isn't bottlenecked by Sonnet's null-return tendency.
- Stage 3 (Sonnet) writes the suggested fix with the file's actual line-numbered content as context. Quotes real lines, not invented ones.
The eval set lives entirely under eval/:
eval/traces/<id>.jsonl— one frozen trace per case, captured byagentprobe collectagainst a pinned commit of the target SDKeval/cases/<id>.json— ground truth: classification + a list of expected issues, each with{ category, file, line, agent_did, why_agent_wrong, why_human_wouldnt, suggested_fix, confidence }
agentprobe eval re-runs the judge against the frozen traces and uses a greedy bipartite matcher (src/eval/score.ts) that matches a predicted issue to a GT issue when:
- file path matches (basename-forgiving), AND
- predicted line is within ±8 of GT line (
LINE_TOL), AND - categories are identical
Matching is greedy and prefers primary_location matches over also_consider matches. The scorer reports precision, primary_recall, any_recall, classification accuracy, and confidence-bin agreement.
To add a new case:
agentprobe collect \
--repo https://github.com/<owner>/<repo> \
--task quickstart \
--label 016-<repo>-quickstartThis runs one sandbox pass, freezes the trace under eval/traces/, and writes an empty eval/cases/016-…json template you fill in by hand.
- Anthropic-only judge — stage 1 hits Haiku, stages 2 / 3 hit Sonnet. Swapping providers means rewriting
src/judge/pipeline.ts'scallWithStructuredOutputto a provider-agnostic shim - Tool_use determinism is approximate — Anthropic's
temperature=0is not bit-reproducible across runs. Per-case TP/FP/FN can drift by ±1 between runs of the same trace. Aggregate metrics are stable; case-level scores are not - Docker required — there's currently no in-process fallback for environments where Docker isn't available
- Category coverage is uneven —
missing_stepandnaming_inconsistencyperform well;ambiguous_choiceandimplicit_assumptionrecall depends heavily on whether the agent explicitly worded the gap (regex + hint heuristics catch the common phrasings but not pure semantic-comparison cases)
- The Docker sandbox sees only the target repo. Your local files don't enter the container unless you mount them with
--repo <local-path>. - agentprobe sends data to Anthropic only — the agent's reasoning during the sandbox phase, the trace excerpt + agent output files during the judge phase.
- No telemetry, no analytics. Logs are local to
runs/andeval/.
Requirements: Node 20+, Docker (or Docker Desktop on Windows/macOS), Git.
git clone https://github.com/agentprobe2026/agentprobe.git agentprobe
cd agentprobe
npm install
npm run dev -- scan --repo <some-public-sdk> # tsx watch-mode equivalent of the built CLI| Script | Purpose |
|---|---|
npm run dev -- <args> |
Run the CLI through tsx without rebuilding (good for iterating on prompts / scoring code) |
npm run build |
tsc + copies prompt .md files and built-in task yaml into dist/ |
npm run typecheck |
Type-check without emitting |
npm test |
Vitest suite (parser tests, scorer tests) |
agentprobe/
├── src/
│ ├── cli.ts CLI entry (commander)
│ ├── tasks/
│ │ ├── builtin/*.yaml task definitions: prompt + success_checks
│ │ └── loader.ts
│ ├── runner/
│ │ ├── sandbox.ts Docker container + Claude Agent SDK driver
│ │ └── trace.ts JSONL event writer
│ ├── judge/
│ │ ├── pipeline.ts 3-stage orchestrator
│ │ ├── prompts/*.md stage 1 / 2 / 3 system prompts
│ │ └── index.ts schemas + types
│ ├── eval/
│ │ ├── score.ts greedy bipartite matcher + reporters
│ │ ├── schema.ts ground-truth case schema (zod)
│ │ └── collect.ts `agentprobe collect` implementation
│ ├── report/markdown.ts issue list → markdown
│ └── utils/logger.ts
├── eval/
│ ├── cases/*.json ground-truth issues per (repo, task) pair
│ └── traces/*.jsonl frozen agent traces — `agentprobe eval` consumes these
└── scripts/copy-assets.mjs copies prompts + builtin yaml into dist on build
Symptom: the CLI exits with ANTHROPIC_API_KEY is not set (looked in .env and environment).
- Verify the key is exported in the same shell you're running agentprobe from:
echo $ANTHROPIC_API_KEY. The value must start withsk-ant-. - If you're using a
.envfile, make sure it sits next to where you invoke agentprobe (the CLI usesdotenv/config). - Generate or rotate keys at https://console.anthropic.com/settings/keys.
Re-run any agentprobe command after fixing — agentprobe list-tasks is the cheapest smoke test.
Symptom: scan / collect errors with "failed to connect to the docker API …" or "no such image".
- Start Docker Desktop (macOS / Windows) or
sudo systemctl start docker(Linux). - First run also has to build the sandbox image (
agentprobe-sandbox:latest). If the image is missing, build it once viadocker build -t agentprobe-sandbox:latest docker/from the repo root.
Symptom: ERR_FS_CP_EINVAL: cannot copy <path> to a subdirectory of self.
You're scanning a directory and writing --out inside that same directory. Move --out outside the target repo, e.g. --out ../scan-runs.
Symptom: task ends early or container exits with non-zero before producing output files.
Long-running tasks may hit Docker's default memory / CPU limits. Raise them in Docker Desktop → Settings → Resources (give Docker ≥ 6 GB RAM for the typical task). If a task consistently times out, raise its timeout_seconds field in the YAML under src/tasks/builtin/.
PRs welcome. The interesting surfaces:
src/judge/prompts/*.md— iterate prompts, runagentprobe eval --only <few cases>to validatesrc/judge/pipeline.ts— hint heuristics, synthesis fallback, signal extractioneval/cases/*.json— add more labeled cases, especially for under-covered categoriessrc/tasks/builtin/*.yaml— new task types
A change is "validated" when its eval-set numbers don't regress on the dev cases and ideally improve on the target case.
MIT — see LICENSE.