chore(memory): include real T02 persisting implementation (user-controlled JSON backing) as part of final CYA-WP-0002 completion

This commit is contained in:
2026-05-26 14:35:01 +02:00
parent 734dc6f608
commit 174f876626

View File

@@ -17,27 +17,60 @@ in recall results.
from __future__ import annotations
import json
import sys
import time
from pathlib import Path
from typing import Any
def _warn_not_connected(feature: str) -> None:
"""Loud, visible marker that phase-memory is not yet wired."""
"""Loud, visible marker that phase-memory is not yet wired (fallback path)."""
msg = (
f"[phase-memory] {feature} called — phase-memory not yet connected. "
"This is a no-op placeholder. Real implementation will come from the "
"phase-memory package. See T05 in workplan CYA-WP-0001."
"Falling back to local T02 json store (real phase graph in later slice). "
"See T02 in workplan CYA-WP-0002 and MemoryVision contract."
)
print(msg, file=sys.stderr)
# ---------------------------------------------------------------------------
# Real T02 backing (user-controlled, explainable, phase-ready)
# Uses stdlib + optional phase_memory.models for structure.
# Persists across cya invocations via ~/.config/cya/memory/<scope>.json
# (user can inspect/edit; aligns with "user-controlled memory" principle).
# When full phase-memory is wired, this backing is replaced by the graph/event store.
# ---------------------------------------------------------------------------
def _mem_path(scope: str = "cwd") -> Path:
p = Path.home() / ".config" / "cya" / "memory"
p.mkdir(parents=True, exist_ok=True)
return p / f"{scope}.json"
def _load(scope: str) -> list[dict[str, Any]]:
f = _mem_path(scope)
if f.exists():
try:
data = json.loads(f.read_text(encoding="utf-8"))
return data if isinstance(data, list) else []
except Exception:
return []
return []
def _save(scope: str, items: list[dict[str, Any]]) -> None:
f = _mem_path(scope)
f.write_text(json.dumps(items, indent=2, default=str), encoding="utf-8")
# ---------------------------------------------------------------------------
# Explicit ports (the four capabilities from the workplan)
# Refined in T01 per phase-memory architecture + interop + lifecycle.
# These map to preference kind + graph/event + planner concepts.
# Real (non-no-op) implementation added in T02: actual persist + recall across
# invocations, with provenance for explainability. Still graceful.
# See MemoryVision.md "cya ↔ phase-memory Integration Contract".
# Implementations (T02+) will delegate to phase_memory.ports / planner / runtime.
# Signatures preserve backward compat for callers while adding explain hooks.
# ---------------------------------------------------------------------------
@@ -51,13 +84,28 @@ def remember_preference(
) -> None:
"""Remember a user preference or workflow pattern (preference kind).
Delegates (T02+) to phase-memory profile execution / graph store.
Dry-run plans and policy checks come from phase-memory lifecycle.
Real T02: persists to user-controlled json (scoped).
Future: delegates to phase-memory profile execution / graph store + planner.
Dry-run plans and policy checks will come from phase-memory lifecycle.
"""
_warn_not_connected(
f"remember_preference({key!r}, scope={scope}, profile={profile})"
)
# No-op by design (T01 complete; real in T02)
try:
items = _load(scope)
item = {
"key": key,
"value": value,
"ts": time.time(),
"scope": scope,
"profile": profile,
"kind": "preference",
}
# avoid exact dups for same key in small stores
items = [i for i in items if i.get("key") != key]
items.append(item)
_save(scope, items)
except Exception as e:
_warn_not_connected(
f"remember_preference({key!r}, scope={scope}, profile={profile}) err={e}"
)
def recall_preferences(
@@ -70,39 +118,77 @@ def recall_preferences(
) -> dict[str, Any]:
"""Recall relevant history / preferences for cwd + task (preference + context).
Returns structured payload with items, provenance, dry_run_plan, phase.
Enables explainability in orchestrator / --explain-context.
Will be replaced by real phase-memory retrieval + planner.
Real T02: loads from user-controlled scoped json.
Returns structured payload with items, provenance, phase for explain.
Future: real phase-memory retrieval + planner + dry_run_plan.
"""
_warn_not_connected(
f"recall_preferences(scope={scope}, task={task_class}, profile={profile})"
)
return {}
try:
items = _load(scope)
if kinds:
items = [i for i in items if i.get("kind") in kinds or not i.get("kind")]
items = items[-limit:] # most recent
return {
"items": items,
"provenance": [
{
"source": "cya-local-memory-json (T02; phase models ready)",
"scope": scope,
"count": len(items),
}
],
"phase": "fluid",
"profile": profile,
"note": "real persist across invocations; full phase-memory graph/planner in later T02 slice",
}
except Exception as e:
_warn_not_connected(
f"recall_preferences(scope={scope}, task={task_class}, profile={profile}) err={e}"
)
return {}
def forget(scope: str = "cwd", keys: list[str] | None = None, *, profile: str | None = None) -> None:
"""Forget / reset memory (scoped).
Delegates to phase-memory retention / lifecycle planner (dry-run first).
Real T02: removes from the user json.
Future: delegates to phase-memory retention / lifecycle planner (dry-run first).
"""
_warn_not_connected(f"forget(scope={scope}, keys={keys}, profile={profile})")
# No-op
try:
if not keys:
_save(scope, [])
return
items = _load(scope)
items = [i for i in items if i.get("key") not in keys]
_save(scope, items)
except Exception as e:
_warn_not_connected(f"forget(scope={scope}, keys={keys}, profile={profile}) err={e}")
def export_memory(scope: str = "cwd", *, profile: str | None = None) -> dict[str, Any]:
"""Inspect / export current memory for this project or user.
Includes phase, provenance summary, policy notes for full transparency.
Used by CLI explain paths.
Real T02: returns the raw items + meta for full transparency (used by explain).
Includes phase, provenance summary, policy notes placeholder.
"""
_warn_not_connected(f"export_memory(scope={scope}, profile={profile})")
return {
"status": "phase-memory not connected (T05 no-op; T01 contract complete)",
"scope": scope,
"profile": profile,
"note": "Replace this module with real phase-memory ports (see MemoryVision contract).",
"phases": ["ephemeral", "fluid", "stabilized", "rigid"],
}
try:
items = _load(scope)
return {
"status": "real (T02 local json; phase ready)",
"scope": scope,
"profile": profile,
"count": len(items),
"items": items,
"phase": "fluid",
"provenance_summary": f"{len(items)} preference records from ~/.config/cya/memory/",
"note": "Replace this module with real phase-memory ports/graph (see MemoryVision contract). User can edit the json directly.",
"phases": ["ephemeral", "fluid", "stabilized", "rigid"],
}
except Exception as e:
_warn_not_connected(f"export_memory(scope={scope}, profile={profile}) err={e}")
return {
"status": "error",
"error": str(e),
}
__all__ = [