Skip to content

Commit bac64ef

Browse files
committed
Why not
1 parent da7a90c commit bac64ef

35 files changed

Lines changed: 4513 additions & 2145 deletions

README.md

Lines changed: 119 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,156 @@
11
# The Format Tax
22

3-
**You're paying it right now. On every LLM call. On every streamed response. On every config file.**
3+
Benchmark suite, paper draft, and recommendation tool for serialization choices in human-AI systems.
44

5-
> The definitive, open-source guide to data serialization for human-AI systems. Benchmarks, decision framework, and the paper that proves JSON is overcharging you.
6-
7-
**[formattax.dev](https://formattax.dev)** | [Read the Paper](docs/paper.md) | [Decision Tree](docs/decision-tree.md) | [Benchmarks](docs/benchmarks.md)
5+
**[formattax.dev](https://formattax.dev)** | [Paper draft](docs/paper.md) | [Recommendation policy](docs/decision-tree.md) | [Benchmarks](docs/benchmarks.md) | [Schemas](docs/schemas.md)
86

97
---
108

11-
## The Tax You Didn't Know You Were Paying
9+
## Thesis
1210

13-
Every time you feed JSON to an LLM, you burn **30-72% of your token budget** on curly braces, repeated keys, and quotation marks. Every time you stream JSON to a UI, your users wait for a closing `}` that hasn't arrived yet. Every time you pick "just use JSON" for a config file, you lose the ability to document *why*.
11+
Serialization is not just an implementation detail.
1412

15-
This isn't a style preference. It's a **tax** -- measured in dollars, latency, accuracy, and carbon.
13+
The same system may cross several boundaries with incompatible needs:
1614

17-
## What's Inside
15+
- model-facing input
16+
- model-facing output
17+
- progressive UI streaming
18+
- human-maintained config
19+
- inter-service transport
20+
- storage
1821

19-
```
20-
the-format-tax/
21-
├── README.md
22-
├── docs/
23-
│ ├── paper.md # "The Format Tax" whitepaper (arXiv target)
24-
│ ├── prd.md # Product Requirements Document
25-
│ ├── benchmarks.md # Benchmark methodology & code
26-
│ ├── schemas.md # Schema & validation ecosystem
27-
│ └── decision-tree.md # Interactive decision framework
28-
├── benchmarks/ # Reproducible benchmark code
29-
├── site/ # formattax.dev (Astro + Tailwind)
30-
└── LICENSE
31-
```
22+
Using one format for all of them imposes a *Format Tax*: unnecessary structural tokens, weaker streaming behavior, poorer ergonomics, or a less reliable output path than the stage actually requires.
3223

33-
## The Tax Bill
24+
## What This Repo Is
3425

35-
| What You're Doing | JSON's Tax | Best Alternative | Savings |
36-
|---|---|---|---|
37-
| Feeding tabular data to an LLM | 66,396 tokens / 1k records | ISON: 18,600 tokens | **72% fewer tokens** |
38-
| LLM retrieves a value from your data | 52.3% accuracy | Markdown-KV: 60.7% | **+8.4 pp** |
39-
| Streaming AI responses to UI | Waits for closing `}` | YAML (json-render): renders at every prefix | **3x faster first paint** |
40-
| Config file for your app | No comments, no docs | Markdown+Frontmatter | **Self-documenting** |
41-
| Backend service-to-service | 7,045 ns/op | FlatBuffers: 711 ns/op | **10x faster** |
42-
| Carbon footprint at scale | EES 0.926 | TOON: EES 0.980 | **6% less CO2 per call** |
26+
This repository contains three related artifacts:
4327

44-
## The Thesis
28+
1. **A paper draft**
29+
An academic-facing document that stays conservative about evidence and separates local results from external claims.
30+
2. **A benchmark suite**
31+
Canonical datasets, question sets, encoders, and runner tracks for measuring format behavior.
32+
3. **A homepage and recommendation engine**
33+
A practitioner-facing tool that returns a primary format, fallback, confidence, and schema/parser pairing.
4534

46-
**There is no single best format.** The right answer depends on which stage of the pipeline you're at:
35+
## Evidence Policy
4736

48-
```
49-
Storage -> Transport -> Validation -> Prompt Injection -> Generation -> Integration
50-
| | | | | |
51-
JSON Protobuf Zod/Pydantic MD-KV/TOON JSON+constrained JSON
52-
Parquet FlatBuf JSON Schema YAML decoding Protobuf
53-
Effect Schema MD+Frontmatter
54-
```
37+
Every claim in the project should fit one of these buckets:
5538

56-
Every stage has a different optimal format. Using JSON for all of them is the Format Tax.
39+
- **Locally measured** — produced by this repository’s benchmark code
40+
- **Externally reported** — from papers, official project benchmarks, or official docs
41+
- **Open hypothesis** — worth testing, not yet proven here
5742

58-
## The Master Rule
43+
This matters because the public serialization discourse is noisy:
5944

60-
```
61-
INPUT: Markdown-KV / TOON family (accuracy + tokens)
62-
OUTPUT: JSON + constrained decoding (validity)
63-
STREAM: YAML wire format (prefix-valid)
64-
CONFIG: YAML (complex) / TOML (simple)
65-
BACKEND: Protobuf / FlatBuffers (speed)
66-
VALIDATE: Zod / Pydantic / JSON Schema (stage-appropriate)
67-
```
45+
- some results are academic
46+
- some are official format-project benchmarks
47+
- some are independent reproductions
48+
- some are placeholder numbers used for site development
49+
50+
The project is being rewritten to keep those apart.
51+
52+
## Current Benchmark Coverage
53+
54+
Implemented or in-progress runner tracks:
55+
56+
- Token efficiency
57+
- Retrieval accuracy
58+
- Streaming readiness
59+
- Generation quality
60+
- Schema guidance
61+
62+
Planned but not yet implemented as a local runner track:
63+
64+
- Binary throughput / transport benchmarking
65+
66+
## Recommendation Layer
67+
68+
The homepage is intentionally more operational than the paper.
69+
70+
It answers:
71+
72+
- what boundary am I at?
73+
- what shape is the data?
74+
- what is the main optimization target?
75+
- what hard constraints apply?
76+
77+
It then returns:
78+
79+
- a primary recommendation
80+
- a fallback
81+
- confidence
82+
- evidence basis
83+
- schema pairing
84+
- parser pairing if relevant
6885

69-
## Formats Covered
86+
The homepage is allowed to be decisive. The paper is not allowed to smuggle those decisions in as settled research results.
7087

71-
- **20+ text formats** -- JSON, YAML, TOML, XML, CSV, JSONL, Markdown-KV, MD+Frontmatter, TOON, ISON, GOON, TRON, and more
72-
- **8 binary protocols** -- Protobuf, FlatBuffers, Cap'n Proto, MessagePack, CBOR, BSON, Avro, Thrift
73-
- **14 schema systems** -- JSON Schema, Zod, Effect Schema, Valibot, Pydantic, Standard Schema, and more
88+
## Schema Position
89+
90+
Validators and schemas are treated as a separate layer.
91+
92+
Current project stance:
93+
94+
- validator libraries are not serialization formats
95+
- the strongest generation benefit usually comes from **schema strategy**, especially JSON Schema-oriented workflows
96+
- library choice mostly matters through:
97+
- JSON Schema export quality
98+
- runtime ergonomics
99+
- ecosystem fit
100+
101+
## Important Status Note
102+
103+
Some benchmark content under `site/src/content/benchmarks/` and format metadata still exists primarily to support site UI development. Those placeholders should not be read as final reproduced benchmark results unless the corresponding run metadata is populated and `dummy` is false.
104+
105+
## Repository Layout
106+
107+
```text
108+
the-format-tax/
109+
├── README.md
110+
├── docs/
111+
│ ├── paper.md
112+
│ ├── decision-tree.md
113+
│ ├── benchmarks.md
114+
│ ├── schemas.md
115+
│ └── superpowers/
116+
├── benchmarks/
117+
│ ├── datasets/
118+
│ ├── questions/
119+
│ ├── references/
120+
│ └── runner/
121+
├── paper/
122+
└── site/
123+
```
74124

75125
## Development
76126

77-
The landing page is an Astro site:
127+
### Site
78128

79129
```bash
80130
cd site
81131
bun install
82132
bun run dev
83133
```
84134

85-
## Contributing
86-
87-
This is an open research project. We want:
88-
89-
- New format benchmarks (especially on newer LLMs)
90-
- Schema system evaluations
91-
- Streaming parser comparisons
92-
- Real-world case studies ("we switched from X to Y and saved Z")
93-
- Corrections and challenges to our data
135+
### Benchmarks
94136

95-
File an issue or open a PR. All benchmarks are reproducible.
137+
```bash
138+
cd benchmarks/runner
139+
bun run src/index.ts --track=1
140+
```
96141

97-
## License
142+
The runner currently expects Bun.
98143

99-
MIT (code) / CC BY-SA 4.0 (paper and documentation)
144+
## Contributing
100145

101-
## Citation
146+
Useful contributions:
102147

103-
```bibtex
104-
@article{formattax2026,
105-
title={The Format Tax: How Serialization Choice Costs Tokens, Accuracy,
106-
and Latency in Human--AI Systems},
107-
author={Brunner, G{\"u}nther},
108-
year={2026},
109-
journal={arXiv preprint},
110-
url={https://code-agents.github.io/format-tax}
111-
}
112-
```
148+
- independent reproductions of public format claims
149+
- better harness normalization
150+
- schema-guidance experiments
151+
- binary transport benchmark implementation
152+
- clearer separation between dummy site content and measured benchmark output
113153

114-
---
154+
## License
115155

116-
*The Format Tax is a research project by [Günther Brunner](https://guntherbrunner.com) / [Code Agents](https://github.com/code-agents).*
117-
*Stop paying the tax. Start engineering your context.*
156+
MIT for code. Documentation and paper material follow the project’s existing documentation license conventions.

benchmarks/runner/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"bench:track1": "bun run src/index.ts --track=1",
88
"bench:track2": "bun run src/index.ts --track=2",
99
"bench:track3": "bun run src/index.ts --track=3",
10-
"bench:track4": "bun run src/index.ts --track=4"
10+
"bench:track4": "bun run src/index.ts --track=4",
11+
"bench:track5": "bun run src/index.ts --track=5"
1112
},
1213
"dependencies": {
1314
"tiktoken": "^1.0.18",

benchmarks/runner/src/index.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { parseArgs } from "util";
2-
import { resolve, join } from "path";
2+
import { resolve } from "path";
33
import { runTrack1 } from "./tracks/track1.ts";
44
import { runTrack2 } from "./tracks/track2.ts";
55
import { runTrack3 } from "./tracks/track3.ts";
66
import { runTrack4 } from "./tracks/track4.ts";
7+
import { runTrack5 } from "./tracks/track5.ts";
8+
import { ensureOutputParent, resolveTrackOutputPath } from "./output-path.ts";
79

810
const { values } = parseArgs({
911
args: Bun.argv.slice(2),
@@ -12,7 +14,7 @@ const { values } = parseArgs({
1214
format: { type: "string", short: "f" },
1315
dataset: { type: "string", short: "d" },
1416
harness: { type: "string", short: "h" },
15-
output: { type: "string", short: "o", default: "../../site/src/content/benchmarks" },
17+
output: { type: "string", short: "o" },
1618
dry: { type: "boolean", default: false },
1719
},
1820
strict: true,
@@ -30,7 +32,7 @@ console.log(` track: ${values.track ?? "(all)"}`);
3032
console.log(` format: ${values.format ?? "(all)"}`);
3133
console.log(` dataset: ${values.dataset ?? "(all)"}`);
3234
console.log(` harness: ${values.harness ?? "(all)"}`);
33-
console.log(` output: ${values.output}`);
35+
console.log(` output: ${values.output ?? "../../site/src/content/benchmarks"}`);
3436
console.log(` dry: ${values.dry}`);
3537
console.log();
3638

@@ -39,7 +41,7 @@ console.log();
3941
const RUNNER_DIR = import.meta.dir;
4042
const DATASET_DIR = resolve(RUNNER_DIR, "../../datasets");
4143
const QUESTION_DIR = resolve(RUNNER_DIR, "../../questions");
42-
const OUTPUT_DIR = resolve(RUNNER_DIR, values.output ?? "../../site/src/content/benchmarks");
44+
const DEFAULT_OUTPUT_DIR = resolve(RUNNER_DIR, "../../site/src/content/benchmarks");
4345

4446
interface Question {
4547
id: string;
@@ -72,7 +74,7 @@ async function loadDatasets(filter?: string): Promise<Record<string, unknown>> {
7274
: [...DATASET_FILES];
7375

7476
for (const name of names) {
75-
const path = join(DATASET_DIR, `${name}.json`);
77+
const path = resolve(DATASET_DIR, `${name}.json`);
7678
try {
7779
const file = Bun.file(path);
7880
datasets[name] = await file.json();
@@ -87,7 +89,7 @@ async function loadDatasets(filter?: string): Promise<Record<string, unknown>> {
8789
async function loadQuestions(): Promise<Question[]> {
8890
const allQuestions: Question[] = [];
8991
for (const name of QUESTION_FILES) {
90-
const path = join(QUESTION_DIR, `${name}.json`);
92+
const path = resolve(QUESTION_DIR, `${name}.json`);
9193
try {
9294
const file = Bun.file(path);
9395
const questions: Question[] = await file.json();
@@ -104,16 +106,16 @@ async function loadQuestions(): Promise<Question[]> {
104106
const tracksToRun: number[] = [];
105107
if (values.track) {
106108
if (values.track === "all") {
107-
tracksToRun.push(1, 2, 3, 4);
109+
tracksToRun.push(1, 2, 3, 4, 5);
108110
} else {
109111
for (const t of values.track.split(",")) {
110112
const n = parseInt(t.trim(), 10);
111-
if (n >= 1 && n <= 4) tracksToRun.push(n);
113+
if (n >= 1 && n <= 5) tracksToRun.push(n);
112114
else console.error(` Unknown track: ${t}`);
113115
}
114116
}
115117
} else {
116-
tracksToRun.push(1, 2, 3, 4);
118+
tracksToRun.push(1, 2, 3, 4, 5);
117119
}
118120

119121
async function main() {
@@ -125,7 +127,13 @@ async function main() {
125127
const dry = values.dry ?? false;
126128

127129
for (const track of tracksToRun) {
128-
const outputFile = join(OUTPUT_DIR, `track${track}.json`);
130+
const outputFile = resolveTrackOutputPath({
131+
baseDir: values.output ? process.cwd() : DEFAULT_OUTPUT_DIR,
132+
outputArg: values.output ?? ".",
133+
track,
134+
totalTracks: tracksToRun.length,
135+
});
136+
await ensureOutputParent(outputFile);
129137

130138
switch (track) {
131139
case 1:
@@ -165,6 +173,15 @@ async function main() {
165173
});
166174
break;
167175

176+
case 5:
177+
await runTrack5({
178+
harnessFilter: values.harness,
179+
formatFilter: values.format,
180+
outputPath: outputFile,
181+
dry,
182+
});
183+
break;
184+
168185
default:
169186
console.error(`Unknown track: ${track}`);
170187
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import test from "node:test";
2+
import assert from "node:assert/strict";
3+
4+
import { resolveTrackOutputPath } from "./output-path.ts";
5+
6+
const CLI_CWD = "/tmp/format-tax/benchmarks/runner";
7+
8+
test("resolveTrackOutputPath keeps explicit json file path for single-track runs", () => {
9+
const outputPath = resolveTrackOutputPath({
10+
baseDir: CLI_CWD,
11+
outputArg: "../tmp-bench/custom-track3.json",
12+
track: 3,
13+
totalTracks: 1,
14+
});
15+
16+
assert.equal(outputPath, "/tmp/format-tax/benchmarks/tmp-bench/custom-track3.json");
17+
});
18+
19+
test("resolveTrackOutputPath appends track filename when output is a directory", () => {
20+
const outputPath = resolveTrackOutputPath({
21+
baseDir: CLI_CWD,
22+
outputArg: "../tmp-bench",
23+
track: 3,
24+
totalTracks: 2,
25+
});
26+
27+
assert.equal(outputPath, "/tmp/format-tax/benchmarks/tmp-bench/track3.json");
28+
});
29+
30+
test("resolveTrackOutputPath rejects json file outputs for multi-track runs", () => {
31+
assert.throws(
32+
() => resolveTrackOutputPath({
33+
baseDir: CLI_CWD,
34+
outputArg: "../tmp-bench/custom.json",
35+
track: 1,
36+
totalTracks: 2,
37+
}),
38+
/select exactly one track/i,
39+
);
40+
});

0 commit comments

Comments
 (0)