LTM-SPEC-0001 · 2026-04-21

Portable context
for AI work sessions.

Git captures what a project is. ltm captures what it ran into: the dead ends, the arguments you had with the model and lost, the constraints that shaped the current code without ever appearing in it. A one-file, two-verb protocol. Self-hosted. Model-agnostic. Open.

Protocol
ltm (long-term memory for AI sessions)
Version
0.2 (draft) · v0.1 archived
Status
draft (call for comment)
Editors
Dennis de Vulder · Thijs Herman
Reference
github.com/dennisdevulder/ltm
License
Apache-2.0
sh
          
$ curl -fsSL https://ltm-cli.dev/install | sh
# ~180 lines of spec. ~1,900 lines of Go. that is the whole thing.

§ 1. Abstract

Why this exists.

Most of a project's state lives in the repo. The commits carry what the code is, and any agent you hand the repo to can read that. What the repo does not carry is what you ran into on the way there: the dead ends that left no artifact, the decisions you argued out and cannot reconstruct from the diff, the constraints that shaped the current code without appearing in it. That part evaporates every time the harness changes. Three months on Claude Code, you try Codex; next quarter you try Cursor, or Windsurf, or Zed. Each harness wants its own instruction file — CLAUDE.md, .cursorrules, AGENTS.md — and none of them were written to move. Same erasure when the machine changes, for the same reason: intent was never separated from setup.

Git has the diffs. ltm has the reasons they look the way they do.

§ 2. Protocol

One packet. Two verbs.

verbdescriptionexample
push Agent emits a Core Memory Packet and pushes it to any ltm-compatible server. $ ltm push packet.json (or cat … | ltm push -)
pull A new session on any machine pulls it back. $ ltm pull 01JDMB4W1YNJ…

HTTPS transport with bearer-token auth, or OAuth 2.0 device-flow against the managed hub. SQLite storage. ULID-keyed packets. Nothing exotic; nothing reinvented.

The CLI wraps those two verbs with ergonomic helpers. Most agents only ever call save — serialize the current session into a packet and push it in one step — and resume on the other end. Run ltm resume with no arguments to get an interactive picker of your recent packets; pick one and its resume block is copied straight to your clipboard, ready to paste into the next session. Pass a packet ID to skip the picker and print the block to stdout. The rest (auth, ls, show, rm, example, update, a self-hostable server, and mcp — a Model Context Protocol stdio server that exposes the same verbs as tools to any MCP-aware agent) are there when you need them. None of them extend the protocol.

§ 3. Design principles

Five invariants.

REQ-01

Intent, not configuration

Your CLAUDE.md, skills, and tool setup stay yours. Only the intent behind what you are doing travels.

REQ-02

Self-host or nothing

No enterprise tier. No hosted-only features. If it does not run on a $5 VPS, it is not done.

REQ-03

Model-agnostic

A packet written by Claude is readable by GPT, Gemini, or the next thing.

REQ-04

Spec first

The protocol is public and versioned. The CLI and server are reference implementations, not the whole product.

REQ-05

Redact aggressively

Secrets and local state never ride along. The client refuses the push when it finds them, and the author has to opt in out loud.

§ 4. Core Memory Packet

An obstacle dossier, not a session log.

A packet describes one bump in the road that warranted capture: the goal, the decisions that locked in, the approaches that failed, the next concrete step. Five required fields. Six recommended. Forward-compatible. Every field earns its byte.

01JDMB4W1YNJZQR7K8F3A2H5P9.json 1.6 KB · ltm v0.2
          
