Skip to content

Commit aa57b3c

Browse files
authored
docs(benchmark): refresh task-aware measurements (#143)
1 parent 552f792 commit aa57b3c

8 files changed

Lines changed: 200 additions & 139 deletions

File tree

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ judgments (not lab benchmarks):
6262
Runs on macOS and Linux (Ubuntu and common CI/test distros); Windows is the
6363
exception.
6464

65-
**Measured agent surface** (same P2 fixture flow, Docker ARM64, 17 Jul 2026 —
66-
point-in-time): Headless warm **147** est. tokens vs Selenium **410** /
65+
**Measured agent surface** (same P2 fixture flow, Docker ARM64, 27 Aug 2026 —
66+
point-in-time): Headless warm **174** est. tokens vs Selenium **410** /
6767
Puppeteer **499**. Full method and limits:
6868
[BENCHMARK.md](apps/headless/docs/BENCHMARK.md).
6969

@@ -236,7 +236,6 @@ published GHCR image under Docker Desktop:
236236
docker run --shm-size=1g ghcr.io/lockintime/headless:latest headless --version
237237
```
238238

239-
240239
### npm / npx
241240

242241
JavaScript-based agent harnesses can run the verified launcher without a

apps/headless/docs/BENCHMARK.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,26 @@ record through the `Continue` transition, and save a final screenshot.
55

66
## Current snapshot
77

8-
Five fresh containers were run for each case on 12 August 2026, on Apple
8+
Five fresh containers were run for each case on 27 August 2026, on Apple
99
Silicon with Docker Linux ARM64. The table reports the median of each metric.
1010
These remain point-in-time measurements; repeat the benchmark before using
1111
them to compare a change. The generated
1212
[`results.json`](../../../packages/benchmark-results/results.json) preserves
1313
all 20 raw samples and the aggregation provenance.
1414

15-
| Workflow | Estimated tokens | Wall time | CPU time | Peak memory |
16-
| --- | ---: | ---: | ---: | ---: |
17-
| Headless, cold | 218 | 3,484 ms | 2,028 ms | 368 MiB |
18-
| Headless, warm | 174 | 3,248 ms | 1,379 ms | 366 MiB |
19-
| Selenium with Python | 410 | 2,880 ms | 2,010 ms | 363 MiB |
20-
| Puppeteer | 499 | 2,402 ms | 1,860 ms | 358 MiB |
15+
| Workflow | Estimated tokens | Wall time | CPU time | Peak memory |
16+
| -------------------- | ---------------: | --------: | -------: | ----------: |
17+
| Headless, cold | 218 | 3,413 ms | 1,794 ms | 381 MiB |
18+
| Headless, warm | 174 | 3,239 ms | 1,218 ms | 379 MiB |
19+
| Selenium with Python | 410 | 2,828 ms | 1,788 ms | 378 MiB |
20+
| Puppeteer | 499 | 2,400 ms | 1,849 ms | 367 MiB |
2121

2222
Estimated tokens are `ceil(workflow source bytes / 4)`. They compare the agent
2323
workflow surface, not billed LLM tokens, tool schemas, prompts, or responses.
2424

2525
Headless has the smallest measured agent surface: the warm workflow uses about
2626
58% fewer estimated tokens than Selenium and 65% fewer than Puppeteer. Its
27-
median CPU time is about 31% lower than Selenium and 26% lower than Puppeteer.
27+
median CPU time is about 32% lower than Selenium and 34% lower than Puppeteer.
2828
Puppeteer is fastest and has the lowest median peak memory; Headless does not
2929
lead those dimensions. The reusable P2 flow command reduces orchestration work
3030
for real agent-driven repeats, but this benchmark retains the comparable
@@ -38,9 +38,9 @@ lookup.
3838

3939
## Method
4040

41-
Each workflow uses Chromium 151 and FFmpeg 5.1 to produce the same two
41+
Each workflow uses Chromium 151.0.7922.173 and FFmpeg 5.1.9 to produce the same two
4242
artifacts: an MP4 that tours both pages and a final viewport PNG. Selenium 4.8.3
43-
uses ChromeDriver 151; Puppeteer Core is 22.15.0. All waits use page load or an
43+
uses ChromeDriver 151.0.7922.173; Puppeteer Core is 22.15.0. All waits use page load or an
4444
explicit URL condition. Every measured run gets a fresh container. The warm
4545
Headless case starts its host and session before timing; the cold case
4646
includes them.

apps/web/lib/repository-content.d.mts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export type BenchmarkWorkflow = {
22
case: string;
3+
label: string;
34
workflow: string;
45
name: string;
56
variant: string;
@@ -19,6 +20,7 @@ export type BenchmarkWorkflow = {
1920

2021
export type BenchmarkContent = {
2122
sectionLabel: string;
23+
methodDate: string;
2224
headline: string;
2325
summary: string;
2426
proofs: Array<{

apps/web/lib/repository-content.mjs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,19 @@ export function loadBenchmarkContent() {
165165
year: "numeric",
166166
timeZone: "UTC",
167167
}).format(generatedAt);
168+
const methodDate = new Intl.DateTimeFormat("en-GB", {
169+
day: "numeric",
170+
month: "long",
171+
year: "numeric",
172+
timeZone: "UTC",
173+
}).format(generatedAt);
168174

169175
const workflows = BENCHMARK_CASES.map((caseName) => {
170176
const entry = cases.get(caseName);
171177
const presentation = PRESENTATION[caseName];
172178
return {
173179
case: caseName,
180+
label: entry.label,
174181
workflow: entry.label.replace(",", ""),
175182
...presentation,
176183
tokens: entry.median.estimatedTokens,
@@ -189,6 +196,7 @@ export function loadBenchmarkContent() {
189196

190197
benchmarkCache = {
191198
sectionLabel: `P2 benchmark / ${date}`,
199+
methodDate,
192200
headline:
193201
warm.estimatedTokens ===
194202
Math.min(...workflows.map((workflow) => workflow.tokens))
@@ -431,6 +439,51 @@ export function loadDocumentationContent() {
431439
export function validateRepositoryContent() {
432440
const benchmark = loadBenchmarkContent();
433441
const documentation = loadDocumentationContent();
442+
const readme = readRepositoryFile("README.md");
443+
const method = readRepositoryFile("apps/headless/docs/BENCHMARK.md");
444+
const measuredClaim = readme.match(
445+
/\*\*Measured agent surface\*\*[\s\S]*?Docker ARM64, ([0-9]{1,2} [A-Z][a-z]{2} [0-9]{4})[\s\S]*?Headless warm \*\*(\d+)\*\* est\. tokens vs Selenium \*\*(\d+)\*\* \/\s*Puppeteer \*\*(\d+)\*\*/,
446+
);
447+
if (!measuredClaim) fail("README measured agent-surface claim is missing");
448+
const workflows = new Map(
449+
benchmark.workflows.map((workflow) => [workflow.case, workflow]),
450+
);
451+
const expectedClaim = [
452+
benchmark.sectionLabel.replace("P2 benchmark / ", ""),
453+
String(workflows.get("headless-warm").tokens),
454+
String(workflows.get("selenium").tokens),
455+
String(workflows.get("puppeteer").tokens),
456+
];
457+
if (
458+
JSON.stringify(measuredClaim.slice(1)) !== JSON.stringify(expectedClaim)
459+
) {
460+
fail(
461+
"README measured agent-surface claim does not match generated results",
462+
);
463+
}
464+
if (!method.includes(`for each case on ${benchmark.methodDate},`)) {
465+
fail("benchmark method date does not match generated results");
466+
}
467+
for (const workflow of benchmark.workflows) {
468+
const row = method
469+
.split("\n")
470+
.find((line) => line.startsWith(`| ${workflow.label}`));
471+
if (!row) fail(`benchmark method is missing row: ${workflow.label}`);
472+
const cells = row
473+
.split("|")
474+
.slice(1, -1)
475+
.map((cell) => cell.trim());
476+
const expectedCells = [
477+
workflow.label,
478+
workflow.tokens.toLocaleString("en-US"),
479+
`${workflow.wallMs.toLocaleString("en-US")} ms`,
480+
`${workflow.cpuMs.toLocaleString("en-US")} ms`,
481+
`${workflow.memoryMiB.toLocaleString("en-US")} MiB`,
482+
];
483+
if (JSON.stringify(cells) !== JSON.stringify(expectedCells)) {
484+
fail(`benchmark method row is stale: ${workflow.label}`);
485+
}
486+
}
434487
return {
435488
benchmarkCases: benchmark.workflows.length,
436489
commandGroups: documentation.commandGroups.length,

docs/ROADMAP.md

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,13 @@ of them:
8181
Honest snapshot, so newcomers know what is real:
8282

8383
**Working and verified**
84+
8485
- 39-verb JSON protocol over a `0600` Unix socket with peer-UID checks
8586
(`apps/headless/Sources/HeadlessProtocol/`).
8687
- Two engines behind one CLI: macOS `WKWebView` app, Linux sandboxed Chromium
8788
over the DevTools fd-3/4 pipe (no debug port).
8889
- Progressive context pruning (`inspect --context
89-
summary|outline|text|actions|full`, `--task`, `--within @rN`, `--budget`)
90+
summary|outline|text|actions|full`, `--task`, `--within @rN`, `--budget`)
9091
with a measured **94.5 % token reduction** on the 120-section fixture.
9192
- Evidence capture: PNG/JPG/PDF screenshots, viewport/section series, MP4/MOV/
9293
WebM/GIF recordings, visual diffs, flows, QA reports.
@@ -107,6 +108,7 @@ Honest snapshot, so newcomers know what is real:
107108
command reference, and a Docker sandbox wrapper.
108109

