harnessdoc 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- harnessdoc-0.1.0/.gitignore +13 -0
- harnessdoc-0.1.0/LICENSE +21 -0
- harnessdoc-0.1.0/PKG-INFO +331 -0
- harnessdoc-0.1.0/README.md +305 -0
- harnessdoc-0.1.0/docs/PRD.md +96 -0
- harnessdoc-0.1.0/docs/case-study.md +71 -0
- harnessdoc-0.1.0/examples/ci/harnessdoc.yml +37 -0
- harnessdoc-0.1.0/examples/demo_app/README.md +11 -0
- harnessdoc-0.1.0/examples/demo_app/WASTE.md +22 -0
- harnessdoc-0.1.0/examples/demo_app/acme_support/__init__.py +0 -0
- harnessdoc-0.1.0/examples/demo_app/acme_support/agent.py +45 -0
- harnessdoc-0.1.0/examples/demo_app/acme_support/extract.py +18 -0
- harnessdoc-0.1.0/examples/demo_app/acme_support/router.py +25 -0
- harnessdoc-0.1.0/examples/demo_app/acme_support/tools.py +37 -0
- harnessdoc-0.1.0/examples/demo_app/harnessdoc.experiment.minimax.yaml +24 -0
- harnessdoc-0.1.0/examples/demo_app/harnessdoc.experiment.yaml +17 -0
- harnessdoc-0.1.0/examples/demo_app/harnessdoc.goals.yaml +15 -0
- harnessdoc-0.1.0/examples/demo_app/jobs/nightly_summary.py +19 -0
- harnessdoc-0.1.0/examples/demo_app/logs/generate_logs.py +145 -0
- harnessdoc-0.1.0/examples/demo_app/prompts/policy.md +66 -0
- harnessdoc-0.1.0/examples/demo_app/tests/evals/classify_intent_cases.jsonl +24 -0
- harnessdoc-0.1.0/examples/demo_app/tests/evals/test_router.py +17 -0
- harnessdoc-0.1.0/pyproject.toml +55 -0
- harnessdoc-0.1.0/skills/harnessdoc/SKILL.md +97 -0
- harnessdoc-0.1.0/src/harnessdoc/__init__.py +3 -0
- harnessdoc-0.1.0/src/harnessdoc/check.py +89 -0
- harnessdoc-0.1.0/src/harnessdoc/cli.py +703 -0
- harnessdoc-0.1.0/src/harnessdoc/compare.py +192 -0
- harnessdoc-0.1.0/src/harnessdoc/data/__init__.py +0 -0
- harnessdoc-0.1.0/src/harnessdoc/data/pricing.json +148 -0
- harnessdoc-0.1.0/src/harnessdoc/experiment.py +404 -0
- harnessdoc-0.1.0/src/harnessdoc/goals.py +102 -0
- harnessdoc-0.1.0/src/harnessdoc/guard.py +106 -0
- harnessdoc-0.1.0/src/harnessdoc/interceptor.py +191 -0
- harnessdoc-0.1.0/src/harnessdoc/log.py +84 -0
- harnessdoc-0.1.0/src/harnessdoc/mcp_server.py +338 -0
- harnessdoc-0.1.0/src/harnessdoc/measure.py +127 -0
- harnessdoc-0.1.0/src/harnessdoc/pricing.py +285 -0
- harnessdoc-0.1.0/src/harnessdoc/report.py +239 -0
- harnessdoc-0.1.0/src/harnessdoc/rules.py +85 -0
- harnessdoc-0.1.0/src/harnessdoc/scan/__init__.py +152 -0
- harnessdoc-0.1.0/src/harnessdoc/scan/checks.py +274 -0
- harnessdoc-0.1.0/src/harnessdoc/scan/model.py +88 -0
- harnessdoc-0.1.0/src/harnessdoc/scan/pyast.py +628 -0
- harnessdoc-0.1.0/src/harnessdoc/sessions.py +317 -0
- harnessdoc-0.1.0/src/harnessdoc/skill/SKILL.md +97 -0
- harnessdoc-0.1.0/src/harnessdoc/usage/__init__.py +7 -0
- harnessdoc-0.1.0/src/harnessdoc/usage/analyze.py +243 -0
- harnessdoc-0.1.0/src/harnessdoc/usage/loader.py +120 -0
- harnessdoc-0.1.0/src/harnessdoc/usage/records.py +299 -0
- harnessdoc-0.1.0/tests/stdio_smoke.py +32 -0
- harnessdoc-0.1.0/tests/test_benchmark_demo.py +61 -0
- harnessdoc-0.1.0/tests/test_check.py +77 -0
- harnessdoc-0.1.0/tests/test_compare.py +119 -0
- harnessdoc-0.1.0/tests/test_coverage_guard.py +186 -0
- harnessdoc-0.1.0/tests/test_experiment.py +321 -0
- harnessdoc-0.1.0/tests/test_fmt_usd.py +26 -0
- harnessdoc-0.1.0/tests/test_goals_report_mcp.py +205 -0
- harnessdoc-0.1.0/tests/test_pricing.py +172 -0
- harnessdoc-0.1.0/tests/test_release.py +49 -0
- harnessdoc-0.1.0/tests/test_scanner.py +385 -0
- harnessdoc-0.1.0/tests/test_sessions.py +166 -0
- harnessdoc-0.1.0/tests/test_usage.py +276 -0
harnessdoc-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Harsh Chhabra
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: harnessdoc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Measure what each successful task of your AI app costs — provider-neutral, deterministic, and CI-ready.
|
|
5
|
+
Project-URL: Homepage, https://github.com/hchhabra1999/harnessdoc
|
|
6
|
+
Project-URL: Issues, https://github.com/hchhabra1999/harnessdoc/issues
|
|
7
|
+
Author: Harsh Chhabra
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: claude-code,cost,evals,harness,llm,mcp,prompt-caching,tokens
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Requires-Dist: pyyaml>=6
|
|
17
|
+
Requires-Dist: typer>=0.12
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: anthropic>=2; extra == 'dev'
|
|
20
|
+
Requires-Dist: mcp<3,>=2.3; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
22
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
23
|
+
Provides-Extra: mcp
|
|
24
|
+
Requires-Dist: mcp<3,>=2.3; extra == 'mcp'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# harnessdoc
|
|
28
|
+
|
|
29
|
+
**Measure what each successful task of your AI app costs — and prove when a change makes it cheaper without
|
|
30
|
+
making it worse.**
|
|
31
|
+
|
|
32
|
+
harnessdoc scans your code for every LLM call, reads your usage logs, and tells you — with `file:line`
|
|
33
|
+
evidence, a dollar estimate and the eval that guards quality — where your harness is wasting tokens. Then it
|
|
34
|
+
re-measures after you change something, so the saving is proven, not claimed.
|
|
35
|
+
|
|
36
|
+
- **Provider-neutral:** Anthropic, OpenAI, Gemini, LiteLLM.
|
|
37
|
+
- **Deterministic:** the same code and logs give the same numbers, so it can gate CI.
|
|
38
|
+
- **No LLM calls of its own:** use it as a CLI, in CI, as an MCP server (Claude Code, Cursor, Codex…) or as a
|
|
39
|
+
Claude Code skill — your agent does the reasoning; harnessdoc does the measuring.
|
|
40
|
+
|
|
41
|
+
> **Status: v0.1.0, alpha.** Everything below works and is tested (180 tests, CI on Python 3.10–3.13), but
|
|
42
|
+
> it has been proven on a demo app we wrote and on scanner precision for two open-source apps — **not yet on
|
|
43
|
+
> measured savings in a real app** (see [limits](#known-limits) and the [case study](docs/case-study.md)).
|
|
44
|
+
|
|
45
|
+
## Use it from your coding agent
|
|
46
|
+
|
|
47
|
+
harnessdoc ships an MCP server and a Claude Code skill. The agent interviews you about what the app is for,
|
|
48
|
+
then calls harnessdoc for every number — harnessdoc itself never calls an LLM.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install "harnessdoc[mcp]"
|
|
52
|
+
|
|
53
|
+
# Claude Code
|
|
54
|
+
claude mcp add harnessdoc -- harnessdoc mcp
|
|
55
|
+
harnessdoc skill install # then type /harnessdoc in Claude Code
|
|
56
|
+
|
|
57
|
+
# Cursor / other MCP clients: add to mcp.json
|
|
58
|
+
# {"mcpServers": {"harnessdoc": {"command": "harnessdoc", "args": ["mcp"]}}}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
| MCP tool | Does | Writes files? |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `scan` | LLM call sites, findings HD001–HD010, uncovered call sites | no |
|
|
64
|
+
| `measure` | Baseline from usage logs; each finding priced in $/month | no |
|
|
65
|
+
| `price` | Price token counts for any model in the price book | no |
|
|
66
|
+
| `explain_rule` | Why a rule matters, how to fix it, how its saving is estimated | no |
|
|
67
|
+
| `read_goals` / `write_goals` | The project's `harnessdoc.goals.yaml` | `write_goals` only |
|
|
68
|
+
| `write_report` | `harnessdoc-report.md` in the project | yes |
|
|
69
|
+
| `scaffold_guard` | Guard eval (pytest + cases file) for an uncovered call site; never overwrites | yes |
|
|
70
|
+
| `compare` | Before/after verdict: SHIP / REJECT / INCONCLUSIVE / NO CLEAR SAVING | no |
|
|
71
|
+
| `check` | What the CI gate would say: new vs fixed findings against the baseline | no |
|
|
72
|
+
| `plan_experiment` | Runs and estimated cost of an experiment; calls nothing | no |
|
|
73
|
+
| `run_experiment` | Runs it with your keys — needs `user_approved=true` and a budget | results file |
|
|
74
|
+
|
|
75
|
+
Paths are confined to the project directory you pass. The [skill](skills/harnessdoc/SKILL.md) adds the
|
|
76
|
+
workflow and its rules: quote only harnessdoc's numbers, label estimates, write a guard eval before
|
|
77
|
+
changing an unguarded call, and apply one approved change at a time.
|
|
78
|
+
|
|
79
|
+
## Prove the saving
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
harnessdoc guard scaffold extract_order_id # guard eval for a call site that has none
|
|
83
|
+
harnessdoc compare --before logs/week44.jsonl --after logs/week45.jsonl
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`compare` answers one question — *is each successful task cheaper now, without hurting quality?* — and is
|
|
87
|
+
built to say "not sure yet" when that's the truth:
|
|
88
|
+
|
|
89
|
+
- **Cost:** cost per successful task before vs after, with a 95% CI on the change (independent windows,
|
|
90
|
+
delta method). LOWER only if the whole interval is below zero.
|
|
91
|
+
- **Quality:** a non-inferiority test on success rate (Newcombe score interval) against the allowed drop in
|
|
92
|
+
`harnessdoc.goals.yaml`. PASS only if even the worst case stays within it.
|
|
93
|
+
- **Verdict:** SHIP (cheaper and quality held), REJECT (quality fell beyond the bar, or cost rose),
|
|
94
|
+
INCONCLUSIVE (with how many tasks per window would settle it), or NO CLEAR SAVING.
|
|
95
|
+
|
|
96
|
+
On the demo app, applying the top recommendations gives:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
Verdict: INCONCLUSIVE (quality INCONCLUSIVE vs allowed −1 pt · cost LOWER)
|
|
100
|
+
Cost per successful task $0.0366 → $0.0214 (−41.6%, 95% CI of the change −$0.0188 to −$0.0117)
|
|
101
|
+
Spend / 30 days $107.94 → $41.23 · cache hit rate 0% → 41.7%
|
|
102
|
+
Success rate +0.8 pt, but the CI (−3.7 to +5.3) still allows a drop beyond −1 pt.
|
|
103
|
+
About 3,060 tasks per window would settle it.
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The saving is real; whether quality held within one point isn't knowable from a week of 480 tickets — so it
|
|
107
|
+
doesn't claim it. With a 5-point bar the same data says SHIP.
|
|
108
|
+
|
|
109
|
+
Guard evals count toward coverage only once their cases file has at least one case, and a test covers an
|
|
110
|
+
LLM call when it calls any function that leads to it (handler → helper → LLM wrapper).
|
|
111
|
+
|
|
112
|
+
## Try a cheaper setup before changing code
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
harnessdoc experiment plan harnessdoc.experiment.yaml # runs + estimated cost; calls nothing
|
|
116
|
+
harnessdoc experiment run harnessdoc.experiment.yaml --budget 0.50
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
An experiment replays a call site's guard-eval cases through **your real code** under several variants —
|
|
120
|
+
another model (`model_overrides`), a different output cap (`max_tokens`), extra request settings
|
|
121
|
+
(`kwargs`, e.g. `cache_control`), or environment flags your app reads (`env`) — with no code edits.
|
|
122
|
+
harnessdoc briefly intercepts the Anthropic / OpenAI / Gemini SDK call to apply the variant and record usage,
|
|
123
|
+
scores every answer, and gives each variant the same SHIP / REJECT / INCONCLUSIVE verdict as `compare`.
|
|
124
|
+
|
|
125
|
+
It is the only part of harnessdoc that costs money, so:
|
|
126
|
+
|
|
127
|
+
- it uses **your** API keys from the environment — harnessdoc never reads or stores them;
|
|
128
|
+
- `--budget` is a hard cap: no new LLM call starts once it is reached (one in-flight call can overshoot by its
|
|
129
|
+
own cost), and the stop can't be swallowed by an app's `except Exception` retry loop;
|
|
130
|
+
- `run` shows the plan and asks before spending (unless `--yes`); over MCP, `run_experiment` refuses
|
|
131
|
+
without `user_approved=true` and a budget;
|
|
132
|
+
- variants are interleaved case by case, so drift doesn't favour one of them; prompts and outputs are never
|
|
133
|
+
written to disk.
|
|
134
|
+
|
|
135
|
+
- if a call can't be priced (a model missing from the price book, or a response with no token usage) the run
|
|
136
|
+
**stops immediately** — a budget can't be enforced on spend it can't see. `--allow-unpriced` overrides it.
|
|
137
|
+
|
|
138
|
+
The demo app ships two: [`harnessdoc.experiment.yaml`](examples/demo_app/harnessdoc.experiment.yaml) tests
|
|
139
|
+
the intent router on Haiku against its 24 labelled cases (72 runs, about one cent), and
|
|
140
|
+
[`harnessdoc.experiment.minimax.yaml`](examples/demo_app/harnessdoc.experiment.minimax.yaml) runs the same
|
|
141
|
+
question on MiniMax's Anthropic-compatible API (`ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic` plus your
|
|
142
|
+
MiniMax key as `ANTHROPIC_API_KEY`), comparing `MiniMax-M3` with the half-price `MiniMax-M3.1-Flash`.
|
|
143
|
+
|
|
144
|
+
**First real run** (2026-11-06, MiniMax's API, 72 runs, $0.0023 of a $0.10 cap):
|
|
145
|
+
|
|
146
|
+
| Variant | Passed | Cost per successful task | Verdict |
|
|
147
|
+
|---|---|---|---|
|
|
148
|
+
| `MiniMax-M3` (baseline) | 22/24 | $0.0000417 | — |
|
|
149
|
+
| `MiniMax-M3.1-Flash` | 23/24 | $0.0000345 (−17%, CI crosses zero) | INCONCLUSIVE — ~118 tasks/window would settle it |
|
|
150
|
+
| Flash, `max_tokens: 16` | 14/24 | $0.0000409 | **REJECT** — success rate −33 pt |
|
|
151
|
+
|
|
152
|
+
Flash costs half as much per token but always thinks, and those tokens bill as output, so the saving was
|
|
153
|
+
~17% rather than 50%. The 16-token cap broke it: thinking used the cap before the label was written — a
|
|
154
|
+
regression a cost-only comparison would have missed. 24 cases is a small sample; one case's difference between
|
|
155
|
+
the first two rows is noise.
|
|
156
|
+
|
|
157
|
+
## What your coding agent costs (Claude Code logs)
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
harnessdoc sessions # every session under ~/.claude/projects
|
|
161
|
+
harnessdoc sessions --since 14d --cwd ~/code/my-app --top 5
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Claude Code writes every session — and every subagent — to JSONL logs. `sessions` reads **only token counts,
|
|
165
|
+
model ids, timestamps and session ids (never your prompts, replies or tool output)** and prices them from the
|
|
166
|
+
price book: where the money goes (cache reads, output, 1-hour vs 5-minute cache writes, uncached input), cache
|
|
167
|
+
hit rate, cost by model and by day, subagents' share, and the costliest sessions.
|
|
168
|
+
|
|
169
|
+
Three details of the log format that a naive reader gets wrong, all handled and tested:
|
|
170
|
+
|
|
171
|
+
- **Each message is logged once per content block**, with the same usage every time. Summing records
|
|
172
|
+
over-counts; in the logs checked it inflated tokens by 1.8–2.95×. Records are de-duplicated by message id.
|
|
173
|
+
- **Cache writes have a TTL split** and the 1-hour write costs 2× input (5-minute: 1.25×). Most writes were
|
|
174
|
+
1-hour, so the TTL is priced, not assumed.
|
|
175
|
+
- **Subagents are separate files** (`<session>/subagents/agent-*.jsonl`); skipping them hides a large share of
|
|
176
|
+
the spend, so they are read and reported separately.
|
|
177
|
+
|
|
178
|
+
Dollars are **API-equivalent at list prices** — a subscription plan doesn't bill per token — so treat the
|
|
179
|
+
breakdowns (what dominates, which sessions, how much subagents) as the actionable part.
|
|
180
|
+
|
|
181
|
+
## CI gate
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
harnessdoc check . --write-baseline harnessdoc-baseline.json # once: today's findings become accepted debt
|
|
185
|
+
harnessdoc check . --baseline harnessdoc-baseline.json --fail-on medium # every PR
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Findings are matched by rule + file + enclosing function, not line number, so unrelated edits don't make
|
|
189
|
+
old findings look new; fixed findings are listed. A ready-made GitHub Actions workflow that also uploads the
|
|
190
|
+
report is in [`examples/ci/harnessdoc.yml`](examples/ci/harnessdoc.yml).
|
|
191
|
+
|
|
192
|
+
## Goals and the report
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
harnessdoc goals init . # what the app does, what a successful task is, target, constraints, logs
|
|
196
|
+
harnessdoc report . # → harnessdoc-report.md
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The report states the goal, the baseline (cost per successful task vs target), recommendations ranked by
|
|
200
|
+
measured $/month with what to do first, which call sites need a guard eval before any change, every
|
|
201
|
+
finding in detail, and how each number was computed. It has no timestamp, so the same inputs produce the
|
|
202
|
+
same file and it can be diffed in a PR.
|
|
203
|
+
|
|
204
|
+
## Scan a project
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
uv venv .venv && uv pip install -p .venv -e ".[dev]"
|
|
208
|
+
.venv/bin/harnessdoc scan path/to/your/app # readable report
|
|
209
|
+
.venv/bin/harnessdoc scan path/to/your/app --json # stable JSON for tooling
|
|
210
|
+
.venv/bin/harnessdoc scan path/to/your/app --fail-on high # exit 1 in CI if any high finding
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The scanner parses your Python (it never imports or runs it), finds every LLM call made through the
|
|
214
|
+
Anthropic, OpenAI, Gemini or LiteLLM SDKs, resolves what goes into each call — model, output cap, system
|
|
215
|
+
prompt (followed across functions, constants, files and modules), tools, caching — and runs these checks:
|
|
216
|
+
|
|
217
|
+
| Rule | Finds | Saving estimate from |
|
|
218
|
+
|---|---|---|
|
|
219
|
+
| HD001 | Volatile values (`datetime.now()`, UUIDs…) at the start of a prompt that bust the cache | logs (later) |
|
|
220
|
+
| HD002 | A large static prefix sent to Anthropic without `cache_control` | price book: cache-read vs input |
|
|
221
|
+
| HD003 | Tool lists built by iterating a `set` (order differs per process → no shared cache) | logs (later) |
|
|
222
|
+
| HD004 | `while True` + `except: continue` retrying forever | — |
|
|
223
|
+
| HD005 | Agent loops with no iteration cap | — |
|
|
224
|
+
| HD006 | A premium model doing classification / extraction / short summaries | price book: small vs premium model |
|
|
225
|
+
| HD007 | `max_tokens` in the thousands for a one-word answer | — |
|
|
226
|
+
| HD008 | An LLM extracting something a regex can (IDs, emails, URLs) | 100% of the call when the regex matches |
|
|
227
|
+
| HD009 | Offline jobs calling the real-time API in a loop | price book: batch multiplier |
|
|
228
|
+
| HD010 | LLM calls no test/eval references (no quality guard) | — |
|
|
229
|
+
|
|
230
|
+
Every finding names the eval that guards the affected code, or says there is none. Savings are labelled
|
|
231
|
+
*estimates* until usage logs are joined (weeks 4–5).
|
|
232
|
+
|
|
233
|
+
**Benchmark:** on the [deliberately wasteful demo app](examples/demo_app) the scanner finds 10/10 planted
|
|
234
|
+
issues with 0 false positives (`tests/test_benchmark_demo.py`). That app was written by us, so we also ran it
|
|
235
|
+
on two real open-source apps — it found every SDK call in both, and the [case study](docs/case-study.md)
|
|
236
|
+
records the three bugs that exposed, what it can't see (models set at runtime), and what it didn't measure.
|
|
237
|
+
|
|
238
|
+
## Measure real usage
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
.venv/bin/harnessdoc measure logs/ # spend, cache hit rate, cost per successful task
|
|
242
|
+
.venv/bin/harnessdoc measure logs/ --scan . # + every finding priced in measured $/month
|
|
243
|
+
.venv/bin/harnessdoc measure logs/ --outcomes outcomes.csv # outcomes kept elsewhere (task_id,success)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`measure` reads the logs you already have, auto-detected line by line: raw Anthropic / OpenAI (Chat and
|
|
247
|
+
Responses) / Gemini responses dumped as JSONL (optionally wrapped with `task_id`, `site`, `ts`),
|
|
248
|
+
OpenTelemetry GenAI spans (flat or OTLP JSON), gzip files and whole folders. No logs yet? Drop in the
|
|
249
|
+
dependency-free logger — it writes token counts and ids only, never prompts or completions:
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
from harnessdoc.log import UsageLogger
|
|
253
|
+
usage_log = UsageLogger("logs/llm_usage.jsonl")
|
|
254
|
+
|
|
255
|
+
response = client.messages.create(...)
|
|
256
|
+
usage_log.record(response, task_id=ticket.id) # site defaults to the calling function
|
|
257
|
+
usage_log.outcome(ticket.id, success=ticket.resolved) # once the task is over
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Cost per successful task** = everything spent on tasks with a known outcome ÷ number of successes, so the
|
|
261
|
+
spend on failed tasks counts against the successes that carry it. It comes with a 95% confidence interval
|
|
262
|
+
(delta method over tasks), next to the success rate (Wilson interval) and per-task mean/median/p90. Spend
|
|
263
|
+
that belongs to no task outcome (e.g. a nightly batch job) is reported separately, not hidden.
|
|
264
|
+
|
|
265
|
+
With `--scan`, each finding is priced from its call site's measured spend, and the combined total does not
|
|
266
|
+
double count: savings on the same call site combine as `1 − Π(1 − share)`.
|
|
267
|
+
|
|
268
|
+
Try it on the demo app with a synthetic week of traffic:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
.venv/bin/python examples/demo_app/logs/generate_logs.py
|
|
272
|
+
.venv/bin/harnessdoc measure examples/demo_app/logs/usage.jsonl --scan examples/demo_app
|
|
273
|
+
# → cost per successful task $0.0366 (95% CI $0.0333–$0.0399) · combined potential ~$65 of ~$108 / month
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Try the cost engine
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
.venv/bin/harnessdoc models --provider anthropic
|
|
280
|
+
.venv/bin/harnessdoc cost anthropic.claude-opus-5-5 --in 3000 --cache-read 6000 --out 400
|
|
281
|
+
.venv/bin/harnessdoc cost gpt-6.1-sol --cache-write 2000 --ttl 12h --json
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Model ids are resolved the way they appear in real code: Bedrock/LiteLLM/Vertex prefixes, dated snapshots and
|
|
285
|
+
`-latest` suffixes all map to the right price.
|
|
286
|
+
|
|
287
|
+
## Prices
|
|
288
|
+
|
|
289
|
+
`src/harnessdoc/data/pricing.json` holds prices (USD per 1M tokens) and caching rules for Anthropic, OpenAI
|
|
290
|
+
and Gemini, each with its source URL and an `as_of` date, plus scheduled price changes and long-context
|
|
291
|
+
tiers. Providers change these often — override or add models per project with a `harnessdoc.pricing.json`
|
|
292
|
+
in your repo root (deep-merged over the defaults).
|
|
293
|
+
|
|
294
|
+
## Benchmark: the wasteful demo app
|
|
295
|
+
|
|
296
|
+
[`examples/demo_app`](examples/demo_app) is a small support bot with 11 planted kinds of harness waste
|
|
297
|
+
(cache-busting timestamps, non-deterministic tool order, an Opus-tier model for classification, an LLM doing
|
|
298
|
+
a regex's job, a real-time API for a nightly batch, uncovered call sites…). The [answer key](examples/demo_app/WASTE.md)
|
|
299
|
+
is how the scanner's recall and false-positive rate are measured.
|
|
300
|
+
|
|
301
|
+
## Known limits
|
|
302
|
+
|
|
303
|
+
- **Python only.** TypeScript/JS apps aren't scanned, and neither are calls made through frameworks such as
|
|
304
|
+
LangChain (the scanner follows the Anthropic, OpenAI, Gemini and LiteLLM SDKs directly).
|
|
305
|
+
- **Models set at runtime are invisible to the static scanner.** On two real open-source apps only 4 of 136 call
|
|
306
|
+
sites had a statically readable model, so cost rules like HD006 couldn't fire there. Usage logs (where the model
|
|
307
|
+
is recorded per request) cover this gap — that's why `measure` and `compare` exist.
|
|
308
|
+
- **Savings are estimates until `compare` says SHIP**, and `compare` says INCONCLUSIVE when the data can't
|
|
309
|
+
support a verdict (small samples) instead of declaring a winner.
|
|
310
|
+
- **No measured saving on a real app yet.** The target of ≥25% lower cost per successful task on an open-source
|
|
311
|
+
app needs one with real traffic logs; the demo-app numbers are synthetic.
|
|
312
|
+
- **`harnessdoc sessions` dollars are API-equivalent** list prices; a subscription plan isn't billed per token.
|
|
313
|
+
- **Prices change.** The price book is dated and each entry has its source; override per project with
|
|
314
|
+
`harnessdoc.pricing.json`.
|
|
315
|
+
|
|
316
|
+
## Roadmap (what v0.1.0 contains)
|
|
317
|
+
|
|
318
|
+
| Area | Status |
|
|
319
|
+
|---|---|
|
|
320
|
+
| Price book + cost engine (Anthropic, OpenAI, Gemini, MiniMax) | ✅ |
|
|
321
|
+
| Python scanner, checks HD001–HD010, eval coverage, CI exit codes | ✅ |
|
|
322
|
+
| Usage analyzer: cost per successful task, findings priced in measured $/month | ✅ |
|
|
323
|
+
| MCP server (12 tools) + `/harnessdoc` skill + goals file + Markdown report | ✅ |
|
|
324
|
+
| `compare` (SHIP / REJECT / INCONCLUSIVE), guard-eval scaffolds, CI gate | ✅ |
|
|
325
|
+
| Experiment runner with a hard budget, tested on a real API | ✅ |
|
|
326
|
+
| Claude Code session-log analysis (`sessions`) | ✅ |
|
|
327
|
+
| Real-app measured saving, TypeScript scanning, resolving models from config | next |
|
|
328
|
+
|
|
329
|
+
## Licence
|
|
330
|
+
|
|
331
|
+
MIT
|