Root cause
resolve_conflicts() (altk_evolve/llm/conflict_resolution/conflict_resolution.py) decides ADD/UPDATE/DELETE/NONE by sending old vs. new entities to an LLM as SimpleEntity objects (altk_evolve/schema/conflict_resolution.py), which carry only id / type / content. task_description — and every other structured field (trigger, rationale, implementation_steps, category) — lives only in metadata, which is stripped before the LLM ever sees it.
The old-entity candidate pool itself is also task-agnostic: for each new guideline, update_entities() (altk_evolve/backend/base.py) runs a content-similarity search (_search_entities_impl, filters={"type": "guideline"}, limit=10) with no task_description scoping at all.
Net effect: the LLM's dedup decision is based purely on whether two guidelines read the same, with no way to know whether they came from the same task or unrelated ones.
Failure mode 1a — identical/paraphrased content across different tasks → NONE
When a new guideline (from task A) reads as a paraphrase of an existing stored guideline (originally learned from a different task), the prompt's "No Change" rule fires. Per the prompt's own few-shot example, a NONE verdict for a retrieved entity means that entity is dropped from the LLM's response entirely — not echoed back with event: "NONE", just absent. Nothing in resolve_conflicts() or the
base.py update loop creates any record for it.
Lost as a result:
- Task A's
task_description, rationale, trigger, implementation_steps, source_task_id for this occurrence — never persisted anywhere.
support is not incremented (confirmed: nothing in resolve_conflicts() or the NONE/absent-entity path touches support; it's only ever recomputed by the separate, manually-triggered consolidate_guidelines() → combine_cluster() → _attribute_support() pipeline in altk_evolve/llm/guidelines/clustering.py).
- Because
support doesn't move, this reconfirmation never nudges the guideline toward core_support promotion in select_guidelines() (altk_evolve/llm/guidelines/retrieval.py) — the "recurred across many tasks, so it generalizes" signal the core mechanism is meant to capture is silently discarded here.
- In the dosage-aware retrieval path's default
similarity_key="source_task", the stored entity's retrieval ranking for future task-A-shaped queries stays anchored to the original task's task_description — it's never reinforced toward matching task A-like queries, even though this occurrence just proved the advice applies there too.
Note: the stored entity itself isn't deleted or made unreachable — it can still surface for task A via content-similarity search (static injection path) or if it's already core. What's lost is the reinforcement signal and task-A-specific provenance, not necessarily all future injectability.
Failure mode 1b — similar-but-not-identical content across different tasks → UPDATE
When the LLM instead judges task A's version as adding "concrete new information" (rule 2, UPDATE), the metadata merge in resolve_conflicts() (conflict_resolution.py, ~line 55-78) does:
merged = {**stored_metadata, **incoming_metadata}
Every key except the sticky generation_method
(_STICKY_STORED_METADATA_KEYS) is overwritten by the incoming (task A) values — including task_description, rationale, category, trigger, implementation_steps, source_task_id. content is also replaced outright with task A's phrasing (that's what the LLM returned as the UPDATE's content).
Result: the original task's provenance is gone from the stored entity, and the canonical version of this guideline is now phrased/scoped to task A's specifics — which may not actually generalize back to the original task. Two distinct tasks' learnings get collapsed into one entity that only accurately represents the more recent one.
Why "just increment support on NONE" isn't the fix
Considered and rejected as too blunt:
- The NONE verdict is an approximate, per-call LLM judgment over a top-10 content-similarity candidate window — not a verified duplicate check. Turning it into a persistent counter would make
support (and therefore core_support promotion) inherit that noise/non-determinism.
- It risks unbounded growth tied to how often a task type runs, not to how broadly useful the advice actually is — the opposite of what
core_support should measure.
- It wouldn't recover the actual lost information (task A's
task_description, trigger, rationale, etc.) — just bumps a number.
support accounting today is deliberately confined to the auditable consolidate_guidelines() pipeline, which conserves total support and warns on mismatch (_attribute_support`). Silently mutating it from inside the write-time dedup path would bypass that discipline.
Root cause
resolve_conflicts()(altk_evolve/llm/conflict_resolution/conflict_resolution.py) decides ADD/UPDATE/DELETE/NONE by sending old vs. new entities to an LLM asSimpleEntityobjects (altk_evolve/schema/conflict_resolution.py), which carry onlyid/type/content.task_description— and every other structured field (trigger,rationale,implementation_steps,category) — lives only inmetadata, which is stripped before the LLM ever sees it.The old-entity candidate pool itself is also task-agnostic: for each new guideline,
update_entities()(altk_evolve/backend/base.py) runs a content-similarity search (_search_entities_impl,filters={"type": "guideline"},limit=10) with notask_descriptionscoping at all.Net effect: the LLM's dedup decision is based purely on whether two guidelines read the same, with no way to know whether they came from the same task or unrelated ones.
Failure mode 1a — identical/paraphrased content across different tasks →
NONEWhen a new guideline (from task A) reads as a paraphrase of an existing stored guideline (originally learned from a different task), the prompt's "No Change" rule fires. Per the prompt's own few-shot example, a
NONEverdict for a retrieved entity means that entity is dropped from the LLM's response entirely — not echoed back withevent: "NONE", just absent. Nothing inresolve_conflicts()or thebase.pyupdate loop creates any record for it.Lost as a result:
task_description,rationale,trigger,implementation_steps,source_task_idfor this occurrence — never persisted anywhere.supportis not incremented (confirmed: nothing inresolve_conflicts()or theNONE/absent-entity path touchessupport; it's only ever recomputed by the separate, manually-triggeredconsolidate_guidelines()→combine_cluster()→_attribute_support()pipeline inaltk_evolve/llm/guidelines/clustering.py).supportdoesn't move, this reconfirmation never nudges the guideline towardcore_supportpromotion inselect_guidelines()(altk_evolve/llm/guidelines/retrieval.py) — the "recurred across many tasks, so it generalizes" signal the core mechanism is meant to capture is silently discarded here.similarity_key="source_task", the stored entity's retrieval ranking for future task-A-shaped queries stays anchored to the original task'stask_description— it's never reinforced toward matching task A-like queries, even though this occurrence just proved the advice applies there too.Note: the stored entity itself isn't deleted or made unreachable — it can still surface for task A via content-similarity search (static injection path) or if it's already
core. What's lost is the reinforcement signal and task-A-specific provenance, not necessarily all future injectability.Failure mode 1b — similar-but-not-identical content across different tasks →
UPDATEWhen the LLM instead judges task A's version as adding "concrete new information" (rule 2, UPDATE), the metadata merge in
resolve_conflicts()(conflict_resolution.py, ~line 55-78) does:Every key except the sticky
generation_method(
_STICKY_STORED_METADATA_KEYS) is overwritten by the incoming (task A) values — includingtask_description,rationale,category,trigger,implementation_steps,source_task_id.contentis also replaced outright with task A's phrasing (that's what the LLM returned as the UPDATE'scontent).Result: the original task's provenance is gone from the stored entity, and the canonical version of this guideline is now phrased/scoped to task A's specifics — which may not actually generalize back to the original task. Two distinct tasks' learnings get collapsed into one entity that only accurately represents the more recent one.
Why "just increment support on NONE" isn't the fix
Considered and rejected as too blunt:
support(and thereforecore_supportpromotion) inherit that noise/non-determinism.core_supportshould measure.task_description,trigger,rationale, etc.) — just bumps a number.supportaccounting today is deliberately confined to the auditable consolidate_guidelines()pipeline, which conserves total support and warns on mismatch (_attribute_support`). Silently mutating it from inside the write-time dedup path would bypass that discipline.