Wire Formats
knowing supports multiple wire format encodings for graph data, each optimized for a different layer of the system. All formats encode the same Payload structure and are selected via the format parameter on MCP tools and CLI commands.
Architecture
┌─────────────┐
│ Graph Store │
└──────┬──────┘
│ Payload
┌────────────┼────────────┐
│ │ │
┌─────▼─────┐ ┌───▼────┐ ┌────▼────┐
│ binary │ │ gcf │ │ json │
│ (transport)│ │ (LLM) │ │ (compat)│
└─────┬─────┘ └───┬────┘ └────┬────┘
│ │ │
┌─────▼─────┐ ┌───▼────┐ ┌────▼────┐
│ daemon ↔ │ │ agent │ │ human │
│ services │ │context │ │ debug │
└───────────┘ └────────┘ └─────────┘
| Codec | Optimizes for | Consumer | Token savings vs JSON | When to use |
|---|---|---|---|---|
gcf |
Maximum compression | AI agents in tight-budget workflows | 84% | Default for MCP tools, session dedup, repeated calls |
binary |
Bytes on wire, speed | Services, caches, daemon IPC | N/A (not text) | Internal transport between knowing processes |
json |
Compatibility | Humans, generic API consumers, debugging | 0% (baseline) | When downstream consumers need standard JSON |
xml |
Structured markup | XML-based toolchains | ~-20% (larger) | Legacy integrations |
markdown |
Human readability | Documentation, display | ~10% | Human consumption, not agent workflows |
How to Select a Format
CLI:
knowing context -task "add caching" -format gcf
knowing context -task "add caching" -format json
MCP tools (via format parameter):
{"tool": "context_for_task", "arguments": {"task_description": "add caching", "format": "gcf"}}
Recommendation: use gcf for all agent workflows. LLM comprehension eval
(session 27, eval/TestLLMFormatComprehension) proved GCF achieves 100% accuracy
on structured extraction tasks at 16% of JSON's token cost. JSON scored 66.7%
(miscounts on large payloads). The "wait for comprehension validation" caveat is
resolved: GCF is both the most compact and the most accurately comprehended format.
- Use
gcffor all AI agent workflows. Recommended default. - Use
jsonwhen debugging, piping to jq, or integrating with systems that expect JSON. - Use
xmlfor human-readable output only. Does not include edge data in output.
The format only affects the output encoding. The retrieval pipeline (seed matching, RWR, HITS, RRF fusion, scoring) is identical regardless of format. With gcf, more symbols fit within the same token budget because each symbol costs fewer tokens.
GCF (Graph Compact Format)
Text-only, graph-native encoding designed for LLM consumption. Exploits three properties of graph data that flat formats cannot:
- Referential identity. Nodes get local IDs (
@0,@1). Edges reference by ID instead of repeating full qualified names. - Graph topology. Edges encoded as
@target<@source typeinstead of verbose JSON objects. - Hierarchical grouping. Distance-based sections (
## targets,## related) eliminate per-row distance fields.
Grammar
payload = header { node-line | edge-line | group-line | comment } ;
header = "GCF" SP "tool=" token { SP key-value } LF ;
group-line = "##" SP text LF ;
node-line = "@" id SP kind SP qname SP score SP provenance LF ;
edge-line = "@" target "<" "@" source SP edge-type [ SP status ] LF ;
comment = "#" SP text LF ;
id = DIGIT { DIGIT } ;
kind = "fn" | "type" | "method" | "iface" | "var" | "const"
| "resource" | "table" | "class" | "selector" | "field"
| "route" | "ext" | "file" | "pkg" | "svc" ;
qname = non-whitespace-text ;
score = float ;
provenance = "ast_inferred" | "ast_resolved" | "lsp_resolved"
| "scip_resolved" | "otel_trace" | "structural" | token ;
status = "added" | "removed" ;
Example
GCF tool=context_for_task budget=5000 tokens=1847 symbols=10
## targets
@0 fn github.com/blackwell-systems/knowing/internal/mcp.requireHash 0.78 lsp_resolved
@1 method github.com/blackwell-systems/knowing/internal/mcp.Server.registerTools 0.74 lsp_resolved
@2 fn github.com/blackwell-systems/knowing/internal/mcp.requireStringArg 0.67 lsp_resolved
@3 fn github.com/blackwell-systems/knowing/internal/mcp.getIntArg 0.66 lsp_resolved
## related
@4 fn github.com/blackwell-systems/knowing/internal/mcp.NewServer 0.54 lsp_resolved
@5 fn github.com/blackwell-systems/knowing/cmd/knowing.cmdServe 0.51 ast_inferred
@6 fn github.com/blackwell-systems/knowing/cmd/knowing.cmdMCP 0.46 ast_inferred
## extended
@7 type github.com/blackwell-systems/knowing/internal/mcp.Server 0.42 lsp_resolved
@8 iface github.com/blackwell-systems/knowing/internal/types.GraphStore 0.38 lsp_resolved
@9 type github.com/blackwell-systems/knowing/internal/store.SQLiteStore 0.35 lsp_resolved
## edges
@0<@4 calls
@0<@5 calls
@0<@6 calls
@1<@4 calls
@2<@5 calls
@2<@6 calls
@9<@8 implements
@4<@8 references
The same data in JSON: ~965 tokens. In GCF: ~233 tokens. 75.9% savings (simple heuristic). Using the word+punctuation token estimator from FINDINGS.md: 84.0% median across 6 fixtures.
Format Elements
Header: GCF tool=<name> budget=<N> tokens=<N> symbols=<N> [pack_root=<64-char hex>]
When a PackRoot is computed (all context_for_task calls), the header includes a pack_root field. Agents can store this value and pass it back as the pack_root parameter on the next call: if the result is unchanged, the server returns "unchanged" instead of resending the full context (93-99% byte savings).
Group headers: ## targets (distance 0), ## related (distance 1), ## extended (distance 2+), ## edges
Node lines: @{id} {kind} {qualified_name} {score} {provenance}
Edge lines: @{target}<@{source} {edge_type} [{status}]
The < arrow points toward the target. @0<@4 calls means "@4 calls @0."
Kind abbreviations:
| Short | Full |
|---|---|
fn |
function |
type |
type |
method |
method |
iface |
interface |
var |
var |
const |
const |
resource |
resource |
table |
table |
class |
class |
selector |
selector |
field |
field |
route |
route_handler |
ext |
external |
file |
file |
pkg |
package |
svc |
service |
Session Statefulness
Across multiple tool calls in a session, previously-transmitted nodes can be referenced without retransmission:
GCF tool=context_for_files tokens=800 symbols=5 session=true
## targets
@0 # previously transmitted
@7 fn github.com/blackwell-systems/knowing/internal/mcp.handleBlastRadius 0.62 lsp_resolved
## edges
@0<@7 calls
Multi-call workflows get progressively cheaper as the session builds a shared vocabulary of known symbols.
Where Token Savings Come From
- No field names. GCF uses positional encoding. JSON repeats 9 field names per symbol.
- Local ID edge references.
@0<@4 calls(5 tokens) vs full qualified name pairs (30+ tokens). - Group headers. One
## relatedreplaces N"distance": 1fields. - Kind abbreviations.
fnvs"function",ifacevs"interface". - No structural delimiters. No
{},[],:,",", indentation whitespace.
Binary (GCB1)
Compact binary encoding optimized for transport between services and persistent caching. Not readable by humans or LLMs directly; designed for machine-to-machine paths where byte efficiency and encode/decode speed matter.
Wire Layout
[magic:4 "GCB1"][version:1]
[header]
tool : length-prefixed string
tokens_used : varint
token_budget: varint
num_symbols : varint
num_edges : varint
[symbols × num_symbols]
qname : length-prefixed string
kind : uint8 (enum)
score : float32 (4 bytes, little-endian)
provenance : uint8 (enum)
distance : uint8
signature : length-prefixed string
blast_radius: float32
confidence : float32
recency : float32
distance_c : float32
[edges × num_edges]
source_idx : varint (index into symbols array)
target_idx : varint (index into symbols array)
edge_type : uint8 (enum)
status : uint8 (enum)
Design Choices
- Varint encoding for integers: small values (symbol counts, indices) use 1 byte instead of 4-8.
- Enum IDs for kinds, provenances, edge types, statuses: 1 byte instead of variable-length strings.
- float32 for scores and components: 4 bytes with sufficient precision (scores only need 2 decimal places).
- Index-based edges: edges reference symbols by array index (varint), not by name.
- Length-prefixed strings: no null terminators, no escaping, no delimiters.
- No padding or alignment: every byte carries data.
Enum Tables
Kind (16 entries): function=1, type=2, method=3, interface=4, var=5, const=6, resource=7, table=8, class=9, selector=10, field=11, route_handler=12, external=13, file=14, package=15, service=16
Provenance (7 entries): ast_inferred=1, ast_resolved=2, lsp_resolved=3, otel_trace=4, scip_resolved=5, runtime_observed=6, structural=7
Edge type (38 entries): calls=1, imports=2, implements=3, references=4, handles_route=5, depends_on=6, deploys=7, exposes=8, configures=9, extends=10, overrides=11, decorates=12, throws=13, owned_by=14, authored_by=15, tests=16, runtime_calls=17, runtime_rpc=18, runtime_produces=19, runtime_consumes=20, contains=21, member_of=22, documents=23, consumes_endpoint=24, implements_rpc=25, consumes_rpc=26, gated_by_flag=27, deployed_by=28, tested_by=29, publishes=30, subscribes=31, connects_to=32, similar_to=33, co_tested_with=34, type_hint_of=35, accesses_field=36, reads_env=37, executes_process=38
Status: unchanged/empty=0, added=1, removed=2
When to Use Binary
- Daemon-to-client IPC (the daemon stores and transmits binary; the client decodes to Payload then re-encodes to GCF or JSON for its consumer)
- Response caching (binary is the most space-efficient on-disk format)
- Cross-service communication where both ends are knowing-aware
- Streaming large graph results where decode latency matters
Tradeoffs vs GCF
| Binary | GCF | |
|---|---|---|
| Byte size | Smallest | ~10-15% larger (text overhead) |
| Token count | N/A (not text) | Optimized |
| LLM-readable | No | Yes |
| Human-readable | No | Yes |
| Score precision | float32 (~7 digits) | 2 decimal places |
| Components preserved | Yes (full) | No (stripped for token savings) |
| Extensibility | Requires version bump | Append new fields freely |
JSON
Standard JSON serialization. Maximum compatibility, zero configuration, works with any consumer. The baseline against which other formats are measured.
{
"tool": "context_for_task",
"tokens_used": 1847,
"token_budget": 5000,
"pack_root": "<64-char hex>",
"symbols": [
{
"qualified_name": "github.com/blackwell-systems/knowing/internal/mcp.requireHash",
"kind": "function",
"score": 0.78,
"signature": "func requireHash(args map[string]any, key string) (types.Hash, error)",
"provenance": "lsp_resolved",
"distance": 0,
"components": {
"blast_radius": 0.40,
"confidence": 0.25,
"recency": 0.06,
"distance": 0.15
}
}
],
"edges": [
{
"source": "github.com/blackwell-systems/knowing/internal/mcp.NewServer",
"target": "github.com/blackwell-systems/knowing/internal/mcp.requireHash",
"edge_type": "calls"
}
]
}
Use JSON when: debugging, piping to jq, integrating with tools that expect JSON, or when the consumer is not token-constrained.
Benchmark Comparison
All three codecs encoding the same 10-symbol, 8-edge payload:
| Codec | Bytes | Tokens | vs JSON (bytes) | vs JSON (tokens) |
|---|---|---|---|---|
| JSON | 4,153 | 965 | baseline | baseline |
| GCF | 1,079 | 233 | -74% | -75.9% |
| Binary | 448 | N/A | -89% | N/A |
Full scorecard across 6 fixture cases (8 to 30 symbols):
| Case | JSON(T) | GCF(T) | Token Savings |
|---|---|---|---|
| context_for_task (10 sym) | 965 | 233 | 75.9% |
| context_for_task (30 sym) | 2,968 | 649 | 78.1% |
| context_for_files (15 sym) | 1,490 | 334 | 77.6% |
| blast_radius (8 sym) | 835 | 208 | 75.1% |
| semantic_diff (12 sym) | 1,206 | 295 | 75.5% |
| graph_query (20 sym) | 2,078 | 423 | 79.6% |
Median token savings (GCF vs JSON): 76.7% (simple heuristic). FINDINGS.md word+punctuation estimator: 84.0% median.
Encode p99 latency: 64 microseconds (30-symbol payload, Apple M4 Pro).
Codec Registry
The internal/wire package provides a pluggable codec registry. Core GCF types and encoding come from the standalone gcf-go library (spec); the wire package re-exports them and adds knowing-specific codecs. Formats are selected by name at runtime:
import (
gcf "github.com/blackwell-systems/gcf-go" // GCF types and encoding
"github.com/blackwell-systems/knowing/internal/wire" // registry, binary, json, bridge
)
// Encode with a named codec.
output, err := wire.EncodeWith("gcf", payload)
// Decode with a named codec.
payload, err := wire.DecodeWith("binary", input)
// List all registered codecs.
for _, c := range wire.List() {
fmt.Printf("%s: %s\n", c.Name, c.Description)
}
// Register a custom codec.
wire.Register(&wire.Codec{
Name: "msgpack",
Description: "MessagePack encoding for external interop",
Encode: myMsgpackEncoder,
Decode: myMsgpackDecoder,
})
Built-in codecs are registered at init time. Custom codecs can be added by any package that imports internal/wire and calls Register() before use.
Adding a New Codec
Implement two functions matching these signatures:
type Encoder func(p *Payload) (string, error)
type Decoder func(input string) (*Payload, error)
Then register:
func init() {
wire.Register(&wire.Codec{
Name: "my-format",
Description: "What this format optimizes for",
Encode: myEncoder,
Decode: myDecoder,
})
}
The MCP tools and CLI pass the format parameter directly to the registry, so new codecs become available to all consumers immediately.
Usage
CLI
knowing context -task "refactor auth" -format gcf # LLM-optimized
knowing context -task "refactor auth" -format json # human/debug
knowing context -task "refactor auth" -format gcb # pipe to another service
MCP Tools
{
"name": "context_for_task",
"arguments": {
"task_description": "refactor auth middleware",
"token_budget": 5000,
"format": "gcf"
}
}
Default is xml for MCP tools (human-readable structured output). CLI default is also xml. Agents that understand GCF should request it explicitly for 84%+ token savings.
Format Comprehension Eval
Token Cost Benchmark (deterministic)
Measures token cost across formats for the same payload. No LLM involved.
Run: GOWORK=off go test ./eval/ -run TestFormatComprehension -v
| Format | Avg tokens | vs JSON |
|---|---|---|
| JSON | 1,818 | baseline |
| XML | 1,818 | 100% |
| GCF | 265 | 15% |
LLM Comprehension Benchmark (session 27)
Sends the same context payload in each format to an actual LLM and measures whether it can answer structured questions correctly. Six questions with objectively verifiable answers: top symbol identification, symbol count, edge count, kind extraction, seed/related group count, and edge type enumeration.
Run: GOWORK=off go test ./eval/ -run TestLLMFormatComprehension -v -timeout 30m
(Uses claude -p by default. Set EVAL_BACKEND=api with ANTHROPIC_API_KEY for direct API calls.)
| Format | Accuracy | Avg Tokens | vs JSON |
|---|---|---|---|
| gcf | 100% (6/6) | 2,687 | 16% |
| json | 66.7% (4/6) | 16,372 | baseline |
| xml | 66.7% (4/6) | 5,026 | 31% |
Note: TOON was also evaluated (100% accuracy, 9,427 tokens, 58% of JSON) but was removed in v0.15.0. GCF provides better compression at the same accuracy.
Per-question results:
| Question | json | xml | gcf |
|---|---|---|---|
| top_symbol (identify highest-scored) | PASS | PASS | PASS |
| symbol_count (count all symbols) | FAIL (113 vs 133) | PASS | PASS |
| edge_count (count all edges) | FAIL (120 vs 131) | FAIL (0 vs 131) | PASS |
| top_kind (kind of top symbol) | PASS | PASS | PASS |
| seed_count (distance-0 symbols) | PASS | PASS | PASS |
| edge_type_list (enumerate edge types) | PASS | FAIL (no edges in XML) | PASS |
Key findings:
-
GCF comprehension is validated. 100% accuracy on all 6 structured extraction tasks. The concern that LLMs couldn't parse GCF's
@Nlocal IDs and@target<@sourceedge notation was unfounded. -
JSON is the worst performer on large payloads. At 36K tokens (symbol_count task), the LLM miscounted symbols (113 vs 133). GCF at 5K tokens counted correctly. Verbosity hurts comprehension, not just token cost.
-
XML doesn't include edge data. The XML formatter (
FormatContextBlock) renders symbols but not edges, causing edge-related questions to fail. This is a format limitation, not an LLM limitation. -
GCF is 6.1x more token-efficient than JSON (2,687 vs 16,372 avg tokens) with higher accuracy. There is no reason to prefer any other format for agent workflows.
Recommendation: GCF should be the default format for all agent workflows.
Delta Encoding
When an agent passes a pack_root from a prior call and the current result differs, the server computes a structural diff and returns only what changed. This is a fourth level of token optimization layered on top of GCF.
GCF tool=context_for_task delta=true base_root=aaa111 new_root=bbb222 tokens=30 savings=81%
## removed
fn github.com/example/project.OldHandler
## added
@0 fn github.com/example/project.NewHandler 0.85 rwr
## edges_added
github.com/example/project.Router -> github.com/example/project.NewHandler calls
Three outcomes when pack_root is sent:
1. Same root: "unchanged" (zero tokens)
2. Different root, prior known: delta encoding (removed + added sections)
3. Different root, prior unknown: full retransmission (fallback)
Delta is only used when it saves more than 40% over full retransmission (60% threshold in DiffPacks.IsWorthIt). The diff operates on node hashes (set difference, O(n)).
Benchmark (session 27): 81.2% token savings at 96.6% symbol overlap on re-query scenarios. See context-packing.md for the full protocol and bench/delta-packing/ for the benchmark.
Implementation
| File | Purpose |
|---|---|
gcf-go |
Standalone GCF library: Payload/Symbol/Edge/Components types, Encode, Decode, Session, EncodeWithSession, DeltaPayload, EncodeDelta |
gcf spec |
GCF specification v1.0: grammar, encoding rules, session statefulness, delta extension |
internal/wire/gcf.go |
Type aliases and delegating wrappers re-exporting gcf-go |
internal/wire/registry.go |
Codec registry (Register, Get, List, EncodeWith, DecodeWith) |
internal/wire/bridge.go |
FromContextBlock: converts ContextBlock to wire Payload with edge discovery |
internal/wire/json.go |
JSON codec (encode/decode via standard library) |
internal/wire/binary.go |
GCB binary codec (varint + length-prefixed, 38 edge type enums) |
internal/context/delta.go |
DiffPacks: structural diff between two ContextBlock values |
internal/wire/gcf_test.go |
GCF round-trip and encoding tests |
internal/wire/session_test.go |
Session deduplication tests |
internal/wire/delta_test.go |
Delta encoding tests |
internal/wire/registry_test.go |
Registry and JSON codec tests |
internal/wire/binary_test.go |
Binary codec tests |
bench/wire-format/ |
Benchmark harness with 6 fixture cases in cases/ |
bench/wire-format/scorecard.md |
Auto-generated comparison table |
bench/wire-format/FINDINGS.md |
Detailed results and interpretation |
bench/delta-packing/ |
Delta packing benchmark (cross-task + re-query simulation) |
eval/format_comprehension_test.go |
Token cost benchmark (deterministic, no LLM) |
eval/format_llm_comprehension_test.go |
LLM comprehension eval (6 questions, 4 formats, cli/api backends) |
Comparison to External Approaches
Table formats (TSV/columnar)
Column headers + TSV rows achieve ~27% savings by eliminating field name repetition. But they treat graph data as flat tables: edge references still require full identifier strings in each row. GCF's local ID system pushes savings to 75%+.
Binary formats (protobuf, MessagePack, FlatBuffers)
Optimize for machine parsing speed and byte size. An LLM cannot read a protobuf payload directly; it must be decoded to text first, eliminating the savings at the point of consumption. knowing's binary codec fills this niche for machine-to-machine paths, while GCF serves the LLM path.
JSON-LD / RDF
Verbose by design (full URIs, type annotations, context declarations). Optimizes for semantic interoperability across systems, not token-constrained LLM consumption.