diff --git a/docs/coding-agent/plans/completed/v0-2-time-plan.md b/docs/coding-agent/plans/completed/v0-2-time-plan.md new file mode 100644 index 00000000..8db5d1dd --- /dev/null +++ b/docs/coding-agent/plans/completed/v0-2-time-plan.md @@ -0,0 +1,334 @@ +# Plan: the moment itself brings memories to mind: what just happened, the dates a question names, and this day in earlier years + +- status: complete +- generated: 2026-09-21 +- last_updated: 2026-09-22 +- work_type: code + +## Goal +- A character arrives at a moment already holding what it did lately, so in the evening it can say how its day went without anyone supplying a topic that happens to match. When a question names a span of time ("last Tuesday", "in March"), what happened then comes to mind even if its words share nothing with the question. On the day something happened in an earlier year, that experience comes back, and is sure of its place when the person it was shared with is present. And when the character recalls something it believes, it knows how long ago that belief was last supported by anything it lived through, so an old belief is held as old and never dropped for being old. + +## Definition of Done +- Recency is a road that contributes a few experiences and promises which are contributed, not which survive. It works as a description does since the scene-words slice: every retrieval contributes the latest episodes at or before the scene's reference time, by recorded scene time, as many as the largest cap in force could hold, as explicit roots of the cue kind recency, so the room nothing else claims fills with what happened lately; the rest of what is recent is not a candidate. A contributed occasion enters with no similarity score of its own (a cue component of zero) and reminder-only standing; at root selection it receives exactly its floor reservation, takes no spare turn, and is admitted otherwise only into room left after every given kind's queue is exhausted, so at a floor of zero with the roots full it takes no root at all; everywhere else it competes in the existing score order, so a real match wins by itself and only a faint or a distant one (a low similarity, or a far hop with a small inherited share) can lose to it. With a scene and no topic, what it contributes and what rests on those occasions is what comes. Its floor ships at zero and the calibration may raise it; at zero it still contributes its single latest occasion. +- A retrieval can carry a time range beside the topic, as it carries the activity: two instants the application gives, echoed in the result. It is a given cue and never a filter: the most recent episodes whose recorded scene time lies inside it, as many as the larger of one and the date-match floor, are contributed as explicit roots of the cue kind date match, hold that floor at root selection and the section caps, take no spare turn at roots (no time kind does) and otherwise enter only room left after every given kind's queue is exhausted, the rest inside the range are not a candidate, and the trace says whether more existed than came, and everything any other cue brings is admitted exactly as before. The library parses no language and calls no model. +- The same calendar day in an earlier year brings its episodes as date match. The calendar day is the day where the experience happened, read from the offset the application gave. Such an occasion is contributed the same way, the most recent first; it holds the date-match floor only when it is shared with a notion the present scene resolves, and otherwise enters with no reserved room, as recency does. +- The own-day scenario passes in both forms a deployment produces (arriving with no topic, by recency; being asked about the day, by the range the application gives), and an episode from another day is shown not to come by the range. +- Time never changes a score. Pack order is the existing combined score everywhere; what this plan promises is which memories are contributed. A memory also found by another cue keeps exactly the score (cue and graph components alike), standing and position it would have without the time cue: being a time root raises nothing that a full-standing road reached. A memory reached only by a time cue is a reminder: it brings what rests on that occasion and does not open a notion's or a thread's history. A time kind names direct contributions only: the contributed occasions and what rests on them within the leaf rule; provenance inherited through an interpreted memory to another episode (an occasion, a memory derived from it, and an older episode that memory also rests on) does not carry the time kind, which is the leaf rule applied to provenance, so an episode outside the contributed set is never named recency or date match. +- Recency and date match join the cue kinds in the trace and the values in `context.cue_floors`, through the one existing helper. The phase draft's "temporal rationale category" is delivered as these two cue kinds: the rationale enumeration it named no longer exists, and cue kinds in the trace replaced it. +- Every admitted interpreted memory reports, in the result and with the trace off, the age in whole seconds between the scene's reference time and the latest experience that supports it. Nothing is removed, omitted or reordered because of it (ADR-D-0018). +- No ranking weight for elapsed time ships. Whether one helps is a measurement for the companion repository once these roads exist, and it becomes a default only through the replacement of ADR-I-0022 that carries its numbers. +- Retrieval reads no clock: the reference scene is captured once and bound into every read, and the same store, scene and input give the same result on any day. + +## Planner-added requirements +- A floor guarantees room only for a cue the scene or the application gave. Needed because: the time alone is on every scene, so it is not a cue anyone gave, and a floor for it would take room on every retrieval merely by existing. Recency therefore ships with a floor of zero: it contributes its single latest occasion with no reserved room, and that occasion competes by score alone. A range is given by the application, so it holds a floor. Neither time kind takes a spare turn at root selection: a kind present takes spare turns today whatever its floor, so a zero-floor recency root would still take a root slot from a strong topic root; the helper's spare-turn rounds therefore skip the time kinds, which receive exactly their floor reservation and then only room left after every given kind's queue is exhausted. This is decision 39 made concrete at the only stage that uses turns. +- An anniversary holds reserved room only when it is shared with a notion the present scene resolves; otherwise it is contributed with no reserved room, like recency. The floor is served only from the reserved queue: a shared anniversary is contributed with the date-match kind and floor eligibility; an unshared one bears the kind on the root, for the trace and the result, but the helper's date-match reservation queue does not contain it, so it competes only for unclaimed room. Omitting it from the date-match order is not enough, since the helper queues every candidate bearing the kind and would pick it at a cap of one. Needed because: today's date is on every scene and is not a given cue, while the person present is; the two together are D9. This conditions reserved room and never recall: a person still remembers the day their father died with nobody in the room, whenever the room is not already full. Shared with is read through the existing both-direction Involves and Mentions-to-episode semantics, never by scanning the participant literal. +- Time never changes a score, and a time cue contributes only a few occasions. Needed because: the existing rank key holds no time, and the scene-words slice replaced turns outside root selection with score order among honest scores (rulings 44, 45 and 47). A time cue has no similarity score, so a time root cannot borrow one: it enters as an explicit root with a cue component of zero, no vector score and no full-standing score, which is the treatment a reminder-only root has today (its standing, its leaf rule at expansion, and floors served to full-standing members of its kind first), except that a description root carries its honest scene-surface cosine as its cue component and a time root carries none. Its final score is therefore the graph and salience components alone. Ruling 47 applies to proximity as it does to the cue: a time-only or reminder-only root contributes its root proximity only to itself and to what rests on it, and an object also reached by a full-standing road keeps the graph component of its full-standing roads and is not raised by being a time root (today the merge takes the larger graph component from any road, which would raise a participant-reached occasion from one half to one merely for being the latest). On a duplicate the merge keeps the larger cue score and the union of kinds, so a topic hit or a keyed occasion that a time cue also reaches keeps its cue and graph components, its full standing and its final score unchanged from the parent, and gains only the time kind. Crowd-out is prevented by construction, not by ordering: a time cue contributes at most as many as the largest cap in force could hold, and the rest is not a candidate at all. +- `Scene.time` carries the offset the application gave (`DateTime`), the stored instant stays as it is today, and commit derives the experience's local date from the offset given and stores it on the episode. Needed because: an anniversary is a calendar day, and a calendar day in UTC is a made-up day for any deployment away from UTC (08:00 at +09:00 is 23:00 the day before). Deriving it in Rust at commit keeps the anniversary read an exact match on the local month and day with a local year earlier than the present scene's local year, so no offset handling happens in the query engine and no scan is needed. Earlier by instant is not earlier by year across offsets (00:30 at +14:00 on a date is before the start of that date at +09:00 and is the same local year), so the year compared is the local one. +- The derived local date is part of what a write says. Needed because: equality of offset-carrying times ignores the offset, so without it a same-id write with the same instant and a different local day would pass as a replay while changing which anniversary recalls the experience. +- The range and the anniversary report one kind, date match. Needed because: the ruled vocabulary is closed and names recency and date match; a memory whose date lies in the span a question names and one whose date is today's in an earlier year both match a date. One kind, two sources. +- The promise is episodes and what rests on them. Needed because: a time root is expanded as any explicit root is, so a reminder brings what was observed and concluded on that occasion through the existing links, and no separate observation lookup is added; a notion or a thread reached from a time-only root is a leaf (ruling 46), so being reminded of a day does not open the history of everyone who was there, and the same leaf rule applies to provenance: an interpreted memory resting on the occasion is brought, but another episode that memory also rests on is not named by the time kind and is not opened through it. The baseline proves what expansion brings from an unlinked occasion and the README says what comes. +- The two new floor values join the calibration the cues plan's closing task already hands to the companion repository, and no calibration task is added here. Needed because: one calibration over all shipped kinds measures them against each other. + +## Scope / Non-goals +- Scope: the production modules, named test files and documents each task lists under owns, at most one decision record (a recorded scene time carries its offset and a derived local date, if the admission test passes), this plan. +- Non-goals: due dates, triggers, direction, actor and counterpart on open loops and commitments, the identity of the self at construction, the pair counterpart, silence relative to a pair's cadence (all the prospective-memory plan). The time since a participant was last met (the state plan's Task_4; nothing here repeats it, and participant occasion selection in `graph_expansion` is reused as it is, not generalized). Rhythms (a weekly game night). Inferring the activity from a conversation's recent episodes: no behavior has been shown to fail without it. A ranking weight for elapsed time, or any time term in a score. A recency window or a minimum salience for a date match: numbers nobody has measured. A content search restricted to the range. An observation lookup for a directly admitted episode. Widening `created_at`, `ended_at`, an observation's time or statistics timestamps to carry offsets. Migrating stored data. Chronological presentation (the renderer's; every admitted memory already reports its scene time). Ordering threads by when they were last touched. Validity intervals. Parsing dates out of text anywhere. The short-term store. + +## Design +- Chosen, the road: the time route is three reads over recorded scene times that contribute explicit roots with a cue kind, selected through `select_with_cue_floors`, exactly as a description contributes its occasions since the scene-words slice. Structure: crate-internal selectors on the graph port return episode id, recorded time and retention without full-store hydration: the latest at or before an instant; those between two instants; those with a given local month and day and an earlier local year. Each source contributes only the larger of one and its kind's floor value: recency the latest by recorded scene time then id at or before the reference time; the range the most recent inside it then id; the anniversary the most recent matching day then id, those shared with a notion the present scene resolves before the rest. Eligibility (only episodes that may be recalled, ruling item 34) is applied before the cut, so an older match is never lost behind a prefix of non-matches, and the rest of each source's matches are not candidates at all: the trace reports one left-out count per time source, as it does per description search. A contributed occasion is an explicit root with a cue component of zero, no vector score and no full-standing score: reminder-only standing, the treatment the helper and expansion already give a root that no full-standing road reached (the standing and leaf rule of rulings 46 and 47), the difference being that a description root carries its honest scene-surface cosine and a time root carries no similarity at all. Its final score is the graph component (one at the root itself, when nothing else reached it) and its salience, with no time term. A time root that duplicates an existing root merges by the existing rule for the cue: the larger score, the union of kinds, full standing if any road gives it; and by ruling 47 for proximity: the reminder road's proximity is set only on objects no full-standing road reached, so a memory the topic or a known participant also reached keeps its cue and graph components and its final score exactly as at the parent, and expands as before. The contributed occasions head their kind's order for the floor at every cap; at root selection the time kinds are excluded from the spare-turn rounds, receiving exactly their floor reservation and then only room left after every given kind's queue is exhausted; at the candidate merge they are absent (it holds vector candidates only) and at the section caps, after each kind's floor is served from its own order, spare fill is the caller's score order. Evolution: due dates and triggers join the same helper in the prospective-memory plan. Verification: service-free on the embedded stores through the public facade; each task first shows its case failing at its parent commit. Operation: the first two reads examine episode time metadata and are linear in episodes (the known ceiling; a chronological index is the upgrade if measured slow); the anniversary read is an exact match. No model call, no clock. Human: the application passes the time it perceives with its offset and, when a question names a span, the span. Safety: nothing is withheld by time; every source only adds candidates. +- Alternative: recency as a ranking weight only. Rejected by ADR-D-0022: a weight cannot admit what was never a candidate, and a scene with no topic has no candidates. +- Alternative: recency with a floor on every retrieval (the first draft, which also kept it out of spare-room sharing). Rejected by ruling: a floor is for a cue someone gave; the time is on every scene, so that floor would move every topical result for no cue at all. A zero floor that still contributes one occasion says the same thing with no special case, and is the rule a description already follows. +- Alternative: time-only candidates considered after every candidate with a given kind at every cap (the approved draft). Withdrawn on 2026-09-22: the scene-words slice withdrew turns and group ordering outside root selection (rulings 45 and 47) and replaced crowd-out prevention with contributing few occasions and letting honest scores decide; the same helper now serves this plan, and a second ordering rule inside it for time kinds would reintroduce what was withdrawn. +- Chosen, the principle for reserved room: given cues hold floors (participant, place, activity, a range, an anniversary shared with someone present); ungiven ones (recency, an anniversary shared with nobody present) are named in the trace and hold no reserved room. Spare turns at root selection are for the given non-time kinds only: the helper's kind list becomes participant, place, activity, date match, topic, recency, and its spare-turn rounds skip date match and recency, which receive exactly their floor reservation and then only room left after every given kind's queue is exhausted. An unshared anniversary is carried in the smallest form consistent with the helper's existing inputs: the helper takes a kind set per candidate and the caller builds that input at each call site, so the caller passes such a root with date match removed from the set the helper sees (an owned filtered set for that root only), while the root and its ranked object keep date match in their own cue kinds for the trace and the result; the helper's reservation queue therefore never contains it, and it neither holds the floor nor spends the date-match turn. If the helper's borrowed input cannot take a filtered set without a change to its rule for the kinds it already serves, stop and report. +- Chosen, selection (which are contributed; never pack order): recency contributes the latest by recorded scene time, then id, as many as the largest cap in force could hold, and its floor (zero by default) reserves nothing of that room; the contributed set is newest first as a selection order only; the range and the anniversary likewise the most recent inside their match, the anniversary's shared ones first. Among what was contributed the existing score decides everywhere, so under a tight cap a contributed occasion of high salience may win over a faint match and loses to a real one, and one older than the contributed set never comes by that cue. An episode reached through several links is counted once before the cut. +- Alternative: order the pack most recent first for time candidates. Rejected by ruling: time never changes a score; the rank key stays score, type, id. Chronological presentation belongs to the renderer. +- Alternative: recent and salient chosen from a window. Rejected for now: the window is a number nobody measured. +- Chosen, the range input: a start and an end instant beside the topic, both required (an open span ends at the scene's time). An end before its start matches nothing and is echoed as given; no new error. ADR-D-0023 allows it because it says when the question is about, not what the character wants. +- Alternative: the library finds the dates in the topic. Rejected: it interprets language inside retrieval and makes recall wait on a model or a grammar per language (ADR-I-0013). +- Alternative: the range filters the content search. Rejected as the meaning of the input: time never gates recall. A content search restricted to the span is left until a scenario fails without it; the README states the ceiling. +- Chosen, the calendar day: `Scene.time` carries the offset; the stored instant literal is unchanged; commit derives the local date from the offset given and stores it on the episode; the anniversary read matches the present scene's local month and day exactly with a local year earlier than the present scene's local year. Whether the stored form is one literal or a month-day key beside a year is the worker's choice, provided the read is an exact match without full-store hydration. Alternative: earlier year judged by instant. Rejected: wrong across offsets. Alternative: compare in UTC. Rejected: wrong for every deployment away from UTC, silently. Alternative: store the offset-carrying literal and extract month and day in the query engine. Rejected: the engine leaves an offset beyond fourteen hours unbound and a filter then drops the row silently, the read is a scan, and chrono equality ignores the offset so replay could not tell two local days apart. Alternative: a UTC offset given only at retrieval. Rejected: wrong whenever the character moved or the clocks changed. Episodes written before this plan have no local date and never match an anniversary; there is no migration. +- Chosen, staleness: a computed fact on the admitted interpreted memory's existing memory-scenes entry. Alternative: leave it to the application. Rejected unless the baseline shows otherwise: the plan review found the result collapses an observation into its parent scene and discards the observation's own time, so a consumer cannot derive it in every case. Alternative: omit or demote memories past an age. Rejected by ADR-D-0018. +- Measurement (standing rule of 2026-09-22): every task that changes what comes to mind, its order or its standing carries a measurement validation owned by the evals side, run at the task's tip before its pull request opens, before and after on the same instrument and inputs, in both id orders, twice. Instrument: the companion evaluation repository's Task_9 generated runner extended with a time family: a store of dated experiences with ids opposed to time, a scene giving only a time, a range beside a topic, and an anniversary shared with a scene-resolved notion and one unshared. Falsifiers, any one of which falsifies the design: the occasion contributed by recency is not the latest by recorded scene time; at a recency floor of zero a topic keeps fewer on-topic memories with recency present than alone; date match holds reserved room for an unshared anniversary. Task_1 measures the first two, Task_2 the second with the range present, Task_3 the third; Task_4 reports an age and changes nothing that comes, so it is exempt. The validator's vocabulary has no measurement kind, so the item is carried as a manual validation owned by the orchestrator, whose detail names the evals worker as the runner. +- Why chosen: the smallest shape that makes "when" a cue like a description of where and with whom, contributing a few occasions and letting scores decide, without letting it take room from cues someone gave. Fit: the phase draft sections 2 and 2.1; philosophy "make time a first-class dimension" and "the character knows how things stand before it has reflected"; ADR-D-0018, D-0022, D-0023, D-0029, D-0038, ADR-I-0013, I-0022. + +## Compatibility stance (required if a contract/interface/persisted format is touched) +- surface: `Scene.time` (offset-carrying); the stored episode (a derived local date; the instant literal unchanged); `RetrievalContext` (the range); `RetrievalCueFloors` and `CueKind` (two values each); the retrieval result (the echoed range; the age of support on the memory-scenes entry); the graph port (time selectors). +- stance: break +- justification: no external consumers; the one locatable consumer is `CharacterMemoryEvals`, the public companion evaluation repository whose tooling is a development aid and not core library functionality, and its plan follows each library slice. No stored-data migration: episodes stored before this plan keep their instants, carry no local date and never match an anniversary. + +## Context (workspace) +- Related files/areas: the routes census `.agent-work/researcher/v0-2-routes-census-report.md` in the main checkout, sections 3 and 10; the plan review `.agent-work/reviewer/v0-2-time-plan-review.md`; the load-bearing decisions list, items 3, 17, 23, 25, 31, 34, 35, 39, 44, 45, 46 and 47; the scene-words plan (Design items 3 to 6, Task_2) and its checkout at tip 63f176f. `src/domain/scene.rs:9`, `src/domain.rs:288` (episode), `src/domain.rs:319` (`observed_at: Option>`), `src/api/types/retrieval.rs` (`RetrievalContext`, `RetrievalCueFloors` at 117, `CueKind` at 728, `MemoryScenes` at 230), `src/usecases/retrieve.rs` (`select_with_cue_floors` at 595; call sites at 686 for sections and 1006 for roots), `src/usecases/retrieve/scene.rs` (the vector candidate merge at 156; `memory_scenes` at 190), `src/usecases/remember.rs:262` (the collision check), `src/usecases/write_planning.rs:322`, `src/adapters/oxigraph/shared.rs` (`parse_timestamp` at 522; literals built as plain strings at 672), `src/adapters/oxigraph/sparql_selectors.rs`, `src/adapters/oxigraph/rdf_mapping.rs`, `src/policy/graph_expansion.rs:68` (participant occasions). +- Existing patterns or references: no retrieval code reads a time today except the latest-occasion rule for a participant and the state plan's last interaction; with no effective cue a retrieval admits nothing; the activity is the pattern both for a retrieval-only input with an echo and for explicit roots from a graph read; a description is the pattern for a cue that contributes only its most recent few occasions (as many as the largest cap in force could hold) with the rest not a candidate, and the trace says whether more existed than came; the helper lists its kinds in a fixed order, serves each kind's floor from the head of that kind's own order with full-standing members before reminders, shares spare room by turns at root selection only, and elsewhere returns spare fill to the caller's score order; a root's standing is full when any road gave it a full-standing score and reminder-only otherwise, expansion from a reminder-only root treats a notion or a thread as a leaf, and what is reached through a root inherits only from its full-standing roads; a duplicate root keeps the larger score and the union of kinds; the stored scene time is a plain string literal with variable fractional seconds, and the query engine's typed cast orders it correctly; chrono equality and order of offset-carrying times compare the instant only; forgetting a source suppresses what rests on it by default, and a source-only policy exists. +- Design record consulted and deviations from its acceptance: ADR-D-0018, D-0020, D-0022, D-0023, D-0028, D-0029, D-0038, ADR-I-0013, I-0020, I-0022. No deviation. ADR-D-0022's "under a loud topic the time route still admits its floor" holds for the time cues someone gave (a range, an anniversary shared with someone present); recency's floor of zero is a default the record leaves to measurement. The draft's "importance and recency weight activation" is met for importance by the existing score and for recency by the road; a weight is deferred to measurement, which ADR-D-0018 leaves open. + +## Open Questions (max 3) +- None. The offset on the scene time and the condition under which an anniversary holds reserved room are ruled and logged for presentation; the decider sees the proposed record, if any, at the slice boundary. + +## Assumptions +- A1: The graph adapter can answer "latest at or before" and "between" over stored scene times inside the query engine, ordered and limited, without full-store hydration. Source: Tier D plan review (a typed cast orders correctly across offsets); checked by Task_1, which carries the stop condition. +- A2: `select_with_cue_floors` takes two more entries in its fixed kind list with no change of selection for the four it serves. Source: Tier D plan review (122,880 existing-kind cases compared identical), made before the scene-words slice changed the helper; re-checked by Task_1 end to end. +- A3: With the recency floor at zero and the roots full, recency takes no root (it takes no spare turn and no given queue is exhausted), so the root list, the pack (members, order, scores) and the echoed result equal the parent commit's exactly. With spare root room, the single recency root enters and the pack changes only where its cue-less score exceeds an admitted member's. The final score today is 0.65 times the cue component plus 0.25 times the graph component (one over one plus the proximity) plus 0.10 times salience, and a descendant inherits three quarters of its root's cue; a cue-less root therefore scores at most 0.35 (graph one, salience one). Against another root the margin is a cue below about 0.15 times the salience difference; against a one-hop descendant (graph one half, inherited cue) it is wider, up to a root cue of about 0.46 (at zero descendant salience and maximal time-root salience), so the parity guarantee is over what the fixture's scores make true, never universal: a fixture keeps parity by giving every admitted member a final score above 0.25 plus 0.10 times the recency occasion's salience. Source: the helper's turn structure and `final_score` at tip 63f176f; checked by Task_1, which states the composition from the code it finds. +- A4: An explicit root with no vector score and no full-standing score is carried by the existing `CandidateRoot`, its merge and expansion without a new field. Source: the scene-words checkout at tip 63f176f (`full_standing_score: None` is the reminder-only case; `vector_score: None` is the keyed-root case); checked by Task_1. +- A5: Widening `Scene.time` leaves every stored time readable and every ordering by instant unchanged; test sources need mechanical type updates and no change of expectation. Source: Tier D plan review; checked by Task_3. +- A6: `memory_scenes` already reads the source observations and their parent episodes of an admitted interpreted memory. Source: Tier D plan review (`src/usecases/retrieve/scene.rs:190`); checked by Task_4's baseline. +- A7: The integrated base holds the state plan's Task_4 (last interaction and time since, in whole seconds, future times not counted) and the scene-words slice through its Task_2 (tip 63f176f). Source: the state plan and the scene-words plan; a precondition of dispatch, not checked by a task here. + +## Tasks + +### Task_1: What happened lately comes to mind when nothing else claims the room +- type: impl +- owns: + - src/usecases/retrieve.rs + - src/usecases/retrieve/scene.rs + - src/api/types/retrieval.rs + - src/ports/graph_authority.rs + - src/adapters/oxigraph/sparql_selectors.rs + - src/adapters/oxigraph/shared.rs + - src/adapters/oxigraph/embedded.rs + - src/adapters/oxigraph/tests.rs + - src/test_support.rs + - src/memory.rs + - src/memory/retrieval_time_tests.rs + - src/memory/retrieval_floor_tests.rs + - tests/public_facade_tests.rs + - README.md +- depends_on: [] +- description: | + Base: the top of the scene-words stack (see Integration). First, at the parent commit, through the public facade on the real embedded stores: a store with episodes today, yesterday and last month, whose ids do not follow their times, one older episode of clearly higher salience, one episode whose observation holds distinct content and has no participant, thread or other connecting link, and one whose participant is a known notion with several older occasions; retrieve with a scene that gives only the evening's time, then with the character's own key as the participant, then under a topic that alone fills the candidate cap, the roots and a section. Record what each brought. The task builds only what fails. Then: the recency source. A crate-internal selector on the graph port returns the latest episodes at or before the scene's reference time by recorded scene time, never creation time, then id, without full-store hydration; only episodes that may be recalled count, eligibility is applied before the cut, and the cut is what the largest cap in force could hold; no count of what was left out is kept for recency: it would grow with the store and inform nobody (Decision Log, 2026-09-22). Stored scene times are plain string literals with variable fractional seconds, so the comparison must be order-safe, by a typed cast in the query or a normalized literal; if neither works without full-store hydration, stop and report. The reference scene is captured once and bound into the query; nothing reads a clock. The contributed occasions enter as explicit roots with the cue kind recency, which `CueKind`, the trace and `RetrievalCueFloors` gain, with a cue component of zero, no vector score and no full-standing score, so they have reminder-only standing: the helper serves their kind's floor to full-standing members first, expansion from one treats a notion or a thread as a leaf, and what it brings is what rests on that occasion. Recency names direct contributions only: the contributed occasions and what rests on them within the leaf rule; an interpreted memory resting on the occasion may also rest on an older episode, and that episode does not inherit the recency kind and is not opened through the recency road (the leaf rule applied to provenance). At root selection the time kinds are excluded from the helper's spare-turn rounds: recency receives exactly its floor reservation and then only room left after every given kind's queue is exhausted. The recency floor ships at zero, so the single latest occasion enters with no reserved room, takes no root while the roots are full, and enters only spare root room; recency is the last kind in the helper's fixed list. A recency root that duplicates an existing root merges by the existing rule for the cue (the larger score, the union of kinds, full standing if any road gave it) and by ruling 47 for proximity: the reminder road sets a graph component only on objects no full-standing road reached, so a topic hit or a participant-reached occasion that is also the latest keeps its cue and graph components, its final score exactly as at the parent, its standing and its expansion, and gains the kind. Pack order is the existing score everywhere, the rank key of score, type and id; no candidate is grouped or ordered by whether its cue was given. State how this is carried in the smallest form the helper allows; if it cannot be carried without changing selection among the kinds it already serves, stop and report. An episode later than the reference time is not recent and is reached by every other cue as before. No observation lookup is added: what a recency root brings is what its expansion brings. The README states the rule, that the floor is zero until measured, that a memory reached only by recency does not open a person's history, and that chronological presentation belongs to the renderer since every admitted memory reports its scene time. +- acceptance: + - With a scene that gives only a time, the latest episode by recorded time comes, whatever its id, with what rests on it; with nothing else claiming the room, the latest occasions that fit it all come, and a floor above zero reserves that many of them; an episode older than the contributed set never comes by recency or is named recency, however salient and even when an interpreted memory resting on the latest occasion also rests on it (the fixture holds such a chain), and the trace names recency for each contributed occasion and what rests on it and keeps no count of what was left out. + - Under a tight section cap with a recency floor above one and no given cue competing, an older high-salience episode within the contributed set may win over the newest, and the test says so; nothing asserts that the newest always survives. + - With a section cap of two, two topic hits at the root, spare root room and the latest episode of top salience reached by nothing else: the fixture gives both topic hits a cue component above 0.16, so each scores above 0.35, the most a cue-less root at graph one can score, and the test states those values; the pack equals the parent commit's (the two topic hits, in their order, with their scores) and the recency root is among the roots. A second fixture with one topic hit a one-hop descendant of low inherited cue (final score below 0.35) shows the recency occasion displacing it under score order, and the report and README name this as the honest outcome, not a defect. + - The unlinked observation with distinct content is not brought by the scene-only retrieval, at the parent commit and after; the report says so. + - The latest episode's known participant, reached only through recency, brings no older occasion of that notion; the same retrieval with that notion's key present brings them as before. + - With the character's own key as the only participant, the same recent episodes come beside what that participant already brings. + - Under a topic that fills every cap, with the default floor of zero, the root list, the pack (its members, order and scores) and the echoed result equal the parent commit's exactly: recency takes no root; diagnostic counts and trace rows may differ, and the test compares what is promised, not whole-result bytes. At a root cap of three with three strong topic roots, the roots stay those three, and the test says so. A memory the topic also found names recency as well and keeps its score, its full standing, its expansion and its position under a tight section cap. + - The latest episode reached as a participant's occasion (proximity one from the participant root) and also contributed by recency keeps the graph component and the final score it has at the parent; the report shows both values. + - With the recency floor set to one in the same retrieval, the latest occasion is admitted by its floor at the roots and at its section, and the trace says so. + - A topic-only retrieval whose room is not full gains only recency-named memories (the occasion and what rests on it within the leaf rule, never an older episode reached through an interpreted memory's provenance) placed by score, and each addition is listed in the report. + - Forgetting the latest episode offers the one before it in its place; a reference time in the past brings what was recent then; the same input run twice gives the same result. + - The report holds the baseline run for each case above, saying which failed there and which already passed; a case that already passes is not claimed as delivered. +- validation: + - kind: command + required: true + owner: worker + detail: "cargo fmt --check; cargo check; cargo clippy --all-targets -- -D warnings; cargo test; the acceptance cases run at the parent commit, with what each brought recorded in the report" + - kind: review + required: true + owner: reviewer + detail: "Tier D diff review, reproducing the baseline evidence at the parent commit and tracing the root's standing, its score components and duplicate merging for score or standing changes; Tier A review of the road against ADR-D-0022, ADR-D-0018 and rulings 39, 45, 46 and 47" + - kind: manual + required: true + owner: orchestrator + detail: "Measurement, run by the evals worker at this task's tip before its pull request opens: the time family on the Task_9 generated runner, before and after on the same inputs, both id orders, twice; falsified if the contributed occasion is not the latest by recorded scene time, or if at a recency floor of zero the topic keeps fewer on-topic memories with recency present than alone" + +### Task_2: A question about a span of time brings what happened then +- type: impl +- owns: + - src/usecases/retrieve.rs + - src/usecases/retrieve/scene.rs + - src/api/types/retrieval.rs + - src/ports/graph_authority.rs + - src/adapters/oxigraph/sparql_selectors.rs + - src/adapters/oxigraph/embedded.rs + - src/adapters/oxigraph/tests.rs + - src/test_support.rs + - src/memory/retrieval_time_tests.rs + - src/memory/retrieval_floor_tests.rs + - tests/public_facade_tests.rs + - README.md +- depends_on: [Task_1] +- description: | + First, at the parent commit: a store where last Tuesday holds five episodes whose words share nothing with the question, among many episodes on other days whose words do; retrieve with the topic "what happened last Tuesday" and the inputs that exist today (the range input does not exist there, so the baseline is the same fixture without it), and record that Tuesday's episodes are not brought. Then: `RetrievalContext` gains a time range beside the topic, a start and an end instant, both required, set by a builder like the activity's and echoed in the result as given. The most recent episodes whose recorded scene time lies inside it, ends included, by the order-safe comparison of Task_1, then id, as many as the larger of one and the date-match floor, are contributed as explicit roots with the cue kind date match, which `CueKind`, the trace and `RetrievalCueFloors` gain, provisional at the value the other given kinds have and named so; the number inside the range left out is one count in the trace. They enter as Task_1's recency roots do: a cue component of zero, no vector score, no full-standing score, reminder-only standing unless another road gives full standing. A range is a given cue: date match holds its floor at root selection and the section caps, served from the head of its own order, placed after activity and before the topic in the helper's list; as a time kind it takes no spare turn at roots and enters further room only after every given kind's queue is exhausted. A memory the topic also found keeps its score and standing and gains the kind. The range removes nothing: a memory outside it that any other cue brings is admitted exactly as without the range. An end before its start matches nothing and is not an error. The library reads no dates from the topic. The README shows the input beside the topic and the activity, says the application supplies the span, and states the ceiling (a topical memory inside a crowded span that the unrestricted search ranks below its cap is not found by the conjunction). +- acceptance: + - With the range set to last Tuesday, a date-match floor of two and a topic that fills every cap, the two most recent of Tuesday's five episodes are in the pack by their floor, and the trace names date match and says that more of Tuesday exists than was brought (no count query: the selector fetches one more than it contributes); no episode from another day is named date match by any retrieval. + - Asked about its day with a topic whose neighbours fill the room and the range set from the day's start to the reference time, the pack holds several of today's episodes; yesterday's is absent from the date-match admissions (the other-day control). + - Every id admitted without the range under a quiet topic is admitted with it, in a store where the pack has room for both. + - A memory found by the topic and lying in the range names both kinds, takes one slot, keeps its full standing and expansion, and has the score it has without the range; a memory reached only by the range brings what rests on it and opens no notion's history, and an episode outside the range that an interpreted memory also rests on is not named date match; an inverted range brings nothing and is echoed. + - The report holds the baseline run and says what it brought. +- validation: + - kind: command + required: true + owner: worker + detail: "cargo fmt --check; cargo check; cargo clippy --all-targets -- -D warnings; cargo test; the baseline case run at the parent commit and recorded in the report" + - kind: review + required: true + owner: reviewer + detail: "Tier D diff review; Tier A review of the input against ADR-D-0023 and ADR-D-0029 (a perceived fact, never a purpose, nothing resolved by the application) and of cue-not-filter against ADR-D-0018 and ADR-D-0038" + - kind: manual + required: true + owner: orchestrator + detail: "Measurement, run by the evals worker at this task's tip before its pull request opens: the time family with the range beside a topic, before and after on the same inputs, both id orders, twice; falsified if the contributed occasions are not the most recent inside the range, or if the topic keeps fewer on-topic memories with the range present than alone beyond the date-match floor's reservation" + +### Task_3: This day in an earlier year comes back +- type: impl +- owns: + - src/domain/scene.rs + - src/domain.rs + - src/usecases/write_planning.rs + - src/usecases/remember.rs + - src/usecases/retrieve.rs + - src/usecases/retrieve/scene.rs + - src/api/types/retrieval.rs + - src/ports/graph_authority.rs + - src/adapters/oxigraph/rdf_mapping.rs + - src/adapters/oxigraph/vocabulary.rs + - src/adapters/oxigraph/shared.rs + - src/adapters/oxigraph/sparql_selectors.rs + - src/adapters/oxigraph/embedded.rs + - src/adapters/oxigraph/tests.rs + - src/test_support.rs + - src/memory/retrieval_time_tests.rs + - src/memory/scene_tests.rs + - src/memory/write_turn_tests.rs + - tests/public_facade_tests.rs + - tests/write_planning_tests.rs + - README.md + - docs/design/database/graph_schema_design.md + - docs/design/database/schema_cheat_sheet.md + - the one proposed record file under docs/decisions/design/ + - any other file only for the mechanical type update where `Scene.time` is constructed or read, with no change of behavior or expectation +- depends_on: [Task_2] +- description: | + First, at the parent commit: someone the character has met many times, one of those meetings a year ago today and salient; retrieve on the anniversary with that person present and no topic, and record whether that meeting comes (the latest-occasion rule brings the newest, not this one). Record also, through the public JSON input, what an episode given at 08:00 with a +09:00 offset reads back as. Then: `Scene.time` becomes `DateTime`; `Scene::now()` keeps its meaning with a zero offset; the stored instant literal stays exactly as it is today, and `created_at`, `ended_at`, an observation's time, participant occasions and statistics timestamps stay instants. In the commit pass, in Rust, the experience's local date is derived from the offset given and stored on the episode, never authored; one literal or a month-day key beside a year is the worker's choice, provided the anniversary read is an exact match without full-store hydration. The local date is part of persisted content and replay equality: a same-id write with the same instant and a different local date is a content difference, rejected as any collision is, and an exact replay after a restart is accepted. The anniversary source matches the present scene's local month and day with a local year earlier than the present scene's local year, never earlier by instant, so an earlier hour of today never counts and neither does the same local date lived at another offset. 29 February matches 29 February only. The matches are contributed as explicit roots named date match, as Task_2's range roots are: the most recent first, then id, as many as the larger of one and the date-match floor, the rest one left-out count in the trace, with a cue component of zero and reminder-only standing unless another road gives full standing. One shared with a notion the present scene resolves, read through the existing both-direction Involves and Mentions-to-episode semantics that participant occasions use (reused, not generalized, and never a scan of the participant literal), is contributed before any unshared one and holds the date-match floor; any other is named date match on the root and in the trace but is passed to the helper with date match removed from the kind set it sees (Design, reserved room), so the reservation queue never contains it, it holds no reserved room, and it competes by score alone, as recency does. An episode reached through several links is counted once before the cut. A range and an anniversary on one retrieval share the date-match floor from one order. Episodes written before this task have no local date and never match; there is no migration, and the README and the schema documents say so. Propose a short record that a recorded scene time carries its offset and a derived local date, and why, if the repository's admission test passes; if it does not, say so and propose none. +- acceptance: + - On the anniversary, with the person present and a topic that fills every cap, the meeting of a year ago is in the pack by its floor and the trace names date match; one day later it is not named date match. + - With nobody present and no topic, an anniversary episode comes with what rests on it and opens no notion's history; with nobody present and the room full of given cues of real similarity, the pack equals the one without it, and the memory stays reachable by its topic. + - An episode at 08:00 at +09:00 on a date matches that date a year later at 20:00 at +09:00, and an episode earlier the same local day does not match, nor does one at 00:30 at +14:00 on that same date of the present year; the report shows the parent commit's reading of the same input. + - A same-id write with the same instant and another offset that changes the local date is rejected as a collision; the identical write replayed after reopening the store is accepted. + - With two matches and room for one, the one shared with the person present comes; an unshared anniversary at a date-match floor of one and a root cap of one is not admitted by the floor and the trace records no floor admission for it; a range and an anniversary together each bring something under a cap that holds two. + - Episodes stored before this task read back the same instants and match no anniversary; every existing test of order by recorded time keeps its expectations. + - The report holds the baseline runs and says which failed. +- validation: + - kind: command + required: true + owner: worker + detail: "cargo fmt --check; cargo check; cargo clippy --all-targets -- -D warnings; cargo test; the baseline cases run at the parent commit and recorded in the report" + - kind: review + required: true + owner: reviewer + detail: "Tier D diff review, tracing every reader and writer of a scene time and the collision check for the local date; Tier A review of the stored-shape change, the reserved-room condition and the proposed record against ADR-D-0029, ADR-D-0018 and the ruling that the library never makes up a time" + - kind: manual + required: true + owner: user + detail: "If a record was proposed, the decider accepts or returns it, in a batch at the slice boundary" + - kind: manual + required: true + owner: orchestrator + detail: "Measurement, run by the evals worker at this task's tip before its pull request opens: the time family with a shared and an unshared anniversary, before and after on the same inputs, both id orders, twice; falsified if date match holds reserved room for an unshared anniversary, or if the shared one is not the occasion contributed first" + +### Task_4: An old belief is held as old +- type: impl +- owns: + - src/usecases/retrieve/scene.rs + - src/api/types/retrieval.rs + - src/memory/retrieval_scene_tests.rs + - tests/public_facade_tests.rs + - README.md +- depends_on: [Task_3] +- description: | + First, at the parent commit: a belief last supported fourteen months before the reference time, admitted by its subject being present, resting on an observation whose own time differs from its episode's; record what the result says about when it was last supported and whether a consumer could derive the age exactly from the result. The plan review found the result collapses an observation into its parent scene and discards the observation's time, so a field is expected; if the baseline shows otherwise in every case below, the task shrinks to a README note saying how. Otherwise: every admitted interpreted memory reports, on its existing memory-scenes entry, the age in whole seconds (the unit the state plan's time since uses) between the scene's reference time and its latest supporting time. A supporting time is an observation's `observed_at` where it has one, else its episode's scene time, never a creation time, computed from the objects `memory_scenes` already reads, before it collapses an observation into its parent scene, with no second provenance read. Only support at or before the reference time counts. A forgotten source does not count. No counted support is reported as none, never zero. A correction's replacement reports from its own sources. The age changes nothing else. +- acceptance: + - A belief supported a day ago and one supported fourteen months ago are both admitted and report those ages in whole seconds against the scene's reference time. + - At each of two fixed reference times, the retrieval selects the same ids, sections, order and scores as the parent commit does at that same reference time. + - In a controlled fixture with no other time cue in play, moving the reference time a month later raises the reported age by that month. + - A belief resting on an observation with its own time reports from that time and not its episode's; one resting on an observation without one reports from the episode's scene time. + - A belief resting on two experiences reports from the later; after the later is forgotten under the existing source-only policy, so the belief stays admitted, it reports from the earlier. + - A belief whose source observation is active while that observation's parent episode is forgotten reports from the observation. + - A belief whose only support is later than the reference time, and one given by the application with no experience behind it, report none. + - The values are in the result with the trace off. +- validation: + - kind: command + required: true + owner: worker + detail: "cargo fmt --check; cargo check; cargo clippy --all-targets -- -D warnings; cargo test; the baseline case run at the parent commit and recorded in the report" + - kind: review + required: true + owner: reviewer + detail: "Tier D diff review, confirming no second provenance read; Tier A review against ADR-D-0018 (reported, never enforced) and ADR-D-0028. No measurement item: this task reports an age and changes nothing that comes to mind, its order or its standing, so it is exempt from the standing rule of 2026-09-22" + +## Integration +- The library stack: the cues pull requests, the fixes pull request, the state plan through its Task_4, the scene-words plan (Task_1 and Task_2 landed at tip 63f176f on `feature/2026-09-21/scene-reminders`; Task_3, the trace field and the documents, in progress), then this plan, prospective memory, write-path warnings, the renderer. +- Base: the top of the scene-words stack. Task_1 is cut from the scene-words Task_2 tip 63f176f; when scene-words Task_3 lands, it is merged into Task_1's branch before Task_1's pull request opens, and the actual tip is pinned in the dispatch brief. The scene-words plan's clause that this plan's Task_1 must not start before that slice lands is satisfied by Task_2's landing, since the mechanism this plan reuses (contribution by floor, reminder-only standing, the leaf rule, the stage distinction in the helper) is all in Task_2; Task_3 adds only a trace field and documents. +- Inherited from the scene-words slice and not redone here: the helper's fixed kind list and stage distinction, the reminder-only standing and its leaf rule in expansion, the per-search left-out count in the trace, the description occasion selection by recorded scene time over a closed pool. This plan adds two kinds to the list, two sources of explicit roots, and two left-out counts. +- Controls that must not change under this plan at a recency floor of zero and no range: the scene-words plan's Task_2 acceptance cases (the keyless case, the overlap case, the same-kind boundary, the verbatim label case) and the state stack's root-turn controls, each of which gains at most one recency root and no change of pack; every other changed expectation is listed with before and after from runs. + +## Task Waves (explicit parallel dispatch sets) + +- Wave 1: [Task_1] +- Wave 2: [Task_2] +- Wave 3: [Task_3] +- Wave 4: [Task_4] + +One crate, shared files: sequential, one worker at a time, each PR stacked on the previous one, the first stacked on the top of the scene-words stack as Integration states. After each task the library commit is handed to the companion evaluation repository, whose own plan schedules what follows: the own-day scenario in both forms with its other-day control, the anniversary with the person present, the reunion after a gap, the date-match floor and the recency floor (from zero) in the calibration the cues plan already hands over, with pollution from floor admissions measured by score and not only starvation, and whether any ordering by elapsed time improves anything. + +## Rollback / Safety +- Each task is one PR and reverts on its own in reverse order; once the companion repository has followed a slice, a revert is coordinated with it. Task_3 adds a derived local date to stored episodes and leaves the stored instant as it was; reverting it leaves values nothing reads. + +## Progress Log (append-only) + +- 2026-09-23 Tasks 1 to 4 implemented and approved by both review tiers: Task_1 recency (PR 145), Task_2 the caller's range and Task_3 the anniversary (PR 146, final tip `d36d96d` after the rulings on the range's contribution and the unshared anniversary's order), Task_4 the time since a belief was last supported (PR 147, final tip `0ce2f35` after the Tier A contract fix: the age follows `include_suppressed` and is named `seconds_since_support`). +- 2026-09-23 Slice measurement at `d36d96d` against `f174f1b`, run by the companion's evaluation worker on its frozen instrument `f3ebb4b` (367 daily occasions over a year, identifiers opposed to time in both orders, each run twice and byte-identical, zero bounded failures). Reading: `CharacterMemoryEvals/docs/evidence/calibration/time-checkpoint-2026-09-22/time-d36d96d.md`, with raw measurements recoverable at `CharacterMemoryEvals@3506bc8090244e55ec6b12fef1b671ca40897fc5`. +- 2026-09-23 Completion value audit (design altitude): everything the slice added earns its place or is already scheduled for deletion or rewrite by the consolidation plan (Tasks 1, 5, 6, 7), so no cleanup commit here; the unearned remainder (the anniversary's more-exist marker, three README sentences, a test that states the falsified claim under a small cap, and review-evidence dumps in tests) is folded into consolidation tasks. Closing record: Definition of Done line on the date-match floor and Design line on one left-out count per time source are met as amended by the rulings on the range's contribution and on recency's count; Tasks 2 and 3 were measured once at the PR 146 tip under the checkpoint rule; no record was proposed for the stored offset, because consolidation Task_6 replaces the stored shape (rulings log item 64). The plan closes once the measurement reading is kept at a tracked location (the companion's evidence plan, Task_3). +- 2026-09-23 Plan complete. The measurement reading is kept at `CharacterMemoryEvals/docs/evidence/calibration/time-checkpoint-2026-09-22/time-d36d96d.md` with its raw measurements at `CharacterMemoryEvals@3506bc8090244e55ec6b12fef1b671ca40897fc5`. All four tasks are approved by both review tiers; the salient-anniversary acceptance line is moved to the consolidation plan, not met here. + +## Decision Log (append-only; re-plans and major discoveries) + +- 2026-09-21 Decision: the time route is recency, a given range, the calendar day and the age of support, and nothing prospective. + - Trigger / new insight: the census shows retrieval reads no time at all and admits nothing when no other cue is effective; the own-day scenario passed earlier only through a defect and fails honestly since. + - Plan delta (what changed): new plan. Recency and date match join the ruled cue kinds; the range is given by the application beside the topic; the scene time carries its offset; staleness is a reported age. No ranking weight, no window, no activity inference, no minimum salience. + - Tradeoffs considered: one time kind with one floor was rejected because the ruled vocabulary names two and their sources differ in who gave them. + - User approval: plan approval waived for plans inside the rulings; decisions judged by character behavior are logged for presentation. + - Record proposed: at most one, in Task_3. +- 2026-09-21 Decision: the draft was revised after its Tier D review and its Tier A review, under six rulings, before dispatch. + - Trigger / new insight: the first draft gave recency a floor on every retrieval and a special case to keep it out of spare room, promised most-recent-first pack order that the rank key cannot give, assumed an admitted episode brings its observations, left the offset out of replay equality, and asked the query engine to read offsets it silently drops beyond fourteen hours. + - Plan delta (what changed): a floor guarantees room only for a cue the scene or the application gave; recency ships at zero and is served last; a range holds a floor and shares spare root room; an anniversary holds reserved room only when shared with a notion the present scene resolves, which conditions room and never recall, and is no longer a question for the decider. Time never changes a score: time candidates are explicit roots placed after every given kind, a duplicate keeps its score, and the promise is membership, tested with ids that do not follow times, an older higher-salience episode and a topic overlap under a tight cap. The stored instant stays as it is and a local day key is derived at commit and takes part in replay equality; no migration. Episodes only. The age of support is computed from what `memory_scenes` already reads, in whole seconds, counting only support at or before the reference time. Owns are narrowed per task and the base is the state stack after its Task_4. Rhythms join the non-goals. + - Tradeoffs considered: keeping a recency floor of one for the draft's "time route admits its floor under a loud topic" was rejected, because the time alone is not a cue anyone gave; the given time cues keep that promise. + - User approval: decided under the standing instruction and logged for presentation. + - Record proposed: unchanged, at most one, in Task_3. +- 2026-09-21 Decision: three bounded corrections from the Tier D re-check, before dispatch. + - Trigger / new insight: an earlier year judged by instant is wrong across offsets; an acceptance implied the newest recent episode always survives, which the score-ordered caps cannot promise; and whole-result equality with the parent cannot hold once trace rows and diagnostic counts see time candidates. + - Plan delta (what changed): commit derives and stores the experience's local date, and an anniversary is the same local month and day in an earlier local year, the stored form being the worker's choice; recency promises which experiences are offered, and the existing score decides among the time-only candidates, which at every cap are considered only after every candidate that has a given kind (a later Tier D case showed a high-score recency-only episode taking a topic hit's place under a section cap of two, and the rule was made the same at every cap); parent equality is bounded to the pack (members, order, scores) and the echoed result. + - Tradeoffs considered: none beyond the corrections. + - User approval: decided under the standing instruction and logged for presentation. + - Record proposed: unchanged. +- 2026-09-22 Decision: the time cues contribute a few occasions and enter as reminder-only roots, aligned with the scene-words slice; the after-every-given-kind ordering is withdrawn. + - Trigger / new insight: since approval, the scene-words slice landed (Task_2 tip 63f176f) and three rulings changed the room-division rules this plan leaned on: turns and any group ordering outside root selection are withdrawn and honest scores arbitrate (ruling 45); a root reached only as a reminder brings what rests on that occasion and does not open a notion's or a thread's history (ruling 46); standing follows the road and each road carries its own score (ruling 47). The approved draft's rule that time-only candidates are considered after every candidate with a given kind at every cap is a second ordering rule inside the same helper, of the kind ruling 45 withdrew. + - Plan delta (what changed): recency and date match now work as a description does: each source contributes only a bounded few (recency the latest by recorded scene time at or before the reference time; the range and the anniversary the most recent matching, the anniversary's shared ones first), the rest is one left-out count per source in the trace; the contributed occasions are explicit roots with a cue component of zero, no vector score and no full-standing score, which is reminder-only standing today (the leaf rule applies; a root the topic or a known participant also reached keeps its score and full standing under the existing merge); they compete by the existing score everywhere outside root selection. Decision 39 stands: recency's floor ships at zero and it still contributes its single latest occasion with no reserved room; the range holds a floor; an anniversary holds it only when shared with a scene-resolved notion. Three Tier D gaps in the first revision were closed the same day. Turns: a kind present takes spare turns today whatever its floor, so a zero-floor recency root took a root slot from strong topic roots (at a root cap of three, roots 1, 2, 3 became 90, 1, 2); the time kinds are now excluded from the helper's spare-turn rounds, receiving exactly their floor reservation and then only room left after every given kind's queue is exhausted, so parent equality with the recency floor at zero and the roots full holds exactly, because recency takes no root; with spare root room the pack changes only where a cue-less root's score exceeds an admitted member's; under the code's composition (0.65 cue, 0.25 graph at one over one plus proximity, 0.10 salience, descendants inheriting three quarters of the cue) a cue-less root scores at most 0.35, the 0.15 margin holds only root against root, a one-hop descendant can lose at a root cue up to about 0.46, and the parity guarantee is over what a fixture's scores make true, which the acceptance now states. Recency names direct contributions only: an older episode reached through an interpreted memory's provenance from the latest occasion does not carry the kind (the leaf rule applied to provenance). Proximity: the merge takes the larger graph component from any road, so making a participant-reached occasion a time root raised its graph component from one half to one; ruling 47 is applied to proximity: a reminder road sets a graph component only on itself and what rests on it where no full-standing road reached, so a time root also reached by a participant keeps its parent final score exactly. Unshared anniversaries: absence from the date-match order does not deny the floor, since the helper queues every candidate bearing the kind and picks it at a cap of one; the floor is served only from the reserved queue, and an unshared anniversary is passed to the helper with date match removed from the kind set it sees, keeping the kind on the root for the trace. The base is the top of the scene-words stack (cut from 63f176f, merging scene-words Task_3 when it lands); an Integration section says so and the not-before clause is satisfied. The selection order "topic-found first, then salience" for a crowded range or anniversary is gone: most recent first, as the description rule. + - Tradeoffs considered: keeping the draft's ordering rule for time kinds only was rejected because it would reintroduce a group order in the helper the rulings withdrew and would make time roots behave unlike the reminder roots already there; giving a time root the keyed roots' score of one was rejected because it would outscore every topic hit with a similarity it does not have; a time root carrying the salience as its cue component was rejected because salience is already a component and counting it twice is a time-adjacent weight nobody measured; letting date match share spare turns as the other given kinds do was rejected because a turn is filled whether or not anything matches (ruling 45) and the floor already reserves what the application asked for; a new floor-ineligibility flag on the helper's input was rejected in favour of filtering the kind set the caller already builds. + - User approval: decided under the standing instruction and logged for presentation. + - Record proposed: unchanged, at most one, in Task_3. +- 2026-09-22 Decision: recency fills the room nothing else claims, rather than contributing one occasion. + - Trigger / new insight: the decider asked that every measurement be read for what it means to the character. The companion's keyless probe showed that with nothing said the scene brought exactly one occasion, while the philosophy has a character walk into a room already carrying what the room calls for, including what happened this morning. The cause was this plan's own alignment, which capped recency at as many as the largest cap in force could hold before anything knew whether the room was claimed. + - Plan delta (what changed): recency contributes the latest eligible occasions up to the largest cap in force, in one bounded read; its floor reserves nothing at zero; time kinds still take no spare turn at root selection, so under a topic that fills every cap the parity guarantee is unchanged; the contributed set is newest first as a selection order, while the existing score still decides at every cap, so an older contributed occasion of higher salience may win a tight cap and a newer one may be left out. The date-match sources keep their bounded contribution: a range and an anniversary are given cues with a reserved count. + - Tradeoffs considered: a recency road that switches on the absence of a topic was rejected again; the room being unclaimed is the condition, not the silence. + - User approval: decided under the standing instruction and logged for presentation (rulings item 53). + - Record proposed: none. +- 2026-09-22 Decision: a time cue says that more exists rather than counting what it left out. + - Trigger / new insight: the recency count was dropped because it cost a query that grows with the store and informed nobody; the range's count is smaller but needs the same unbounded query over the span, and a reader of a question about a day needs only to know that the day holds more than came. + - Plan delta (what changed): the selector fetches one more than it contributes and the trace carries a marker that more lay inside the range; the acceptance asking for a count of three now asks for the two most recent and that marker. The group order for time-only candidates outside root selection is not restored: score decides there, which gives the same outcome for a candidate with no similarity score of its own. + - User approval: decided under the standing instruction and logged for presentation. + - Record proposed: none. +- 2026-09-22 Decision: a range the caller gives contributes as much as recency, not less. + - Trigger / new insight: the Tier A review of Task_2 found that ruling 53 had been applied to recency only: recency, a cue nobody gave, contributed up to the largest cap while the range, a cue the caller gave, contributed the larger of one and its floor. Asked what happened last Tuesday with nothing else said, the character would have brought one Tuesday occasion and filled the rest of the room with today. + - Plan delta (what changed): the range contributes the latest occasions inside it up to the largest cap in force, plus one to tell whether more existed; it reserves only its floor and takes no spare turn at root selection; in room nothing else claims, date-match candidates come before recency-only ones. The anniversary stays bounded to the larger of one and its floor, because nobody asked for it. + - Tradeoffs considered: letting the range outrank the topic by score was rejected, since time never changes a score; a reservation is how a given cue claims room. + - Lesson: when a rule changes for one cue kind, check every sibling kind for the same asymmetry. + - User approval: decided under the standing instruction and logged for presentation. + - Record proposed: none. + +- 2026-09-23 Decision: the slice measurement confirms the direction for what a caller gives and questions it for one thing nobody gave. + - Trigger / new insight: after-numbers at `d36d96d`. A caller's Tuesday range brings 5 of 5 Tuesdays where it brought none, newest first, keeping today's occasions; under a loud topic it takes one observation's place. Meeting a person by key on the anniversary of a shared occasion brings last year's occasion under a loud topic, at the cost of one topic episode (6 of 8 to 5 of 8). With the person absent or the name ambiguous, no anniversary comes. A morning at +09:00 matches the local day, not the UTC one. An ordinary unshared anniversary never displaces a recent day. All other families are unchanged. What this means for the character: when asked about a day, or when meeting someone on the day of something they shared, the character remembers it, at the price of one slot of the conversation, and the calendar alone brings nothing. Direction confirmed. + - Direction questioned: a highly salient unshared anniversary (something important that happened a year ago today) never comes in a year-deep store. It is cut at the root cap before its salience is weighed, because every zero-score root is ordered by time and twelve recent days fill the cap. Task_3's acceptance line "a more salient one still comes" held only in a fixture with spare root room. + - Plan delta (what changed): the fix belongs to the root order the consolidation slice rewrites: among equal scores, the root stage orders by the section's own standing measure (salience) before time, so nothing the section would choose is starved earlier (rulings log item 65). That acceptance line moves to the consolidation plan and is measured at its slice end; this plan closes with it recorded as moved, not met. + - Tradeoffs considered: a reserved root seat for unshared anniversaries (rejected: nobody gave it, and it adds a special case), a salience threshold (rejected: a new tuning value). + - User approval: decided under the standing instruction and logged for presentation. + - Record proposed: none. + +## Notes +- Risks: with a recency floor of zero, recency adds one cue-less root per retrieval, so the pollution and context-size baselines of ADR-I-0022 move only where a faint match sat at a cap; the companion still re-measures once. The score a time root carries (graph and salience alone) is an honest zero similarity, not a measured value; if the calibration shows contributed occasions displacing real matches at their floor, that is the floor value's problem, and if it shows them never surviving spare room at any salience, the companion reports it and no time term is added here. There is no relevance threshold under a floor (decision 25): a wide range gives date match a floor and a share of spare roots for whatever is most salient inside it, and the calibration measures that by score. The first two time reads are linear in episodes. +- Edge cases, with the expected result: an empty store brings nothing and no time kind is present; a range wider than the store contributes its most recent few up to the floor, counts the rest and names date match; an episode both today's latest and inside the range names both kinds in one slot with one score; an episode still running is placed by its recorded start; an offset of any size chrono admits derives a local date in Rust, so nothing is lost at query time; two writes of one instant whose offsets give the same local date are the same content. +- 2026-09-22 Decision: recency keeps no count of what it left out. + - Trigger / new insight: the worker measured the COUNT query at about 14 ms per retrieval over a thousand experiences, growing with the store; the number (the store size minus the contributed occasions) informs nobody, unlike the description searches' count over a bounded pool. + - Plan delta (what changed): Task_1's description and acceptance no longer require the count; recency's reads are bounded to the contributed occasions with the trace on or off. + - User approval: decided under the standing instruction and logged for presentation. + - Record proposed: none. diff --git a/docs/coding-agent/rules/orchestrator.md b/docs/coding-agent/rules/orchestrator.md index 76f2cd3a..04565c26 100644 --- a/docs/coding-agent/rules/orchestrator.md +++ b/docs/coding-agent/rules/orchestrator.md @@ -2,7 +2,7 @@ rule_schema_version: 2 suite_id: "rules-cm-20260719" rule_file: "orchestrator" -last_updated: "2026-09-14" +last_updated: "2026-09-22" --- # Orchestrator Repository Rules @@ -34,6 +34,15 @@ last_updated: "2026-09-14" - Design-value audit verdict mechanics and triggers are harness-owned (long-horizon-audit appendix; third-bounce and pre-merge-churn triggers). Repo policy retained: the audit is judged against this repo's roadmap deliverables and philosophy — does it serve a meaningful purpose NOW — and is assigned to a Claude Tier A agent. +## Measurement Of Behavior Changes (plan and task requirements) + +- Every implemented improvement or feature that changes behavior is measured, so the design and its assumptions are confirmed or corrected; a design that ships unmeasured is unfinished (decider ruling 2026-09-22, after a measurement forced a large design change). +- A plan's `Design` section names the measurement instrument, an existing generated family in the companion evaluation repository or the family it needs, and the number that would falsify the design; a plan that cannot say what would falsify it is a plan-review finding, not a wording nit. +- Measurement runs at checkpoints where the character's behavior can shift materially, not at every step (decider refinement 2026-09-22): once when a slice completes, against the falsifiers its plan names; once at the final reviewed tip of a task that introduces a new road or a new design rule the plan flags as high-risk; and when a review changes a design rule rather than fixing a bug. Each run is before and after on the same instrument and the same inputs, with identifiers ordered against time, run twice. Intermediate fix tips, bug fixes proven by red and green tests, docs, trace fields and cleanups are not measured. +- The plan validator has no measurement kind and no evaluation-side owner, so the item is carried as `kind: command`, `owner: orchestrator`, with `detail` naming the evaluation worker who runs it, the family, and the falsifying number; the orchestrator dispatches it and is accountable for its evidence. +- The before and after numbers go into the plan's Decision Log and into the report of the task or slice they measure; a plan is not complete until its slice-end numbers exist. +- Numbers are never relayed bare: every after-numbers report carries a paragraph on what the result means for the character against the behaviors the philosophy illustrates and an explicit verdict, direction confirmed or direction questioned; a result that contradicts the intended behavior reopens the design, not the acceptance line (decider instruction 2026-09-22). + ## Design-Consult Threshold (coordination/advice separation) - Escalation-ruling tiers and the blast-radius obligation are harness-owned (lifecycle-gates Escalation Ruling). Repo policy retained, stronger than the harness default: contract-shape escalations — public API surfaces, serialization schemas, cross-repo obligations, deferral-boundary questions — REQUIRE a design consult before ruling. diff --git a/docs/coding-agent/rules/reviewer.md b/docs/coding-agent/rules/reviewer.md index 6ebe2e5e..b5282fa7 100644 --- a/docs/coding-agent/rules/reviewer.md +++ b/docs/coding-agent/rules/reviewer.md @@ -2,7 +2,7 @@ rule_schema_version: 2 suite_id: "rules-cm-20260719" rule_file: "reviewer" -last_updated: "2026-07-23" +last_updated: "2026-09-22" --- # Reviewer Repository Rules @@ -25,6 +25,8 @@ last_updated: "2026-07-23" | Retrieval or entity-policy changes | Entity-neutrality check (no name/role special-casing) | roadmap invariant 2.7 | | Live integration evidence | Endpoint + pass/skip census; no silent skips | worker.md skip-gating notes | | Pruning or closed-contract changes | Touched-file suppression census; bidirectional totality evidence for each claimed single source; empty/non-empty parity tests for every adapter implementing the port | lesson 2026-07-21 (Task_3 pruning wave) | +| Plan review (any plan whose tasks change what comes to mind, its order or its standing) | The `Design` section names the instrument and the falsifying number, and every such task carries the measurement item; a plan that cannot say what would falsify it is a finding | orchestrator.md Measurement Of Behavior Changes (decider ruling 2026-09-22) | +| Implementation review of a task at a measurement checkpoint | Before and after numbers on the same instrument and inputs are present in the worker report; a task measured only at slice end is reviewed on its tests | orchestrator.md Measurement Of Behavior Changes (decider ruling 2026-09-22) | ## Review Heuristics diff --git a/docs/coding-agent/rules/worker.md b/docs/coding-agent/rules/worker.md index ce099d5e..dab6f220 100644 --- a/docs/coding-agent/rules/worker.md +++ b/docs/coding-agent/rules/worker.md @@ -2,7 +2,7 @@ rule_schema_version: 2 suite_id: "rules-cm-20260719" rule_file: "worker" -last_updated: "2026-07-23" +last_updated: "2026-09-22" --- # Worker Repository Rules @@ -13,6 +13,7 @@ last_updated: "2026-07-23" - Qdrant client deadlines must be configured through `QdrantConfig::timeout`; per-request builder `.timeout()` may be used only for an intentionally server-side operation limit verified by a live probe (lesson 2026-07-03: 2.41s -> 0.048s). - Diagnostics over generated plans must trace every compared field through the production-default constructor and include a negative regression using production-default options (lesson 2026-07-18). - New validators and admission checks classify their failures with an owned structured error type AT INTRODUCTION (typed variants/fields per the design's error conventions), with tests asserting variants and fields; anyhow/prose belongs only at outer boundaries. Three same-phase recurrences of retrofitting prose validators forced this rule (2026-07-23). +- A task whose plan names a measurement checkpoint at its tip is reported with the before and after numbers the evaluation side produced on the same instrument and inputs, both runs; a task measured only at slice end says so and is done on its tests and reviews (decider ruling 2026-09-22, refined the same day). ## Repo CI / Checks Mapping