Once a whole file is in an agent's context it is replayed on every turn, and nothing that compresses it afterwards can undo that without breaking the prompt cache. sym keeps the file out in the first place: a skeleton, one symbol, or a budgeted map of a directory, parsed with tree-sitter.
$ cargo install starlab-sym # the binary is `sym` $ sym ls src/ops.rs # every symbol: line range + signature src/ops.rs — 271 lines, 14 symbols 10-15 pub struct LsOut 58-104 pub fn read(file: &Path, symbol: &str) -> Result<ReadOut, String> ... $ sym read src/ops.rs read # one symbol, with its doc block $ sym map src/ --budget 800 # files ranked by PageRank over the imports, cut at 800 tokens
Add --json for structured output and --est for a token estimate. Nested symbols are addressed by leaf or qualified path (Widget::new, Runner.helper, Server.Serve).
A skill that says when to use which verb, the MCP server, and a hook that nudges any whole-file Read of a big source file toward the skeleton. Silent when sym is not installed.
$ claude plugin marketplace add https://github.com/codylwalker/sym $ claude plugin install sym@starlab
Tools sym_ls, sym_read, sym_map over stdio. Same arguments as the CLI; json: true for structured results.
{ "sym": { "command": "sym", "args": ["mcp"] } }Managed agents, web agents and sandboxes without cargo get map and read over a git URL. The server clones shallowly, caches for an hour, and answers in the same shape as the CLI. It is also an MCP server over HTTP, one line in any client:
$ claude mcp add s2ar --transport http https://api.s2ar.dev/mcp --header "Authorization: Bearer <key>" $ claude plugin install s2ar@starlab # or: the same server as a plugin, with skills and free reminders; no key needed to start tools: sym_map_repo $0.01 · sym_ls_repo · sym_read_repo · sym_find_repo $0.005 · sym_where_repo $0.01 · score_text $0.005 · assert_output $0.005 · inspect_x402 $0.005 · certify_image $0.01 · buy_credits every paid tool takes an optional x402_payment argument (USDC on Base per call, no key); ten languages: Rust, Python, TypeScript/TSX, JavaScript, Go, Lua, C, C++, Java, Ruby
Over plain HTTP every route takes GET with query parameters or POST with a JSON body, and repo is owner/repo, github.com/owner/repo or the URL. Payment is the 402 handshake, in three rails ordered by how autonomous the agent can be.
GET https://api.s2ar.dev/v1/sym/read?repo=BurntSushi/ripgrep&ref=14.1.1&file=crates/core/flags/defs.rs&symbol=Glob%20as%20Flag
← 402 Payment Required
{ "x402Version": 1, "accepts": [ … ] } # x402: USDC on Base, no account; v2 in the PAYMENT-REQUIRED header
{ "credits_card": { "5": "https://buy.stripe.com/…", … } } # a human buys a pack; the key is shown once, then Authorization: Bearer
{ "how_to_pay": "…", "docs": "…", "example_get": "…" } # every quote says how, and where the worked example is
← 200, paid
{ "ok": true, "text": "impl Glob as Flag (crates/core/flags/defs.rs, lines 2459-2512) …", "charged_usd": 0.005,
"receipt": { "evidence_hash": "sha256:…", "cost_usd": 0.005, "determinism": "replayable" } }
| rail | who pays | autonomy | minimum |
|---|---|---|---|
| Authorization: Bearer sk-… | a human bought credits once | full, after the first purchase | $5 pack |
| PAYMENT-SIGNATURE / X-PAYMENT (x402) | the agent's USDC wallet on Base; over MCP, the x402_payment argument | full | $0.005 |
Authorization: Payment (MPP, card) → /v1/credits/buy | the human's card through the Link agent wallet; buys a $5/$25/$100 pack and returns a key | one approval tap per pack, by Link's design; then full | $5 pack (cards need $0.50+, a call costs cents) |
| Authorization: Payment (MPP, Tempo) | the agent's USDC wallet on Tempo, settled by Stripe | full | $0.01 · when Stripe's crypto capability is enabled on our account (not yet) |
The agent-side skill for cards is npx skills add stripe/link-cli: it answers the 402 on /v1/credits/buy, and the key in the response pays for every call after that. Nothing is charged for a failed request.
Token-reduction numbers measured on one payload say little about what a session costs; several context compressors cost more than running nothing once caching is counted. So the number on this page is the median cost of a fixed task list run as fresh sessions with and without the plugin, on a pinned public repo, cache reads billed, losses included. The first full run says something we did not expect and will not hide: Claude Code already avoids whole-file reads on its own (it greps, then reads a range), so on file-anchored questions the plugin is close to a wash and the agent reached for sym in one session out of twenty-four. Where sym earns its keep is orientation (map), agents without grep or ranged reads (the hosted tier), and the measurement itself.
| task | arm | cost USD | input | cache read | turns |
|---|---|---|---|---|---|
| defs-colors | mod | 0.2361 | 2 | 41064 | 1 |
| defs-colors | mod | 0.1229 | 20 | 394209 | 10 |
| defs-colors | mod | 0.1302 | 22 | 436158 | 11 |
| defs-colors | plain | 0.1363 | 16 | 283769 | 8 |
| defs-colors | plain | 0.0813 | 12 | 223123 | 6 |
| defs-colors | plain | 0.1480 | 22 | 412802 | 11 |
| defs-colors | plugin | 0.1307 | 22 | 436923 | 11 |
| defs-colors | plugin | 0.1277 | 20 | 392557 | 10 |
| defs-colors | plugin | 0.1121 | 18 | 355237 | 9 |
| defs-glob | mod | 0.0386 | 8 | 150801 | 4 |
| defs-glob | mod | 0.0294 | 6 | 112872 | 3 |
| defs-glob | mod | 0.0324 | 6 | 112724 | 3 |
| defs-glob | plain | 0.0450 | 8 | 142760 | 4 |
| defs-glob | plain | 0.0359 | 6 | 107217 | 3 |
| defs-glob | plain | 0.0554 | 10 | 180191 | 5 |
| defs-glob | plugin | 0.0386 | 8 | 150802 | 4 |
| defs-glob | plugin | 0.0312 | 6 | 112943 | 3 |
| defs-glob | plugin | 0.0965 | 10 | 178287 | 5 |
| defs-sort | mod | 0.0316 | 6 | 112865 | 3 |
| defs-sort | mod | 0.0340 | 6 | 112646 | 3 |
| defs-sort | mod | 0.0398 | 8 | 150731 | 4 |
| defs-sort | plain | 0.0448 | 8 | 142000 | 4 |
| defs-sort | plain | 0.0831 | 8 | 133188 | 4 |
| defs-sort | plain | 0.0376 | 6 | 106911 | 3 |
| defs-sort | plugin | 0.0344 | 6 | 112647 | 3 |
| defs-sort | plugin | 0.0334 | 6 | 112879 | 3 |
| defs-sort | plugin | 0.0308 | 6 | 112831 | 3 |
| gitignore-matched | mod | 0.0423 | 6 | 114009 | 4 |
| gitignore-matched | mod | 0.0424 | 6 | 115239 | 5 |
| gitignore-matched | mod | 0.0479 | 6 | 113860 | 5 |
| gitignore-matched | plain | 0.0427 | 6 | 106356 | 3 |
| gitignore-matched | plain | 0.0886 | 8 | 133045 | 4 |
| gitignore-matched | plain | 0.0534 | 6 | 110191 | 3 |
| gitignore-matched | plugin | 0.0869 | 6 | 103164 | 3 |
| gitignore-matched | plugin | 0.0434 | 6 | 113996 | 4 |
| gitignore-matched | plugin | 0.0414 | 6 | 115239 | 3 |
| glue-fill | mod | 0.0393 | 6 | 112789 | 4 |
| glue-fill | mod | 0.0325 | 6 | 114241 | 4 |
| glue-fill | mod | 0.0545 | 8 | 153414 | 5 |
| glue-fill | plain | 0.0855 | 8 | 133075 | 4 |
| glue-fill | plain | 0.1100 | 12 | 205285 | 6 |
| glue-fill | plain | 0.0970 | 10 | 169339 | 5 |
| glue-fill | plugin | 0.0804 | 6 | 102120 | 4 |
| glue-fill | plugin | 0.0403 | 6 | 112787 | 4 |
| glue-fill | plugin | 0.0324 | 6 | 114245 | 4 |
| json-begin | mod | 0.0849 | 14 | 275548 | 9 |
| json-begin | mod | 0.0523 | 8 | 153917 | 6 |
| json-begin | mod | 0.0389 | 6 | 113762 | 5 |
| json-begin | plain | 0.0797 | 8 | 133875 | 4 |
| json-begin | plain | 0.0369 | 6 | 107072 | 3 |
| json-begin | plain | 0.0966 | 12 | 205630 | 6 |
| json-begin | plugin | 0.0935 | 8 | 143221 | 6 |
| json-begin | plugin | 0.0520 | 8 | 153902 | 6 |
| json-begin | plugin | 0.0394 | 6 | 113759 | 5 |
| orient-flag-arch | mod | 0.1205 | 14 | 289340 | 11 |
| orient-flag-arch | mod | 0.0963 | 12 | 241986 | 8 |
| orient-flag-arch | mod | 0.1158 | 14 | 286739 | 12 |
| orient-flag-arch | plain | 0.1687 | 22 | 444583 | 12 |
| orient-flag-arch | plain | 0.1497 | 14 | 291000 | 12 |
| orient-flag-arch | plain | 0.1876 | 20 | 423203 | 16 |
| orient-flag-arch | plugin | 0.2023 | 22 | 447094 | 13 |
| orient-flag-arch | plugin | 0.1260 | 14 | 282377 | 9 |
| orient-flag-arch | plugin | 0.1033 | 12 | 244343 | 10 |
| orient-printer | mod | 0.0962 | 10 | 196582 | 8 |
| orient-printer | mod | 0.0624 | 10 | 192157 | 5 |
| orient-printer | mod | 0.0968 | 10 | 199713 | 8 |
| orient-printer | plain | 0.1185 | 20 | 365422 | 16 |
| orient-printer | plain | 0.0985 | 18 | 333268 | 9 |
| orient-printer | plain | 0.1160 | 14 | 243755 | 7 |
| orient-printer | plugin | 0.1309 | 10 | 181296 | 7 |
| orient-printer | plugin | 0.1379 | 16 | 342330 | 11 |
| orient-printer | plugin | 0.0436 | 6 | 113750 | 3 |
| orient-tests | mod | 0.0264 | 4 | 74946 | 3 |
| orient-tests | mod | 0.0545 | 10 | 189829 | 5 |
| orient-tests | mod | 0.0540 | 10 | 189779 | 5 |
| orient-tests | plain | 0.0473 | 10 | 177795 | 5 |
| orient-tests | plain | 0.0596 | 12 | 214367 | 6 |
| orient-tests | plain | 0.0459 | 10 | 177647 | 5 |
| orient-tests | plugin | 0.0878 | 8 | 140182 | 6 |
| orient-tests | plugin | 0.0445 | 8 | 150605 | 4 |
| orient-tests | plugin | 0.0357 | 6 | 112761 | 4 |
| orient-walker | mod | 0.0406 | 4 | 74976 | 2 |
| orient-walker | mod | 0.0559 | 8 | 150486 | 4 |
| orient-walker | mod | 0.0406 | 4 | 74976 | 2 |
| orient-walker | plain | 0.0365 | 6 | 106412 | 3 |
| orient-walker | plain | 0.0640 | 4 | 61464 | 3 |
| orient-walker | plain | 0.0474 | 8 | 142275 | 6 |
| orient-walker | plugin | 0.0818 | 4 | 64284 | 3 |
| orient-walker | plugin | 0.0552 | 6 | 113868 | 3 |
| orient-walker | plugin | 0.0693 | 10 | 189605 | 6 |
| standard-sep | mod | 0.0428 | 8 | 164707 | 5 |
| standard-sep | mod | 0.0738 | 10 | 203193 | 6 |
| standard-sep | mod | 0.0497 | 6 | 117196 | 3 |
| standard-sep | plain | 0.0645 | 10 | 183470 | 5 |
| standard-sep | plain | 0.0497 | 8 | 145783 | 4 |
| standard-sep | plain | 0.0759 | 6 | 98710 | 4 |
| standard-sep | plugin | 0.0617 | 8 | 159967 | 5 |
| standard-sep | plugin | 0.0516 | 6 | 117194 | 4 |
| standard-sep | plugin | 0.0623 | 8 | 160233 | 4 |
| walk-skip | mod | 0.0316 | 6 | 112605 | 3 |
| walk-skip | mod | 0.0282 | 6 | 113570 | 3 |
| walk-skip | mod | 0.0202 | 4 | 75732 | 2 |
| walk-skip | plain | 0.0706 | 6 | 97033 | 3 |
| walk-skip | plain | 0.0342 | 6 | 106187 | 3 |
| walk-skip | plain | 0.0351 | 6 | 106239 | 3 |
| walk-skip | plugin | 0.0726 | 6 | 101937 | 3 |
| walk-skip | plugin | 0.0226 | 4 | 74946 | 2 |
| walk-skip | plugin | 0.0327 | 6 | 112623 | 3 |
2026-10-08T2020Z on https://github.com/BurntSushi/ripgrep@14.1.1, 3 runs per cell, model claude-sonnet-5, Claude Code 2.1.294. mod vs plain: -21.8% (median of per-task paired deltas; cheaper on 11 of 12 tasks; pooled -32.8%). plugin vs plain: -10.5% (median of per-task paired deltas; cheaper on 10 of 12 tasks; pooled -13.4%). The headline above is the mod arm (the install on Claude Code 2.1.287+). Answers, judged by haiku against the plain arm's: mod agreed with plain on 5 of 12 tasks (partial 6, disagree 1); plugin agreed with plain on 4 of 12 tasks (partial 7, disagree 1). Answers in this result were stored cut at 600 characters; most "partial" verdicts cite the cut, not a different answer. Later results store 2,000. Ablation (run 2026-10-09T0418Z, same tasks, 3 runs per cell): the mod -26.9% (cheaper on 8 of 12); the mod without its repo map +16.5% (cheaper on 5 of 12); the mod with Haiku file summaries on the map -2.3% (cheaper on 6 of 12); answers agreed with plain (full-length, haiku judge): mod 10/12, mod-nomap 8/12, mod-sum 10/12. The repo map with the first message is where the saving comes from; summaries cost more tokens per turn than they save here, so they stay opt-in. Full file: bench/results/2026-10-08T2020Z.json.
The harness is bench/ in the repo, with a README on how to run it on any plugin or mod and how to read the numbers; every run is published there, losses included. Run it on your own repo; the table above regenerates from bench/results/latest.json.
The CLI, the Claude Code plugin and the MCP server. Source-available under PolyForm Shield 1.0.0: read it, build it, self-host it.
/v1/sym/map $0.01 · /v1/sym/read, /ls, /find $0.005 · /v1/sym/where (code by meaning, over a semantic index we build once per checkout on our own GPU) $0.01 · /v1/score (a text against a form, programmatic verifiers) $0.005 · /v1/assert (deterministic checks over an agent's output; a signed record) $0.005 · /v1/x402/inspect (what an x402 endpoint declares, checked and signed) $0.005 · /v1/certify (compress an image, prove the model still reads it; a signed record) $0.01. Three keyless calls a day are free on each route. Credit packs of $5, $25 and $100 by card, or USDC on Base per call over x402. One ledger for every s2ar API, and every answer carries a receipt. The pay-per-call walkthrough → · where this sits in the market → · today's proceedings →
| front | call | returns |
|---|---|---|
| CLI | sym ls | read | map [--json] [--est] | the legacy text, or JSON with tokens_est |
| MCP (hosted, HTTP) | claude mcp add s2ar --transport http https://api.s2ar.dev/mcp … | sym_map_repo, sym_ls_repo, sym_read_repo, sym_find_repo, sym_where_repo, score_text, certify_image, buy_credits; a keyless call answers "payment required" with the x402 accepts, the card packs and how_to_pay |
| MCP (stdio) | sym_ls {file} · sym_read {file,symbol} · sym_map {dir,budget} | one text content block; failures are isError, never protocol errors |
| HTTP (local) | sym serve --port 8431 --root DIR → POST /ls /read /map | {ok, text, data, tokens_est}; every path jailed under the root |
| HTTP (hosted) | GET /v1/sym/map?repo=&ref=&budget= · /v1/sym/read?repo=&file=&symbol= (or POST the same fields as JSON) | the same, plus charged_usd and a receipt; 402 with every rail and how_to_pay when unpaid |
| hook | sym hook pre | PreToolUse additionalContext for a big-file Read; exit 0 always |
The repo map resolves each file's imports to files in the tree (Rust mod/use, Python, JS/TS relative paths, Go packages, Lua require, C includes, Java imports, Ruby requires), runs PageRank over that graph with test files voting at a discount, and emits top-level signatures (twelve per file, then "+N more") until the budget is spent.