Repository navigation
Phase 8: episodes behind facts - #24
Conversation
capture takes an optional episode key, and recall takes episodes: cited or search. The block shows a same-namespace source as key@revision. The plan records the owner's decisions of task 1 and the budgets of task 3.
capture takes an episode key: remember writes the facts, each citing the episode, and then one put writes the episode. A capture of an unchanged episode calls no extractor. recall takes episodes, cited or search, and adds whole episodes after the facts inside the budget. The block shows a same-namespace source as key@revision. Conformance tests run on both drivers, and the ledger, skill commands, and changelog follow.
The runner gains --session-episodes, which captures each session under the key session-<hash> with no session:<hash> source on the facts, and --episodes, the recall value. Each result row lists the episode keys of its block. The diagnosis replays capture the same way and records, for each miss, whether the block held an evidence session as an episode.
docs/benchmarks.md reports each episodes value and budget on sample A, the best value on sample B, the cost of each run, and the diagnosis. search at 64,000 characters scored 45 of 54 on both samples, against 29 and 31 with facts alone, at about 12,800 reader tokens. The README gets the row, and the contract keeps the budget note beside its default.
| continue | ||
| revision = int(source.rpartition("@")[2]) | ||
| found = self._store.get(namespace, key, revision) | ||
| if found is not None and found.kind == "episode": |
There was a problem hiding this comment.
Blocking: _cited can put an episode that is past its forget_after into the block. Skip such a record with is_forgotten from hotmemory._rules:
| if found is not None and found.kind == "episode": | |
| if ( | |
| found is not None | |
| and found.kind == "episode" | |
| and not is_forgotten(found, self._clock()) | |
| ): |
Store.get does not hide a record that is past its forget_after. list and search do hide it (docs/guarantees.md line 49).
Failure scenario: a consumer stores an episode with forget_after set to a past time, and sweep has not run yet. A fact cites that episode. recall(..., episodes="search") leaves the episode out, but recall(..., episodes="cited") shows its full content. Retention data then reaches the prompt of an agent.
Do not use is_listed here. A cited revision can be superseded on purpose, and is_listed drops a superseded revision. Add a conformance test for the forgotten case. State the rule in the "cited" bullet of docs/contracts.md.
There was a problem hiding this comment.
Review
Blocking Issues
src/hotmemory/contract.py:244:Memory._citedreads each cited episode withStore.get.Store.getdoes not hide a record that is past itsforget_after. Thusrecall(..., episodes="cited")shows an episode thatlistandsearchhide.episodes="search"leaves the same episode out.
Action Required
- In
_cited, skip a record whenis_forgotten(found, self._clock())is true. Keep superseded revisions. - Add a conformance test for a cited episode that is past its
forget_after. - State the rule in the
"cited"bullet ofdocs/contracts.mdand indocs/guarantees.md.
recall with episodes="cited" read each episode with get, which does not hide a record past its forget_after. The block could then show an episode that search hides. _cited now skips such an episode, and keeps a superseded revision, which the fact cites on purpose.
* Close phase 8 and plan phases 9 to 11 The roadmap closes phase 8 with PR #24 and adds three phases: faster evaluation, the ranking with episode chunks, and a large memory store. The later phases move to 12 to 15. plan.md holds the tasks of phases 9 and 10, and the parts of phase 11 discussed so far. * Correct the phase 8 run times in the plan The seven phase 8 runs on sample A took 1,133 to 2,085 seconds, not 1,145 to 1,507. Both copies now name duration_s in the result files as the source.
This PR adds episodes behind facts.
capturecan store the text it reads as an episode, andrecallcan add episodes to the block after the facts. On the benchmark, the memory arm goes from 29 to 45 of 54 on sample A, and from 31 to 45 on sample B. Closes #22.How
Memory.capturetakes an optionalepisodekey.rememberwrites the facts, each with the episode id insources, and then oneputwrites the episode. A capture of an unchanged episode calls no extractor and writes nothing.Memory.recalltakesepisodes:"cited"(the episodes that the facts of the block cite) or"search"(a second search, over the episodes). The facts take the budget first. Whole episodes then fill what is left, under a lineepisodes:, each with a header## <key>@<revision> [observed: <date>].<key>@<revision>. The stored source keeps the full id. So the run id of the benchmark never reaches a prompt, and runs after the first replaycapturefrom the extractor cache.bench/e2e.pygains--session-episodes(episode keysession-<hash>, nosession:<hash>source on facts) and--episodes. Each result row lists the episode keys of its block.bench/diagnose.pyrecords whether the block of each miss held an evidence session.Storemethod and no new record field.One change from the agreed proposal: it said one writer for the facts and the episode. A flush of
HotdataStoreloads its tables at the same time, so the episode could land without the facts, and a retry would then skip the extractor. Two flushes in a fixed order, as inload, avoid that.docs/internal/plan.mdrecords it.Why
After phase 7, the largest groups of misses were "no stored fact held the answer" (16 on A, 14 on B) and "the block held some needed facts" (6 and 7). The extractor drops details, and nothing kept the session text. The retrieval arm showed that whole sessions answer 45 of 54.
Results
All on the local stack, GPT-4o reader and judge,
--extractor detail. Full tables are indocs/benchmarks.md.none(facts only), 16,000cited, 16,000 / 32,000 / 64,000search, 16,000 / 32,000 / 64,000The diagnosis of
searchat 64,000: misses with no stored fact went from 17 to 5 on each sample. In 8 of the 9 misses on each sample, the block held an evidence session. The runs and diagnoses cost at least $8.10.Verification
make verify: 236 passed.make integration: 117 passed onHotdataStore, run at c501fc9. The later commits touch onlybench/and docs.citedandsearchblocks against a pinned string, the budget rule, andas_ofon episodes.bench/tests: the new options, the episode key, and a check that two runs with different run ids send the same extractor prompts.recallwith episodes in the cloud.Notes
MemoryStore.