Imported from coltonbearden/carrel (
docs/AGENTS.md). Install upstream withnpx skills add coltonbearden/carrel --skill docs. Copyright stays with the author.
AGENTS — the agents that built carrel, and the agents carrel ships
Two distinct casts:
- Builder agents in
.claude/agents/— Claude Code subagents that built this repo, wave by wave. They stay useful for maintenance. - Shipped agents & skills in
plugins/— what users get when they install the marketplace plugins (MARKETPLACE.md).
The five builder agents
| Agent | Role | Ground rule that keeps it honest |
|---|---|---|
module-builder |
Implements exactly one spec from specs/ — command module + its tests |
Touches only the paths its spec's Owns line lists; must paste real pytest output; no stubs/TODOs |
test-engineer |
Fixtures, integration tests, cookbook validation | Fixtures generated programmatically (tests/fixtures/generate.py), never hand-crafted binaries; drives the real CLI, no fs mocking |
integration-reviewer |
Adversarial cross-module review | Verifies by execution, not by reading reports — runs --help, fixture invocations, `--json |
doc-smith |
Reference docs, guides, cookbook recipes | Never documents a flag it didn't see in real --help output; runs every recipe before writing it down |
design-artist |
Visual identity: SVG logo/banner, palette, README/TUI theming | Original hand-authored SVG only; palette defined once in docs/BRAND.md |
How they actually built carrel: waves
The orchestrator session dispatched ≤4 builder agents in parallel per wave
(BUILD_PLAN.md), verified each wave personally by running smoke tests,
then committed wave(N): ...:
- Wave 1 — test-engineer (fixtures + core tests) ∥ module-builders (doctor+mcp, pack, edit). No cross-deps: Wave 1 tests synthesized their own inputs because the shared fixtures were being built concurrently.
- Wave 2 — module-builders: convert, ocr, inspect+diff, index/search/tag/note (now against shared fixtures).
- Wave 3 — module-builders: thumb/extract-images/proof/color, watch/organize/dedupe, redact/sign/form, and the marketplace + 5 plugins. MVP line.
- Wave 4 — audiobook, the desk TUI, snippets+cookbook seeds (doc-smith), and an integration-reviewer sweep whose findings (flag drift in a plugin doc, a wrong exit code, a silent overwrite) were all fixed and re-verified — see TEST_REPORT.md.
The enforcement pattern that made this work: every agent's completion report must contain executed output (pytest tails, real invocations), and the reviewer re-runs everything anyway. Claims are verified by execution, never trusted.
The shipped agents (what plugin users get)
doc-converter (carrel-convert)
Batch conversion specialist. Invoke it when many files need converting/OCR-ing/
thumbnailing at once. Its method: Glob the exact inputs → check support
(carrel convert --help matrix, carrel doctor --json) → one multi-source
carrel --json convert SRC... --to EXT --out-dir DIR → verify every output with
carrel inspect --json → report a converted/failed/skipped scoreboard. Never
overwrites without an explicit --force ask.
file-librarian (carrel-agent)
Question-answering over a local document collection, with citations. Its method:
locate the desk root → carrel --json --root DIR index (incremental, always safe) →
carrel --json --root DIR search "QUERY" with 2-3 FTS5 formulations → open the top
hits to verify (snippets are leads, not answers) → answer with a Sources: list of
file paths. Persists findings on request via carrel tag add / carrel note add.
Driving carrel from Claude Code in practice
Day-to-day, you combine three layers:
- Slash commands for one-offs:
/inspect report.pdf,/pack ./papers as xml. - Agents for batches and Q&A: "convert everything in ~/scans to searchable PDFs" (doc-converter), "which of my papers mention distillation?" (file-librarian).
- The
agent-workflowsskill (carrel-agent) for recurring pipelines. It teaches four patterns: watch +claude -p, index-then-ask, desk-over-MCP, and pack-for-a-second-opinion.
Worked example: the watch + claude -p loop
The pipeline from the skill — auto-summarize every PDF dropped into a folder — with
every flag verified against the installed carrel watch --help / carrel convert --help:
carrel watch ~/inbox --on created --glob '*.pdf' \
--run 'sh -c "carrel convert {path} --to txt -o /tmp/drop.txt --force && claude -p \"Summarize /tmp/drop.txt in 5 bullets\" >> ~/inbox/summaries.md"'
How it works, piece by piece:
carrel watch DIRis non-recursive and watchdog-based;--on createdfires on new files only;--glob '*.pdf'narrows matches.--run CMDruns per event;{path}(also{name},{dir}) is substituted with the triggering file, already shell-quoted. Repeatable — multiple--runflags execute in order.- Inside the action: convert the PDF to text, then a headless Claude turn (
claude -p) appends a 5-bullet summary to a running markdown log. Keep the prompt self-contained and route output to a file, never the terminal. - Test before trusting: add
--once --timeout 60for a bounded dry run, drop a fixture PDF in from another shell, checksummaries.md.--debounce 500(default) absorbs editor save-storms;--json-linesgives machine-readable logs.
Loop hygiene (from the skill, enforced by the CLI's own design): indexing is
incremental and --if-indexed makes hook-style reindexing a no-op until a desk exists,
so loops can run unconditionally; carrel dedupe cannot delete anything without both
--delete <policy> and --apply, so keep automation on the report side; bound every
unattended loop with --timeout.
The MCP server: tools and resources
With the carrel-agent plugin enabled (.mcp.json → carrel mcp), the whole desk is
available as structured tools, so Claude does not need Bash for any of it. The list
below is what tools/list returned on 2026-09-04 (newline-delimited JSON-RPC 2.0 over
stdio; initialize advertises capabilities: {"tools": {}, "resources": {}}):
| Tool | Purpose | Arguments (required in bold) |
|---|---|---|
carrel_search |
Full-text search of the desk index under a root; needs a prior carrel index |
query, root, limit, types, tags, meta |
carrel_pack |
Pack a file or directory into LLM-ready context (tree + extracted text of text, pdf, office and ebook files); query ranks through the index. FTS5 AND-s the terms of a query, so a natural-language question often matches nothing; the CLI exits 5 under --json rather than returning an empty document |
path, max_bytes, tree_only, format, include, exclude, root, query, top |
carrel_inspect |
Metadata for one file: type, size, mtime, sha256, mime, per-type detail | path, deep, root |
carrel_tag |
Add/remove/list tags on a file, or find files by tags | action (add/rm/ls/find), path, tags, root |
carrel_note |
Attach a free-text note to a file, or list its notes | action (add/ls), path, body, root |
carrel_index |
Build or refresh the index; returns indexed/skipped/pruned counts | paths, update, prune, ocr, root |
carrel_convert |
Convert a file to another supported type; text targets return the content inline (capped at 1 MiB) | path, to, out_dir, force, root |
carrel_diff |
Compare two files (text / struct / pdf / image); differ is data, never an error |
a, b, mode, root |
carrel_redact |
Redact patterns from a text file's contents and return the result; never writes; PDFs must go through the CLI | path, builtin, pattern, replacement, root |
carrel_doctor |
Environment report: tools found, per-command status, capability table | — |
carrel_meta |
Typed fields on desk files: set/get/ls/rm on a file, or find files by conditions (total>1000, due<2026-11-01, paid?) |
action (set/get/ls/rm/find), path, fields, key, keys, conditions, source, root |
carrel_fields |
Vendor, invoice number, PO, dates, subtotal/tax/total, currency, IBAN, account last-4 from a document, each with a confidence; save writes desk fields |
path, profile, date_order, ocr, save, root |
carrel_mail |
Email files: save a message's attachments into a directory, or group the messages of files/directories into threads | action (attachments/threads), path, out_dir, force, root |
carrel_refs |
Find reference numbers (invoice, PO, order, check, account, tracking, ticket, IBAN, routing, EIN, VAT, ISBN, GTIN, DOI, UPS, USPS) in a file or directory; tag writes ref:<kind>:<value> tags, link groups files by shared value |
path, kinds, patterns, tag, link, all, ocr, root |
Relative paths resolve against the server's root (its cwd unless carrel --root … mcp);
every tool accepts root per call. Failures arrive as isError: true carrying the same
message the CLI prints — including the install hint for a missing binary — never a crash.
The server is confined to that root (D-021). Every path a client names — a tool's
path/paths/out_dir, a per-call root, and both carrel:// resource URIs — is resolved
with symlinks followed and refused if it lands outside: isError: true with exit code 2
for tools, resource-not-found for resources. So a per-call root can narrow the desk but
never leave it. Start the server with carrel mcp --allow-outside-root when a client
genuinely needs the whole filesystem; note that carrel --root / mcp is unconfined by
construction, because / is then the desk the user named.
Two resource templates (resources/templates/list) let Claude read without calling a tool:
| URI template | MIME | Returns |
|---|---|---|
carrel://file/{path} |
text/plain |
Extracted text of one file (URL-encode the path; relative to the root) |
carrel://search/{query} |
application/json |
The carrel_search payload for a URL-encoded FTS5 query |
resources/list is intentionally empty (enumerate a desk with carrel_pack +
tree_only), and an unknown URI is JSON-RPC error -32002. A real
resources/read of carrel://file/tests/fixtures/sample.txt returned
{"contents": [{"uri": …, "mimeType": "text/plain", "text": "Carrel sample text fixture. …"}]}.
Wire details in ARCHITECTURE.md; plugin setup in
MARKETPLACE.md.
See also
- PLUGIN_AUTHORING.md — ship your own agent/skill in a plugin.
examples/cookbook/— executable versions of these pipelines (02 = watch loop, 08 = pack-for-Claude, 10 = pack what matters with--query).