Prompt Token-Cost Baseline
Every prompt surface in the monorepo that reaches an LLM, with the token cost of the fixed text it always emits. This file is re-recorded as the Agent Prompt & Workflow Efficiency epic proceeds, so the figures below are current rather than original. The epic started from 22,670 per-call / 13,630 unique at 2a64b185 — the last recording before any prompt was rewritten, and the number the epic's overall reduction should be measured against. Use --compare for the delta since whatever is recorded here now.
- Recorded at — 2026-09-11T12:15:04.131Z
- Commit —
1ce09a26728b - Content hash —
e10365e5d95f379b(identifies the measurement itself;tests/e2e/prompt-census.test.jsfails when the repo no longer matches it) - Model for cost/context figures —
claude-sonnet-5 - Surfaces — 36
- Per-call total — 21,688 tokens (what every surface costs, summed)
- Unique fixed text — 13,201 tokens (distinct text a rewrite has to edit)
How to reproduce
pnpm build # the census imports built builders
node scripts/prompt-census.mjs # measure HEAD
node scripts/prompt-census.mjs --compare # before/after vs this file
node scripts/prompt-census.mjs --dump hench # print one assembled prompt
node scripts/prompt-census.mjs --write # re-record the baselineWhat the two numbers mean
Fixed is the prompt text a builder always emits, extracted from the string literals in its body. It is what a rewrite reduces. Context is what the run supplies — file lists, PRD items, analysis output — and it does not shrink because someone tightened a sentence. Keeping them apart is what stops a reduction being credited to the wrong surface.
Counting literals rather than output is also what makes the three construction styles comparable. core writes a few large template literals per prompt; hench pushes dozens of short fragments onto an array; rex and sourcevision declare a list of named sections. A literal-per-file scan undercounts the last two badly; counting literals per builder does not.
Token counts come from budgetPreflight() in @n-dx/llm-client — the same estimator the runtime uses for context-window preflight, so a number here is the number the runtime would produce for the same text.
Per-call vs unique
Own is the text written inside a builder. Shared is the prompt constants it interpolates — PRD_SCHEMA, FEW_SHOT_EXAMPLE and friends, which rex reuses across many builders. Per-call is their sum: what one invocation actually sends.
The two totals answer different questions. Per-call is the bill. Unique is the edit surface — a constant used by ten builders is written once, so shortening it is worth ten times its own size. Reporting only the per-call sum would point a rewrite at the largest builders rather than at the most-reused text.
Measurement revisions
A total in this file can move because prompts changed or because the measurement changed. Only the first is a result. Revisions of the second kind are recorded here so a jump is never mistaken for a regression or a win.
- Envelope migration — rex's and sourcevision's prompts moved onto
PromptEnvelope, and every builder was renamed*Prompt→*Envelope, so a--compareacross that change shows the whole registry as NEW/REMOVED. The assembled text is byte-identical apart from removed doubled blank lines, proved by theprompt-text-identitysnapshot suites in both packages. In the same change the extractor learned to follow module-local helper calls: factoring duplicated prompt text into a helper had been dropping it from the count entirely. That correction raised the recorded totals by ~5% with no prompt growing — the earlier figures were an undercount. - rex redundancy pass — the first entry here that is a real reduction rather than a measurement change: -904 per-call / -278 unique, by deleting instructions that the same prompt already gave elsewhere and resolving a task-size contradiction (hours vs engineer-weeks). No instruction was removed from a prompt that did not still state it.
rex — 15,217 per-call / 7,195 unique, 19 surfaces
| Builder | File | Purpose | Literals | Own | Shared | Per-call |
|---|---|---|---|---|---|---|
buildFileImportEnvelope | packages/rex/src/analyze/reason.ts | Propose PRD items from a single source document. | 18 | 123 | 1,042 | 1,165 |
buildScanImportEnvelope | packages/rex/src/analyze/reason.ts | Propose PRD items from a batch of scanner findings. | 40 | 400 | 1,257 | 1,657 |
buildAddEnvelope | packages/rex/src/analyze/reason.ts | Propose new PRD items from a natural-language description. | 25 | 210 | 1,430 | 1,640 |
buildMultiAddEnvelope | packages/rex/src/analyze/reason.ts | Propose items for several scan targets in one call. | 27 | 195 | 1,430 | 1,625 |
buildBreakdownEnvelope | packages/rex/src/analyze/reason.ts | Split proposals judged too large into child tasks. | 17 | 200 | 483 | 683 |
buildConsolidateEnvelope | packages/rex/src/analyze/reason.ts | Merge overlapping proposals before they enter the PRD. | 19 | 234 | 484 | 718 |
buildAssessmentEnvelope | packages/rex/src/analyze/reason.ts | Assess whether proposal tasks are at the right granularity. | 40 | 497 | — | 497 |
buildIdeasEnvelope | packages/rex/src/analyze/reason.ts | Extract proposals from free-form notes that local parsing missed. | 28 | 358 | 1,430 | 1,788 |
buildConsolidationGuardEnvelope | packages/rex/src/analyze/consolidation-guard.ts | Second-opinion check before a consolidation is applied. | 21 | 273 | 432 | 705 |
buildDecompositionEnvelope | packages/rex/src/analyze/decompose.ts | Decompose a task whose level-of-effort exceeds the threshold. | 23 | 270 | — | 270 |
buildDisambiguationEnvelope | packages/rex/src/analyze/extract.ts | Resolve an ambiguous extraction against existing PRD items. | 21 | 186 | 541 | 727 |
buildClarifyEnvelope | packages/rex/src/analyze/guided.ts | Ask clarifying questions during guided PRD authoring. | 33 | 309 | — | 309 |
buildSpecEnvelope | packages/rex/src/analyze/guided.ts | Turn guided answers into a structured spec. | 22 | 150 | 432 | 582 |
buildModifyEnvelope | packages/rex/src/analyze/modify-reason.ts | Apply a natural-language edit to an existing PRD item. | 29 | 295 | 492 | 787 |
buildGroupRenameEnvelope | packages/rex/src/analyze/propose-group-renames.ts | Rename a group of sibling items to a consistent scheme. | 27 | 214 | 29 | 243 |
buildRenameEnvelope | packages/rex/src/analyze/rename-resolve.ts | Pick the better of two colliding item titles. | 23 | 204 | 29 | 233 |
buildReshapeEnvelope | packages/rex/src/analyze/reshape-reason.ts | Propose a restructure of the PRD hierarchy. | 7 | 18 | 1,402 | 1,420 |
buildBodyMergeEnvelope | packages/rex/src/analyze/reshape-reason.ts | Merge two item descriptions into one during a reshape. | 12 | 106 | — | 106 |
buildValidationFeedbackEnvelope | packages/rex/src/analyze/escalate.ts | Retry feedback appended to a prompt whose response failed validation. | 12 | 62 | — | 62 |
sourcevision — 2,659 per-call / 2,194 unique, 7 surfaces
| Builder | File | Purpose | Literals | Own | Shared | Per-call |
|---|---|---|---|---|---|---|
buildFirstPassEnvelope | packages/sourcevision/src/analyzers/enrich-batch.ts | First-pass zone enrichment for a batch of zones. | 57 | 317 | 463 | 780 |
buildLaterPassEnvelope | packages/sourcevision/src/analyzers/enrich-batch.ts | Later-pass zone enrichment, given the previous pass's output. | 50 | 265 | 434 | 699 |
buildMetaEnvelope | packages/sourcevision/src/analyzers/enrich-config.ts | Meta-evaluation choosing the enrichment strategy for a repo. | 23 | 531 | 14 | 545 |
buildSingleZoneFirstPassEnvelope | packages/sourcevision/src/analyzers/enrich-per-zone.ts | Per-zone enrichment, first pass — names and describes one zone. | 25 | 138 | 16 | 154 |
buildSingleZoneLaterPassEnvelope | packages/sourcevision/src/analyzers/enrich-per-zone.ts | Per-zone enrichment, later pass — adds only what pass 1 missed. | 27 | 120 | 41 | 161 |
buildLLMClassifyEnvelope | packages/sourcevision/src/analyzers/classify.ts | Classify file archetypes the heuristic classifier could not. | 19 | 91 | — | 91 |
buildPrimerEnvelope | packages/sourcevision/src/analyzers/primer.ts | Distil CONTEXT.md into the startup primer every agent run inherits. | 21 | 229 | — | 229 |
hench — 3,531 per-call / 3,531 unique, 9 surfaces
| Builder | File | Purpose | Literals | Own | Shared | Per-call |
|---|---|---|---|---|---|---|
buildSystemPrompt | packages/hench/src/agent/planning/prompt.ts | The agent's system prompt — role, rules, workflow, error handling. | 62 | 768 | — | 768 |
buildGoLanguageContext | packages/hench/src/agent/planning/prompt.ts | Go toolchain and convention context, added when the project is Go. | 26 | 278 | — | 278 |
formatTaskBrief | packages/hench/src/agent/planning/brief.ts | Render the task brief section — task, parent chain, requirements. | 42 | 214 | — | 214 |
buildReviewSystemPrompt | packages/hench/src/agent/analysis/adversarial-review.ts | System prompt for the adversarial review pass. | 27 | 331 | — | 331 |
buildReviewBrief | packages/hench/src/agent/analysis/adversarial-review.ts | Brief handed to the reviewer — what to attack and where to report. | 129 | 1,460 | — | 1,460 |
buildOrientationSystemPrompt | packages/hench/src/agent/lifecycle/orientation.ts | System prompt for the one-off repository orientation pass. | 9 | 114 | — | 114 |
buildOrientationPrompt | packages/hench/src/agent/lifecycle/orientation.ts | Orientation task prompt — what to establish about the repo. | 15 | 191 | — | 191 |
buildRetryNotice | packages/hench/src/agent/lifecycle/cli-loop.ts | Notice appended on retry telling a fresh session what is on disk. | 4 | 55 | — | 55 |
formatPlanModeAppendix | packages/hench/src/agent/lifecycle/plan-mode-prompt.ts | Appendix re-spawning a session that stalled in plan mode. | 12 | 120 | — | 120 |
core — 281 per-call / 281 unique, 1 surfaces
| Builder | File | Purpose | Literals | Own | Shared | Per-call |
|---|---|---|---|---|---|---|
buildReviewerPrompt | packages/core/pair-programming.js | QA reviewer prompt for the pair-programming second opinion. | 6 | 281 | — | 281 |
Shared prompt constants
Ranked by total per-call cost — size times the number of builders that interpolate it. This is the leverage ordering for a rewrite.
| Constant | Package | Tokens | Used by | Per-call total |
|---|---|---|---|---|
FEW_SHOT_EXAMPLE | rex | 432 | 10 | 4,320 |
PRD_SCHEMA | rex | 333 | 6 | 1,998 |
reshapeRoleContent | rex | 1,054 | 1 | 1,054 |
TASK_QUALITY_RULES | rex | 156 | 6 | 936 |
CONSOLIDATION_INSTRUCTION | rex | 223 | 4 | 892 |
formatProjectShape | sourcevision | 393 | 2 | 786 |
placementContent | rex | 155 | 3 | 465 |
OUTPUT_INSTRUCTION | rex | 52 | 8 | 416 |
ANTI_PATTERNS | rex | 61 | 6 | 366 |
RESHAPE_FEW_SHOT | rex | 326 | 1 | 326 |
JSON_OBJECT_ONLY | sourcevision | 16 | 4 | 64 |
formatFileHeaders | sourcevision | 54 | 1 | 54 |
ONLY_NEW_INSIGHTS | sourcevision | 25 | 2 | 50 |
summarizeExisting | rex | 9 | 4 | 36 |
projectContextContent | rex | 10 | 3 | 30 |
formatMemberSection | rex | 29 | 1 | 29 |
formatItemSection | rex | 29 | 1 | 29 |
summarizePRD | rex | 22 | 1 | 22 |
formatAnnotatedFinding | sourcevision | 15 | 1 | 15 |
Assembled prompts (fixed representative input)
One entry point per package, invoked with a checked-in fixture so the number is reproducible without a model call. Dump any of them with --dump <package>.
| Package | Entry point | Input | Fixed | Context | Assembled |
|---|---|---|---|---|---|
| rex | buildAssessmentEnvelope | Granularity assessment over one two-task proposal. | 497 | 245 | 742 |
| sourcevision | buildPrimerEnvelope | Primer distillation over a fixed 3-zone CONTEXT.md excerpt. | 229 | 137 | 366 |
| hench | buildPromptEnvelope | Full agent envelope (system + brief) for a CLI-provider run. | 982 | n/a — 398 of the fixed text is on another branch | 584 |
| core | buildReviewerPrompt | Pair-programming reviewer prompt over three changed files. | 281 | 16 | 297 |
A fixed figure above the assembled length is not an error: the fixed column counts every branch in the builder, and one run takes one path. buildSystemPrompt alone carries separate CLI/API, auto-commit, and self-heal branches. The unreached text is still worth shortening — it just is not billed on this particular path.
rex envelope sections
buildAssessmentEnvelope assembles its prompt from named sections, so its cost is reported per section rather than as one literal. These are the same sections extractPromptSectionDiagnostics() reports at runtime, over the fixture in Granularity assessment over one two-task proposal.
| Section | Chars | Tokens | Share |
|---|---|---|---|
input | 1,055 | 264 | 35.6% |
output | 634 | 159 | 21.4% |
anti-patterns | 498 | 125 | 16.8% |
structure | 406 | 102 | 13.7% |
quality | 229 | 58 | 7.8% |
role | 134 | 34 | 4.6% |
sourcevision envelope sections
buildPrimerEnvelope assembles its prompt from named sections, so its cost is reported per section rather than as one literal. These are the same sections extractPromptSectionDiagnostics() reports at runtime, over the fixture in Primer distillation over a fixed 3-zone CONTEXT.md excerpt.
| Section | Chars | Tokens | Share |
|---|---|---|---|
input | 585 | 147 | 40.3% |
rules | 402 | 101 | 27.7% |
output | 312 | 78 | 21.4% |
role | 156 | 39 | 10.7% |
hench envelope sections
buildPromptEnvelope assembles its prompt from named sections, so its cost is reported per section rather than as one literal. These are the same sections extractPromptSectionDiagnostics() reports at runtime, over the fixture in Full agent envelope (system + brief) for a CLI-provider run.
| Section | Chars | Tokens | Share |
|---|---|---|---|
system | 1,663 | 416 | 71.4% |
brief | 668 | 167 | 28.6% |
Workflow skills — 17,217 tokens, 13 skills
A skill body enters the agent's context whole the moment the skill is invoked, so its size is a per-invocation bill in the same way a builder's fixed text is a per-call one. The two totals are NOT added together: a skill run and an analyze call are different events.
shipped marks the skills ndx init installs into other repositories. The rest exist only here, which makes them easy to forget — they were exempt from the portability guards until this table gave them a number.
| Skill | Shipped | Chars | Tokens |
|---|---|---|---|
ndx-adversarial-review | yes | 20,512 | 5,128 |
iso-map | — | 8,285 | 2,072 |
triage | — | 7,421 | 1,856 |
ndx-reshape | yes | 5,816 | 1,454 |
dev-link | — | 5,054 | 1,264 |
ndx-plan | yes | 4,437 | 1,110 |
ndx-capture | yes | 4,407 | 1,102 |
ndx-config | yes | 3,322 | 831 |
ndx-work | yes | 2,954 | 739 |
ndx-feedback | yes | 2,565 | 642 |
no-plan-mode | yes | 2,476 | 619 |
ndx-zone | yes | 817 | 205 |
ndx-status | yes | 780 | 195 |
Packages with no LLM prompt surfaces
Recorded so the absence is a finding rather than an oversight — these were checked.
| Package | Why |
|---|---|
| web | Serves the dashboard and proxies MCP. Every LLM call it surfaces is made by rex, sourcevision, or hench behind a gateway; web composes no prompt text of its own. |
| llm-client | Foundation tier. Carries the prompt envelope and token types that the other packages fill in, but composes no prompt text itself. |
Out of scope: interactive readline prompts
These are questions to a human, not to a model. They are listed by name pattern so a later audit does not mistake one for an LLM prompt surface.
Name patterns:
/^prompt[A-Z]//^confirmPrompt$//^defaultPrompt$//Prompt(Input|Choice|Confirm)$/
Specific builders whose names match a prompt pattern but which are not LLM prompts:
| Builder | File | Why it is out of scope |
|---|---|---|
buildPrompt | packages/rex/src/cli/commands/chunked-review-state.ts | Readline prompt string ((3/8 accepted) > ) passed to promptLine. Named buildPrompt but never reaches a model. |
parseDuplicatePromptInput | packages/rex/src/cli/commands/smart-add.ts | Parses a human's answer to a readline prompt. |
promptReview | packages/hench/src/agent/analysis/review.ts | Interactive review gate — asks the operator, not a model. |
extractPromptSectionDiagnostics | packages/hench/src/agent/lifecycle/prompt-diagnostics.ts | Measures a prompt envelope; emits no prompt text. |
logPromptSections | packages/hench/src/agent/lifecycle/prompt-diagnostics.ts | Logs envelope section sizes to the CLI. |
promptForTestCommand | packages/hench/src/tools/test-command-resolver.ts | Asks the operator for a test command. |