109110
**Not yet real**
111+
110112
- ~~No CI on pull requests or `main`~~ — PR CI landed (`.github/workflows/ci.yml`,
111113
backlog §D1/§D3). Correctness fixes in §A are still outstanding, and the
112114
macOS E2E is nightly/label-gated rather than a per-PR gate.
@@ -121,7 +123,7 @@ Honest snapshot, so newcomers know what is real:
121123
three places each and will drift; the site has no deploy pipeline.
122124
- Windows is not supported.
123125
- A list of real code defects (thread-safety on shutdown, oversized `qa
124-
report` responses, `@eN` ref invalidation surprises, host code duplication)
126+
report` responses, `@eN` ref invalidation surprises, host code duplication)
125127
— all catalogued in the [improvements backlog](roadmap/improvements-backlog.md).
126128

127129
---
@@ -152,23 +154,23 @@ codebase's existing behavior because they are the product:
152154

153155
## 4. Platform support
154156

155-
| Platform | Status today | Target |
156-
| --- | --- | --- |
157-
| **macOS 13+** (Apple Silicon) | Universal signed release pipeline awaiting tag | Signed + notarized, Homebrew, universal binary |
158-
| **macOS Intel** | Universal release pipeline awaiting tag | Universal binary in release CI |
159-
| **Linux** (Debian/Ubuntu, non-Snap Chromium) | Verified installer and GHCR pipeline awaiting tag | curl installer, published Docker image (GHCR), apt guidance |
160-
| **Linux other distros** | Works where a non-Snap Chromium exists | Documented candidate paths per distro family |
161-
| **Windows 10/11** | Not supported | **Stretch goal (Phase W)** — Chromium host ported; not required for "done". See [architecture decisions §6](roadmap/architecture-decisions.md). |
162-
| **Any OS via Docker** | Works (build locally) | `docker run ghcr.io/…/headless` one-liner, including as the practical Windows answer (WSL2/Docker Desktop) until Phase W lands |
157+
| Platform | Status today | Target |
158+
| -------------------------------------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
159+
| **macOS 13+** (Apple Silicon) | Universal signed release pipeline awaiting tag | Signed + notarized, Homebrew, universal binary |
160+
| **macOS Intel** | Universal release pipeline awaiting tag | Universal binary in release CI |
161+
| **Linux** (Debian/Ubuntu, non-Snap Chromium) | Verified installer and GHCR pipeline awaiting tag | curl installer, published Docker image (GHCR), apt guidance |
162+
| **Linux other distros** | Works where a non-Snap Chromium exists | Documented candidate paths per distro family |
163+
| **Windows 10/11** | Not supported | **Stretch goal (Phase W)** — Chromium host ported; not required for "done". See [architecture decisions §6](roadmap/architecture-decisions.md). |
164+
| **Any OS via Docker** | Works (build locally) | `docker run ghcr.io/…/headless` one-liner, including as the practical Windows answer (WSL2/Docker Desktop) until Phase W lands |
163165

164166
Agent-harness support (the other axis of "platform"):
165167

166-
| Harness | Today | Target |
167-
| --- | --- | --- |
168-
| MCP clients (Claude Code, Cursor, Codex, …) | `headless-mcp` stdio server | unchanged core; per-client setup docs + `.mcp.json` example |
169-
| Claude Code | manual skill pointer | root `CLAUDE.md` + discoverable skill |
170-
| Codex / OpenCode / Amp / others | `AGENTS.md` convention | root `AGENTS.md` (done in this change) |
171-
| Plain shell agents | CLI + `headless capabilities` | unchanged; capabilities doc kept machine-checked |
168+
| Harness | Today | Target |
169+
| ------------------------------------------- | ----------------------------- | ----------------------------------------------------------- |
170+
| MCP clients (Claude Code, Cursor, Codex, …) | `headless-mcp` stdio server | unchanged core; per-client setup docs + `.mcp.json` example |
171+
| Claude Code | manual skill pointer | root `CLAUDE.md` + discoverable skill |
172+
| Codex / OpenCode / Amp / others | `AGENTS.md` convention | root `AGENTS.md` (done in this change) |
173+
| Plain shell agents | CLI + `headless capabilities` | unchanged; capabilities doc kept machine-checked |
172174

173175
---
174176

@@ -184,7 +186,7 @@ every item carries file/line detail.
184186
Write down what the product is, what must not change, and everything that is
185187
wrong. Add agent rule files so every harness can work on this repo.
186188

187-
*Exit test:* this document set is merged; `AGENTS.md`/`CLAUDE.md` exist at
189+
_Exit test:_ this document set is merged; `AGENTS.md`/`CLAUDE.md` exist at
188190
root.
189191

190192
### Phase 1 — Trust the build (CI + correctness)
@@ -202,7 +204,7 @@ known races exist.
202204
`ELEMENT_NOT_FOUND` (backlog §A7).
203205
- Web app gets `next build` + eslint in the same CI (backlog §D3).
204206

205-
*Exit test:* a PR cannot merge with failing tests; the E2E suites pass on CI
207+
_Exit test:_ a PR cannot merge with failing tests; the E2E suites pass on CI
206208
runners, not just laptops; the known-crash list in the backlog §A is empty.
207209

208210
### Phase 2 — One host, written once (deduplication refactor)
@@ -216,7 +218,7 @@ errors end-to-end (backlog §B).
216218
This is also the **prerequisite for Windows**: after it, a Windows port is one
217219
new engine + one new transport backend, not a third copy of everything.
218220

219-
*Exit test:* adding a hypothetical new verb touches one dispatch site; the
221+
_Exit test:_ adding a hypothetical new verb touches one dispatch site; the
220222
error-code mapping is a typed enum, not string matching; the capability matrix
221223
(clipboard, network mock, PDF fidelity, …) is generated from code and asserted
222224
in tests.
@@ -237,7 +239,7 @@ in tests.
237239
- **npm wrapper** (`npx headless-browser` style) that downloads the platform
238240
binary — the cheapest path into JS-centric agent stacks. (Backlog §E.)
239241

240-
*Exit test:* a new user on a clean macOS or Linux machine gets from zero to
242+
_Exit test:_ a new user on a clean macOS or Linux machine gets from zero to
241243
`headless start` + first `visit` in under two minutes without touching a
242244
compiler, and without a Gatekeeper override on macOS.
243245

@@ -258,7 +260,7 @@ Make Headless the obvious choice inside every harness:
258260
implemented so `fill` can type literal `--json`; response pagination for
259261
large reports remains (backlog §G).
260262

261-
*Exit test:* a fresh Claude Code, Cursor, and Codex session can each discover
263+
_Exit test:_ a fresh Claude Code, Cursor, and Codex session can each discover
262264
and drive Headless with zero manual prompting beyond repo checkout.
263265

264266
### Phase 5 — Website and docs as a product surface
@@ -269,11 +271,11 @@ and drive Headless with zero manual prompting beyond repo checkout.
269271
generated from the CLI) (backlog §F).
270272
- Add the missing pages: install, security model, MCP setup, command
271273
reference, changelog, platform matrix.
272-
- Re-run the benchmark with the task-aware inspect flow before quoting any
273-
token number — the site currently markets `--task` while quoting pre-`--task`
274-
measurements (backlog §F4).
274+
- Keep the generated benchmark current with the task-aware inspect flow before
275+
quoting any token number. The site imports the generated medians and carries
276+
their point-in-time caveat (backlog §F4).
275277

276-
*Exit test:* site deploys on merge; every number and command on it is
278+
_Exit test:_ site deploys on merge; every number and command on it is
277279
generated or test-asserted; the "stale benchmark" warning is gone because the
278280
benchmark is current.
279281

@@ -295,7 +297,7 @@ Phase 2's engine/transport split. Shape of the work (detailed in
295297
- Until then, the documented Windows answer is Docker Desktop/WSL2 with the
296298
published image (Phase 3 dependency).
297299

298-
*Exit test:* the Linux E2E scenario passes on a Windows runner with the
300+
_Exit test:_ the Linux E2E scenario passes on a Windows runner with the
299301
Chromium engine; `winget install headless` works.
300302

301303
---
@@ -315,7 +317,7 @@ of the following hold:
315317
returns an explicit capability error; the matrix is generated and asserted.
316318
4. **Agent-native:** any MCP-capable harness and any AGENTS.md-reading harness
317319
can drive Headless from a fresh checkout with no human glue; `headless
318-
capabilities` is machine-accurate.
320+
capabilities` is machine-accurate.
319321
5. **Evidence current:** benchmark re-run on the shipping workflow; QA
320322
evidence regenerated for the release; website deployed and drift-free.
321323
6. **Docs:** this roadmap's Phases 1–5 checked off, with Windows either