1{
2 "ltm_version": "0.2",
3 "id": "01JDMB4W1YNJZQR7K8F3A2H5P9",
4 "created_at": "2026-04-18T22:41:03Z",
5 "provenance": {
6 "agent": "claude-code",
7 "model": "claude-sonnet-4-5",
8 "host": "darwin-arm64"
9 },
10 "goal": "Get llama.cpp inference running on a local GPU for a Rust side-project; benchmark tok/s vs. CPU.",
11 "decisions": [
12 {
13 "what": "Abandon Metal backend on M2",
14 "why": "Kernel compile fails with Xcode 16.2; not worth diagnosing further.",
15 "locked": true
16 },
17 {
18 "what": "Continue on Fedora 40 + RTX 4070 (CUDA 12.4)",
19 "why": "Known-working GPU path; matches target deployment hardware.",
20 "locked": true
21 },
22 {
23 "what": "Pin llama.cpp to b3821",
24 "why": "Newer builds regress on sm_89; b3821 bench-verified.",
25 "locked": false
26 }
27 ],
28 "attempts": [
29 {
30 "tried": "brew install llama.cpp",
31 "outcome": "builds, but MPS allocator OOMs on 13B Q4",
32 "learned": "MPS path has a hard ceiling well below VRAM — not a tuning issue."
33 },
34 {
35 "tried": "GGML_METAL=1 make -j",
36 "outcome": "link error: _ggml_metal_graph_compute undefined",
37 "learned": "Metal symbols moved; would need a Xcode 15 toolchain — out of scope."
38 }
39 ],
40 "open_questions": [
41 "Is flash-attn worth enabling on sm_89 for <14B?",
42 "Does llama-bench reflect real inference? Need an e2e harness."
43 ],
44 "next_step": "On the Fedora box: clone llama.cpp@b3821, build with -DLLAMA_CUDA=ON, run llama-bench -m qwen2.5-7b-q4_k_m.gguf -ngl 99."
45}
SIZE
1543 bytes
FIELDS
5 required 6 recommended
OMITS
paths, secrets, transcripts
IDENTITY
ULID per packet
[ full schema → ]

Read attempts as "approaches that failed and shaped the current decision," not "things I did." A packet is not a changelog. The 90% of work that went smoothly never needs a packet, because the commits already carry that state. Only the shaped-by-a-constraint stuff needs portable intent, because that is the part a fresh agent cannot reconstruct from the repo.

§ 5. Redaction pre-flight

Packets travel. Secrets don't.

A protocol whose core promise is "packets travel" has to take seriously what travels with them. Every packet is scanned on the client, before the first network call, and any hit blocks the push. The author can override with --allow-unredacted, but they have to say so out loud.

This is a load-bearing trust property, not a checkbox. Packets are meant to move between machines, teams, and agents; the person writing a packet is not always the person reading it. The scanner is designed to be noisy rather than quiet: block a legitimate push and make the caller opt in, rather than silently leak a credential. Implementation and test vectors live in internal/packet/packet.go (see redactionPatterns). A second implementation of the protocol MUST ship this pre-flight before its first network call.

§ 6. Scope

In scope / out of scope.

IN SCOPE
  • +Developers using AI agents across multiple machines
  • +Small teams sharing context without sharing setup
  • +Self-hosters who want their memory on their own box
OUT OF SCOPE
  • People looking for a managed memory SaaS
  • Teams that want a single-vendor Claude/Cursor/Codex stack
  • Anything that needs enterprise SSO on day one

§ 7. Test drive

Sixty seconds, no server required.

Install the CLI, grab the bundled example packet onto your clipboard, and paste it into any agent session. The packet is a mid-task snapshot pointing at a small demo repo (github.com/dennisdevulder/ltm-example); a fresh session reads it and picks up the work — no briefing, no ID, no server.

sh
          
$ curl -fsSL https://ltm-cli.dev/install | sh
$ ltm example --resume
# ✓ Copied the example packet to your clipboard.
# paste into your agent session. it resumes the work mid-thought.

§ 8. MCP integration

Wire it into your agent.

ltm mcp is a Model Context Protocol server over stdio. Register it once and your agent can call save, resume, ls, show, push, pull, rm, example, and whoami as tools. No paste step.

sh · claude code
          
$ claude mcp add ltm -- ltm mcp
# registered. ask your agent to resume. it picks the bundled example
# packet and continues the work, no id needed.

For Cursor, Zed, Claude Desktop, and Continue, drop this into the client's MCP config:

mcp.json
          
{ "ltm": { "command": "ltm", "args": ["mcp"] } }

§ 9. Reference implementation

Install the CLI.

sh · client
          
$ curl -fsSL https://ltm-cli.dev/install | sh
$ ltm auth https://your-server.dev <token>
$ ltm resume
# arrow keys to pick a packet. block copied to clipboard. paste into any agent.

Self-hosting the server is two commands:

sh · server
          
$ ltm server init
$ ltm server --addr :8080
# sqlite on disk. bearer tokens. done.

§ 10. Provenance

Written with help. Said out loud.

ltm is written with LLM assistance. A human drives the design, writes the prose, reviews every line, and is accountable for what lands; a coding agent helps with implementation. Commits touched by an agent carry an Assisted-by: trailer naming the tool — the same convention as the Linux kernel's AI Coding Assistants policy. Disclosure, not disguise.

If a PR you send was partly written by an LLM, please do the same: add an Assisted-by: trailer, read the diff as if you had written it yourself, and be the one accountable for it. Details in CONTRIBUTING.md.