Status (2026-05-16): This was the original Tier 1-4 token roadmap. Most of Tier 1 and Tier 3 has shipped —
1.5stderr footer compaction,1.6structured truncation hint (ASH-121, ASH-127),3.1no-arg help one-liner (ASH-147),3.2compact per-arg help (ASH-143),3.4error shape via_meta(ASH-127). Tier 2 is partial:2.3positionalash git --opshipped; the broad2.1flag rename pass landed for some flags (--old/--new,--meta,--max,--mpf,--bytes) and not others. Tier 4 explicitly deferred. Treat this doc as a roadmap, not a live status surface — verify any specific item againstbin/ash help --verb <name>andgit log --grep ASH-NNbefore acting on it.
Update (2026-05-17, ASH-131): The original Tier 1–4 here is a delivery roadmap (what to ship next), not a usage classification (who reads the output). The new optimization-tier framework in optimization-tiers.md is the latter — it classifies each verb as A/B/C/D based on usage pattern. When re-evaluating remaining items in this doc, ask "does this item touch a Tier A verb in optimization-tiers.md?" before greenlighting. Items targeting Tier D verbs (
report/metrics/bench/replay/usage/stop) should be deprioritized or reconsidered — the per-call aggregate savings are negligible vs the readability cost. Example:2.1flag renames on--all-roots(report-only) is no longer worth doing.
ash is targeted exclusively at coding agents — it has no human users. That gives us full freedom to optimize the verb/flag/response surface for token cost without needing to preserve human affordances. The recursive-development premise of this project (you are the first user, and your friction drives design) makes the dogfood signal especially clean: every token we shave off ash's surface is a token freed for actual reasoning.
Today the surface has 17 verbs, ~80 flags, an msgpack-internally / pretty-on-the-wire envelope, and a help-text format inherited from human-targeted CLI conventions (snake_case, fixed-width columns, prose descriptions, default-true booleans, repeated long field keys per row in JSON). proto.go:5 explicitly describes Request.V as "the seam through which schema-dictionary optimization will arrive without breaking older clients" — the proto was already designed with this work in mind.
This doc deeply evaluates the current surface and itemizes proposed improvements with token-savings estimates (cl100k_base, but the principles generalize to o200k and other BPE tokenizers — any change that removes underscore-splits or repeated long field names is a win across all of them).
Backwards compatibility is not a constraint at this prototype stage. Renames, breaking proto changes, and removed flags are all acceptable. Recommendations below assume a clean break, not a migration period.
Real cl100k_base tokenization measurements:
| Call | tokens_out |
|---|---|
ash help (no args) |
3,703 |
ash help --verb find |
309 |
ash report --since 1h |
1,202 |
ash metrics --last 3 (pretty) |
107 |
Plus the per-call stderr footer ([ash metrics: bytes_in=…bytes_out=…tokens_in=…tokens_out=… (real:cl100k_base) latency_us=…/… phases=walk:…/io:…/regex:…]) costs ~25–35 tokens every single call.
Input side (~80 flags): All snake_case. cl100k splits on _, so respect_gitignore ≈ 4–5 tokens; include_hidden ≈ 3; context_before ≈ 3; latency_dispatch_us ≈ 3. Some heavily-used verbs have no positional shortcut (e.g., ash git --op log instead of ash git log — pure overhead).
Output side — bigger cost driver:
--format jsonis broken:Response.Dataismsgpack.RawMessagetaggedjson:"data,omitempty"(proto.go:53-58). Go's stdlib base64-encodes any[]byteto JSON, so the entire result payload arrives as opaque base64. Agents can't parse it. This forces everyone ontopretty, which then has its own cost (column headers, summary lines, separators).- The
Metricsenvelope (proto.go:60-84) emitstokens_in,tokens_out,tokens_method,bytes_in,bytes_out,latency_parse_us,latency_exec_us,latency_serialize_uson every response withoutomitempty. ~5–15 tokens of dead weight per call. - Long snake_case JSON keys repeat per row in row-shaped responses (
metrics,report.by_verb,git --op log,test,find,grep). Formetrics --last 50that's ~14 keys × 50 rows ≈ 700+ tokens just on keys. truncation_hintis prose:"hit limit of 10 records. narrow with --glob, --type, --max_depth, or --exclude; or raise --limit (max 4096)."≈ 30 tokens. Truncation is common in find/grep.- Help text (help.go:469 PrettyResponse / :489 writeArg) is fixed-width columnar with prose descriptions designed for humans.
1.1. Fix --format json so it isn't base64 msgpack — unblocks 1.2 and 1.4.
- Decode
Response.Datato a typed result via the verb's existingproto.UnmarshalData(proto.go:165) and re-marshal as JSON. Todaydataround-trips as base64-encoded msgpack. - Where: cmd/ash/main.go JSON branch; central per-verb
Result()factory registered alongside the verbs in internal/verbs/verbs.go. - Token savings: Indirect (no callers can use JSON today). This is a correctness fix that unlocks the rest.
- Risk/cost: Low. ~50 LOC + a verb→Result-factory registry.
1.2. Add --format compact (array-of-arrays for row-shaped results)
- Emits
{"k":["ts","verb","ok","ti","to","ex_us","bi","bo"],"r":[[...],[...]]}— keys appear once. - Where: New
internal/proto/compact.go. Apply to row-shaped verbs only:metrics,report.by_verb,git --op log/status/diff,test packages[].tests[],find,grep,stat. Other verbs fall back to JSON. - Token savings: Replaces ~14 keys/row with positional values.
metrics --last 50saves ~600–900 tokens on key overhead alone. 40–60% reduction on row-shaped responses. - Format invariant (ASH-93):
prettyis the default for all verbs.--format compactis available explicitly;--format jsonemits the full envelope. The bench tokenizesprettyoutput (viad.Pretty()) which matches what agents receive — the measurement is honest. Never makecompactthe default: it couples the bench substrate to the on-wire format and causes silent misalignment. - Risk/cost: Low. ~120 LOC + per-verb shims (~10 LOC × 7 verbs).
1.3. Aggressive omitempty on Metrics + drop default-zero fields
- Add
omitemptytoTokensIn,TokensOut,TokensMethod,BytesIn,BytesOut,LatencyParseUs,LatencyExecUs,LatencySerializeUs. Droptokens_methodfrom the wire when it equals the daemon default ("real:cl100k_base"). - Where: internal/proto/proto.go:60-84 struct tags.
- Token savings: ~5–15 tokens per call on the metrics envelope. Heavy session of 100 calls: 500–1,500 tokens.
- Risk/cost: Trivial. ~15 LOC of struct-tag changes + tests.
1.4. Short metric field names (proto v2 negotiation)
- Bump
proto.ProtocolVersionto 2; introduce short keys:lp(latency_parse_us),le,ls,ld,ti,to,tm,bi,bo,tr,ph(phases) withw/io/r/rc. The seam already exists:Request.Vis documented at proto.go:5 for exactly this purpose. - Where: Metrics + Phases tags in proto.go:60-99; maybe internal/proto/tracer.go.
- Token savings:
latency_dispatch_usis 3 tokens,tokens_methodis 2,latency_serialize_usis 4 — per-call 15–25 tokens on the metrics envelope. Aggregate 1,000–2,000 session-wide. - Risk/cost: Medium. ~80 LOC. Bench tests need updating. (No bw-compat needed — bump V to 2 anyway as a cache-bust marker.)
1.5. Compact stderr metrics summary line
- Trim per-call footer. Current:
[ash metrics: bytes_in=N bytes_out=N tokens_in=N tokens_out=N (real:cl100k_base) latency_us=N/N phases=walk:N/io:N/regex:N]≈ 25–35 tokens. Compact:[ash bi=N bo=N ti=N to=N us=N/N w=N io=N r=N]≈ 12–18 tokens. Drop tokenizer name unless mismatched, drop zero phases. - Where: cmd/ash/main.go ~lines 130–155 (the stderr summary block).
- Token savings: ~15 tokens × every call. 100-call session: ~1,500 tokens.
- Risk/cost: Trivial. ~20 LOC.
1.6. Replace prose truncation_hint with structured {trunc:1, limit:N, max:N}
- The pretty formatter reconstructs the hint phrase locally; agents using JSON/compact get the structured form.
- Where: Each verb's Result struct (
find,grep,git,read,metrics,report); pretty hooks. - Token savings: 20–30 tokens per truncated call. Truncation hits ~5–10% of calls in heavy sessions. 300–800 session-wide.
- Risk/cost: Low. ~60 LOC across verbs.
2.1. Rename snake_case multi-word flags to short forms (direct rename — no aliases)
--include_hidden→--hidden(or-H)--respect_gitignore→--gi; accept--no-gifor negation--context_before→--cb;--context_after→--ca(and add--context Nfor symmetric)--fixed_string→--lit--files_only→--fo(or--paths-only)--no_text→--no-text(kebab; no underscore split)--max_matches→--max(verb-specific) ;--max_per_file→--mpf--max_depth→--depth--replace_all→--all--new_string/--old_string→--new/--old--new_content→--new(canonical stays stdin)--with_meta→--meta--follow_symlinks→--followor-L--limit_bytes→--bytes--range_kind→--unit--create_only→--no-clobberor-n--dry_run→--dry--no_registry→--no-registry--tool_name→--tool;--file_path→--file--all_roots→--all- Where: Per-verb
ParseArgsin internal/verbs/ and internal/verbs/argutil/argutil.go; addAliases []stringto the help registry in internal/verbs/help/help.go. - Token savings: 2–8 tokens per multi-flag call. Session aggregate 300–800 tokens in.
- Risk/cost: Medium. ~150 LOC + table-driven tests. Updates to help/CLAUDE.md/hook ride along in the same PR.
2.2. --no-foo negation form for default-true booleans
--no-gi,--no-untracked,--no-mkdirinstead of--respect_gitignore=falseetc.- Where: Flag parser in cmd/ash/main.go;
argutil.RequireBool. - Token savings: ~3–5 tokens per use; modest, mostly QoL.
- Risk/cost: Low. ~20 LOC.
2.3. More positional slots (high-value, near-trivial)
git: positionalop(ash git loginstead ofash git --op log) — saves 2 tokens per git call.report/metrics: positionalverbfilter (ash report find).help: positionalverb(ash help findinstead ofash help --verb find) — saves 2 tokens.bench: positionalverb/case.- Where:
verbPositionalsmap at cmd/ash/main.go:194. - Token savings: 2–3 tokens per affected call. git is common — 100–300 tokens session-wide.
- Risk/cost: Trivial. ~20 LOC.
ash help (no args) emits 3,703 tokens today. This is the single biggest fixed cost — agents read help to learn the surface, often repeatedly across sessions.
3.1. ash help (no args) returns one-liner per verb only
- Today the no-arg form dumps every verb's full schema. Reduce to verb name + one-line description (~150 tokens for 17 verbs). Full schema available via
ash help <verb>. - Where: internal/verbs/help/help.go:469 PrettyResponse switch on
Verb == "". - Token savings: ~3,500 tokens saved per call. Even 1–2 calls per session is the largest single-item win in the entire plan.
- Risk/cost: Low. ~30 LOC. CLAUDE.md already directs agents to
ash help --verb Xfor specifics.
3.2. Compact agent-targeted help format (one-line per arg)
- Replace columnar layout (help.go:489 writeArg) with
<key>:<type>[:!][=<default>] — <one-line>form. Dropoptional/requiredkeywords (use!suffix), dropdefault=prefix, drop "Pass false to suppress" boilerplate. - Where: internal/verbs/help/help.go:469-489.
- Token savings: Per-verb help drops ~50%. Combined with 3.1 brings full help corpus from 3,700 → ~1,500–2,000 tokens (45–60% reduction).
- Risk/cost: Low. ~80 LOC + golden-file test updates.
3.3. Tighten descriptions; gate prose behind --verbose
- Rewrite all
Description:fields to ~10–15 words. Move existing prose ("Doublestar pattern; matched against the path relative to --path." etc.) behind--verbose. Drop redundant enum echo when the value is in the default-line. - Where: Every entry in the registry in internal/verbs/help/help.go (~17 verb structs, ~80 args).
- Token savings: Compounds with 3.2. Default per-verb help drops to ~50–100 tokens.
- Risk/cost: Low-medium. ~200 LOC of editorial string churn. No code-path changes.
3.4. Numeric truncation reasons in errors
- Standardize error structure:
Error.Codealready exists (proto.go:55). Move long human messages into ahintfield that pretty renders locally; keepMsgshort and stable. - Token savings: 5–15 tokens per error response. Multiplies across iterative agent failure loops.
- Risk/cost: Medium. Touches every error-construction site. ~150 LOC.
- Verb renames — most verbs are 1 token already (read/find/grep/git/edit/diff/write/test/stat/help/hook/init/stop/bench).
metrics/report/uninitare 1–2 tokens. Churn cost (CLAUDE.md, hook, docs, session-note history, every Linear ticket title) outweighs savings. Skip. - Per-session schema-dictionary handshake — LLMs can't track per-session state reliably. Static dict (1.4) captures the win without the complexity. Skip.
- Streaming responses — agents read full responses synchronously through the harness Bash tool. No clear path. Park.
- Drop
prettyoncecompactis default — pretty is genuinely useful for human debugging at low marginal cost. Soft no. - Flip default-true booleans to default-false (e.g.,
mkdir) — defaults are already what agents want; flipping just adds tokens. Skip.
For a representative heavy ash session (100 calls: 30 grep, 20 find, 15 read, 10 edit, 10 git, 5 metrics/report, 5 test, 5 help/other):
| Source | Per-session tokens saved (est.) |
|---|---|
| 1.5 stderr summary trim | 1,000–1,500 |
| 1.3 omitempty hygiene | 500–1,000 |
| 1.4 short metric keys | 1,000–2,000 |
| 1.2 compact format on row verbs | 1,500–4,000 |
| 1.6 truncation hint compaction | 300–800 |
| 2.1 flag aliases | 300–800 |
| 2.3 positional slots | 100–300 |
| 3.1 + 3.2 + 3.3 help compaction | 3,500–5,000 (mostly one-shot per session) |
Total: ~8,000–15,000 tokens per heavy session. Against an ash-heavy session of ~30k–60k output tokens for ash specifically: roughly 20–35% reduction in ash-related output tokens, with help-text wins (3.1/3.2/3.3) doing the heaviest single-call lift.
- 1.1 removes a real correctness bug —
--format jsonis currently unusable for downstream tooling. - 1.2 is also smaller on the msgpack wire —
bytes_outdrops in lockstep withtokens_out, which improves the ledger's signal-to-noise (truncation/cost analyses get cleaner). - 3.1/3.2/3.3 strip ~200 LOC of prose churn from the help registry and address the "help can lag code" gotcha called out in CLAUDE.md.
- 1.3 + 1.4 simplify the msgpack codec slightly and remove dead-zero-field serialization branches.
- 1.6 lets the pretty formatter own truncation-hint phrasing — single source of truth, easier to evolve.
- 2.3 (positional
git) makes the hook's suggested replacements more natural and brings the verb closer to bash-gitergonomics agents already know.
- 1.1 (json correctness) — unblocks downstream
- 3.1 (
ash helpno-arg one-liner) — biggest single-call win, smallest diff - 1.5 (stderr footer) + 1.3 (omitempty) — trivial, instant wins
- 1.6 (truncation hint structuring) + 3.2 (help format)
- 1.2 (compact format) + 1.4 (short metric keys, proto v2)
- 2.1 (flag aliases) + 2.3 (positionals) — incremental
- 3.3 (description rewrite) — editorial sweep, do last so other changes don't churn it
- internal/proto/proto.go — envelope and Metrics struct (lines 53–101)
- internal/proto/pretty.go — request/response pretty rendering
- internal/verbs/help/help.go — registry + PrettyResponse / writeArg (~lines 469, 489)
- cmd/ash/main.go —
extractFormat(~173),verbPositionals(~194), flag parsing, stderr footer (~130–155) - internal/verbs/argutil/argutil.go — shared key/type lookup
- internal/verbs/verbs.go — verb registration (extension point for Result-factory registry)
Each ticket above ships with a measurement plan: capture tokens_out for a representative call before and after via ash report --since 5m (which already aggregates p50/p95 tokens_out per verb). The ledger is the substrate — every claim in this doc can be re-validated against it post-implementation. For help-text changes specifically, also capture ash help and ash help --verb <every> token counts before/after to confirm the 45–60% reduction on the help corpus. Ledger queries: ash report --verb help, ash report --verb metrics --since 1d, etc.