generated from coulomb/repo-seed
chore(memory): include real T02 persisting implementation (user-controlled JSON backing) as part of final CYA-WP-0002 completion
This commit is contained in:
@@ -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__ = [
|
||||
|
||||
Reference in New Issue
Block a user