From 00060d867f814ca527d235dfe61507c323677838 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 21 Apr 2026 18:20:17 +0300 Subject: [PATCH 1/5] add all message to identity --- anton/core/session.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/anton/core/session.py b/anton/core/session.py index 74afa0a5..6c81b1e6 100644 --- a/anton/core/session.py +++ b/anton/core/session.py @@ -105,6 +105,7 @@ def __init__(self, config: ChatSessionConfig) -> None: list(config.initial_history) if config.initial_history else [] ) self._pending_memory_confirmations: list = [] + self._identity_buffer: list[str] = [] self._turn_count = ( sum(1 for m in self._history if m.get("role") == "user") if config.initial_history @@ -782,8 +783,11 @@ async def turn_stream( self._turn_count += 1 self._persist_history() if self._cortex is not None and self._cortex.mode != "off": - if self._turn_count % 5 == 0 and isinstance(user_input, str): - asyncio.create_task(self._cortex.maybe_update_identity(user_input)) + self._identity_buffer.append(user_input) + if self._turn_count % 5 == 0: + buffered = "\n\n".join(self._identity_buffer) + self._identity_buffer.clear() + asyncio.create_task(self._cortex.maybe_update_identity(buffered)) # Periodic memory vacuum (Systems Consolidation) self._cortex.maybe_vacuum() From 056fe489d907cef2716125af49708f631a58437f Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 21 Apr 2026 18:36:40 +0300 Subject: [PATCH 2/5] fix --- anton/core/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anton/core/session.py b/anton/core/session.py index 6c81b1e6..498eeda9 100644 --- a/anton/core/session.py +++ b/anton/core/session.py @@ -783,7 +783,7 @@ async def turn_stream( self._turn_count += 1 self._persist_history() if self._cortex is not None and self._cortex.mode != "off": - self._identity_buffer.append(user_input) + self._identity_buffer.append(user_msg_str) if self._turn_count % 5 == 0: buffered = "\n\n".join(self._identity_buffer) self._identity_buffer.clear() From 4cb6b2cf498979b6f919080db3062b0fbb5e20a9 Mon Sep 17 00:00:00 2001 From: Alejandro Cantu Date: Thu, 23 Apr 2026 20:13:41 -0700 Subject: [PATCH 3/5] fix(memory): enforce identity singularity across scopes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `encode()` routed profile engrams by declared scope, so `memorize(kind=profile)` with the default `scope=project` landed in `project/profile.md` — which no read path ever loads, silently dropping the data. Force `kind=profile` to `global_hc` regardless of scope, and add a one-time startup migration that merges any orphaned project identity into global then clears the project copy. Co-Authored-By: Claude Sonnet 4.6 --- anton/core/memory/base.py | 4 ++++ anton/core/memory/cortex.py | 12 +++++++++++- anton/core/memory/hippocampus.py | 4 ++++ tests/test_cortex.py | 30 ++++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/anton/core/memory/base.py b/anton/core/memory/base.py index aa5b11cd..4a5cbf47 100644 --- a/anton/core/memory/base.py +++ b/anton/core/memory/base.py @@ -104,6 +104,10 @@ def rewrite_identity(self, entries: list[str]) -> None: """Merge new entries into the identity snapshot and rewrite.""" ... + def clear_identity(self) -> None: + """Delete the identity snapshot file if present.""" + ... + # --- update / delete --- def del_rule(self, id: str) -> None: diff --git a/anton/core/memory/cortex.py b/anton/core/memory/cortex.py index 329a7c64..558b9c40 100644 --- a/anton/core/memory/cortex.py +++ b/anton/core/memory/cortex.py @@ -129,6 +129,13 @@ def __init__( self._llm = llm_client self._turn_count = 0 + # One-time migration: identity is singular and global. Any entries that + # landed in project scope from the old encode() bug are merged upward. + orphaned = [e.text for e in self.project_hc.get_identities()] + if orphaned: + self.global_hc.rewrite_identity(orphaned) + self.project_hc.clear_identity() + # ~6000 chars ≈ ~1500 tokens — above this, use LLM to filter rules _RULES_BUDGET_CHARS = 6000 @@ -297,7 +304,10 @@ async def encode(self, engrams: list[Engram]) -> list[str]: actions: list[str] = [] for engram in engrams: - hc = self.global_hc if engram.scope == "global" else self.project_hc + if engram.kind == "profile": + hc = self.global_hc + else: + hc = self.global_hc if engram.scope == "global" else self.project_hc if engram.kind == "profile": hc.rewrite_identity([engram.text]) diff --git a/anton/core/memory/hippocampus.py b/anton/core/memory/hippocampus.py index f2873fe5..13790aae 100644 --- a/anton/core/memory/hippocampus.py +++ b/anton/core/memory/hippocampus.py @@ -184,6 +184,10 @@ def save_identities(self, entries: list[Engram]) -> None: content = "# Profile\n" + "\n".join(f"- {e.text}" for e in entries) + "\n" self._encode_with_lock(self._profile_path, content, mode="write") + def clear_identity(self) -> None: + if self._profile_path.is_file(): + self._profile_path.unlink() + # --------- lessons -------------- diff --git a/tests/test_cortex.py b/tests/test_cortex.py index 17288058..968103c4 100644 --- a/tests/test_cortex.py +++ b/tests/test_cortex.py @@ -96,6 +96,14 @@ async def test_encode_profile(self, cortex, dirs): assert (g / "profile.md").exists() assert "Name: Jorge" in (g / "profile.md").read_text() + async def test_encode_profile_with_project_scope_routes_to_global(self, cortex, dirs): + g, p = dirs + engram = Engram(text="Name: Jorge", kind="profile", scope="project") + await cortex.encode([engram]) + assert (g / "profile.md").exists() + assert "Name: Jorge" in (g / "profile.md").read_text() + assert not (p / "profile.md").exists() + async def test_off_mode_returns_disabled(self, dirs): g, p = dirs cortex = Cortex(global_hc=Hippocampus(g), project_hc=Hippocampus(p), mode="off") @@ -104,6 +112,28 @@ async def test_off_mode_returns_disabled(self, dirs): assert any("disabled" in a.lower() for a in actions) +class TestOrphanedIdentityMigration: + def test_migrates_project_identity_to_global_on_init(self, dirs): + g, p = dirs + # Simulate orphaned state from the old bug: identity entries in project scope. + Hippocampus(p).rewrite_identity(["Name: Jorge", "TZ: PST"]) + assert (p / "profile.md").exists() + + Cortex(global_hc=Hippocampus(g), project_hc=Hippocampus(p), mode="copilot") + + assert (g / "profile.md").exists() + merged = (g / "profile.md").read_text() + assert "Name: Jorge" in merged + assert "TZ: PST" in merged + assert not (p / "profile.md").exists() + + def test_migration_noop_when_project_identity_empty(self, dirs): + g, p = dirs + Cortex(global_hc=Hippocampus(g), project_hc=Hippocampus(p), mode="copilot") + assert not (g / "profile.md").exists() + assert not (p / "profile.md").exists() + + class TestEncodingGate: def test_autopilot_never_confirms(self, dirs): g, p = dirs From dff794f67d3d1327ef5a8d0091ec2043fe27df20 Mon Sep 17 00:00:00 2001 From: Alejandro Cantu Date: Thu, 23 Apr 2026 20:28:57 -0700 Subject: [PATCH 4/5] fix(memory): preserve fresh global identity during orphan migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous migration called `global_hc.rewrite_identity(orphaned)` which treats new entries as authoritative and strips existing global entries sharing the same key prefix. A stale `Name: Alec` in project scope could silently overwrite a fresh `Name: Alejandro` in global, corrupting data the user had explicitly corrected. Global wins on key conflicts now — only orphaned keys not already present globally are imported. Co-Authored-By: Claude Sonnet 4.6 --- anton/core/memory/cortex.py | 19 ++++++++++++++++++- tests/test_cortex.py | 15 +++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/anton/core/memory/cortex.py b/anton/core/memory/cortex.py index 558b9c40..a3e75500 100644 --- a/anton/core/memory/cortex.py +++ b/anton/core/memory/cortex.py @@ -131,9 +131,26 @@ def __init__( # One-time migration: identity is singular and global. Any entries that # landed in project scope from the old encode() bug are merged upward. + # Global wins on key conflicts — orphaned entries are likely stale + # (the bug wrote them; the user may have since corrected to global), + # so we only import keys that don't already exist globally. orphaned = [e.text for e in self.project_hc.get_identities()] if orphaned: - self.global_hc.rewrite_identity(orphaned) + existing_global_keys = { + e.text.split(":", 1)[0].strip().lower() + for e in self.global_hc.get_identities() + if ":" in e.text + } + to_migrate = [ + fact + for fact in orphaned + if not ( + ":" in fact + and fact.split(":", 1)[0].strip().lower() in existing_global_keys + ) + ] + if to_migrate: + self.global_hc.rewrite_identity(to_migrate) self.project_hc.clear_identity() # ~6000 chars ≈ ~1500 tokens — above this, use LLM to filter rules diff --git a/tests/test_cortex.py b/tests/test_cortex.py index 968103c4..69facbec 100644 --- a/tests/test_cortex.py +++ b/tests/test_cortex.py @@ -127,6 +127,21 @@ def test_migrates_project_identity_to_global_on_init(self, dirs): assert "TZ: PST" in merged assert not (p / "profile.md").exists() + def test_migration_does_not_overwrite_fresh_global_entries(self, dirs): + # Orphaned project data is likely stale (old bug wrote it, user may + # have since corrected to global). Global must win on key conflicts. + g, p = dirs + Hippocampus(g).rewrite_identity(["Name: Alejandro"]) + Hippocampus(p).rewrite_identity(["Name: Alec", "TZ: PST"]) + + Cortex(global_hc=Hippocampus(g), project_hc=Hippocampus(p), mode="copilot") + + merged = (g / "profile.md").read_text() + assert "Name: Alejandro" in merged + assert "Name: Alec" not in merged + assert "TZ: PST" in merged # non-conflicting keys still migrate + assert not (p / "profile.md").exists() + def test_migration_noop_when_project_identity_empty(self, dirs): g, p = dirs Cortex(global_hc=Hippocampus(g), project_hc=Hippocampus(p), mode="copilot") From a27011f510c3cbbde90465efc55b191d70ea5b94 Mon Sep 17 00:00:00 2001 From: Alejandro Cantu Date: Thu, 23 Apr 2026 21:16:00 -0700 Subject: [PATCH 5/5] Revert "Merge remote-tracking branch 'origin/fix-maybe-identity' into fix/identity-scope-singular" This reverts commit 3e1d119d3db75de0ea8cc04fde7fb875b5c43f39, reversing changes made to 4cb6b2cf498979b6f919080db3062b0fbb5e20a9. --- anton/core/session.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/anton/core/session.py b/anton/core/session.py index ffbc3719..15795b01 100644 --- a/anton/core/session.py +++ b/anton/core/session.py @@ -106,7 +106,6 @@ def __init__(self, config: ChatSessionConfig) -> None: list(config.initial_history) if config.initial_history else [] ) self._pending_memory_confirmations: list = [] - self._identity_buffer: list[str] = [] self._turn_count = ( sum(1 for m in self._history if m.get("role") == "user") if config.initial_history @@ -785,11 +784,8 @@ async def turn_stream( self._turn_count += 1 self._persist_history() if self._cortex is not None and self._cortex.mode != "off": - self._identity_buffer.append(user_msg_str) - if self._turn_count % 5 == 0: - buffered = "\n\n".join(self._identity_buffer) - self._identity_buffer.clear() - asyncio.create_task(self._cortex.maybe_update_identity(buffered)) + if self._turn_count % 5 == 0 and isinstance(user_input, str): + asyncio.create_task(self._cortex.maybe_update_identity(user_input)) # Periodic memory vacuum (Systems Consolidation) self._cortex.maybe_vacuum()