LWON for AI
Models read structured data all day: tool results, API responses, database rows, retrieved documents. LWON lets them read the same data for fewer tokens, which means lower cost, lower latency and more room in the context window.
Where the savings come from
In a JSON array of 100 records, every key is written 100 times, wrapped in quotes and followed by a colon. LWON writes each key once in a header and lists only the values after it. The more records and the deeper the nesting, the bigger the gap. See the benchmarks.
Convert before the model reads it
The tokens are saved only if the model never sees the JSON. Encode in your own code, where the data arrives: the tool handler, the API client, the retrieval step.
import { encode, PRIMER } from '@lwon/core';
const rows = await db.query('select id, name, plan, mrr from accounts limit 200');
const response = await client.messages.create({
model: 'claude-sonnet-5-5',
system: PRIMER, // explain the format once
messages: [{ role: 'user', content: `Which accounts churned?\n\n${encode(rows)}` }],
});
Giving the model an encode tool does not help: to call it, the model
would first have to read the JSON, so the tokens are already spent.
Tell the model about the format
Models do not know LWON yet, so send this primer once per conversation, ideally in the system prompt
where prompt caching makes it nearly free after the first call. It is about 328 Claude tokens and
is exported by the library as PRIMER.
Data below is in LWON, a compact form of JSON. Line 1 is a header naming the fields; each following row `[...]` holds one object's values in header order.
- Read a row by position: the 1st value belongs to the 1st header field, the 2nd value to the 2nd field, and so on.
- `~` holds the place of a key the object does not have. A row that ends early does not have the remaining keys. `null` means the key is there with the value null.
- A header ending in `*`: a row's last value may be a JSON object holding more of that object's keys, by name. Those keys exist only in the rows that show them.
- `k[x y]` is an object with fields x, y. `[k]` is an array of values. `[k[x y]]` is an array of objects. `@` and `@k` reuse the fields of the top level or of the enclosing k.
- Header `{a b}` is a single object instead of a list; header `*` means plain JSON follows. Unquoted words are strings, and `{...}` values are JSON. At the typical saving in our benchmarks (37%), the primer pays for itself once the data would take about 900 tokens as minified JSON. Below that, send JSON. With a cached system prompt the threshold is much lower.
Reading, not writing
Start by using LWON for data the model reads. Asking a model to write LWON saves
output tokens, but positional formats are easy to get subtly wrong. If you do, always check the
output with decode and retry on an error, which includes the line and column.
MCP servers
Many MCP servers return large JSON tool results. @lwon/mcp-proxy
sits between your client and any stdio MCP server and turns those results into LWON before the model
sees them. The server needs no changes: put npx -y @lwon/mcp-proxy -- in front of the
command that starts it.
claude mcp add github -- npx -y @lwon/mcp-proxy -- npx -y @modelcontextprotocol/server-github In Claude Desktop's claude_desktop_config.json, or a project's .mcp.json:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@lwon/mcp-proxy", "--", "npx", "-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "..." }
}
}
}
The proxy converts a tools/call result only when it is JSON, at least 2,000 characters
long and at least 10% shorter as LWON; everything else passes through unchanged. It adds the primer
to the server's instructions, so the model reads it once per session. It runs on your machine as a
child process of your client, so no data goes anywhere else. Options, such as which tools to convert,
are in the README.
Accuracy
Fewer tokens only help if the model still understands the data. Claude Haiku, Sonnet and Opus 5.5 each answered 44 questions about the benchmark data in JSON, TOON and LWON. With LWON they got 132 of 132 right, the same as with JSON (132 of 132). On a harder set of 150 sparse log events, LWON stayed close to JSON; the only misses, in every format, were counts over many rows that were off by one to three, or a sum. Full results.
When it pays off
Fewer input tokens is not the whole bill: models also think about the data, and output tokens cost several times more than input. Measured on Claude Sonnet 5.5, the full cost of a question compared with JSON was:
- Regular data, sent once: −25%. The main case LWON is for: a tool result or API response read once.
- Regular data, cached and reused for many questions: −15%.
- Sparse data with counting, sent once: −11%; cached: +9%.
The first version of LWON wrote every key by position. On sparse data that made models count their
way through rows full of ~, and a question cost up to three times as much as with JSON.
LWON now writes keys most rows lack by name, which brought the cost close to JSON's even there.
Use LWON for large payloads the model reads. Keep JSON for payloads of a few hundred tokens, where the primer costs more than LWON saves, and check your own prompts if they need exact counts over many sparse records, where JSON was marginally more accurate in our tests.