# How it works

memprove never asks a model whether a memory still holds. A fact cites test runs; each run sealed the repository state it ran in; on every read the daemon probes the repository again and a fixed rule gives the fact its verdict. Here is that happening in a real repository, then how it is built.

## One fact, three verdicts

[tinyini](https://github.com/memprove/memprove.github.io/tree/main/demo/tinyini) is a small INI parser in Python, kept in this site's repository so that every output here is real. `demo/record.sh` builds it commit by commit and runs the commands below with memprove 0.1.0 and pytest. Nothing in the output is edited, except that the home directory is written as `~`.

### Verified: recorded on a passing run

Session 1 commits the change for the Task `parser: handle empty input`, runs the tests through `memprove run` (they pass as `r1`) and records four facts citing that run. The repository is still in the state `r1` ran in, so every fact reads `verified`:

````text
$ memprove resume --text
Task「parser: handle empty input」 (in_progress): verified (ledger sequence 7, current HEAD 5ebf8ad58118f1d7bf8cf479951c3b36932f9676)
Sessions: 1 in all, 0 ended normally, 0 ended abandoned, 1 not ended
Session 1 01a1dc2c-4168-7511-b3a3-220085a788c5 (the Session this directory's context file records) not ended (started 2026-11-14T22:27:30.280957974Z, last wrote to the ledger at 2026-11-14T22:27:30.825220406Z)
#5 open item: 「an unterminated section header such as [core is untested」 (no evidence cited; from Session 1)
#1 verified: 「parse() returns an empty document for empty or whitespace-only input」 (evidence bound to commit 5ebf8ad58118f1d7bf8cf479951c3b36932f9676, current HEAD 5ebf8ad58118f1d7bf8cf479951c3b36932f9676; verdict: verified; from Session 1)
#2 verified: 「input of only comments is an empty document」 (evidence bound to commit 5ebf8ad58118f1d7bf8cf479951c3b36932f9676, current HEAD 5ebf8ad58118f1d7bf8cf479951c3b36932f9676; verdict: verified; from Session 1)
#3 verified: 「keys before the first section header go into the default section」 (evidence bound to commit 5ebf8ad58118f1d7bf8cf479951c3b36932f9676, current HEAD 5ebf8ad58118f1d7bf8cf479951c3b36932f9676; verdict: verified; from Session 1)
#4 verified: 「a duplicate key keeps the last value」 (evidence bound to commit 5ebf8ad58118f1d7bf8cf479951c3b36932f9676, current HEAD 5ebf8ad58118f1d7bf8cf479951c3b36932f9676; verdict: verified; from Session 1)
#6 next action: 「write the unterminated-header test and run pytest」 (no evidence cited; from Session 1)
````

### Stale: the code moved on

Session 1 ends. Someone then commits `558cb252ae87` ("parser: strip inline comments from values") without running anything through memprove. The facts were not edited, but the state they were bound to is gone: every one reads `stale`, with the reasons, the files that changed, and the command that would confirm them again:

````text
$ memprove resume --text
Task「parser: handle empty input」 (in_progress): stale (ledger sequence 8, current HEAD 558cb252ae874fa54bdc721048ab8d042610a88e) (reasons: head_changed, index_changed, evidence_stale)
Sessions: 1 in all, 1 ended normally, 0 ended abandoned, 0 not ended
#5 open item: 「an unterminated section header such as [core is untested」 (no evidence cited; from Session 1)
#1 fact stale: 「parse() returns an empty document for empty or whitespace-only input」 (evidence bound to commit 5ebf8ad58118f1d7bf8cf479951c3b36932f9676, current HEAD 558cb252ae874fa54bdc721048ab8d042610a88e; changed files since 5ebf8ad58118f1d7bf8cf479951c3b36932f9676: 「tests/test_comments.py」, 「tinyini/__init__.py」; verdict: stale; from Session 1) (reasons: head_changed, index_changed, evidence_stale)
#2 fact stale: 「input of only comments is an empty document」 (evidence bound to commit 5ebf8ad58118f1d7bf8cf479951c3b36932f9676, current HEAD 558cb252ae874fa54bdc721048ab8d042610a88e; changed files since 5ebf8ad58118f1d7bf8cf479951c3b36932f9676: same as #1; verdict: stale; from Session 1) (reasons: head_changed, index_changed, evidence_stale)
#3 fact stale: 「keys before the first section header go into the default section」 (evidence bound to commit 5ebf8ad58118f1d7bf8cf479951c3b36932f9676, current HEAD 558cb252ae874fa54bdc721048ab8d042610a88e; changed files since 5ebf8ad58118f1d7bf8cf479951c3b36932f9676: same as #1; verdict: stale; from Session 1) (reasons: head_changed, index_changed, evidence_stale)
#4 fact stale: 「a duplicate key keeps the last value」 (evidence bound to commit 5ebf8ad58118f1d7bf8cf479951c3b36932f9676, current HEAD 558cb252ae874fa54bdc721048ab8d042610a88e; changed files since 5ebf8ad58118f1d7bf8cf479951c3b36932f9676: same as #1; verdict: stale; from Session 1) (reasons: head_changed, index_changed, evidence_stale)
Rerun: 「python3 -m pytest -q」 on the current HEAD 558cb252ae874fa54bdc721048ab8d042610a88e (for #1, #2, #3, #4)
#6 next action: 「write the unterminated-header test and run pytest」 (no evidence cited; from Session 1)
````

### Verified again: a passing rerun

Session 2 reads that, runs `python3 -m pytest -q` through `memprove run` (it passes as `r1` of this Session) and reaffirms the stale facts on it with `checkpoint create --reaffirm-all`. The old statements are kept as superseded; the new ones are bound to `558cb252ae87`:

````text
$ memprove resume --text
Task「parser: handle empty input」 (in_progress): verified (ledger sequence 17, current HEAD 558cb252ae874fa54bdc721048ab8d042610a88e)
Sessions: 2 in all, 1 ended normally, 0 ended abandoned, 1 not ended
Session 2 01a1dc2c-452f-7891-b208-8570b22d9fea (the Session this directory's context file records) not ended (started 2026-11-14T22:27:31.247079849Z, last wrote to the ledger at 2026-11-14T22:27:31.711334172Z)
#5 open item: 「an unterminated section header such as [core is untested」 (no evidence cited; from Session 1)
#7 verified: 「parse() returns an empty document for empty or whitespace-only input」 (evidence bound to commit 558cb252ae874fa54bdc721048ab8d042610a88e, current HEAD 558cb252ae874fa54bdc721048ab8d042610a88e; verdict: verified; from Session 2)
#8 verified: 「input of only comments is an empty document」 (evidence bound to commit 558cb252ae874fa54bdc721048ab8d042610a88e, current HEAD 558cb252ae874fa54bdc721048ab8d042610a88e; verdict: verified; from Session 2)
#9 verified: 「keys before the first section header go into the default section」 (evidence bound to commit 558cb252ae874fa54bdc721048ab8d042610a88e, current HEAD 558cb252ae874fa54bdc721048ab8d042610a88e; verdict: verified; from Session 2)
#10 verified: 「a duplicate key keeps the last value」 (evidence bound to commit 558cb252ae874fa54bdc721048ab8d042610a88e, current HEAD 558cb252ae874fa54bdc721048ab8d042610a88e; verdict: verified; from Session 2)
#11 next action: 「write the unterminated-header test and run pytest」 (no evidence cited; from Session 2)
superseded 5: #1–#4, #6; withdrawn 0 (not listed one by one; resume --text --all lists them all)
````

## What the next session reads

When Session 2 started, Claude Code's SessionStart hook put this in front of the agent before its first message: one opening statement, then the Context Pack in a fence. The same ledger, repository state and budget always give the same bytes, and the last line is the SHA-256 of the lines above it.

````text
$ memprove hook session-start   # run by Claude Code at the start of a session
memprove: below is the Context Pack of Task 01a1dc2c-4162-74e0-a5da-be1779c133a7 at SessionStart (startup), chosen from this directory's context. The current UTC time is 2026-11-14T22:27:31Z. Under the unattended takeover rule, this session records each Session it leaves open as an open item 「another Session (Session k) may still be working, not ended」 in its first checkpoint.
```text
Pack: memprove-pack v1; capsule_digest sha256:2440ba5f63482c175ae6825d8390ab85a93cc0edc88381bce7b682b6ca6b00f0; ledger sequence 8; probed HEAD 558cb252ae874fa54bdc721048ab8d042610a88e; budget 9000 UTF-16 code units
Ontology: continuity.work-state-ontology.v4 (the version of the verdict rules)
Task「parser: handle empty input」 (in_progress): stale (reasons: head_changed, index_changed, evidence_stale); active claims 6: facts 4 (stale 4), open items 1, decisions 0, next actions 1; superseded 0; withdrawn 0 (facts 0)
Integrity: no inconsistency found (the event chain is valid, and every record of this Task recomputes and agrees with the ledger)
Sessions: 1 in all, 1 ended normally, 0 ended abandoned, 0 not ended, 0 declared a resumed_from Pack
Session 1 01a1dc2c-4168-7511-b3a3-220085a788c5 ended normally (started 2026-11-14T22:27:30.280957974Z)
Cannot: complete the Task (transitionTask; reasons: evidence_stale; needs mcp-write)
Can: begin a new Session (createSession; needs mcp-write)
Can: record a fact citing the latest run (createCheckpoint; needs mcp-write)
Base: 5ebf8ad58118f1d7bf8cf479951c3b36932f9676; files changed since: 「tests/test_comments.py」, 「tinyini/__init__.py」
#5 open item: 「an unterminated section header such as [core is untested」 (no evidence cited; from Session 1)
#1 fact stale: 「parse() returns an empty document for empty or whitespace-only input」 (reasons: head_changed, index_changed, evidence_stale; evidence bound to commit 5ebf8ad58118; from Session 1)
#2 fact stale: 「input of only comments is an empty document」 (reasons: head_changed, index_changed, evidence_stale; evidence bound to commit 5ebf8ad58118; from Session 1)
#3 fact stale: 「keys before the first section header go into the default section」 (reasons: head_changed, index_changed, evidence_stale; evidence bound to commit 5ebf8ad58118; from Session 1)
#4 fact stale: 「a duplicate key keeps the last value」 (reasons: head_changed, index_changed, evidence_stale; evidence bound to commit 5ebf8ad58118; from Session 1)
Rerun: 「python3 -m pytest -q」 on the current HEAD (for #1, #2, #3, #4)
#6 next action: 「write the unterminated-header test and run pytest」 (no evidence cited; from Session 1)
Omitted: level 0; bases with files only counted 0, bodies shortened 0, bodies omitted 0, folded into one line 0, not listed 0; full text of given numbers: memprove resume --pack --claims <numbers> (such as 3,5-7)
Digest: sha256:210efbefabe5507a82157336d13976f119a58705799a9ff65205239a1bffe932
```
````

## Every command, as recorded

<details>
<summary>Both Sessions, command by command (standard output and standard error)</summary>

````text
$ memprove init
memprove init: ~/tinyini
configuration directory: ~/.memprove/config (from HOME/.memprove/config)
clients: claude-code
Project 01a1dc2c-40dd-7160-b318-7542e520ca6e registered for ~/tinyini
~/.memprove/config/contexts/44d77834010dab48a216a9deaf071bf532ac599b796b1a642592935beffdfcb8.pack.json written (the Project for the first `memprove begin` here)
~/tinyini/.git/info/exclude written
~/tinyini/.claude/settings.local.json written
~/tinyini/.mcp.json written
note: permissions.allow was left as it is; the rules an unattended `claude -p` needs are "mcp__memprove", "Bash(memprove run:*)", and "Bash(~/.memprove/bin/memprove run:*)"; `memprove init --allow-tools` adds them
doctor:
PASS data-dir:   ~/.memprove/data has mode 0700 (named by ~/.memprove/config/data-dir)
PASS projects-dir: ~/.memprove/data/projects and its 1 Project directory, and ~/.memprove/data/tmp, have mode 0700 or stricter
PASS socket:     ~/.memprove/data/memproved.sock has mode 0600
PASS daemon:     memproved answers and is ready on ~/.memprove/data/memproved.sock
PASS token-file: ~/.memprove/config/token has mode 0600 and holds one token
PASS token:      memproved accepts the token, and it reaches the mcp-read and mcp-write operations
PASS build:      memprove and memproved are the same build, 0.1.0 (71674684285e)
PASS config-dir: ~/.memprove/config is outside every Git worktree
PASS path:       ~/.memprove/bin is on PATH, so `memprove run` runs this executable and the rule Bash(memprove run:*) matches it
PASS guide:      the guide at ~/.memprove/share/guide.md was installed with this binary (build 71674684285e)
WARN workspace:  the Workspace ~/tinyini has no current context yet: `memprove begin --new-task <TITLE>` there starts the first Session, in Project 01a1dc2c-40dd-7160-b318-7542e520ca6e (the one `memprove init` registered)
PASS hook:       ~/tinyini/.claude/settings.local.json runs ~/.memprove/bin/memprove hook session-start, which exists; its --config-dir is ~/.memprove/config, the configuration directory doctor uses (the argument wins over env.MEMPROVE_CONFIG_DIR)
PASS mcp-entry:  ~/tinyini/.mcp.json has mcpServers.memprove, which runs ~/.memprove/bin/memprove; its --config-dir is ~/.memprove/config, the configuration directory doctor uses (the argument wins over env.MEMPROVE_CONFIG_DIR)
WARN ledger:     not checked: no context names a Project here
memprove doctor: 12 PASS, 2 WARN, 0 FAIL
next: memprove begin --new-task "<TITLE>" in this repository starts the first Session; the agent's token and the daemon are found through ~/.memprove/config

$ memprove begin --new-task "parser: handle empty input"
{"schema_version":1,"workspace_root":"~/tinyini","project_id":"01a1dc2c-40dd-7160-b318-7542e520ca6e","workspace_id":"01a1dc2c-40dd-7160-b318-755b385c24a2","agent_id":"01a1dc2c-4167-7cba-8d2f-d0c8cb0baebd","task_id":"01a1dc2c-4162-74e0-a5da-be1779c133a7","task_title":"parser: handle empty input","task_revision":3,"task_state":"in_progress","session_id":"01a1dc2c-4168-7511-b3a3-220085a788c5","session_revision":1,"snapshot_id":null,"evidence_id":null,"evidence_status":null,"runs":[],"next_run":1}
memprove: Session started; current context written to ~/.memprove/config/contexts/44d77834010dab48a216a9deaf071bf532ac599b796b1a642592935beffdfcb8.json

$ memprove run --test-format pytest -- python3 -m pytest -q
........                                                                 [100%]
8 passed in 0.01s
memprove: "python3" exited with 0 (2026-11-14T22:27:30.401Z to 2026-11-14T22:27:30.714Z)
memprove: counted 8 test cases
{"artifact_type":"test_run","canonical_digest":"sha256:e0fee9e943549ca237436969976256996399775dcf277310892073ef1a8655ea","collector":{"name":"memprove-cli","version":"0.1.0"},"evidence_id":"01a1dc2c-431c-7bb1-a520-6192267fceb6","id":"01a1dc2c-431c-7bb1-a520-6192267fceb6","kind":"test_result","observed_at":"2026-11-14T22:27:30.714Z","payload":{"argv":["python3","-m","pytest","-q"],"exit_code":0,"finished_at":"2026-11-14T22:27:30.714Z","passed":true,"started_at":"2026-11-14T22:27:30.401Z","stderr":"","stderr_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","stdout":"........                                                                 [100%]\n8 passed in 0.01s\n","stdout_sha256":"71288863ec9567395a647dd0c8fbf0e2048bee72283202612ad6f90a7a526ef3","test_case_count":8,"test_format":"pytest","type":"test_result"},"payload_digest":"sha256:6b145997b630503791ea3548618fbd15d0830d7610aa4b3f2f7e0ed493f735b4","revision":1,"schema_version":1,"snapshot":{"branch":"main","canonical_digest":"sha256:fed966dc463d9e02488430f9d760dbfba2085fa1335c6339ae957201e2e28df0","capture_manifest_digest":"sha256:94a9f494f6446abe28923b896b7f3f1a8fa7d145655f428122673735f09719b5","captured_at":"2026-11-14T22:27:30.716742564Z","command_evidence_ids":[],"git_head_evidence_id":"01a1dc2c-431c-7bb1-a520-61a8bcc2cf48","git_worktree_evidence_id":"01a1dc2c-431c-7bb1-a520-61b4b414e64a","head_oid":"5ebf8ad58118f1d7bf8cf479951c3b36932f9676","is_dirty":false,"snapshot_id":"01a1dc2c-431a-7820-914d-e78543891b7a","task_id":"01a1dc2c-4162-74e0-a5da-be1779c133a7","test_evidence_ids":["01a1dc2c-431c-7bb1-a520-6192267fceb6"],"workspace_id":"01a1dc2c-40dd-7160-b318-755b385c24a2"},"snapshot_ids":["01a1dc2c-431a-7820-914d-e78543891b7a"],"status":"PASS","subject":{"test_name":"python3 -m pytest -q","type":"test_result"},"task_id":"01a1dc2c-4162-74e0-a5da-be1779c133a7","workspace_id":"01a1dc2c-40dd-7160-b318-755b385c24a2"}
memprove: runs of this repository state you can cite with --evidence <LABEL>: r1 (PASS)
memprove: recorded PASS, 8 test cases, evidence_id 01a1dc2c-431c-7bb1-a520-6192267fceb6, label r1

$ memprove checkpoint create --fact "parse() returns an empty document for empty or whitespace-only input" --evidence r1 --fact "input of only comments is an empty document" --evidence r1 --fact "keys before the first section header go into the default section" --evidence r1 --fact "a duplicate key keeps the last value" --evidence r1 --open "an unterminated section header such as [core is untested" --next "write the unterminated-header test and run pytest"
{"checkpoint_id":"01a1dc2c-4389-7bb3-b197-50270eef820c","created_at":"2026-11-14T22:27:30.825220406Z","digest":"sha256:9006ef2670ad4e7c4d5418a1c7ffce15e95884ada5d6bdfd74ef4e2968a2faf7","id":"01a1dc2c-4389-7bb3-b197-50270eef820c","next_action":{"classification":"recommendation","evidence_ids":[],"text":"write the unterminated-header test and run pytest"},"objective":"parser: handle empty input","open_items":[{"classification":"unknown","evidence_ids":[],"text":"an unterminated section header such as [core is untested"}],"project_event_sequence":6,"revision":1,"schema_version":1,"session_id":"01a1dc2c-4168-7511-b3a3-220085a788c5","snapshot":{"branch":"main","canonical_digest":"sha256:fed966dc463d9e02488430f9d760dbfba2085fa1335c6339ae957201e2e28df0","capture_manifest_digest":"sha256:94a9f494f6446abe28923b896b7f3f1a8fa7d145655f428122673735f09719b5","captured_at":"2026-11-14T22:27:30.716742564Z","command_evidence_ids":[],"git_head_evidence_id":"01a1dc2c-431c-7bb1-a520-61a8bcc2cf48","git_worktree_evidence_id":"01a1dc2c-431c-7bb1-a520-61b4b414e64a","head_oid":"5ebf8ad58118f1d7bf8cf479951c3b36932f9676","is_dirty":false,"snapshot_id":"01a1dc2c-431a-7820-914d-e78543891b7a","task_id":"01a1dc2c-4162-74e0-a5da-be1779c133a7","test_evidence_ids":["01a1dc2c-431c-7bb1-a520-6192267fceb6"],"workspace_id":"01a1dc2c-40dd-7160-b318-755b385c24a2"},"snapshot_id":"01a1dc2c-431a-7820-914d-e78543891b7a","task_id":"01a1dc2c-4162-74e0-a5da-be1779c133a7","verified_completed_work":[{"classification":"verified_fact","evidence_ids":["01a1dc2c-431c-7bb1-a520-6192267fceb6"],"text":"parse() returns an empty document for empty or whitespace-only input"},{"classification":"verified_fact","evidence_ids":["01a1dc2c-431c-7bb1-a520-6192267fceb6"],"text":"input of only comments is an empty document"},{"classification":"verified_fact","evidence_ids":["01a1dc2c-431c-7bb1-a520-6192267fceb6"],"text":"keys before the first section header go into the default section"},{"classification":"verified_fact","evidence_ids":["01a1dc2c-431c-7bb1-a520-6192267fceb6"],"text":"a duplicate key keeps the last value"}],"workspace_id":"01a1dc2c-40dd-7160-b318-755b385c24a2"}
memprove: new statements: #1 fact, #2 fact, #3 fact, #4 fact, #5 open_item, #6 next_action
memprove: superseded: none
memprove: withdrawn: none
memprove: reaffirmed: none

$ memprove resume --pack
Pack: memprove-pack v1; capsule_digest sha256:9b2f9bd45319be25010bbe0ecbb937aaa7902f465f76af15a1f176edd669e43b; ledger sequence 7; probed HEAD 5ebf8ad58118f1d7bf8cf479951c3b36932f9676; budget 9000 UTF-16 code units
Ontology: continuity.work-state-ontology.v4 (the version of the verdict rules)
Task「parser: handle empty input」 (in_progress): verified; active claims 6: facts 4 (verified 4), open items 1, decisions 0, next actions 1; superseded 0; withdrawn 0 (facts 0)
Integrity: no inconsistency found (the event chain is valid, and every record of this Task recomputes and agrees with the ledger)
Sessions: 1 in all, 0 ended normally, 0 ended abandoned, 1 not ended, 0 declared a resumed_from Pack
Session 1 01a1dc2c-4168-7511-b3a3-220085a788c5 not ended (started 2026-11-14T22:27:30.280957974Z, last wrote to the ledger at 2026-11-14T22:27:30.825220406Z)
Last passed: 「python3 -m pytest -q」 (5ebf8ad58118; --test --test-format pytest)
Can: complete the Task (transitionTask; cites 1 passed runs of the current repository state; needs mcp-write)
Can: begin a new Session (createSession; first end it as the line of Session 1 says, or confirm it is still working; needs mcp-write)
Can: record a fact citing the latest run (createCheckpoint; needs mcp-write)
Base: 5ebf8ad58118f1d7bf8cf479951c3b36932f9676; files changed since: none
#5 open item: 「an unterminated section header such as [core is untested」 (no evidence cited; from Session 1)
#1 verified: 「parse() returns an empty document for empty or whitespace-only input」 (evidence bound to commit 5ebf8ad58118; from Session 1)
#2 verified: 「input of only comments is an empty document」 (evidence bound to commit 5ebf8ad58118; from Session 1)
#3 verified: 「keys before the first section header go into the default section」 (evidence bound to commit 5ebf8ad58118; from Session 1)
#4 verified: 「a duplicate key keeps the last value」 (evidence bound to commit 5ebf8ad58118; from Session 1)
#6 next action: 「write the unterminated-header test and run pytest」 (no evidence cited; from Session 1)
Omitted: level 0; bases with files only counted 0, bodies shortened 0, bodies omitted 0, folded into one line 0, not listed 0; full text of given numbers: memprove resume --pack --claims <numbers> (such as 3,5-7)
Digest: sha256:372b545739b632561676feda70b67506425a070275722cb3d026b26338b9551b

$ memprove end --reason "empty input handled"
{"agent_id":"01a1dc2c-4167-7cba-8d2f-d0c8cb0baebd","revision":2,"schema_version":1,"session_id":"01a1dc2c-4168-7511-b3a3-220085a788c5","state":"ended","workspace_id":"01a1dc2c-40dd-7160-b318-755b385c24a2"}
memprove: the Session was cleared from the current context

$ git commit -m "parser: strip inline comments from values"

$ memprove hook session-start   # run by Claude Code at the start of a session
memprove: below is the Context Pack of Task 01a1dc2c-4162-74e0-a5da-be1779c133a7 at SessionStart (startup), chosen from this directory's context. The current UTC time is 2026-11-14T22:27:31Z. Under the unattended takeover rule, this session records each Session it leaves open as an open item 「another Session (Session k) may still be working, not ended」 in its first checkpoint.
```text
Pack: memprove-pack v1; capsule_digest sha256:2440ba5f63482c175ae6825d8390ab85a93cc0edc88381bce7b682b6ca6b00f0; ledger sequence 8; probed HEAD 558cb252ae874fa54bdc721048ab8d042610a88e; budget 9000 UTF-16 code units
Ontology: continuity.work-state-ontology.v4 (the version of the verdict rules)
Task「parser: handle empty input」 (in_progress): stale (reasons: head_changed, index_changed, evidence_stale); active claims 6: facts 4 (stale 4), open items 1, decisions 0, next actions 1; superseded 0; withdrawn 0 (facts 0)
Integrity: no inconsistency found (the event chain is valid, and every record of this Task recomputes and agrees with the ledger)
Sessions: 1 in all, 1 ended normally, 0 ended abandoned, 0 not ended, 0 declared a resumed_from Pack
Session 1 01a1dc2c-4168-7511-b3a3-220085a788c5 ended normally (started 2026-11-14T22:27:30.280957974Z)
Cannot: complete the Task (transitionTask; reasons: evidence_stale; needs mcp-write)
Can: begin a new Session (createSession; needs mcp-write)
Can: record a fact citing the latest run (createCheckpoint; needs mcp-write)
Base: 5ebf8ad58118f1d7bf8cf479951c3b36932f9676; files changed since: 「tests/test_comments.py」, 「tinyini/__init__.py」
#5 open item: 「an unterminated section header such as [core is untested」 (no evidence cited; from Session 1)
#1 fact stale: 「parse() returns an empty document for empty or whitespace-only input」 (reasons: head_changed, index_changed, evidence_stale; evidence bound to commit 5ebf8ad58118; from Session 1)
#2 fact stale: 「input of only comments is an empty document」 (reasons: head_changed, index_changed, evidence_stale; evidence bound to commit 5ebf8ad58118; from Session 1)
#3 fact stale: 「keys before the first section header go into the default section」 (reasons: head_changed, index_changed, evidence_stale; evidence bound to commit 5ebf8ad58118; from Session 1)
#4 fact stale: 「a duplicate key keeps the last value」 (reasons: head_changed, index_changed, evidence_stale; evidence bound to commit 5ebf8ad58118; from Session 1)
Rerun: 「python3 -m pytest -q」 on the current HEAD (for #1, #2, #3, #4)
#6 next action: 「write the unterminated-header test and run pytest」 (no evidence cited; from Session 1)
Omitted: level 0; bases with files only counted 0, bodies shortened 0, bodies omitted 0, folded into one line 0, not listed 0; full text of given numbers: memprove resume --pack --claims <numbers> (such as 3,5-7)
Digest: sha256:210efbefabe5507a82157336d13976f119a58705799a9ff65205239a1bffe932
```

$ memprove begin
memprove: the Session declares that it resumes from the Pack of ledger sequence 8 (pack_digest sha256:210efbefabe5507a82157336d13976f119a58705799a9ff65205239a1bffe932)
{"schema_version":1,"workspace_root":"~/tinyini","project_id":"01a1dc2c-40dd-7160-b318-7542e520ca6e","workspace_id":"01a1dc2c-40dd-7160-b318-755b385c24a2","agent_id":"01a1dc2c-4167-7cba-8d2f-d0c8cb0baebd","task_id":"01a1dc2c-4162-74e0-a5da-be1779c133a7","task_title":"parser: handle empty input","task_revision":3,"task_state":"in_progress","session_id":"01a1dc2c-452f-7891-b208-8570b22d9fea","session_revision":1,"snapshot_id":null,"evidence_id":null,"evidence_status":null,"runs":[],"next_run":1}
memprove: Session started; current context written to ~/.memprove/config/contexts/44d77834010dab48a216a9deaf071bf532ac599b796b1a642592935beffdfcb8.json

$ memprove run --test-format pytest -- python3 -m pytest -q
..........                                                               [100%]
10 passed in 0.02s
memprove: "python3" exited with 0 (2026-11-14T22:27:31.256Z to 2026-11-14T22:27:31.549Z)
memprove: counted 10 test cases
{"artifact_type":"test_run","canonical_digest":"sha256:caa9b8692554cb35f40f19f260dbc6d56eafbee4501760a48a6e84b3b197385d","collector":{"name":"memprove-cli","version":"0.1.0"},"evidence_id":"01a1dc2c-465f-7452-ace1-5249a5819346","id":"01a1dc2c-465f-7452-ace1-5249a5819346","kind":"test_result","observed_at":"2026-11-14T22:27:31.549Z","payload":{"argv":["python3","-m","pytest","-q"],"exit_code":0,"finished_at":"2026-11-14T22:27:31.549Z","passed":true,"started_at":"2026-11-14T22:27:31.256Z","stderr":"","stderr_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","stdout":"..........                                                               [100%]\n10 passed in 0.02s\n","stdout_sha256":"4c78d91fecebd9ff2601366b209fedef5a34c74b72f29aadd727b0d4e4957970","test_case_count":10,"test_format":"pytest","type":"test_result"},"payload_digest":"sha256:e69ce33c165aac0b1e4325c616d5dd639d60a5cbca0191cf44e4aced735f6286","revision":1,"schema_version":1,"snapshot":{"branch":"main","canonical_digest":"sha256:178a1549ae50b7d3a8ae06638f18e92773af221d62f2a156fb9a989ed4f3596b","capture_manifest_digest":"sha256:8f9b3d2a9676dfc59cf1b8fe8e50524ca6d36e0c8362a9b3dbe8a48b62e6fd9c","captured_at":"2026-11-14T22:27:31.551942635Z","command_evidence_ids":[],"git_head_evidence_id":"01a1dc2c-465f-7452-ace1-52583276fc63","git_worktree_evidence_id":"01a1dc2c-465f-7452-ace1-526e956906e9","head_oid":"558cb252ae874fa54bdc721048ab8d042610a88e","is_dirty":false,"snapshot_id":"01a1dc2c-465d-7d89-bc7d-4b003921b1c8","task_id":"01a1dc2c-4162-74e0-a5da-be1779c133a7","test_evidence_ids":["01a1dc2c-465f-7452-ace1-5249a5819346"],"workspace_id":"01a1dc2c-40dd-7160-b318-755b385c24a2"},"snapshot_ids":["01a1dc2c-465d-7d89-bc7d-4b003921b1c8"],"status":"PASS","subject":{"test_name":"python3 -m pytest -q","type":"test_result"},"task_id":"01a1dc2c-4162-74e0-a5da-be1779c133a7","workspace_id":"01a1dc2c-40dd-7160-b318-755b385c24a2"}
memprove: runs of this repository state you can cite with --evidence <LABEL>: r1 (PASS)
memprove: recorded PASS, 10 test cases, evidence_id 01a1dc2c-465f-7452-ace1-5249a5819346, label r1

$ memprove checkpoint create --reaffirm-all --next "write the unterminated-header test and run pytest"
{"checkpoint_id":"01a1dc2c-46ff-75f2-838a-9ab2fdd33cd0","created_at":"2026-11-14T22:27:31.711334172Z","digest":"sha256:d48ccc98cce6724cfa7f49f93c710f2b4cf67bd8d4bb2dd2ee3df30d999ac586","id":"01a1dc2c-46ff-75f2-838a-9ab2fdd33cd0","next_action":{"classification":"recommendation","evidence_ids":[],"text":"write the unterminated-header test and run pytest"},"objective":"parser: handle empty input","open_items":[],"project_event_sequence":12,"revision":1,"schema_version":1,"session_id":"01a1dc2c-452f-7891-b208-8570b22d9fea","snapshot":{"branch":"main","canonical_digest":"sha256:178a1549ae50b7d3a8ae06638f18e92773af221d62f2a156fb9a989ed4f3596b","capture_manifest_digest":"sha256:8f9b3d2a9676dfc59cf1b8fe8e50524ca6d36e0c8362a9b3dbe8a48b62e6fd9c","captured_at":"2026-11-14T22:27:31.551942635Z","command_evidence_ids":[],"git_head_evidence_id":"01a1dc2c-465f-7452-ace1-52583276fc63","git_worktree_evidence_id":"01a1dc2c-465f-7452-ace1-526e956906e9","head_oid":"558cb252ae874fa54bdc721048ab8d042610a88e","is_dirty":false,"snapshot_id":"01a1dc2c-465d-7d89-bc7d-4b003921b1c8","task_id":"01a1dc2c-4162-74e0-a5da-be1779c133a7","test_evidence_ids":["01a1dc2c-465f-7452-ace1-5249a5819346"],"workspace_id":"01a1dc2c-40dd-7160-b318-755b385c24a2"},"snapshot_id":"01a1dc2c-465d-7d89-bc7d-4b003921b1c8","task_id":"01a1dc2c-4162-74e0-a5da-be1779c133a7","verified_completed_work":[{"classification":"verified_fact","evidence_ids":["01a1dc2c-465f-7452-ace1-5249a5819346"],"text":"parse() returns an empty document for empty or whitespace-only input"},{"classification":"verified_fact","evidence_ids":["01a1dc2c-465f-7452-ace1-5249a5819346"],"text":"input of only comments is an empty document"},{"classification":"verified_fact","evidence_ids":["01a1dc2c-465f-7452-ace1-5249a5819346"],"text":"keys before the first section header go into the default section"},{"classification":"verified_fact","evidence_ids":["01a1dc2c-465f-7452-ace1-5249a5819346"],"text":"a duplicate key keeps the last value"}],"workspace_id":"01a1dc2c-40dd-7160-b318-755b385c24a2"}
memprove: new statements: #7 fact, #8 fact, #9 fact, #10 fact, #11 next_action
memprove: superseded: #6 by #11
memprove: withdrawn: none
memprove: reaffirmed: #1 as #7, #2 as #8, #3 as #9, #4 as #10
memprove: reaffirm-all reaffirmed: #1 (r1) as #7: verified, #2 (r1) as #8: verified, #3 (r1) as #9: verified, #4 (r1) as #10: verified
memprove: reaffirm-all skipped: none
memprove: reaffirmed 4 (verified 4), skipped 0

$ memprove resume --pack
Pack: memprove-pack v1; capsule_digest sha256:9414b8d8e72952af25d7789995690c6d062a89c9a9ff0037dbb41dbc30783c23; ledger sequence 17; probed HEAD 558cb252ae874fa54bdc721048ab8d042610a88e; budget 9000 UTF-16 code units
Ontology: continuity.work-state-ontology.v4 (the version of the verdict rules)
Task「parser: handle empty input」 (in_progress): verified; active claims 6: facts 4 (verified 4), open items 1, decisions 0, next actions 1; superseded 5; withdrawn 0 (facts 0)
Integrity: no inconsistency found (the event chain is valid, and every record of this Task recomputes and agrees with the ledger)
Sessions: 2 in all, 1 ended normally, 0 ended abandoned, 1 not ended, 1 declared a resumed_from Pack
Session 1 01a1dc2c-4168-7511-b3a3-220085a788c5 ended normally (started 2026-11-14T22:27:30.280957974Z)
Session 2 01a1dc2c-452f-7891-b208-8570b22d9fea not ended (started 2026-11-14T22:27:31.247079849Z, last wrote to the ledger at 2026-11-14T22:27:31.711334172Z); continues from the Pack at ledger sequence 8 (self-reported, digest sha256:210efbefabe5)
Last passed: 「python3 -m pytest -q」 (558cb252ae87; --test --test-format pytest)
Can: complete the Task (transitionTask; cites 1 passed runs of the current repository state; needs mcp-write)
Can: begin a new Session (createSession; first end it as the line of Session 2 says, or confirm it is still working; needs mcp-write)
Can: record a fact citing the latest run (createCheckpoint; needs mcp-write)
Base: 558cb252ae874fa54bdc721048ab8d042610a88e; files changed since: none
#5 open item: 「an unterminated section header such as [core is untested」 (no evidence cited; from Session 1)
#7 verified: 「parse() returns an empty document for empty or whitespace-only input」 (evidence bound to commit 558cb252ae87; from Session 2)
#8 verified: 「input of only comments is an empty document」 (evidence bound to commit 558cb252ae87; from Session 2)
#9 verified: 「keys before the first section header go into the default section」 (evidence bound to commit 558cb252ae87; from Session 2)
#10 verified: 「a duplicate key keeps the last value」 (evidence bound to commit 558cb252ae87; from Session 2)
#11 next action: 「write the unterminated-header test and run pytest」 (no evidence cited; from Session 2)
Superseded and withdrawn: superseded 5: #1–#4, #6; withdrawn 0 (not listed one by one)
Omitted: level 0; bases with files only counted 0, bodies shortened 0, bodies omitted 0, folded into one line 0, not listed 0; full text of given numbers: memprove resume --pack --claims <numbers> (such as 3,5-7)
Digest: sha256:0383a5b2867948164753057d6f578f65fb38fe6cfb722797a64cf7c49d59f17d

$ memprove ledger verify
{"checked":{"checkpoints":2,"events":17,"evidence":6,"snapshots":2,"tasks":1},"first_failure":null,"head_event_sha256":"sha256:1e58ce6d8ca5bf6d850e018529699268a7f608c5293299b639921df7de83b453","project_id":"01a1dc2c-40dd-7160-b318-7542e520ca6e","project_sequence":17,"schema_version":1,"valid":true}

$ memprove end --reason "facts reaffirmed after the inline-comment change"
{"agent_id":"01a1dc2c-4167-7cba-8d2f-d0c8cb0baebd","revision":2,"schema_version":1,"session_id":"01a1dc2c-452f-7891-b208-8570b22d9fea","state":"ended","workspace_id":"01a1dc2c-40dd-7160-b318-755b385c24a2"}
memprove: the Session was cleared from the current context
````

</details>

## How it is built


One page on how memprove is built. The agent's manual is `docs/guide.md`.

### The parts

```text
 Claude Code / Codex / Cursor                shell
 ┌──────────────────────────────┐    ┌──────────────────────────┐
 │ SessionStart hook  MCP tools │    │ memprove begin / run / │
 │ (Context Pack)     (10)      │    │ checkpoint / end / …     │
 └──────────────┬───────────────┘    └─────────────┬────────────┘
                │ HTTP/1.1 over a Unix socket (mode 0600), bearer token
                ▼
        memproved  (one process per data directory, file lock)
        ┌─────────────────────────────────────────────────────┐
        │ registry.sqlite: Projects, Workspaces, tokens        │
        │ projects/<id>/state.sqlite: the Project's ledger     │
        └─────────────────────────────────────────────────────┘
```

- **`memproved`** (`crates/memprove-daemon`) is a foreground process
  that serves the control API (`openapi/control-api-v1.yaml`) on
  `<data dir>/memproved.sock`. There is no TCP listener and no network
  access. A token is a 32-byte random secret; the daemon stores only its
  SHA-256 digest and its scopes (`control` for the operator, `mcp-read` and
  `mcp-write` for agents).
- **`memprove`** (`crates/memprove-cli`) is the client: the shell
  commands an agent runs, the SessionStart hook for Claude Code, the MCP
  server (`memprove mcp serve`, stdio), `init`, `doctor`, `backup` and
  `restore`. It keeps one small context file per Workspace (and per client
  session) outside the repository: Project, Workspace, Task, Session.
- **The store** (`crates/memprove-store-sqlite`) owns the SQLite schema
  and its append-only migrations; **the engine** and **the domain**
  (`memprove-engine`, `memprove-domain`) hold the rules; **the
  protocol** (`memprove-protocol`) holds the wire types and their strict
  JSON.

### The ledger

Every write to a Project is an Event in a hash chain: each Event carries the
digest of the one before it, and every record it names (Task, Session,
Checkpoint, Snapshot, Evidence) has its own canonical-JSON digest. `ledger
verify` recomputes all of it and reports the first record that does not
hold (exit 5), so a change made behind the daemon's back is found. A Task
whose records do not hold is `tampered`: none of its documents then says
`verified`.

Statements are claims of a Checkpoint: facts, open items, decisions and the
one next action in force. A later claim supersedes or withdraws an earlier
one; nothing is deleted, and `resume --text --all` still lists what was
replaced. Sessions declare which agent (name, role, parent Session) wrote
each claim, and who ended a Session that was left open.

### Evidence and verdicts

`memprove run -- <command>` runs the command in the caller's shell,
records its result (and, for a test run, how many cases passed) and seals a
Snapshot of the repository state: HEAD, branch, index, tracked-file digests,
untracked files. A fact cites such runs. Reading a Task, the daemon probes
the repository again and gives every fact one of four verdicts by
deterministic rules, never by a model:

| verdict | meaning |
|---|---|
| `verified` | the cited test run passed and the repository is still in the state it ran in |
| `stale` | the state moved on (a commit, an edit, a branch) since the run |
| `partial` (`unconfirmed`) | something needed for the judgement is missing: no passed test, untracked files, a failed later run of the same command |
| `tampered` | a record of the Task does not recompute against the ledger |

The reason codes (`head_changed`, `worktree_changed`, `evidence_not_passed`,
`object_digest_mismatch`, …) say why. A Task is completed only on passed runs
of the current state; `transitionTask` to `completed` refuses otherwise.

### The Context Pack

`resume --pack` renders a Task's Capsule as a fixed set of English lines
(`Pack:`, `Task`, `Integrity:`, `Sessions:`, `Session k …`, `Last passed:`,
`Can:`/`Cannot:`, `Base:`, the claim lines, `Rerun:`, `Omitted:`, `Digest:`)
within a budget of UTF-16 code units (9000 by default), leaving things out
in five deterministic levels when it does not fit, and ends with the SHA-256
of itself. The same ledger, probe and budget give the same bytes. A `query`
selects the statements that match a phrase (an in-memory FTS5 index), best
first.

### The hook and the MCP server

Claude Code's **SessionStart hook** (`memprove hook session-start`) prints
one opening statement (the Task, the current UTC time, the agent the
environment declares, the takeover rule's open item) and the Pack in a
fence, within 8 seconds, and always exits 0; it only reads. The **MCP
server** gives every client ten tools: `pack`, `claims`, `context`, `tasks`
and `ledger_verify` read, `begin`, `checkpoint`, `decide`, `end` and
`complete` write. No tool runs a command: tests are recorded with
`memprove run` in a shell and cited by their labels (`r1`, `r2`, …), so
every piece of Evidence comes from a real run.

### Taking over

A new session reads the Pack first. A Session another agent left open is
ended as abandoned only under the unattended takeover rule (its agent is not
you, it looks gone for 15 minutes or its dispatcher has ended, and it is not
your own parent); otherwise it stays open and an open item records it. The
takeover evaluation (`docs/takeover-eval.md`) runs a real client through six
such scenarios and scores the archive deterministically.

