Repository navigation
feat(memories): occurredAt, when a Memory's event happened - #163
Conversation
The harness's default judge is now GPT-5.6 Luna through the logged-in Codex CLI, at the same low reasoning effort as Sol. The Sol profile stays, because stored runs and verdicts name it; a profile's identity is part of every cache key, so Luna re-grades stored answers without new reader calls. Re-grading the 500-question LongMemEval-S runs of 2026-10-22 with Luna kept their comparison's verdict (improved, p = 0.0023, against p = 0.0014 with Sol). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Memory may now carry occurredAt: when the event or statement it records happened, as its writer states it. It is not createdAt or updatedAt, which for imported or late-written history only say when Lore stored the row. The writer supplies it as an RFC 3339 date-time with an offset; Lore keeps the UTC instant to the millisecond, never infers it from content, never chunks, embeds, or ranks it, and returns it with every Memory: list, detail, search hit, context packet, and archive. - Core: validateMemoryOccurredAt (UTC years 0001-9999), the column in every write and read, and an update that compares the instant, so the same time with another offset writes nothing. - Schema revision 18 adds memories.occurred_at, its Proposal columns, NOT VALID CHECKs, the outbox field, the review guard, a 19-argument lore.submit_memory_proposal, and the memoryOccurredAt capability; revision 19 validates the CHECKs. compatible_from stays 9. - HTTP, OpenAPI, SDK, CLI (--occurred-at, --no-occurred-at), MCP (write tools ask for it; search results return it), Proposals, and the web editor, Memory detail, and Proposal review. - Workspace archives move to lore-workspace-v4, which carries occurredAt; import still accepts v1 to v3. The per-Memory export allowance grows from 256 to 320 bytes. - The eval harness writes each LongMemEval-S and LoCoMo session's own time as occurredAt and shows it to the reader on each evidence tag. Why: on 500 LongMemEval-S questions, only about a fifth of reader passages carried their session's date, because it is written once at the top of a session. Showing each passage's time raised answers from 57.0% to 60.4% (23 fixed, 6 broken, p = 0.0023), temporal-reasoning from 47.2% to 57.5%. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bun audit now flags next 16.0.0 through 16.4.0: two unauthenticated remote code execution advisories (GHSA-2464-fhr4-w9xw in image optimization with SVG files, GHSA-673q-mjm6-r5xr in `use cache` functions returning typed arrays) and one denial of service in Server Actions (GHSA-p3m5-59mp-jv5r). Next 16.4.2 fixes all three. The pinned @opennextjs/cloudflare 1.20.8 accepts next >= 16.3.8, so Wrangler stays as it is. typecheck, next build, smoke:next (401 for an unauthenticated GET /), the edge middleware entry, the OpenNext build, and wrangler deploy --dry-run pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…vision 18 A keyed create or update replays the body its first attempt stored. A body an instance of revision 17 stored has no occurredAt, so during the 24-hour ledger window after the upgrade a retry would answer a Memory without the field the contract now requires. The Memory service answers such a replay with occurredAt null, which is what that Memory had; a body that states the field is returned unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Approved at 2690bb8 after three review rounds.
Reviewed the diff from merge base f7abe1a, including timestamp validation and mutation semantics, migration and Proposal guards, RLS visibility, archive v4, public clients, browser fields, and evaluation plumbing. The legacy retry-body finding is fixed for both create and update. I independently reproduced that issue with PGlite and ran the focused regression after the fix. No findings remain.
All required checks passed on this exact head: static, tests, database, packages, build, and the aggregate check. CI evidence: https://github.com/corespeed-io/lore/actions/runs/39890131828. The Next.js dependency fix clears the previously failing audit.
The PR records the product trade-offs and reports the standard evaluation gate as no-evidence. Its full evaluation is still pending as stated; this technical approval does not claim a measured quality improvement.
Reviewer session: f2ae8d47-9773-4c4a-a4ce-3a8f772bc656.
What changes
A Memory may now carry
occurredAt: when the event or statement it records happened, as its writer states it. This is notcreatedAtorupdatedAt. Those only say when Lore stored or changed the row, and for imported or late-written history they equal the import or writing time.2026-10-20T15:30:00-07:00. Lore keeps the UTC instant to the millisecond. The instant must fall in the UTC years 0001 to 9999.Surfaces:
validateMemoryOccurredAt,MEMORY_OCCURRED_AT_LIMITS, the column in every write and readmemories.occurred_at, the Proposal columns, NOT VALID CHECKs, the outbox field, the review guard, a 19-argumentlore.submit_memory_proposal, thememoryOccurredAtcapability. Revision 19: validate the CHECKs.compatible_fromstays 9.occurredAton Memory create, update, output, Proposals, and context packets--occurred-at TIME, and--no-occurred-aton update and propose updateoccurredAt; search results and context packets return itlore-workspace-v4, which carriesoccurredAt; import still accepts v1 to v3occurredAtand shows it to the reader as anoccurred-atattribute on each evidence tagThe first commit is a separate harness change: the default eval judge is now
gpt-5.6-luna.gpt-5.6-solstays as a profile, because stored verdicts name it.A review fix makes keyed retries safe across the upgrade. A create or update response stored by revision 17 has no
occurredAt, so its replay now answersoccurredAt: null(replayedMemory).A dependency commit patches
nextfrom ^16.3.8 to ^16.4.2.bun auditbegan failing CI on new advisories: two critical remote code execution advisories and one high denial of service, covering 16.0.0 through 16.4.0. The installed@opennextjs/cloudflare1.20.8 already accepts it, so Wrangler is unchanged. The Next build, the 401 smoke test, the edge middleware entry, the OpenNext build, andwrangler deploy --dry-runall pass.Why
On LongMemEval-S, the harness writes a session's date once, at the top of its first chunk. On main, only about 22% of the passages the reader sees carry a date. The reader then cannot order events, or picks the only dated value, which is often the older one.
Before building this, I measured the effect with a harness-only stand-in that put each passage's session time in front of it. The test used all 500 questions, the same retrieval, and the reader
gemini-3.5-flash.Under both judges the verdict was improved with no category regression. temporal-reasoning went from 47.2% to 57.5% (14 fixed, 1 broken).
Eval gate (this PR, real product path, Luna judge)
bun run eval gate --base origin/main --dataset locomo,longmemeval-s --tier standard: no-evidence on both.LongMemEval-S by category: knowledge-update +15.0 (3 / 0), temporal-reasoning +10.0 (2 / 0), preference −5.0 (1 / 2). LoCoMo turns already start with
DATE:, so no change was expected there. Retrieval is unchanged, and there were no isolation failures. Reader input grows by about 7% on LongMemEval-S and 29% on LoCoMo, because of the extra attribute.The standard tier is too small for this effect; the stand-in also showed no evidence at standard (p = 0.23).
The full 500-question gate passes: improved. I ran
bun run eval gate --base origin/main --dataset longmemeval-s --tier fullwith the Luna judge, on this branch's product path at43bc345. The commits after it change only thenextversion and replay handling, not search or the reader.The overall gain is +3.6 points, 95% interval [+1.6, +5.8], McNemar p = 0.00091, with no category regression. Retrieval is unchanged at 97.9% all-gold, and there were no isolation failures. Reader input grows from 2,538 to 2,854 tokens per question (+12%).
Decisions for the PM
valid_from/valid_until) or Memory revision history ("what did Lore believe on date X"). The experiment did not test those, and the second needs a decision on whether edited or deleted content is retained.updated_at. Filters such asoccurredAfterwould need a newlore.lexical_candidatessignature, so they are left for a follow-up.occurredAtwhenever content records an event. This adds a small token cost per write, and search output grows by one field per result.Verification
design:check,typecheck,lint,test(1,537 tests),build,packages:smoke,smoke:next, Cloudflare build, andwrangler deploy --dry-runall pass. One exception:tests/modules/code/code-index.test.ts› "accounts for every Git tree entry" fails here and on main in this sandbox, because its global gitignore hides the fixture'sdist/.🤖 Generated with Claude Code