curldb

HTTP/1.1 200 OK

  • X-Thesiscurl is all you need
  • X-Shapeprose in, headers out
  • X-StoresHTTP messages, verbatim
  • X-Installpip install curldb
  • Content-Typetext/plain

Every message an AI sends is already an HTTP message. curldb keeps them: one SQLite file per session, queried by the envelope, zero daemon, zero dependencies.

HTTP is the best control plane

A message has three places. A status line for what happened. Headers for what the message is. A body for the payload. Thirty years of software already reads those three places: proxies, caches, logs, rate limiters, your terminal.

AI APIs put the method in one JSON field, the role in another, the content in a third, and send the whole thing over HTTP. HTTP inside JSON inside HTTP. The moment a header hides inside prose, every tool on the wire goes blind.

Ask a model to answer as an HTTP response and it does, with headers nobody taught it: X-Verdict, X-Confidence, X-Scope. It learned the shape from the web. The envelope was always there. The API swallowed it; curldb keeps it.

Prose in, structure out. Not JSON.

You ask in plain words. The answer comes back with a status line and headers. That is structure without a schema: no fields you had to define first, no parser to keep in sync. A header like X-Confidence: 0.8 already means something to every reader that knows HTTP, models included. Store the message and the headers are the index; nothing had to be extracted.

HTTP already has a place for the verb, the resource, and the outcome. Putting them inside the payload again gives you the oldest joke on the wire:

HTTP/1.1 200 OK
Content-Type: application/json

{"status": 404, "error": "not found"}

Every proxy, cache, and log on the way saw a success. Only the parser at the far end knows it failed. The status line was right there, unused.

tee

The conversation stays on your screen. tee sends a copy to curldb. Close the terminal; the copy is still there.

$ claude 'how do I design a REST API for a todo app?' | tee /dev/tty | curldb add
HTTP/1.1 200 OK
X-Scope: design
X-Confidence: 0.8

Nouns, not verbs. /todos and /todos/42. POST creates, PATCH edits, DELETE removes. Let status codes carry the outcome.
#1 200 +2 headers

$ claude 'latest news on Rust async?' | tee /dev/tty | curldb add
HTTP/1.1 200 OK
X-Scope: news
X-Confidence: 0.6

Async closures stabilised. The runtime story is unchanged: pick one, stay on it.
#2 200 +2 headers

$ curldb query 'header:X-Scope=design'
      1  09-11 11:13   200   Nouns, not verbs. /todos and /todos/42. POST creates

$ curldb tags
  X-Scope: design(1) news(1)
  X-Confidence: 0.8(1) 0.6(1)

Each reply prints as usual and lands in curldb with a number. Nothing was parsed on the way in; the headers the model wrote are the index. Query by status, any header, or full text of the body.

Four rules

One session, one file
The file name is the session. Backup is cp. Searching across sessions is querying each file in turn.
Headers are tags
No schema. X-Verdict: shaky is unreadable to a program and obvious to a model, so an unknown header is fine. curldb tags lists what exists; the vocabulary grows out of the data.
The body is never touched
Whoever adds headers, you, the model, an annotating model, a hook, adds headers only. Annotators sign with Via:.
The server is a cabinet
Every stored message gets a number; a POST answers with it in Location. GET /2 returns message 2 exactly as it was stored, as message/http. A stored reply is data; it is never replayed as the server's own answer. Localhost, no token, one CLI.

Where the messages come from

Nobody types HTTP. People speak in prose; machines add the envelope.

The model answers in HTTP because its system prompt asks it to. Your own words get wrapped on the way in: a small model on a tee branch reads what you typed and writes the headers, X-Topic, X-Intent, whatever fits, without touching a word of the body. Tool calls need no model at all; a hook translates them mechanically, POST /tool/Read with JSON args, a response with the result. Markdown notes with YAML front matter are header plus body already.

One turn of a conversation, one file, one index over all of it: what was said, what was decided, which tool failed, which conclusion did not hold.

pip install curldb

One file, standard library only, Python 3.10 or newer. MIT.

HTTP/1.1 200 OK

  • X-Thesiscurl is all you need
  • X-Shapeprose in, headers out
  • X-StoresHTTP 消息,原样
  • X-Installpip install curldb
  • Content-Typetext/plain

AI 发出的每条消息本来就是一条 HTTP 消息。curldb 把它们留下来:一个 session 一个 SQLite 文件,按信封查,零进程,零依赖。

HTTP 是最好的控制面

一条消息有三个位置。状态行说发生了什么,header 说这条消息是什么,body 是载荷。三十年的软件都认得这三个位置:代理、缓存、日志、限流器、你的终端。

AI API 把方法放进一个 JSON 字段,角色放进另一个,内容放进第三个,再整个用 HTTP 发出去。HTTP 里套 JSON 再套 HTTP。header 一旦藏进正文,线上的每个工具都瞎了。

让模型用 HTTP response 回答,它就会,还带着没人教过它的 header:X-VerdictX-ConfidenceX-Scope。它从整个 web 学会了这个形状。信封一直都在,API 把它吞了,curldb 把它留下。

进去是白话,出来是结构。不是 JSON。

你用白话问,回答带着状态行和 header 回来。这是没有 schema 的结构:不用先定义字段,不用维护解析器。X-Confidence: 0.8 这样一行,每个懂 HTTP 的读者一看就明白,模型也算。把消息存下来,header 就是索引,什么都不用抽取。

动词、资源、结果,HTTP 里本来就各有各的位置。再往 payload 里塞一遍,就是线上最老的那个笑话:

HTTP/1.1 200 OK
Content-Type: application/json

{"status": 404, "error": "not found"}

一路上的代理、缓存、日志看到的都是成功,只有最末端那个解析器知道失败了。状态行就在那,没人用。

tee

对话照常在你屏幕上。tee 分一份给 curldb。关掉终端,那一份还在。

$ claude 'how do I design a REST API for a todo app?' | tee /dev/tty | curldb add
HTTP/1.1 200 OK
X-Scope: design
X-Confidence: 0.8

Nouns, not verbs. /todos and /todos/42. POST creates, PATCH edits, DELETE removes. Let status codes carry the outcome.
#1 200 +2 headers

$ claude 'latest news on Rust async?' | tee /dev/tty | curldb add
HTTP/1.1 200 OK
X-Scope: news
X-Confidence: 0.6

Async closures stabilised. The runtime story is unchanged: pick one, stay on it.
#2 200 +2 headers

$ curldb query 'header:X-Scope=design'
      1  09-11 11:13   200   Nouns, not verbs. /todos and /todos/42. POST creates

$ curldb tags
  X-Scope: design(1) news(1)
  X-Confidence: 0.8(1) 0.6(1)

每条回答照常打印出来,同时进了 curldb,拿到一个编号。进去的路上什么都没解析,模型自己写的 header 就是索引。按 status、任意 header、body 全文查。

四条规矩

一个 session 一个文件
文件名就是 session。备份是 cp。跨 session 查就是对每个文件各查一遍。
header 是 tag
没有 schema。X-Verdict: shaky 程序读不懂,模型一看就懂,所以没见过的 header 也没关系。curldb tags 列出库里有什么,词表从数据里长出来。
body 永远原样
加 header 的人,你、模型、标注模型、hook,只加 header。标注者用 Via: 留名。
server 是档案柜
每条存进去的消息有一个编号,POST 的回复在 Location 里告诉你。GET /2 取回第 2 条,和存进去时一个字节不差,类型是 message/http。存的回答是数据,永远不当 server 自己的回答回放。只绑 localhost,没有 token,一个 CLI。

消息从哪来

没人手写 HTTP。人说人话,信封机器来套。

模型用 HTTP 回答,因为 system prompt 要求它这么做。你自己的话在进门的路上被套上信封:tee 分出一支给一个小模型,它读你打的字、写 header,X-TopicX-Intent,合适什么写什么,body 一个字不碰。工具调用连模型都不用,一个 hook 机械翻译,POST /tool/Read 带 JSON 参数,response 带结果。带 YAML front matter 的 markdown 笔记,本来就是 header 加 body。

一段对话,一个文件,一个索引盖住全部:说了什么,定了什么,哪个工具失败了,哪个结论没站住。

pip install curldb

一个文件,只用标准库,Python 3.10 以上。MIT。