ID-132 SPEC — G-MEMO-DELTA: per-concept memo delta-determinism ({132.38})
ID-132 SPEC — G-MEMO-DELTA per-concept memo delta-determinism
Section titled “ID-132 SPEC — G-MEMO-DELTA per-concept memo delta-determinism”Kind: Spec slice — a standalone, ratifiable slice that defines numbered, testable
memo-determinism invariants (MD-N) the Orchestrator can ratify and later dispatch
implementation from. It does not implement; it specifies the per-concept content-versioning
mechanism that makes BI-18 (delta-only determinism) actually satisfiable against the installed
cocoindex==1.0.7 engine.
Authored: 12/07/2026 · Task Planner (fresh dispatch, {132.38} DESIGN-FIRST slice).
Sibling dependency: {132.35} depends on this slice ({132.38}) — Task-132-internal, sibling-only;
no cross-Task dependency, no escalation.
Governing rulings to respect (cite, not re-litigate): DR-047 (pipeline degrade posture is
scoped narrowly — never a silent correctness loss; this slice’s over-invalidation bias is the
DR-047-compliant choice, MD-6), DR-054 / DR-027 (the landed client-overlay path — the
EffectiveOntology threads through the concept-write gate; MD-9 rules it OUT of the Pass-1
draft fingerprint), DR-055 (deploy topology — the forced-run surface that gives the memo its
ComponentContext, MD-1). This slice formalises; it does not re-open any of those.
Owner ratification (S469 — READ FIRST; binds over the OQ recommendations + DR-intent below where they differ)
Section titled “Owner ratification (S469 — READ FIRST; binds over the OQ recommendations + DR-intent below where they differ)”Both open questions were owner-ratified at S469 close; the core mechanism (MD-1..MD-7, MD-9..MD-11) is ratified as written. Two decisions reverse the Planner’s in-line recommendations — where this block and the text below disagree, this block wins (now also binding as DR-060):
- OQ-MD-1 → MANUAL
version=bump, NOTdeps=auto-invalidation. Drafting-config (prompt/model/max-tokens) changes do not fold into the logic fingerprint and do not auto-invalidate the corpus. A config-driven re-draft is a deliberate operatorversion=bump, recorded in the bundle’s OKF §7log.md(already emitted bybundle_writer.append_log_entry). Rationale (OKF-aligned): OKF treats concepts as durable, curated, git-versioned artifacts (“indefinite curation, not regeneration”); silently re-billing a full-corpus draft on a prompt tweak is contrary to OKF intent and does not scale to a live client corpus. A prompt bug-fix that genuinely needs a re-draft is still one explicit, loggedversion=bump. Implementation impact: MD-8 → do not adddeps={...}to the decorator; keepversion:as the documented manual lever; data-change staleness is still auto-handled per-concept bycontent_version(MD-3/6/7, unchanged). - OQ-MD-2 → id-131 schema migration (
updated_at+ON UPDATEtrigger), NOT the producer-side content-hash.entity_mentions/entity_relationshipsget a realupdated_atcolumn + trigger; the version aggregate then usescount(*) + max(updated_at)uniformly across all tables. Implementation impact: MD-7’s “MUST contribute a content hash” for those two tables is superseded — they useupdated_atlike the rest. Sequencing: {132.38} implementation now depends on that id-131 migration landing first — it is no longer a single self-contained producer dispatch (the spec’s original single-dispatch framing is void; the migration is the gating predecessor).
Empirical-verification note (pre-ratification, external library
cocoindex==1.0.7). Every engine claim below was import-and-call verified against the installed package on 12/07/2026; file:line cites are into the installed source (~/Library/Python/3.14/lib/python/site-packages/cocoindex/_internal/). Results:
Symbol (pinned cocoindex==1.0.7)Claim verified Result _internal.function._AutoFunctionBuilder.__init__@coco.fnexposesmemo, memo_key, version, logic_tracking, deps(NOttl)PRESENT _internal.function._apply_memo_key(args, kwargs, spec)a param mapped to Noneis excluded from the fingerprint input (('KEY','SRC') → ('KEY',))PRESENT _internal.memo_fingerprint._canonicalize(<frozen dataclass>)fingerprints all fields in definition order; adding/changing content_versionchanges the canonical form; equal value → identicalPRESENT _internal.memo_fingerprint._canonicalize(<obj holding a lock/pool>)pickle fallback raises TypeError: Unsupported type for memoization key: …(reproduces staging RUN 1’sLRecordsSourcefailure)PRESENT (confirms the bug) _internal.function._compute_logic_fingerprint(fn, deps=…)/version=…depschange → logic-fp change (stable when unchanged);versionbump → logic-fp changePRESENT No
ABSENT/SIGNATURE_DRIFT. All other symbols cited (ConceptKey,enrich_concept,list_concepts,LRecordsSource) are internal Canonical Platform surfaces — import-and-call N/A.
Context
Section titled “Context”Code-intelligence orientation (verbatim; repo:'canonical'). Per the Planner code-intel binding
rule; tool outputs quoted verbatim and cross-checked against direct file reads.
gitnexus_query({query:'concept memoisation enrich fingerprint', repo:'canonical'})→"processes": [],"process_symbols": [].definitionssurfaced the exact symbols this slice touches, verbatim:Function:…/producer/enrich.py:enrich_concept(startLine 572, endLine 638, module "Producer"— line extent re-pinned to HEADc3e8f734after the a997fb3a docstring expansion shifted the module by ~32 lines; C-audit Amendment B), plus the composition contextFunction:…/producer/flow_def.py:_draft_concepts(197–232) and…/producer/flow_def.py:run_producer_flow(304–469). No memoisation/fingerprint/content-version process exists — confirming the delta signal is net-new capability over the existing draft path.gitnexus_context({name:'enrich_concept', repo:'canonical'})→"uid": "Function:…/producer/enrich.py:enrich_concept"(572–638re-pinned, Amendment B);"incoming": { "calls": [ { "name": "_draft_concepts" } ] }(the sole caller — the plainforloop, NOT a cocoindexmount_each);"outgoing": { "calls": [ … "list_concepts", "read_concept", "run_tool_use_loop", "build_concept_frontmatter" … ] };"processes": [].gitnexus_context({name:'list_concepts', repo:'canonical'})→"status": "ambiguous", 6 candidates; the production symbol isMethod:…/sources/l_records.py:LRecordsSource.list_concepts#0(451–463),"outgoing": { "calls": [ "_list_topic_concepts", "_list_product_concepts", "_list_company_concepts", "_list_certification_concepts", "_list_case_study_concepts", "_list_won_bid_case_study_concepts" ] },"incoming": { "calls": [ "find", "run_producer_flow", … the TestListConcepts* suite ] }. The six hard-coded per-type enumeration methods are exactly the surface the content-version aggregate attaches to.
Direct reads (cross-check) — the seams this slice governs:
producer/enrich.py:572@coco.fn(memo=True)onasync def enrich_concept(key: ConceptKey, source: Source, *, model=ANTHROPIC_MODEL, max_tokens=_MAX_TOKENS_PASS1). The current module docstring (:50-59) asserts “source… is not part of the memo fingerprint’s data-varying surface” — this is false against the installed engine (the engine fingerprints every arg; that is the RUN-1 bug).enrich_conceptcallsawait source.read_concept(key)(:594) with the SAME key objectlist_conceptsreturned.sources/l_records.py:102-181@dataclass(frozen=True) class ConceptKey— 7 identity/locator fields (rel_path,concept_type,scope_tag,domain,subtopic,entity_id,workspace_id);__post_init__validates membership. It carries no content-hash / updated_at / version field, so two enumerations of the same concept over CHANGED content produce an identical key.sources/l_records.py:452-557list_concepts()fans out to the six_list_*methods; eachSELECTalready carries a deterministicORDER BY(:234-236module note). The enumeration queries select only identity (DISTINCT scope_tag,DISTINCT canonical_name, buyer, …) — no content-version column.sources/l_records.py:324-341— theentity_mentions/entity_relationshipscolumn lists carrycreated_atonly, noupdated_at; and (flow.py:2598,:2666,:3366-3414) these two tables are written by row-level UPSERT (ON CONFLICT), so an in-place edit to an existing mention (e.g.confidence,context_snippet,metadata) does not movecreated_at. Acount(*) + max(created_at)signal is therefore blind to in-place mention edits (MD-7).producer/flow_def.py:215-217—enrich_conceptis invoked from a plainfor key in conceptsloop (draft = await enrich_concept(key, source)), not a cocoindex-composed component.server.py:1051-1072(verified engine note) +:1091-1138_build_forced_producer_report— the forced-run surface constructs a dedicatedcoco.Appand runsrun_producer_flowinside itsmain_fnunderupdate_blocking(), which givesenrich_concept’s@coco.fn(memo=True)a live ComponentContext. The engine’s own gate (function.py:741-746sync /:1231async:parent_ctx is None → execute UNMEMOISED) means the memo is inert without that context — the forced-run App and the post-walk hook are what make it engage (MD-1).producer/bundle_writer.py:242-250bundle_write_path_for_key(key)derives the physical path fromkey.rel_path(+concept_type/workspace_idfor the won-bid redirect) — never a content-version. Confirms a newcontent_versionfield will not leak into the write path (MD-4).producer/prompts.py:61PASS1_INSTRUCTION_PROMPT = """…"""andextraction.py:119ANTHROPIC_MODEL = "claude-opus-4-6"— module-level constants imported byenrich.py(snapshot-safe fordeps=, MD-8). Neither appears inenrich_concept’s own body AST (they enter via_cached_system()/ the default kwarg), so the AST logic fingerprint does not track them.- Effective ontology reach.
grep -rn "EffectiveOntology|effective_ontology|overlay"overenrich.py,agent_loop.py,prompts.py→ zero hits. The DR-054/DR-027 overlay threads into the concept-write gate ({132.34}/{132.35}), downstream of the Pass-1 draft — it does not shape the draft (MD-9). ccc searchfallback: not required — gitnexus + direct read both returned the real surface. No greenfield disclaimer applies.
Problem
Section titled “Problem”BI-18 (delta-only determinism) is UNSATISFIABLE today in both directions, empirically established in staging RUN 1 (18/18 concepts) and confirmed against the installed engine:
- The
sourcearg is unfingerprintable → every draft aborts.enrich_conceptis@coco.fn(memo=True); the engine fingerprints every positional arg (memo_fingerprint.py:372-401_make_call_canonical→fingerprint_call:412-440).sourceis anLRecordsSourceholding an asyncpg pool; it has no__coco_memo_key__and is unpickleable, so_canonicalizehits the pickle fallback and raisesTypeError: Unsupported type for memoization key: LRecordsSource(memo_fingerprint.py:360-369). RUN 1: 18/18 failed with exactly this. Blocker 1. - Even with
sourcefixed, the fingerprint is IDENTITY-ONLY → stale drafts served silently.ConceptKeyis a frozen dataclass, so_canonicalize_dataclass(memo_fingerprint.py:131-151) keys on its field values — but every field is a locator/identity token.list_concepts’s six sub-queries select no content-hash /updated_at/ version. A record edit that leaves concept identity unchanged (e.g. an answer rewrite under the samescope_tag, aconfidencebump on an existing entity mention) memo-hits → the stale prior draft is returned silently. That is a direct violation of BI-18 direction 2 and of DR-047’s no-silent-correctness-degrade posture. Blocker 2.
A canary test scripts/tests/test_l_records_source.py::TestMemoKeyProtocolEscalation (commit
a997fb3a, in-flight) pins the CURRENT unfixed state green against the installed engine; this slice
names its required evolution (MD-11).
Non-goals.
- Making the memo engage. The ComponentContext precondition is already satisfied by the
forced-run App and the post-walk hook (
server.py:1091-1138, MD-1). This slice does not touch that wiring; it states the precondition so the Checker verifies the fix is exercised where the memo is live. - Composing the effective ontology into the Pass-1 draft. Ruled OUT (MD-9); the overlay is a write-gate concern (DR-054/DR-027), not a drafting input.
- Changing the enumeration grain / concept identity (BI-2).
content_versionis additive; the identity fields,__post_init__validation, dedup, routing and physical write path are unchanged (MD-4). - A schema migration on
entity_mentions/entity_relationships. Closing theirupdated_atgap producer-side (content-hash) is the in-scope choice; an id-131 schema addition is an OQ, not a requirement (OQ-MD-2). - Invalidating a concept when a cross-linked concept’s content changes (BI-9).
read_concept_raw/sample_rowscan target any catalogue ref for cross-linking narrative;content_versiondoes not track this transitively (MD-7a). Named as an accepted residual limitation, not solved here.
Behaviour — memo-determinism invariants (MD-N)
Section titled “Behaviour — memo-determinism invariants (MD-N)”Each invariant carries an inline
(per DR-NNN)tag where a ruling drives it, and a*Test:*clause the Checker uses as its acceptance criterion. MD-N numbers are append-only. The empirical tests (MD-2/3/8) run at the_canonicalize/_compute_logic_fingerprintlevel against the installed engine — no Rust/LMDB boot required, mirroring the existing canary.
Fingerprint correctness
Section titled “Fingerprint correctness”-
MD-1 (memo-engagement precondition — DR-055). The content-version mechanism is load-bearing only where the memo engages: a live cocoindex ComponentContext. The forced-run App (
server.py:1091-1138) and the post-walk hook runrun_producer_flowunderupdate_blocking(), which supplies that context; the bare_draft_conceptsloop (unit tests, cocoindex stubbed) runs unmemoised by design (function.py:741-746/:1231:parent_ctx is None → execute raw). No change is made to this wiring; it is stated so the BI-18 Run-1 proof (MD-10) is run through a memo-live surface, never the bare loop. Test: the forced-run App path establishes a ComponentContext (existingserver.pycoverage); a directenrich_concept(key, source)call with no ambient context executes without attempting a fingerprint (noUnsupported typeraise) — the pre-fix escape hatch that must not be mistaken for a pass. -
MD-2 (the
sourcearg is excluded from the fingerprint).enrich_conceptis decorated@coco.fn(memo=True, memo_key={'source': None}, …). Perfunction.py:418-448a parameter mapped toNoneis dropped from the fingerprint input, so the unpickleableLRecordsSourcenever reaches_canonicalizeand the RUN-1TypeError: Unsupported type for memoization key: LRecordsSourcecannot recur.sources/l_records.pystays cocoindex-free (no memo protocol added to the data class — a hard property of that module). Test: fingerprinting anenrich_concept-shaped call withmemo_key={'source': None}succeeds where the same call without it raises the RUN-1TypeError(empirical,cocoindex==1.0.7). -
MD-3 (a per-concept
content_versiondrives the fingerprint — the BI-18 delta lever). Acontent_version: str = ""field is added to the frozenConceptKey. Because_canonicalize_dataclassfingerprints all fields (memo_fingerprint.py:131-151), two keys with identical identity but differentcontent_versionproduce different fingerprints (re-draft), and identicalcontent_versionproduces the same fingerprint (memo-hit). Test (empirical):_canonicalize(ConceptKey(id…, content_version="a")) != _canonicalize(ConceptKey(id…, content_version="b"))and== _canonicalize(ConceptKey(id…, content_version="a")). -
MD-4 (
content_versionparticipates ONLY in the memo fingerprint). It is excluded from every identity / routing / dedup / physical-write-path / provenance consumer:ConceptKey.__post_init__validation,read_concepttype routing,bundle_write_path/bundle_write_path_for_key, the won-bid buyer dedup (_list_won_bid_case_study_concepts),catalogue_paths, andfind()’s_concept_haystackall ignore it. Rationale: concept identity isrel_path(BI-2); a content change must re-draft the same concept, not mint a new one. Test: two keys differing only incontent_versionyield identicalbundle_write_path_for_key, identicalread_conceptrouting, and identicalfind()membership.
The content-version signal contract
Section titled “The content-version signal contract”-
MD-5 (computed at
list_concepts()SQL time; one cheap set-based aggregate per enumeration query).content_versionis populated inside the six_list_*methods from a single set-based aggregate query per method, grouped by the concept’s identity — never a per-concept round-trip and never a per-record round-trip. Enumerating N concepts issues O(number-of-enumeration-methods) aggregate queries, not O(N). (Cost lens: the whole point of BI-18 is to skip LLM drafting calls; a handful of grouped aggregates over the bounded first-client corpus is negligible against even one avoided draft.) Test: enumerating a fixture corpus of N concepts issues a bounded, N-independent number of DBfetchcalls for the version signal (assert againstFakePool.calls). -
MD-6 (sensitivity contract — second-order determinism, DR-047).
content_versionchanges if and only if a backing row of that concept is inserted, deleted, or edited (including in-place); it does not change on byte-identical backing content. It is deterministic: derived from sorted row content, carrying no wall-clock / run timestamp. The bias is explicit and DR-047-mandated: over-invalidation is safe (a redundant re-draft costs LLM spend but never serves stale), under-invalidation is a forbidden silent correctness loss. A record shared by several concepts (e.g. onesource_documentfeeding a topic and the company concept) legitimately re-drafts every concept whose backing set it belongs to — that is “exactly the affected concept(s)”, not over-invalidation. Test: mutating one backing row changes that concept’scontent_version(and only concepts whose read grid includes that row); a no-op re-enumeration yields a byte-identicalcontent_version. -
MD-7 (backing-set coverage per enumeration method — the read grid;
updated_at-less tables need a content hash). For each of the six methods, the version aggregate covers the same tablesread_conceptreads for that type (the TECH per-type join grid):topic→q_a_pairs(byscope_tagordomain/subtopic) + parentsource_documents+reference_items+record_lifecycle+entity_mentions+entity_relationships;product→source_documents(filename match) +q_a_pairs(by those docs orentity_id) +reference_items;company→source_documents(company/team) +reference_items+entity_mentions;certification→source_documents(compliance) +reference_items+entity_mentions(byentity_type='certification'+canonical_name, across all docs);case_study(named-clients) →source_documents(named-clients) +q_a_pairs+reference_items;case_study(won-bid) →workspaces+derived_from_form_responseq_a_pairs(bysource_workspace_id, published) + wonform_templates.
For the two tables with no
updated_atthat are UPSERT-written (entity_mentions,entity_relationships), acount(*) + max(created_at)signal is insufficient (blind to in-place edits) — those tables MUST contribute a content hash (e.g.md5(string_agg(id || confidence || context_snippet || metadata ORDER BY id))). Tables withupdated_atMAY use the cheapercount(*) + max(updated_at); a uniform content-hash over every table is the robust default (see Design, Option (i)). Test: for each concept type, an in-place edit to any table in its read grid — including aconfidencechange on an existingentity_mentionsrow — changes that concept’scontent_version.
7a. MD-7a (cross-link read surface — accepted residual limitation, not solved by this slice).
enrich_concept’s Pass-1 tool loop exposes read_concept_raw/sample_rows against ANY catalogue
ref (producer/enrich.py:_build_tool_executors, BI-9 cross-linking), not only the concept’s own
key.rel_path. A concept’s draft may therefore incorporate content read from ANOTHER concept’s
backing rows. content_version (MD-3/MD-7) is scoped to the concept’s OWN read grid only and does
not detect a change to a cross-linked concept’s backing data — a concept whose draft
narratively referenced another concept can serve a stale cross-reference indefinitely after that
other concept’s content changes, without the referencing concept’s own content_version moving.
This is a genuine, DR-047-relevant under-invalidation gap this slice does not close: resolving
it deterministically at list_concepts() time is not tractable in one dispatch (which concepts get
cross-linked is only known AFTER Pass-1 runs — a circular dependency), so it is named here as an
accepted, bounded limitation rather than silently left unaddressed. Mitigation / escape hatch:
the version: manual bump lever (MD-8, Design (iv)) remains available for an operator-triggered
full re-draft if stale cross-links are observed in practice; a future slice may explore a two-pass
fixed-point scheme (re-check content_version of any concept actually cross-linked into a prior
draft) if this proves materially stale at real-corpus scale.
Config-surface invalidation and scope
Section titled “Config-surface invalidation and scope”-
MD-8 (drafting-config folded into the logic fingerprint via
deps=). The Pass-1 draft is shaped byPASS1_INSTRUCTION_PROMPT,ANTHROPIC_MODEL, and_MAX_TOKENS_PASS1, none of which the AST logic fingerprint tracks (imported constants / default kwargs;_compute_logic_fingerprintdumps onlyenrich_concept’s own body,function.py:596-640). They are passed as@coco.fn(…, deps={"prompt": PASS1_INSTRUCTION_PROMPT, "model": ANTHROPIC_MODEL, "max_tokens": _MAX_TOKENS_PASS1}), which folds them into the logic fingerprint (snapshotted at decoration;function.py:1861-1882) so a drafting-config change invalidates all concepts — correct, because every prior draft was shaped by the old config. Test (empirical):_compute_logic_fingerprint(enrich_concept, deps=A) != deps=Band== deps=A(cocoindex==1.0.7). -
MD-9 (the effective ontology is NOT in the Pass-1 fingerprint — DR-054/DR-027). The
EffectiveOntologyoverlay governs the concept-write gate, not the Pass-1 draft (verified: zero ontology imports inenrich.py/agent_loop.py/prompts.py). An overlay change must not re-draft concepts (the draft is identical; only admission at write time differs). Forward guard: if a future change threads the ontology into Pass-1 prompting, it MUST then joindeps=(MD-8). Test: a change to the effective ontology triggers zeroenrich_conceptre-drafts; a grep asserts no ontology symbol is imported into the Pass-1 modules.
Determinism proof + canary
Section titled “Determinism proof + canary”-
MD-10 (deterministic memoised output — the BI-18 Run-1 proof). The
ConceptDraftreturn is deterministic in its inputs: the frontmatter per-run timestamp (build_concept_frontmatter(timestamp=datetime.now(...)),enrich.py:601) is an output, never fingerprinted, so a memo-hit returns the cached draft verbatim (old timestamp) and a memo-miss mints a fresh one.read_concept’s existing per-queryORDER BYkeeps reads reproducible. Two consecutive producer runs over unchanged L-records make zero Anthropic drafting calls (the memo-hit no-op). Test: through one memo-live surface (two/producer-runcalls, or a walk-triggered run then a no-delta walk), run-1 populates the memo and run-2 over identical records issues zero Anthropic drafting calls and produces a no-op bundle diff. -
MD-11 (canary evolution — same PR).
test_l_records_source.py::TestMemoKeyProtocolEscalation(commita997fb3a) currently pins the unfixed state green (source-argTypeError; identity-only key). It MUST evolve in the same PR to pin the fixed contract: source excluded (MD-2),content_versiondrives the fingerprint (MD-3), config viadeps=(MD-8). It must not be left asserting pre-fix behaviour. Test: the evolved canary asserts MD-2/MD-3/MD-8 against the installed engine and would fail against the pre-fix decorator.
Design — options evaluated, positions taken
Section titled “Design — options evaluated, positions taken”Adopted: (i) content-version on ConceptKey computed at list_concepts() SQL time. This is the
core. It is the only option that gives per-concept delta granularity computable cheaply, once,
at enumeration — exactly where the identity is already derived. Realisation position: a
per-backing-table content signal combined in fixed table order into one content_version string;
a uniform md5(string_agg(id || <mutable content cols> ORDER BY id)) per table is the robust default
(no updated_at dependency, catches inserts/updates/deletes/in-place-edits), and count(*) + max(updated_at) is an acceptable cheaper substitute only for tables that have updated_at. The
two updated_at-less UPSERT tables (entity_mentions, entity_relationships) MUST use the hash
(MD-7). Empirically grounded: _canonicalize fingerprints the added field (MD-3 probe).
Adopted (fix, not the delta lever): memo_key={'source': None} over a __coco_memo_key__ on
LRecordsSource. Option (iii) proposed a stable __coco_memo_key__ on the source class plus an
explicit content-version arg. The content-version half is adopted via (i). The source half is
refined: memo_key={'source': None} (function.py:418-448, empirically excludes the arg) is
cleaner than a class dunder because (a) it keeps sources/l_records.py cocoindex-free — a hard,
documented property of that module; (b) it localises the entire memo concern to the one decorator
site; (c) a __coco_memo_key__ returning a constant still contributes a constant to the
fingerprint, whereas None excludes the arg entirely (the honest model: source carries no
draft-shaping data). No content lives on source, so nothing is lost by exclusion.
Adopted: (iv) deps= for config surfaces. deps (function.py:596-640, :1861-1882;
empirically confirmed) folds the prompt/model/max-tokens into the logic fingerprint (MD-8). Also under
(iv): version: int is a manual global-invalidation lever (bump to force a full re-draft when a
change is invisible to both AST and deps — e.g. a model behaviour shift under a pinned id, or a
memo-store reset); it is noted as an available escape hatch, not wired by default. No TTL exists
in cocoindex==1.0.7 (decorator params are memo, memo_key, batching, max_batch_size, runner, version, logic_tracking, deps — verified empirically); time-based expiry is not an option and is not
needed (the content-version is the invalidation signal).
Rejected: (ii) the native memo-state / validator mechanism. The engine does support hit-time
state validation — __coco_memo_state__ / register_memo_key_function(typ, key_fn, state_fn=…)
(memo_fingerprint.py:196-207, :42-51, :287-303 “shook” tag) drives a can_reuse /
states_changed decision at memo-hit time (function.py:165-368). It is rejected as unnecessary
complexity: state-validators exist for signals you cannot compute at key-construction time (they
re-check external state when a hit is proposed). Here the invalidation signal is computable at
list_concepts() time, so embedding it in the key (option i) is strictly simpler, needs no state
storage/registry, and is unit-testable at the _canonicalize level. Documented so a future maintainer
knows the mechanism was considered and why it was not used.
Position: content_version lives on ConceptKey (a field), not as a separate enrich_concept
argument. The _draft_concepts loop and run_producer_flow’s source_workspace_ids comprehension
already pass the ConceptKey through; a field rides that plumbing with zero signature churn, and the
default "" keeps every existing ConceptKey(...) construction (tests, the agent cross-link path)
valid. The conceptual contract is clarified, not muddied: the memo key is (which concept) + (what
content) — precisely BI-18. MD-4 fences the field out of every identity consumer so BI-2 identity
semantics are preserved.
Implementation checklist (dispatch order — scoped to one executor dispatch)
Section titled “Implementation checklist (dispatch order — scoped to one executor dispatch)”sources/l_records.py—ConceptKey.content_version. Addcontent_version: str = ""as the last field (afterworkspace_id); leave__post_init__unchanged (MD-4). Docstring: state it is a memo-fingerprint-only field, excluded from identity (BI-2).sources/l_records.py— the six enumeration aggregates. Add one content-version aggregate SQL per_list_*method, grouped by identity, covering that type’s read grid (MD-7); content-hash forentity_mentions/entity_relationships,count+max(updated_at)permitted elsewhere. Populatecontent_versionon each constructedConceptKey. Keep every queryORDER BY-deterministic and the module cocoindex-free (MD-2/MD-5/MD-6). Prefer folding the aggregate into (or beside) the existing enumeration query per method to hold the round-trip count (MD-5).producer/enrich.py— the decorator. Change to@coco.fn(memo=True, memo_key={'source': None}, deps={"prompt": PASS1_INSTRUCTION_PROMPT, "model": ANTHROPIC_MODEL, "max_tokens": _MAX_TOKENS_PASS1})(MD-2/MD-8). Rewrite the docstring’s memo note (:50-59): the old claim that the engine ignoressourceis false — state thatsourceis excluded viamemo_key, and thatcontent_versionon the key is the BI-18 delta signal.- Canary evolution —
scripts/tests/test_l_records_source.py. EvolveTestMemoKeyProtocolEscalationfrom the unfixed-state pin to the fixed contract (MD-11: MD-2/MD-3/MD-8). - Tests (behaviour-first,
test-philosophy.md). Inscripts/tests/test_l_records_source.py: content_version sensitivity per type incl. theentity_mentionsin-place-edit case (MD-6/MD-7). Inscripts/tests/test_producer_enrich.py:memo_keyexclusion +depsat the_canonicalize/_compute_logic_fingerprintlevel (MD-2/MD-3/MD-8); MD-4 non-leak. Inscripts/tests/test_cocoindex_server.py::TestForcedProducerReportWiring: the MD-10 Run-1 no-op proof (extend the existing forced-run coverage — do not boot the real engine in unit tests).
testStrategy (the slice must make this satisfiable): After the fix, enrich_concept’s call
fingerprints without raising on source; a record edit that leaves concept identity unchanged (e.g.
an in-place entity_mentions.confidence change, or an answer rewrite under the same scope_tag)
changes exactly that concept’s content_version and re-drafts only it; an unchanged corpus re-run
makes zero Anthropic drafting calls; a prompt/model change re-drafts all concepts; an effective-ontology
change re-drafts none. — satisfied by MD-2 (source), MD-3/6/7 (delta), MD-10 (no-op proof), MD-8
(config), MD-9 (ontology out).
Coordination notes (implementation-time, not owner questions)
Section titled “Coordination notes (implementation-time, not owner questions)”- C-1 (memo-live proof surface). Run the MD-10 proof through one cocoindex-App surface end to
end (
server.py:1091-1138forced-run twice, or walk→no-delta-walk) — a memo populated under one App namespace is not guaranteed hit under another (server.py:1074-1086). Do not compare a walk-triggered run against a forced run and expect a cross-App hit. - C-2 (fixture corpus for the sensitivity tests). The existing
FakePool(test_l_records_source.py) matches queries by marker substring; the new aggregate queries needFakePool.when(...)rules. The in-place-edit test (MD-7) is expressible purely at the fixture level (two enumerations with the same identity rows but a changedconfidence), no DB needed. - C-3 (docstring drift).
enrich.py:50-59currently mis-states the engine’ssourcehandling; fixing the behaviour without fixing the docstring would leave an actively misleading comment — treat the docstring edit as part of the same change (item 3).
Open questions (OQ-MD-N) — recommendations; owner ratifies
Section titled “Open questions (OQ-MD-N) — recommendations; owner ratifies”- OQ-MD-1 [RESOLVED S469 → MANUAL
version=bump +log.md; NOTdeps=. See Owner ratification / DR-060. The recommendation below was NOT taken.] (drafting-config invalidation policy — cost vs correctness). Should a change toPASS1_INSTRUCTION_PROMPT/ANTHROPIC_MODELauto-invalidate every concept (viadeps=, MD-8 — correct, but costs a full-corpus re-draft on any prompt tweak), or be a manual operator decision (via aversion:bump, cheaper but risks silent drift between the prompt change and each concept’s next content change)? Recommendation:deps=(auto-invalidate). A changed drafting prompt makes every prior draft stale by construction; leaving them is exactly the silent drift DR-047 forbids in spirit, and the full re-draft is an owner-visible, intended consequence of editing the prompt. The owner ratifies accepting that cost posture. - OQ-MD-2 [RESOLVED S469 → id-131 schema
updated_at+ON UPDATEtrigger; NOT the producer-side hash. See Owner ratification / DR-060. The recommendation below was NOT taken.] (entity_mentions/entity_relationshipsupdated_atgap — producer hash vs id-131 schema).** Close the in-place-edit blind spot producer-side with a content-hash aggregate (MD-7 — self-contained, no migration, marginally heavier read), or request id-131 to addupdated_at(+ anON UPDATEtrigger) to those two tables (cleaner long-term signal, but a cross-Task migration- coordination)? Recommendation: producer-side content-hash now. It keeps {132.38} a single producer-scoped dispatch and removes the blind spot immediately; a schema addition is worth raising to id-131 only if the hash aggregate proves too costly at real corpus scale. The owner/id-131 owner ratifies whether to also schedule the schema change.
DR-intent (for the Orchestrator, on main — not written in-branch)
Section titled “DR-intent (for the Orchestrator, on main — not written in-branch)”A new binding ruling is warranted to record the producer memo delta-determinism contract, refining DR-047’s application to the producer memo path:
source(and any non-data adapter arg) is excluded from theenrich_conceptfingerprint viamemo_key, keeping the source adapter cocoindex-free;- the BI-18 delta signal is a per-concept
content_versioncomputed atlist_concepts()time as a deterministic content aggregate over the concept’s read grid, over-invalidation-biased (a redundant re-draft is safe; a silent stale draft is not — DR-047); entity_mentions/entity_relationshipsget a realupdated_atcolumn +ON UPDATEtrigger (id-131 schema migration, OQ-MD-2 ratified) so the version aggregate usescount+max(updated_at)uniformly — the producer-side content-hash was rejected;- drafting-config (prompt/model/max-tokens) is NOT folded via
deps=; a config re-draft is a manualversion=bump recorded inlog.md(OQ-MD-1 ratified — DR-060); - the effective ontology is excluded from the Pass-1 fingerprint (write-gate concern only — DR-054/DR-027);
- the mechanism does not transitively invalidate a concept when a cross-linked concept’s backing
content changes (MD-7a) — an accepted, bounded residual limitation, with the
version:manual bump as the operator escape hatch.
Surfaced as intent only — the Orchestrator writes it on main per the decision-register discipline
(pending OQ-MD-1/OQ-MD-2 ratification).