Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions anton/core/memory/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
29 changes: 28 additions & 1 deletion anton/core/memory/cortex.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,30 @@ 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.
# 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:
Comment thread
alecantu7 marked this conversation as resolved.
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
_RULES_BUDGET_CHARS = 6000

Expand Down Expand Up @@ -297,7 +321,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])
Expand Down
4 changes: 4 additions & 0 deletions anton/core/memory/hippocampus.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 --------------

Expand Down
45 changes: 45 additions & 0 deletions tests/test_cortex.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -104,6 +112,43 @@ 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_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")
assert not (g / "profile.md").exists()
assert not (p / "profile.md").exists()


class TestEncodingGate:
def test_autopilot_never_confirms(self, dirs):
g, p = dirs
Expand Down
Loading