SPECIMEN · .CURT V1 · OPEN FORMAT
One file, one conversation.
.curt is an open, single-file format for AI chat conversations: one file, one conversation, full fidelity — images and tool calls ride verbatim. Born in Curtis AI Chat, specified openly so any app can read and write it. Your history is files, not hostages.
The file
The shape of a record.
A .curt file is UTF-8 JSON. The generator field lets your app sign what it wrote — readers must ignore it. The agent layer is the point: tokens, cost, memories used, tool linkage — the fields other formats throw away.
{
"curt": "curtis-conversation",
"version": 1,
"generator": "[email protected]",
"conversation": {
"id": "conv_1760000000000_k3x9f2q7a",
"title": "Vault migration plan",
"createdAt": 1760000000000,
"updatedAt": 1760000600000,
"provider": "anthropic",
"model": "claude-sonnet-4-5",
"skillIds": ["obsidian-expert"],
"messages": [
{ "id": "msg_1760000001000_a4b2c1", "role": "user",
"content": "Draft the plan.", "timestamp": 1760000001000,
"attachedNotes": ["Vault/Migration notes.md"] },
{ "id": "msg_1760000009000_z9y8x7", "role": "assistant",
"content": "Here it is.", "timestamp": 1760000009000,
"model": "claude-sonnet-4-5",
"tokens": { "promptTokens": 412, "completionTokens": 388,
"totalTokens": 800, "cachedTokens": 190 },
"cost": 0.0043, "memoriesUsedIds": ["mem_0192"] }
]
}
}Names are derived from immutable fields — date slug id.curt — so renames never orphan or collide, and ids are conv_/msg_ plus timestamp and randomness that readers never validate the shape of. Re-import a file and the title-plus-count fingerprint makes it a no-op. The full field tables live in the spec.
The guarantee
Why the format can be trusted.
Full fidelity
One conversation, one file, UTF-8 JSON. Images ride inline as base64, tool calls keep their linkage, and the round-trip is checked, not assumed — the porter's verify command re-serializes every file and compares bytes.
Strict validation
A reader accepts a file as .curt v1 only when the body parses as JSON, the magic field is exactly "curtis-conversation" with version 1, and every message validates. A corrupted file is rejected whole, never half-imported.
Open by construction
Extra fields ride along on round-trip and never fail a reader. The extension registry tracks which are stable — an entry only goes stable once two independent implementations round-trip it.
Versions change rarely and only additively within a version; version-1 files stay readable forever. Conformance vectors ship in the repository so a second implementation doesn't have to guess.
What survives
Every shape, honestly.
- .curt
- everything — the full model, lossless round-trip
- curtis markdown
- the readable twin — hidden markers carry the machine data, and it re-imports whole
- chatgpt-shape json
- title, order, content, timestamps to the second — for TypingMind-class importers
- api jsonl
- one request line per conversation, safe to re-send
- foreign imports
- title, roles, content, timestamps, provider tag — and nothing invented
Foreign parsers keep user and assistant turns only — system and tool messages are dropped, and per-message model survives just ChatGPT's model_slug. That isn't loss: foreign exports don't carry the agent layer, and .curt refuses to invent it.
Implement it
Read and write it.
The reference implementation is TypeScript, zero dependencies:
npm install curt-formatimport { serializeCurt, parseCurt } from 'curt-format';
const body = serializeCurt(conversation, '[email protected]');
const back = parseCurt(body); // null means invalidcurt-format on npm· dot_curt repository· SPEC.md· round-trip fixtures
Third-party writers are welcome. PRs for bindings in other languages too — keep the reference implementation and the spec in lockstep.
Stated plainly
No license required.
The format is free for any tool to adopt — plain text, open spec, no permission asked. The reference implementation is MIT licensed; the format itself belongs to whoever needs it.