Guide
The write policy, kinds, checkpoints and resume, lines vs threads and the mesh, budget and ranking, the Python SDK, local to cloud.
The README gets you running; this page explains the pieces you meet once you do.
For the why of each design decision, the source is LIFELINE.md.
Core concepts
- Entry — the atomic unit. Content-addressed:
id = sha256(kind, author, agent, provider, model, summary, body, sorted parents). Timestamp anddedup_keystay outside the hash, so the same content yields the same id on any machine — the basis for dedup, sync and the mesh. - Three layers, one ledger — the ledger (hashed append-only DAG, source of truth), the state (current truth reduced from it: decisions in force, open threads, pins, checkpoints, threads) and recall (relevance, every hit anchored to its entry).
- Supersession — a
correctionwhose parents name another entry removes it from the current truth. The past is never edited; it stays in history, marked reverted. - Anchor — every item an AI reads carries the id of its entry. No anchor, no entry.
- View —
LIFELINE.mdis generated from the store (.lifeline/*.db), diffable and mergeable in git. The store is the runtime; the text is the versioned artifact. A fresh clone rebuilds the store withlifeline migrate --from LIFELINE.md.
Kinds
bootstrap (project identity) · decision · open (a thread) · checkpoint (where work stopped
on a thread) · feature · fix · incident · milestone · release · note · correction
(supersedes its parents) — plus the entity kinds of the typed graph: service · api · test ·
requirement · debt · relation (lifeline relate, lifeline graph).
Write policy
Like the permission modes of a coding agent. The human's log commits directly. An AI's write is
routed by .lifeline/policy.json — direct (enters the line now, with an audit row), propose
(pending until a human approves — the default) or deny — per agent and per kind. Without a file,
checkpoint is direct and everything else is proposed. Write-time anti-junk requires the why.
{ "default": "propose",
"kinds": { "checkpoint": "direct" },
"agents": { "claude-code": { "note": "direct", "feature": "direct", "fix": "direct" } } }
lifeline init --agent claude-code (or lifeline policy --init --agent claude-code) writes that;
lifeline audit --since 7d lists what entered directly. Curation becomes review-after-the-fact: a
wrong direct write is superseded by a correction, never edited. The file is versioned — it is
project config, like .mcp.json.
Discipline (guard, hooks, rules)
Whether every AI updates the line is decided by mechanisms at three boundaries, not by prompts:
the Stop hook of the agent's session (lifeline guard --session, blocks once when the tree
changed and the line didn't), the pre-push hook of the repo (lifeline guard --git, fails when
code moved after the last entry), and the rule file each client reads (lifeline install-rules).
lifeline capture is the net when an AI forgets; lifeline audit and lifeline exam are the
measure. See INTEGRATION.md.
Lines, threads and the mesh
A line is one independent ledger with its own view: a project has ledger → LIFELINE.md by
default and adds more with --line <name> (.lifeline/<name>.db + LIFELINE.<name>.md). Use lines
for different contexts or audiences — a public engineering ledger next to a private business one,
a subsystem, an experiment. Every command takes --line; the MCP server picks one with
LIFELINE_LINE, or any tool takes line="<name>".
lifeline --line businessplan log --kind decision --summary 'Pricing: $20/seat/mo' --body "Covers infra at ~50 seats."
lifeline --line businessplan context
lifeline lines
A thread is a branch inside a line. lifeline log --thread auth … labels the entry
(thread: auth in the body) and chains it to the thread's head instead of the global one, so the
DAG is a real tree; --parents a,b merges. lifeline context --thread auth (or
lifeline_brief("auth")) is the brief a sub-agent gets; lifeline checkpoint --thread auth is
where it stopped. A thread can also be the id of an open entry.
The mesh connects lines without losing the anchor:
lifeline promote --from research --to ledger --id <id>copies an entry as a root and records the full source id in anorigin:block — a hash anchor, not prose; re-promoting never duplicates.--parents research:<id>makes an entry depend on another line's entry.lifeline verifyresolves such parents in the sibling lines of the same.lifeline/and reports them as external anchors (--meshlists them;--no-meshis the strict single-line check).lifeline where <id>andlifeline lines --meshshow the welds.
Budget, ranking and delivery
The context is assembled within a budget — characters, or tokens with --budget-tokens N (a
deterministic estimate; pip install "lifeline-context[tokens]" for tiktoken). What always fits:
identity, open threads, the resume section, recent entries, the footer. Decisions fill the rest:
when they don't all fit, the oldest in force (the foundation) and the newest survive, the
why excerpt shrinks first, and the middle is omitted with its ids listed. lifeline pin <id> keeps
a decision in the foundation under any budget (an anchored note; unpin is a correction).
The payload is two halves — a stable prefix (identity + decisions) and a volatile tail — so a
provider's prompt cache reuses the prefix between connects. lifeline context --format
anthropic|openai|gemini wraps the same markdown in the provider's system-message envelope, with the
cache breakpoint on the stable block.
Recall
lifeline recall "<query>" (and context --query, lifeline_recall) is BM25 over SQLite FTS5:
accent-insensitive, stopwords dropped, PT↔EN synonyms, scores relative to the best hit with an
abstention floor — it returns nothing rather than noise. lifeline reindex rebuilds the index. A
dense embedder is opt-in (pip install "lifeline-context[embeddings]", LIFELINE_EMBEDDER=dense).
Python SDK
import asyncio
from lifeline import Entry, SQLiteEventStore, StateEngine, ContextAssembler, make_recall
async def main():
store = SQLiteEventStore(".lifeline/ledger.db")
await store.initialize()
await store.append(Entry(kind="decision", author="me", summary="DB: PostgreSQL", body="ACID for audit."))
parts = await ContextAssembler(StateEngine(store)).assemble_parts() # .stable / .volatile / .text
print(parts.text)
hits = await make_recall(store).search("which database", k=3) # anchored relevance
await store.close()
asyncio.run(main())
EventStore is a port: SQLiteEventStore is the local adapter, SupabaseEventStore the cloud one;
deliver(parts, "anthropic") gives the provider envelope; estimate_tokens(text) the budget unit.
Local → cloud
Everything is content-addressed, so pushing a local line to the cloud is lossless and idempotent — same ids, re-seeding dedupes itself.
lifeline --store supabase migrate --from LIFELINE.md # seed (repeatable — no dupes)
lifeline --store supabase context # operate against the cloud
Just want to share the text? lifeline push / pull / clone sync the view through git and
rebuild the store. Cloud setup, the remote MCP server and OAuth: M3_TIER1_SUPABASE.md,
MCP_REMOTE.md, DEPLOY.md.
The 7 laws
The constitution lives in CLAUDE.md: no memory without an immutable anchor; append-only; deterministic content-addressing; provider-agnostic storage, delivery in the provider's format; the why outweighs the what; budget is first-class; MCP-native. Lifeline records reasoning, not execution — it is not an orchestrator, a workflow engine or a git replacement.
Milestones
| Milestone | State |
|---|---|
| M1 — the loop (ledger → state → assembly → MCP), authorship, recall, CLI | done |
| M3 — git sync; Supabase store + cloud HITL; remote MCP + OAuth resource server | done, live-validated |
| 0.6 — long horizon: checkpoints, threads + brief, write policy, mesh, ranking + pins, token budgets + envelopes, BM25 recall, hooks | done, unreleased |
| M4 — multi-user (concurrent DAG merge) / hub | planned |