Skip to content

Repository files navigation

AgentFmt

CI Go Reference License: MIT

Adaptive JSON-to-terminal output for language-model agents and humans.

AgentFmt is a final display gate, not another storage format. Give it the JSON value your tool was about to print. It measures several representations with a real tokenizer and emits the best one for the selected profile:

  • readable indentation for nested or text-heavy data;
  • schema-once rows for uniform arrays;
  • compact JSON when every friendlier representation is materially larger.
$ online search "structured output" --json | agentfmt
query: structured output
status: ok
results[3]{title,url,published,snippet}:
  Structured outputs,https://example.org/one,2026-05-28,Evaluation summary
  JSON RFC 8259,https://www.rfc-editor.org/rfc/rfc8259,2017-12-01,JSON grammar
  TOON specification,https://github.com/toon-format/spec,null,Token-oriented format

Why adaptive?

No single text notation is smallest and clearest for every JSON shape. Indentation alone does not guarantee token savings. Repeated keys dominate uniform arrays, while table syntax can become worse for irregular or deeply nested values.

AgentFmt therefore generates eligible candidates, counts their tokens using o200k_base or cl100k_base, and applies a profile:

Profile Behavior
balanced Prefer outline; use table at ≥8% savings or JSON at ≥20% additional savings
tokens Select the eligible candidate with the fewest measured tokens
readable Always use the indented outline
json Emit deterministic compact JSON for programmatic consumers

The output is deterministic, preserves JSON field order and numeric lexemes, quotes ambiguous strings, rejects duplicate object keys, and isolates multiline strings with collision-safe literal delimiters.

Install

Universal CLI

go install github.com/khanglvm/agentfmt/cmd/agentfmt@latest

Prebuilt macOS, Linux and Windows archives are also attached to each GitHub release.

Then place it at the final stdout boundary:

my-agent-tool --json | agentfmt
agentfmt response.json
agentfmt -profile tokens -encoding cl100k_base response.json
agentfmt -stats response.json  # decision evidence goes to stderr

Go

import "github.com/khanglvm/agentfmt"

result, err := agentfmt.RenderJSON(payload)
if err != nil {
    return err
}
fmt.Println(result.Text)

Configure or reuse a renderer:

renderer, _ := agentfmt.NewRenderer(
    agentfmt.WithProfile(agentfmt.ProfileBalanced),
    agentfmt.WithEncoding(agentfmt.EncodingO200K),
)
result, err := renderer.Render(toolResult)

Python

The thin adapter delegates to the same executable, so it cannot drift from the Go engine:

pip install "agentfmt-adapter @ git+https://github.com/khanglvm/agentfmt.git"
go install github.com/khanglvm/agentfmt/cmd/agentfmt@latest
from agentfmt import render

text = render(tool_result)

Use AGENTFMT_BIN=/path/to/agentfmt or binary=... when the executable is not on PATH.

JavaScript / TypeScript

npm install github:khanglvm/agentfmt
go install github.com/khanglvm/agentfmt/cmd/agentfmt@latest
import { render, renderSync } from "agentfmt-adapter";

const text = await render(toolResult);
const immediate = renderSync(toolResult, { profile: "tokens" });

The Python and JS adapters send JSON through subprocess stdin with shell disabled. Binary resolution is: explicit option, AGENTFMT_BIN, then PATH.

Agent and MCP integration

Use AgentFmt for human/agent-facing text only:

native result → JSON-compatible value → AgentFmt → terminal/tool text

For MCP, put AgentFmt text in a text content block. Keep structuredContent or other application-consumed fields as JSON. This avoids breaking clients while reducing the representation placed in model context.

Benchmark

The committed benchmark uses five fixtures covering uniform rows, search results, nested configuration, heterogeneous tool output, and long untrusted content.

go run ./cmd/agentfmt-bench -encoding o200k_base
go run ./cmd/agentfmt-bench -encoding cl100k_base

o200k_base results:

Fixture Compact JSON Pretty JSON Outline Table Balanced Strategy
heterogeneous tool result 84 149 117 84 JSON
long content 87 108 95 95 outline
nested config 95 169 116 116 outline
search results 248 332 283 214 214 table
uniform records 183 327 268 118 118 table
Total 697 1,085 879 627 adaptive

On these fixtures, balanced AgentFmt is 10.0% smaller than compact JSON and 42.2% smaller than pretty JSON. These are reproducible fixture results, not a claim that AgentFmt wins on every dataset or tokenizer. See benchmarks/results.md for both encodings and exact methodology.

Format and design

Important boundary

AgentFmt output is intentionally not guaranteed to round-trip into the original JSON. It may omit redundant per-row field names and use human-oriented literal blocks. Use -profile json or your original JSON API when a program must parse, persist, sign, hash, or validate the result.

Literal blocks protect the display grammar, not the model from prompt injection. Treat web pages, logs, and user-controlled strings as untrusted even when fenced.

License

MIT

About

Adaptive JSON-to-agent terminal output with exact token-aware format selection

Topics

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages