Skip to content

Instantly share code, notes, and snippets.

@cheery
Last active September 11, 2025 17:59
Show Gist options
  • Select an option

  • Save cheery/40bdb1b2926afe735692de692ea339d4 to your computer and use it in GitHub Desktop.

Select an option

Save cheery/40bdb1b2926afe735692de692ea339d4 to your computer and use it in GitHub Desktop.
chatfmt first draft

📦 Minimal LLM Chat Log Format

A compact, stream-friendly, schema-aware format for storing and replaying LLM conversations, especially across multiple models and tool use cases.

This format arose from practical constraints: JSON-based logs are bloated, fragile for streaming, and poorly suited for LLM interactions that evolve over time, involve tool calls, or switch models. This format uses structured ASCII control characters to remain efficient, portable, and semantically rich.


🧱 Key Features

  • Streaming-compatible: aligns with how LLMs produce output (in parts, not wholes)
  • Semantically tagged: supports roles, tool calls, metadata, output types
  • Compact: much smaller than JSONL, YAML, or Markdown logs
  • Portable: can encode logs from virtually any LLM chat format
  • Agent-aware: supports multi-step reasoning, tool interaction, turn-based control

🔤 Format Summary

This is a text-based format using ASCII control characters to structure and separate content:

  • FS (\x1C) – File Separator: separates individual messages
  • GS (\x1D) – Group Separator: separates message header from body chunks
  • RS (\x1E) – Record Separator: separates key-value metadata
  • US (\x1F) – Unit Separator: separates key and value within a key-value pair

Escaping for control and special characters is done using:

  • \nn – where nn is a two-digit hex ASCII code (e.g. \20 for space)

All string fields are encoded as escaped UTF-8 text.


📐 Formal Grammar (Corrected)

file    := (msg FS)*
msg     := header (GS chunk meta*)?
header  := tag ((US keyword)? RS value)*
meta    := US keyword RS value 

chunk   := escaped UTF-8 text
tag     := escaped UTF-8 text
keyword := escaped UTF-8 text
value   := escaped UTF-8 text

🧠 Schema: Message Types

The format supports a small schema useful for LLM-driven applications. Each message begins with a tag, and may include metadata, followed by one or more body chunks.

System / Kernel Message

kernel(){initialization parameters or base context}

User Message

user(){user prompt or message}

Assistant Message

Supports reasoning-phase tagging and constraints.

assistant(**channel=thought|commentary|final,constrain=json|n/a){}

Tool Call (Request / Response)

request(name){}             // tool call request
response(name){}           // response from tool

Control Signal

turn()                // signals LLM turn control

📄 Example (Raw Format with Control Chars)

Represented here with readable tokens instead of raw bytes:

assistant␞Hello, how can I help today?␜
user␞I'd like a summary of this text.␜
assistant␟channel␞thought␜First, I’ll read the text...␜
assistant␟channel␞final␜Here's the summary:␜

(Raw control characters: = FS, = GS, = RS, = US)


🔄 Interop with Other Formats

The format was designed to encode and unify diverse LLM chat protocols, including:

  • OpenAI Chat API (role/content)
  • Anthropic Claude format (system/human/assistant)
  • Mistral/OpenChat format (user/assistant pairs)
  • Local LLMs with streamed tokens
  • Tool-based agents (function calls / toolchains)

All can be transcribed into this format with role, metadata, and content intact.


💡 Design Notes

  • The format reflects how LLMs actually work — outputting text in parts, responding to turns, and interacting with tools or environments.
  • It’s trivially parsable with a simple state machine or line-based reader.
  • Suitable for use in:
    • Agent orchestration
    • Debugging / comparison across models
    • Replay of conversations
    • Dataset generation
    • Persistent logs for privacy-aware local LLMs

📎 Future Work

  • Schema versioning
  • Reference parser and formatter (Python / Rust)
  • Markdown or JSON export
  • Tooling: chatlog view, chatlog diff, chatlog replay
  • Visualization UI (maybe TUI)

🛠 License

MIT or Unlicense — your call.

📜 The Chat Log / chatfmt Manifesto

This format wasn’t born out of theory. It came from necessity.

I was writing a library for interfacing with llama.cpp, just trying to log and interact with a local language model in a structured way. What I encountered was a mess of verbose, incompatible formats — JSON logs, Markdown chats, YAML transcripts, schema-laden APIs. All of them bloated. All of them pretending to be simple. None of them pleasant to use.

So I built something else.

It’s not a library. It’s not a framework. It’s not another universal protocol.

It’s a flat, streamable, semantically rich text format — something close to raw plain text, but expressive enough to represent LLM interactions, tool calls, and human input all in one go.

It uses ASCII control characters nobody else touches: FS, GS, RS, and US. They’re ghosts from early computing. And they’re perfect. Out of the way. Non-intrusive. Made for structure, yet forgotten by modern formats that want everything to look like JSON.

This is not a format that tries to do everything. It does less, on purpose.


✳️ The Philosophy

  • Flat is better than nested. Deeply nested data structures are hard to stream, hard to diff, and hard to reason about. Flat records, with tags and key-value fields, are easier to work with — both for humans and machines.

  • Structure emerges from constraint. The design wasn’t theorized — it was shaped by real-world constraints: streaming logs, interoperating across LLMs, editing with simple tools.

  • Streaming is a first-class citizen. Language models generate content in chunks. Every real-world deployment streams responses. This format leans into that — bodies are streamed as chunks after the message header.

  • Human-editable is not a luxury. You can write and inspect this format in any text editor. You don’t need a parser to make sense of it. Tools make it better — but they aren’t required.

  • Control characters are infrastructure. FS/GS/RS/US weren’t a gimmick — they were part of early hypertext and data stream systems. This format is rediscovering their purpose, not inventing a new one.


🪵 What It Is

It’s a record-based, linear chat format. Each message has:

  • A tag (user, assistant, tool, etc.)
  • Optional metadata fields
  • A streamed body

Its formal structure is simple:

file    := (msg FS)*
msg     := header (GS chunk)*
header  := tag ((US keyword)? RS value)*

All elements are escaped UTF-8 strings. That’s it.

No schemas. No recursive nesting. No object trees.


🧠 Why It Works

It works because LLMs share a common interaction shape — user input, system context, assistant output — and this format matches that shape naturally.

It works because tool use (function calls, agent requests, tool responses) also fits naturally into this flat record structure.

It works because even with no ecosystem, it’s already useful.

It works because it emerged from code, not design-by-committee.


🚫 What It Refuses

  • It refuses to pretend JSON is still simple.
  • It refuses to wrap every token in metadata.
  • It refuses to assume one model or backend is “correct.”
  • It refuses to enforce deep hierarchy where none is needed.
  • It refuses to become another kitchen-sink schema.

This format is a rebellion against deeply nested objects. It is not just smaller — it is conceptually lighter.


🔄 Compatibility

Despite its simplicity, this format maps cleanly onto:

  • OpenAI’s role/content chat structure
  • Claude’s assistant/human/system log
  • Local inference libraries like llama.cpp
  • Multi-agent toolchains
  • Function call formats

You can convert into and out of this format easily. In many ways, it’s a lingua franca for chat logs.


🌍 Broader Potential

Yes, it was made for LLMs — but it’s not only for them.

This format could serve as:

  • A structured command protocol for agents
  • A replacement for JSONL in streaming pipelines
  • A hypertext/hypermedia container for interactive web content
  • An IPC or function-calling layer across tools
  • A log format for composable tasks, events, and narratives

It’s relational. It’s streamable. It’s human-readable. And it’s fast to scan, edit, and grep.

In other words, it's a small spec with large affordances.


✒️ Final Note

This format wasn’t designed to catch on.

It just happened to solve the problems I had — juggling structured logs across models, replaying conversations, storing them efficiently, editing them cleanly.

And that’s why it deserves to exist.

If others find value in it — great. If not, I’ll keep using it anyway.

But maybe, just maybe, we’re overdue for formats that do less — and work better because of it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment