PAKT — data that knows its own type
Human-authorable. Streaming. Self-describing. Every value carries its type — no inference, no ambiguity, no surprises.
app-name:str = 'midwatch'
version:(int, int, int) = (2, 14, 0)
active:bool = true
server:{host:str, port:int} = { 'localhost', 8080 }
# Stream events with << — one per line
events:[{ts:ts, level:|info, warn, error|}] <<
{ 2026-06-01T14:30:00Z, |info }
{ 2026-06-01T14:31:00Z, |warn }You already know most of this
If you've written JSON, YAML, or TOML, PAKT will feel right at home. Same concepts — keys, values, nesting — just with types baked in.
Keys and values
Named fields with values, just like JSON objects or YAML mappings. Write name:str = 'hello' and you're done.
Nesting and structure
Maps inside maps, tuples, lists — compose types naturally. server:{host:str, port:int} reads like you'd expect.
Comments
Line comments with #, just like YAML and TOML. Document your data inline — it's meant to be read by humans.
Here's where it gets fun
PAKT isn't just another config format. Types and streaming make it something genuinely different.
Every value has a type
Type annotations are mandatory — count:int = 42, not just count = 42. The producer declares intent, the parser enforces it. No guessing.
Self-describing data
Type annotations are validated at parse time — producers assert types, parsers enforce them. No schema negotiation, no external files needed.
Packs with <<
Open a pack with << and emit records one by one. No enclosing array, no delimiters between items. Data flows as it's produced.
PAKT vs JSON
Same data, different philosophy. PAKT makes types explicit and structure visible at a glance.
PAKT
server:{host:str, port:int} = {
'localhost'
8080
}
debug:bool = falseJSON
{
"server": {
"host": "localhost",
"port": 8080
},
"debug": false
}Types are built in. Structure is explicit. Every value is self-describing.
How it works
Three steps, one pipeline.
Write
Author .pakt files with type-annotated fields. Human-readable, self-documenting, impossible to get wrong.
Parse
The streaming parser emits events as it reads — no full-unit buffering. One event per call, constant memory per nesting level.
Consume
Iterate properties with UnitReader, stream pack elements with PackItems[T], or unmarshal an entire unit with UnmarshalNew[T].
Pick your ecosystem
First-class implementations for Go and .NET. Same spec, idiomatic for each platform.
Go
Streaming UnitReader with iter.Seq iterators, generic ReadValue[T], pack streaming, custom converters.
go install github.com/trippwill/pakt@latest.NET
NewSource-generated serialization modeled after System.Text.Json. Ref-struct tokenizer, zero-copy, forward-only.
dotnet add package Pakt