An EvidenceAtom could say what was remembered and nothing else: not whose memory it
is, not where it came from, not when it was true, not in what situation it may be
used. The baseline measured the cost of that: the same ranker scores recall@10 0.412
over the whole corpus and 0.866 inside one holder's memory, so the missing holder
column is worth more than any ranking change in the ablation.
- schemas.py: MemoryOrigin, TimePrecision, Visibility; EvidenceAtom gains holder_id,
origin, scenario, event_time / event_time_end / event_time_precision /
event_time_source, valid_from / valid_to, supersedes, visibility, retention_until.
`time` is renamed `ingested_at` — ingestion time gets one name, not two.
A validator refuses valid_to before valid_from, event_time_end before event_time,
and an atom that supersedes itself.
- migrations.py: schema v2 with PRAGMA user_version. A first-version database is
upgraded in place (ALTER + RENAME COLUMN), rows intact, and a database newer than
this build refuses to open instead of being silently downgraded.
- v_atoms_current hides memories that are closed, lapsed or not yet begun; unknown
bounds read as visible. Time travel is db.atoms_as_of(moment, holder) because a
SQLite view cannot take a parameter.
- evaluation_schemas.py: the proof and benchmark report shapes move out of schemas.py
to keep it under the file-size limit; importers follow.
- backend/tests/test_memory_schema.py: 8 tests, including the v1 -> v2 upgrade and the
validity timeline.
Verification of record: pytest backend/tests bench/tests -q -> 70 passed; ruff check on
every touched file -> all checks passed; mypy (fresh cache) on db.py, migrations.py,
schemas.py, evaluation_schemas.py and backend/tests -> no issues. evals.py carries 40
pre-existing mypy errors at HEAD and 40 after this change (measured on both copies);
its pyright findings are the same debt, and p0-6 rewrites that file. `peft` and
`pyarrow.parquet` "could not be resolved" is the documented pyright artifact: pi-lens
runs it with 0 config files, and both import at runtime.
Three defects made the external baseline numbers and labels wrong.
- bench/systems.py: _sentence_model rebuilt the model on every query, so dense arm
latency was mostly model loading and the latency-vs-size curve was flat because a
constant dominated it. Models are now cached per process.
- bench/counterpart.py: visible() and search() walked 404k Python objects per query.
Per-item holder/source/time columns are built once and the query path is numpy;
12.5 queries/s instead of 0.7.
- bench/metrics.py: the aggregate key was named wrong_holder_confidence_unanswerable
while it counts top hits that ARE from the asking holder. BASELINE.md read that
backwards. Renamed to top_hit_from_holder_unanswerable and the sentence rewritten.
- bench/run_external.py: the by-modality table read row['answerable'] but aggregate
emits n_answerable, so the writer died after scoring finished. Each store is now
scored twice, [holder scope] and [whole corpus], so the gain from the holder
boundary is separable from the gain from ranking; progress lines every 500 queries.
Verification: pytest bench/tests -q -> 61 passed; ruff check bench/ -> all checks
passed; mypy (fresh cache) -> no issues in 25 source files.
Latency measured while another job hogs the cores is not comparable: the scale curve
moved from 1.315s to 0.809s at 50,000 sources between two runs of the same code.
- bench/systems.py: machine_facts() reports cpus plus the 1/5/15 minute load average.
- bench/run.py, bench/run_external.py: both reports carry it, and BASELINE.md labels
the latency table a ceiling when the machine was busy.
Verification: pytest bench/tests -q -> 60 passed; ruff check bench/ -> all checks
passed; mypy (fresh cache) -> no issues in 25 source files.
- bench/ingest_baseline.py drives the unmodified product IngestionService over
all 50,789 corpus files in sharded SQLite DBs, then merges them. Thread and
memory ceilings are set explicitly: ONNX Runtime defaulted to every core
(95 threads/worker, load 490) and its CPU arena kept freed pages mapped,
which OOM-killed workers.
- bench/metrics.py: recall@k, MRR, nDCG@10, Wilson lower bound, chance
baseline. No model, no product import, so the scorer cannot grade itself.
- bench/systems.py + bench/run.py: the five ablation arms over the merged DB,
latency percentiles, scale curve, and a top-10 equivalence check against the
live RetrievalService.
- bench/counterpart.py + bench/run_external.py: in-repo Mem0-style flat store
and Zep-style temporal store as the external reference point.
- bench/audit.py: enforces single-attempt scoring, frozen n, a chance row
beside every score, and producer != verifier. It already flags the product's
retry-until-target loop at backend/src/ely_eye/evals.py:2351.
- backend/pyproject.toml: rapidocr-onnxruntime>=1.4.4 was unsatisfiable
(PyPI maxes at 1.2.3); the successor is the renamed rapidocr package.