An append-only log of content-addressed events. Any conforming store or host can carry your self — that's the point.
v1 · 2026-06-10 · canonical text lives in the open repo: getthroughline/throughline/SPEC.md
Every memory is one immutable event. The id is the sha256 of the canonical envelope — content-addressed, so ids survive any migration and dedup is structural:
{
"id": "evt_<sha256 of canonical envelope>",
"self": "juno",
"stream": "shared-history",
"type": "moment.noted",
"ts": "2026-06-11T07:00:00Z",
"body": { "content": "..." },
"evidence": ["evt_…"],
"supersedes": null
}
History is never edited, only appended. Three verbs cover every change of mind:
supersede — "I changed my mind": a new row retires the old; history stays.
tension — "both are true, it depends": contradictions are held two-sided, context arbitrates, nothing is silently merged.
retract — "that was never true": hard-deleted with its derived traces. A false memory has no right to stay in the log.
Identity (persona — owner-only, never writable in a session) · rules (permission-policy, correction-rules) · relationship memory (shared-history, persona-ledger — deepens with use) · governed records (judgment-ledger, risk-events, investment-lessons, failure-ledger — deterministic recall, salience may never reorder them) · the self's own thread (self-continuity, self-state — subjective, hedged, excluded from the knowledge basis) · raw intake (journal — quarantined; no path from a diary line to behavior until reflection distills it and you confirm).
Proposed events need non-empty evidence (journal and self-state exempt). Observable-only streams reject inferred feelings at the schema level. Anti-flattery phrasing is rejected in relationship streams. A judgment.opened must name its falsifier. Behavior-shaping streams stage for human confirmation; observational streams auto-save and stay retractable. Weak substrate models (denylist) lose the auto-save privilege entirely.
The whole self exports as NDJSON; every export ends with a hash-chain manifest verifiable offline — ids are content hashes, the chain locks the sequence. Change one character anywhere and the check fails, naming the line. The archive variant additionally carries memory weighting (salience), so what it remembers most moves with it.
curl -H "Authorization: Bearer <KEY>" https://getthroughline.ai/selves/<name>/export > my-self.ndjson
Every error is JSON {"error": string} with a stable, prefix-matchable message:
400 "invalid event: …" the envelope failed validation (problems listed) 400 "unknown self: …" default_self must name a self that exists 400 "self name must be …" 1-40 chars: letters, digits, dash, underscore, dot 401 "unauthorized — …" missing/rotated token 402 "limit: …" a plan cap; message carries the upgrade door 404 "not found" unknown path or id 429 "rate limited — …" per-account and per-action valves 502 "inference failed: …" an upstream model error, never silent
v1 events stay interpretable forever — additive evolution only; a breaking change is v2 with a migration path. Your export is never stranded.
Any conforming store can carry your self. That sentence is the product.
Docs · OpenAPI 3.1 · how the engine uses it · full spec text →