Repository navigation
Release 1.9.0: time that answers the past, owner data, integrity, a dark router, and the memory judges - #244
Merged
Merged
Conversation
…ocess (PLAN 40.40) It seeded from string.GetHashCode(), which .NET randomises per process, so its "deterministic" vectors changed from run to run; on 4-dimension test vectors "Copenhagen" sometimes resolved to "Lyon", and ClosedFactTakesItsEdgeIntegrationTests failed in the v1.8.0 release run while the PR run of the same commit passed. The seed now comes from the text's SHA-256. Unit 6,085 + 42 and integration 601 (29 skipped) pass with the new vectors. Co-Authored-By: Claude Opus 5.5 <[email protected]>
….65) ExtractionOptions.BitemporalChanges, off by default. Without it every closing is a retraction: after "I moved to Madrid" the old city is no longer believed at all, so an as-of question read with today's belief cannot return it, and an undated new value counts as true at every past moment. With it, a closing says why (invalidated_reason): - a change ends the old value's valid time when the new one began (its stated start, else when it was said), records when that end was learned, and keeps it believed; - an undated new value starts when it was said (valid_from_inferred, read only as of); - a marked correction withdraws belief and leaves valid time alone; - a change's mirrored edge ends at the same moment. Live recall is unchanged either way: a change still stamps invalidated_at. Only the as-of fact queries read the reason, through one pair of predicates (TemporalQueries.FactBelievedAsOf / FactValidAsOf) so they cannot drift; firing keeps reading a changed value as withdrawn. Stores written without the option read exactly as before. New: FactClosureReason, an IFactRepository.SupersedeAsync overload taking it (the default implementation keeps the old semantics), MemoryHistoryRecord.ClosedAs, shown as closedAs by the MCP memory_lineage tool. Tests: 5 unit, 7 integration on a real Neo4j (two pin today's behaviour with the option off). Unit suites green; the related integration tests green (58). Co-Authored-By: Claude Opus 5.5 <[email protected]>
…5, G1) The Neo4j repositories stamped closings, live recall's "now", the recency reranker's "now" and updated_at from DateTimeOffset.UtcNow, while the persistence stage and the assembler used IClock. A host, a test or a replay that registered its own clock set only half of the times one recall compares. The fact, entity, preference, relationship and message repositories now take IClock (optional; the system clock without DI), the reranker takes the caller's now, and the streaming extractor stamps its chunk message from the clock. OneClockGuardTests scans AgentMemory.Core and AgentMemory.Neo4j and fails on a new wall-clock read outside five named exceptions (the clock itself, the per-day session name, the migration ledger, the bootstrap backfill, the malformed-data default). A_replayed_clock_stamps_every_time_a_closing_writes proves it end to end on a real store: created_at, invalidated_at, a change's end, when it was recorded and an undated value's inferred start all come from the replayed clock. Unit suites green; the full integration suite green (609, net10.0). Co-Authored-By: Claude Opus 5.5 <[email protected]>
… the clocks each question asks on (PLAN 40.66) The verb recalled every timestamped question at its own date on the valid clock and the machine's now on the transaction clock, with every fact stamped at ingestion time: right for a corpus ingested moments ago, and blind to both clocks. "As of 19 January, which city did the record show?" could not be asked of the store at all. Three switches, off by default so every sealed measurement keeps its path: - --bitemporal-clocks: a ReplayClock the profile registers as IClock (G1 makes every stamp read it), moved to each session's date before it is stored and extracted and to the question's date before recall; each question is recalled at the time it names (the library's own TemporalQueryParser, after a leading "As of <date>,") and, for a belief question, at the corpus's asof_instant, read from the corpus as shipped because AgentEval's entry model drops the typedmemeval block. - --bitemporal-changes: ExtractionOptions.BitemporalChanges (40.65). - --preset: the LongMemEval verb's product presets. The dry run now prints one question of each clock with the instants it will be recalled at, so a run is proven to see the phenomenon before anything is spent. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Found live in a demo application on 2026-10-02: "Rosa | is named | Rosa" was stored and recalled as the person's first memory, taking a recall slot and saying nothing. The naming fact itself is already protected from being renamed under the user's name, so the tautology arrives from the model's own subject. The fix sits where a fact's stored subject and object are decided: when they are the same name (case and outer spaces ignored), the fact is skipped with an IngestionItemOutcome (Validation, Skipped, MEMORY_FACT_TAUTOLOGY) and a debug line, and the facts beside it are written as before. Unit suite green (6,093); the extraction integration tests green (29). Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ging it (PLAN 40.65)
With BitemporalChanges, write-time supersession closed every replaced value as a change.
A new value that starts no later than the one it replaces ("Calderwick as of February",
then "actually Ardenholm as of February") is a correction of that period: the old value
was never right for it, so belief is withdrawn and its valid time is left alone. Found
reading the TypedMemEval Bitemporal corpus, whose records are corrected exactly this way.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
--judge-max-output-tokens, default unchanged (AgentEval's calibrated 1500). On the Bitdeer lineage the judge is GLM-5.3, a reasoning model; at 1500 it ran out twice on one question and returned no verdict, which is spend with no measurement. The lineage records 4096 for the LongMemEval judge; runs on it now pass the same. AgentEval stamps the value per run. Co-Authored-By: Claude Opus 5.5 <[email protected]>
MergeEntities moved MENTIONS, SAME_AS and RELATED_TO and left (:Fact)-[:ABOUT]->(source) behind, on an entity every live read skips. They now move, under the same owner rule as the edges. Move rather than chase merged_into: one rule with the other links, nothing for readers to learn. Co-Authored-By: Claude Opus 5.5 <[email protected]>
RenameOnCorrectedName restated the facts said about the old name and left the ones naming it as
their object ("Priya | walks | Rex" after "the dog is Max, not Rex"). They now follow: closed and
restated, owner-scoped; a naming fact of another subject is left alone. Restatements close as
corrections (BitemporalChanges keeps their valid time) and, with LinkFactsToEntities, get the
ABOUT links a new fact would. IFactRepository.GetByObjectAsync, default none.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
… second pass) The matrix's "a date in the question becomes as of" row had unit tests only. On Neo4j: with ResolveTemporalQueries, "Where did the user live 6 years ago?" after a move recalls the old city and a question naming no time stays live; off (the library default), the date is not read. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ith no model (PLAN 40.57) A pack (agentmemory-pack/1) carries its ontology, conversations with what extraction yields for each message, the storage they must leave and the questions they must answer, with the memory kinds that hold each answer (the routing set's gold, 40.61). The runner builds the real stack per pack (its preset and switches, scripted extraction, stub embeddings, a replayed clock) and checks storage, recall membership and, on every question, isolation. Each run writes under a fresh prefix and deletes nothing. Six core packs ship (changes and corrections on both clocks plus a date in the question, names, preferences, episodic, the graph, working memory); four planted defects fail the checks that name them. agentmemory evaluate --pack core|<file>|<dir>. AgentMemory.Validation is HELD. Found by the names pack: the rename's merge cleared the survivor's embedding and nothing re-embedded it, so a renamed person dropped out of entity recall. Re-embedded from its name. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…model (PLAN 40.61) routing-set/1: each question or turn with the kinds that hold its answer, as groups (any kind in a group serves it, every group is needed) plus kinds that may hold it too, and a dev or held-out split. The scorer reports served answers, group recall, reads and wasted reads per item, silence on turns that need nothing, and per-kind selection. agentmemory routing-score --set <file> [--split] [--policy everything]: today's recall, the baseline a router must beat. The frozen set itself stays out of the repository: its turns come from recorded demo conversations. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…(PLAN 40.56) Recall read every kind on every turn; the Agent Framework hook (#88) could skip kinds but no policy chose them per question, and no other host had it. MemoryOptions.Routing.Enabled (off): a statement reads nothing; a question reads facts plus the graph, preferences or the session's relevant messages when its words call for them. Restrict-only on the effective caps (a host's caps are kept, never raised); rules are data (Routing.Rules adds a host's or a module's kind); the choice and the rule behind each kind are recorded (MemoryRoutePlan.Routed, memory.route.kinds). RecallRequest.Question carries the turn being answered; the provider sets it. Dev split of the frozen routing set (7595f87c): 51/51 answers served, 1.59 reads per turn (read everything: 5.00), silent on 39/39 turns that need nothing. Every core validation pack passes with routing on; a statement recalls nothing on a real store. These rules are frozen at this commit for the held-out split. routing-score --policy rules. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…e (PLAN 40.61) Co-Authored-By: Claude Opus 5.5 <[email protected]>
…PLAN 40.55 c) The as-of path ran no relevant-message search, so a date in the question dropped the session's episodes. It now runs the live search and keeps what was said by the transaction instant and not withdrawn by then, GetRecentMessagesAsOf's rule; MaxRelevantMessages leaves the as-of divergence list, and TotalItemsRetrieved counts the section. Co-Authored-By: Claude Opus 5.5 <[email protected]>
IngestionItemOutcome.Effect: Created, AlreadyStored (the upsert matched a stored fact, preference or relationship), MergedWithinExtraction (a duplicate folded into another item); Closed lists the facts a change or a correction closed, collected where the closing is written (an AsyncLocal set for the persist in flight), so no extra query. Entities report Unreported until resolution says whether it matched. Additive: every field a caller read before reads the same. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…AN 40.50, G6) IMemoryIntegrityService and agentmemory integrity [--owner]: no edge joins two owners' memories; a relationship edge belongs to its owned endpoints' owner; a fact closed as a change or a correction has its successor, and a live fact has none; validity windows end after they begin; every fact has a source (a warning: an API write may not). One read query per rule, a count and up to five ids each. Each rule fails on a planted defect and passes on a clean store; every validation pack now checks it on what its ingestion wrote, and all six hold. Reworded from the plan: 'no live fact past its validity' is legitimate in a bitemporal store (a fact that ended is still known); what is inconsistent is an inverted window or a live fact with a successor. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The history read already carried validity, why a row closed, its successors and sources; it lacked an instant. MemoryHistoryQuery.AsOf (agentmemory history --as-of): rows written after it are left out, and live-only keeps a row invalidated after it (it was live then). A dossier or a module reads an owner's memory through it with no Cypher of its own. Co-Authored-By: Claude Opus 5.5 <[email protected]>
IMemoryOwnerDataService and agentmemory owner export|import|erase. Erase deletes every node stamped with the owner and their conversations with every message, in batches, reporting what went by label; the CLI asks for --confirm. Export: entities, facts and preferences live and closed, relationships, supersessions and ABOUT links (agentmemory-export/1, no embeddings). Import: fresh ids under an owner, embedded again, closings and links kept; JSON metadata turned back into plain values. On a real store an erased owner leaves no node and others are untouched; an export carried through JSON and imported under a new owner recalls the same, live and about the past. The repositories' node mappers became internal so the export reuses them. Not carried: valid_until_recorded_at (documented). Co-Authored-By: Claude Opus 5.5 <[email protected]>
IMemoryRecallExplainer.WhyNotFactAsync: for a recall request and a fact, the gate that kept it out, in the order recall applies them: another owner's, the router, closed (with what replaced it), decayed, invalidated, outside its valid time, below the similarity floor (its score on the store's (1 + cos) / 2 scale, and the floor), outranked, cut by the budget; or recalled. Off the hot path: it reads the fact and runs the recall again; normal recall is unchanged. On a real store a planted fact below the floor is reported with its score, a closed one with its successor. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…review) The export took every entity of the owner's, merged-away and forgotten ones included, so an import revived them live beside their survivor; and a fact still linked to such an entity (data from before F8) was linked, in the same store, to the original owner's node, the edge the integrity check forbids. Export now takes live entities, and only the links and relationships whose ends are exported or shared; an import places a shared end by its id. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…e branch review) With BitemporalChanges a change keeps the old value believed as history, and a correction closed only live facts, so "I never lived in Bilbao" left a past question answering Bilbao. A new correction now also closes change-closed facts of the value it names, and the store turns that closing into a correction from now on (reason and instant). A value said again with the old one named (the correction is not new) leaves the past alone. Off: unchanged. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ty 0.10, PLAN 40.62) IMemoryRouter.Route(question, additionalRules): a module's own rules run in the same engine and on the same question as core recall, so the two never disagree about the core kinds; added rules compile once per list (a module set holds one) and without backtracking, as spec 0.8 §6.2 asks of patterns over untrusted text. IMemoryRouter.Check names a rule that needs backtracking or does not compile. Neo4jMemoryContextProvider.QuestionOf (protected static): the one derivation of the question, now used for RecallRequest.Question, for a subclass that routes module sections. Co-Authored-By: Claude Opus 5.5 <[email protected]>
… (PLAN 40.57) A pack session can be shared (ExtractionRequest.ShareWithEveryone, no user): what it says is stored with no owner. core.shared: a book taught by one owner is recalled by the other, while the teacher's own memories stay hers; isolation holds on every question. Passes plain and routed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
docs/time-model.md: the two clocks, how a change and a correction are closed with and without BitemporalChanges, asking about the past, what reads valid time, what is not modelled, the NAMS backend. docs/routing.md: what the router chooses, configuring rules, how it is scored, modules, the NAMS backend. CHANGELOG: stores written before keep their closings. Co-Authored-By: Claude Opus 5.5 <[email protected]>
IMemoryRecallExplainer.WhyNotAsync(request, kind, id) replaces the facts-only call (unreleased): facts, entities and preferences go through one path of gates. New gates: merged (an entity merged into another; the history now carries MergedIntoId) and not-embedded (no embedding, so similarity search cannot find it; found on a real store: a direct merge leaves the survivor unembedded until the backfill runs). Real-store test: merged names its survivor, not-embedded until the backfill then recalled, another owner's, a preference recalled then invalidated. Co-Authored-By: Claude Opus 5.5 <[email protected]>
routing-score gets --policy today: an Agent Framework host's default, TrivialTurnRecallPolicy, which narrows a greeting or a thanks to the recent messages and reads every kind on any other turn. On the frozen set it scores the same as reading everything (0/39 silent on dev): the turns that need no memory are statements, not greetings, so the earlier baseline was fair. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…(PLAN 40.67-40.82) tools/AgentMemory.RouterArena: a recorder (a throwaway Neo4j, local embeddings, every turn recalled through every door), askers for a model lane and a System One decision model, a referee that scores routing (served, unneeded let in, doors, time), one contestant per folder, rounds, finals on unseen turns, a timer, and the gate pairs a local gate is trained on. Its private data is read from --data or ROUTER_ARENA_DATA. Validation packs carry reasoning traces and run a decay pass; the CLI's routing record verbs moved into the arena; a few comments no longer name a private host. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015c3cQexHvNzUZYcexSMaYH
…d surname (PLAN 40.91, 40.93) 40.91. Relationship recall returned the same few edges whatever was asked: they were ordered by confidence and age only. They are now ordered by the rank of their best end among the recalled entities, then among the names the recalled facts give; confidence and recency only break ties. And a relationship whose endpoint was met in an earlier message is no longer dropped at extraction: with an owner, persistence resolves the endpoint against the owner's stored entities, and skips it there, with the same outcome as before, when none is found. 40.93. Similarity alone no longer makes two people with different given names one person. "Erik Halvorsen" and "Sven Halvorsen" score 0.88 on name embeddings; resolving one onto the other erased the brother and pointed his relationships at the owner. A different order, an initial, a short form, an honorific or a one-letter typo still resolve; exact names and aliases are never affected. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015c3cQexHvNzUZYcexSMaYH
…and takes a logging hook ValidationPackRunner registered a caller's IEmbeddingGenerator through a factory, so each pack's container owned it and disposed it with the pack's store. Every pack after the first then wrote its memories without embeddings: the failure was logged, but the runner registered no logging, so nothing showed. The caller's generator is now wrapped in a borrowing adapter whose Dispose does nothing; the caller keeps ownership. An integration test runs two packs on one runner and fails without the fix. The runner also takes an optional logging configuration, so a host sees what the stack decided and what failed. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015c3cQexHvNzUZYcexSMaYH
…ge on the write path closes what a change replaces (PLAN 41.06)
Two seams in Abstractions, both used only when registered:
- IMemoryGate: given the turn, the conversation and every memory a wide recall found, the probability that each one
helps the reply. Measured on two unseen test sets, a gate in front of today's recall served 96.1% and 96.5% of turns
(every needed memory in the prompt) against today's 67.6% and 76.8%, with less than half the memory tokens.
- IMemoryUpdateJudge: given what was said and pairs of (new memory, most similar stored memory), the probability that
the new one replaces the stored one. PersistenceStage takes it as an optional last constructor parameter; for each
fact and preference an extraction created it asks about the owner's top UpdateJudgeCandidates (3) live memories of
the same kind, and closes those at or above UpdateJudgeThreshold (0.8): a fact as a change (valid time ends, belief
stays), a preference by supersession. Without a judge the write path is exactly what it was. A judge that fails or
leaves a pair unanswered closes nothing and says so in the log.
AgentMemory.Gate 0.1.0 (held from release, [Experimental("AMGATE001")]) implements both over System One endpoints:
- AddAgentMemoryGate decorates the registered IMemoryContextAssembler. Modes: Today (the inner assembler, unchanged),
Judge (a wide recall, then the blended judges keep what scores at or above the threshold), Everything (the wide recall
as found). Today's recall is the fallback: on a timeout (3 s), an error, or a memory type no judge answered, the
request goes to the inner assembler unchanged. The decision is in the context's metadata (gate.*) and in the log;
every fallback is a warning with its reason.
- Several judges blend by weight, renormalised over those that answered; an optional bank of labelled examples gives
each question its three nearest by embedding.
- The update judge asks one keyed question per pair with written criteria.
ExtractionStageResult gains SourceText (what the extraction read), so the update judge is shown what was said. The
service-interface catalog in docs/architecture.md and its guard go from 50 to 52.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015c3cQexHvNzUZYcexSMaYH
… through the library (PLAN 40.83-40.98, 41.07, 41.08) - jar cv|freeze|read|replay: the selection in .NET. Every family is estimated by five-fold cross-validation on the training turns, chosen on all of them and frozen with a fingerprint; a test set is read once and a second read is refused; replay rescores a set already read and compares with the recorded read. - store-jar: writes each labelled turn through the real write path on a throwaway store loaded with the world, and records what was written, so storage routers are decided offline on recorded writes. - showcase: a few questions through the library end to end, three ways (everything a wide recall finds, today's recall, the gate), with tokens, recall and end-to-end time, replies and the context each saw. - store-showcase: a conversation of changes written twice (today's write path, then with the update judge), read back (created, closed, still live), then asked about through the gate. - The facts the recalled facts replaced, read beside the as-of readings (40.91); a second world with its own person (40.92); doors timed alone and the store warmed on every turn (40.83). - Every run logs to the console: warnings from the whole stack, the gate's and the write path's decisions. A run that loads the same world every turn embeds each text once (a cache in front of the embedder). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015c3cQexHvNzUZYcexSMaYH
A substitute matched a non-nullable MemoryScope with Arg.Any<MemoryScope?>(); a test blocked on a task instead of awaiting it. The Release build of the solution is back to zero warnings. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015c3cQexHvNzUZYcexSMaYH
… 41.06) MemoryGateMode.Today becomes Floor: recall cut by the similarity floor (RecallOptions.MinSimilarityScore) and a cap per type, as without the gate, and the fallback when no judge answers. The context's metadata and the fallback warnings say "floor". Everything's summary now says what was measured: ten to seventeen times the memory tokens of Judge, replies graded about five points more accurate. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015c3cQexHvNzUZYcexSMaYH
… 41.10) A write whose embedding call fails stores the memory without one, and only logs it: recall by meaning never finds it again. That is how a test runner that disposed its embedding generator went unnoticed for a whole storage run. The integrity check now counts live facts, preferences and entities with no embedding (recall.memory-has-embedding, a warning: the store is repairable with IMemoryMaintenance.GenerateEmbeddingsBatchAsync, and no write fails). Closed memories are not counted. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015c3cQexHvNzUZYcexSMaYH
…41.06, 41.10) Measured again on the fixed instrument: a judge asked whether the stored memory "stops being true now: changed, cancelled or corrected", with past results and earlier events named as history that stays true, closed more replaced memories without closing history. With a write gate before it, it met every condition written before the test reads on both unseen sets: no labelled write lost, fewer unasked writes, 13 of 21 replaced memories closed, one wrong closure within the budget. UpdateJudgeThreshold's default becomes 0.65, the bar cross-validation chose for that question. The changelog records the gate, the update judge, the integrity warning and the two recall fixes. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015c3cQexHvNzUZYcexSMaYH
…ettles
IMemoryGate, IMemoryUpdateJudge, their request and decision records, and ExtractionOptions.UpdateJudgeThreshold and
UpdateJudgeCandidates carry [Experimental("AMGATE001")], like the AgentMemory.Gate package that implements them. They ship
in the release without being locked by SemVer: the write side is still being measured, and a writer per memory type may
change what the update seam looks like. Core uses the update seam internally and suppresses the diagnostic.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015c3cQexHvNzUZYcexSMaYH
…ark router, and the memory judges Version 1.9.0; the changelog's unreleased section is dated 2026-10-05. Two packages added on this line stay held from NuGet (AgentMemory.Gate, AgentMemory.Validation), like the Extensibility pair. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015c3cQexHvNzUZYcexSMaYH
joslat
force-pushed
the
phase40/core-first
branch
from
October 5, 2026 08:04
8766b16 to
f5476be
Compare
…lease review) Without an owner, the judge's candidates came from a vector search with no scope, which reads every owner's memories, so an ownerless write could have closed another person's fact or preference. Candidates are now only the writer's own memories, or, without an owner, ownerless ones: a single-tenant store, where nothing has an owner, keeps the judge. A test for an ownerless write and for another owner's write fails without the fix. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015c3cQexHvNzUZYcexSMaYH
…very recall, and reads its configuration section (PLAN 41.19) Every live recall through the gate now carries a MemoryGateTrace in its metadata, whatever the mode: each memory considered, the judges' probability and whether it went in, and why recall fell back to the floor. A memory.gate span carries the counts (never a memory's text). Judge mode with no judge configured is the floor, said once, without paying for a wide search. AddAgentMemoryGate(IConfiguration) binds the AgentMemory:RetrievalRouter section, and an undefined mode fails at startup. The preferences the update judge closes are on the write's outcome, as facts are. AgentMemory.Gate is listed for the release: it rides the core version, opt-in, with every public type experimental (AMGATE001), and the AgentFramework consumer check composes it from the package. Docs: a section in routing.md. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015c3cQexHvNzUZYcexSMaYH
…tegration test, found in CI)
Its vectors were one-hot at string.GetHashCode() over the fixture's four dimensions: the hash is randomised per process,
so on some runs a quarter of unrelated texts were identical and the pack's correction read as a restatement ("Ana works
at Northwind" stayed live). It now wraps StubEmbeddingGenerator, whose vectors are seeded from the text's SHA-256 (the
40.40 lesson), and only watches for disposal. Three local runs of the validation integration tests: 21/21 each.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015c3cQexHvNzUZYcexSMaYH
This was referenced Nov 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release 1.9.0
Version 1.9.0 with a dated changelog (2026-10-05).
AgentMemory.GateandAgentMemory.Validationare new on this line andheld from NuGet, like the Extensibility pair; the judge seams in Abstractions are
[Experimental("AMGATE001")]until thestorage work settles.
What this branch does
Core-first work on the memory layer, then the memory routers it measured: one clock, time that answers questions about
the past, owner data you can erase and export, an integrity check, a dark router, and an opt-in judge at the fan-in and on
the write path. Everything new that changes behaviour is off unless registered or enabled; the defaults a host runs today
are unchanged except where a fix says so.
Time
IClock(40.45).changes; a correction reaches a value a change made history (40.65, B-13).
that names a time keeps the conversation's messages (40.55 c).
Owner data and integrity
keeps, read-only (40.50); why a memory was not recalled, for facts, entities and preferences (40.49).
name reaches the facts that name it (38.2).
Recall fixes
endpoint was met in an earlier message is resolved against the owner's stored entities instead of dropped (40.91).
Routing
IMemoryRouter, dark: which memory kinds a recall reads, per question; restrict-only and deterministic (40.56), withthe module contract for extensibility (40.62).
IMemoryGateandIMemoryUpdateJudgeseams (41.06), used only when registered:it served 96.1% / 96.5% of turns with every needed memory in the prompt, against 67.6% / 76.8% for today's recall,
with less than half the memory tokens;
PersistenceStage, closes the stored fact or preference a new one replaces (threshold 0.8,top 3 similar live memories). Without a judge the write path is exactly what it was.
AgentMemory.Gate0.1.0 implements both over System One endpoints. It is held from release and marked[Experimental("AMGATE001")]. Modes Today / Judge / Everything; today's recall is the automatic fallback on atimeout, an error or an unanswered memory type, with a warning naming the reason.
Validation and tooling
runner no longer disposes a caller's embedding generator (every pack after the first ran without embeddings) and
takes a logging hook.
tools/AgentMemory.RouterArena, not packaged): frozen-set scoring, the jar'scross-validated selection with one read per test set, a storage jar on the real write path, and showcases through
the library. Every run logs the stack's warnings and the components' decisions.
StubEmbeddingGeneratorreturns the same vector for the same text in everyprocess (40.40).
only logs it).
Checks
AgentMemory.slnx, every sample included: 0 warnings, 0 errors.(Bitdeer): no failure or warning lines, memory recalled across turns.
net8.0, net9.0 and net10.0 (12 of 12).
🤖 Generated with Claude Code
https://claude.ai/code/session_015c3cQexHvNzUZYcexSMaYH