Stabilize core correctness and sync project plan state
This commit is contained in:
@@ -14,7 +14,6 @@ Memories have:
|
||||
- optional link to source chunk: traceability
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
@@ -57,6 +56,7 @@ def create_memory(
|
||||
"""Create a new memory entry."""
|
||||
if memory_type not in MEMORY_TYPES:
|
||||
raise ValueError(f"Invalid memory type '{memory_type}'. Must be one of: {MEMORY_TYPES}")
|
||||
_validate_confidence(confidence)
|
||||
|
||||
memory_id = str(uuid.uuid4())
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
@@ -64,8 +64,9 @@ def create_memory(
|
||||
# Check for duplicate content within same type+project
|
||||
with get_connection() as conn:
|
||||
existing = conn.execute(
|
||||
"SELECT id FROM memories WHERE memory_type = ? AND content = ? AND status = 'active'",
|
||||
(memory_type, content),
|
||||
"SELECT id FROM memories "
|
||||
"WHERE memory_type = ? AND content = ? AND project = ? AND status = 'active'",
|
||||
(memory_type, content, project),
|
||||
).fetchone()
|
||||
if existing:
|
||||
log.info("memory_duplicate_skipped", memory_type=memory_type, content_preview=content[:80])
|
||||
@@ -74,9 +75,9 @@ def create_memory(
|
||||
)
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO memories (id, memory_type, content, source_chunk_id, confidence, status) "
|
||||
"VALUES (?, ?, ?, ?, ?, 'active')",
|
||||
(memory_id, memory_type, content, source_chunk_id or None, confidence),
|
||||
"INSERT INTO memories (id, memory_type, content, project, source_chunk_id, confidence, status) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, 'active')",
|
||||
(memory_id, memory_type, content, project, source_chunk_id or None, confidence),
|
||||
)
|
||||
|
||||
log.info("memory_created", memory_type=memory_type, content_preview=content[:80])
|
||||
@@ -96,6 +97,7 @@ def create_memory(
|
||||
|
||||
def get_memories(
|
||||
memory_type: str | None = None,
|
||||
project: str | None = None,
|
||||
active_only: bool = True,
|
||||
min_confidence: float = 0.0,
|
||||
limit: int = 50,
|
||||
@@ -107,6 +109,9 @@ def get_memories(
|
||||
if memory_type:
|
||||
query += " AND memory_type = ?"
|
||||
params.append(memory_type)
|
||||
if project is not None:
|
||||
query += " AND project = ?"
|
||||
params.append(project)
|
||||
if active_only:
|
||||
query += " AND status = 'active'"
|
||||
if min_confidence > 0:
|
||||
@@ -129,28 +134,46 @@ def update_memory(
|
||||
status: str | None = None,
|
||||
) -> bool:
|
||||
"""Update an existing memory."""
|
||||
updates = []
|
||||
params: list = []
|
||||
|
||||
if content is not None:
|
||||
updates.append("content = ?")
|
||||
params.append(content)
|
||||
if confidence is not None:
|
||||
updates.append("confidence = ?")
|
||||
params.append(confidence)
|
||||
if status is not None:
|
||||
if status not in ("active", "superseded", "invalid"):
|
||||
raise ValueError(f"Invalid status '{status}'")
|
||||
updates.append("status = ?")
|
||||
params.append(status)
|
||||
|
||||
if not updates:
|
||||
return False
|
||||
|
||||
updates.append("updated_at = CURRENT_TIMESTAMP")
|
||||
params.append(memory_id)
|
||||
|
||||
with get_connection() as conn:
|
||||
existing = conn.execute("SELECT * FROM memories WHERE id = ?", (memory_id,)).fetchone()
|
||||
if existing is None:
|
||||
return False
|
||||
|
||||
next_content = content if content is not None else existing["content"]
|
||||
next_status = status if status is not None else existing["status"]
|
||||
if confidence is not None:
|
||||
_validate_confidence(confidence)
|
||||
|
||||
if next_status == "active":
|
||||
duplicate = conn.execute(
|
||||
"SELECT id FROM memories "
|
||||
"WHERE memory_type = ? AND content = ? AND project = ? AND status = 'active' AND id != ?",
|
||||
(existing["memory_type"], next_content, existing["project"] or "", memory_id),
|
||||
).fetchone()
|
||||
if duplicate:
|
||||
raise ValueError("Update would create a duplicate active memory")
|
||||
|
||||
updates = []
|
||||
params: list = []
|
||||
|
||||
if content is not None:
|
||||
updates.append("content = ?")
|
||||
params.append(content)
|
||||
if confidence is not None:
|
||||
updates.append("confidence = ?")
|
||||
params.append(confidence)
|
||||
if status is not None:
|
||||
if status not in ("active", "superseded", "invalid"):
|
||||
raise ValueError(f"Invalid status '{status}'")
|
||||
updates.append("status = ?")
|
||||
params.append(status)
|
||||
|
||||
if not updates:
|
||||
return False
|
||||
|
||||
updates.append("updated_at = CURRENT_TIMESTAMP")
|
||||
params.append(memory_id)
|
||||
|
||||
result = conn.execute(
|
||||
f"UPDATE memories SET {', '.join(updates)} WHERE id = ?",
|
||||
params,
|
||||
@@ -174,6 +197,7 @@ def supersede_memory(memory_id: str) -> bool:
|
||||
|
||||
def get_memories_for_context(
|
||||
memory_types: list[str] | None = None,
|
||||
project: str | None = None,
|
||||
budget: int = 500,
|
||||
) -> tuple[str, int]:
|
||||
"""Get formatted memories for context injection.
|
||||
@@ -186,33 +210,42 @@ def get_memories_for_context(
|
||||
if memory_types is None:
|
||||
memory_types = ["identity", "preference"]
|
||||
|
||||
memories = []
|
||||
for mtype in memory_types:
|
||||
memories.extend(get_memories(memory_type=mtype, min_confidence=0.5, limit=10))
|
||||
|
||||
if not memories:
|
||||
if budget <= 0:
|
||||
return "", 0
|
||||
|
||||
lines = ["--- AtoCore Memory ---"]
|
||||
used = len(lines[0]) + 1
|
||||
included = []
|
||||
|
||||
for mem in memories:
|
||||
entry = f"[{mem.memory_type}] {mem.content}"
|
||||
entry_len = len(entry) + 1
|
||||
if used + entry_len > budget:
|
||||
break
|
||||
lines.append(entry)
|
||||
used += entry_len
|
||||
included.append(mem)
|
||||
|
||||
if len(included) == 0:
|
||||
header = "--- AtoCore Memory ---"
|
||||
footer = "--- End Memory ---"
|
||||
wrapper_chars = len(header) + len(footer) + 2
|
||||
if budget <= wrapper_chars:
|
||||
return "", 0
|
||||
|
||||
lines.append("--- End Memory ---")
|
||||
available = budget - wrapper_chars
|
||||
selected_entries: list[str] = []
|
||||
|
||||
for index, mtype in enumerate(memory_types):
|
||||
type_budget = available if index == len(memory_types) - 1 else max(0, available // (len(memory_types) - index))
|
||||
type_used = 0
|
||||
for mem in get_memories(
|
||||
memory_type=mtype,
|
||||
project=project,
|
||||
min_confidence=0.5,
|
||||
limit=10,
|
||||
):
|
||||
entry = f"[{mem.memory_type}] {mem.content}"
|
||||
entry_len = len(entry) + 1
|
||||
if entry_len > type_budget - type_used:
|
||||
continue
|
||||
selected_entries.append(entry)
|
||||
type_used += entry_len
|
||||
available -= type_used
|
||||
|
||||
if not selected_entries:
|
||||
return "", 0
|
||||
|
||||
lines = [header, *selected_entries, footer]
|
||||
text = "\n".join(lines)
|
||||
|
||||
log.info("memories_for_context", count=len(included), chars=len(text))
|
||||
log.info("memories_for_context", count=len(selected_entries), chars=len(text))
|
||||
return text, len(text)
|
||||
|
||||
|
||||
@@ -222,10 +255,15 @@ def _row_to_memory(row) -> Memory:
|
||||
id=row["id"],
|
||||
memory_type=row["memory_type"],
|
||||
content=row["content"],
|
||||
project="",
|
||||
project=row["project"] or "",
|
||||
source_chunk_id=row["source_chunk_id"] or "",
|
||||
confidence=row["confidence"],
|
||||
status=row["status"],
|
||||
created_at=row["created_at"],
|
||||
updated_at=row["updated_at"],
|
||||
)
|
||||
|
||||
|
||||
def _validate_confidence(confidence: float) -> None:
|
||||
if not 0.0 <= confidence <= 1.0:
|
||||
raise ValueError("Confidence must be between 0.0 and 1.0")
|
||||
|
||||
Reference in New Issue
Block a user