docs/roadmap/improvements-backlog.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -431,9 +431,11 @@ Owner-decided scope: package managers, no hosted service.
431431
matrix, the README's comparison table (strongest positioning content, not
432432
on site). Plus `robots.txt`, `sitemap`, OG metadata, per-page `metadata`,
433433
404 page.
434-
- **F4. Stale-benchmark honesty:** ([#50](https://github.com/LockInTime/headless/issues/50)) site quotes pre-`--task` numbers while
434+
- **F4. Stale-benchmark honesty:** ([#50](https://github.com/LockInTime/headless/issues/50)) ~~site quotes pre-`--task` numbers while
435435
marketing `--task`, and drops BENCHMARK.md's re-run warning; headline says
436-
"Measured, not claimed." Fix by refresh (D5) or by carrying the caveat.
436+
"Measured, not claimed." Fix by refresh (D5) or by carrying the caveat.~~
437+
**Done:** a new five-repeat, task-aware snapshot was generated on 27 August 2026. The website imports its medians and date, preserves the point-in-time
438+
warning, and web lint rejects README claims that drift from generated data.
437439
- **F5. Dead weight:** ([#51](https://github.com/LockInTime/headless/issues/51)) `components/ui/side-rays.tsx` + `ogl` dep (unused),
438440
`public/scan-dashboard.png` (unreferenced), leftover shadcn `.dark` block in
439441
`globals.css`, 8 unused button variants; two WebGL stacks (~700 KB) for

0 commit comments

Comments
 (0)