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
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
def _warn_not_connected(feature: str) -> None:
|
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 = (
|
msg = (
|
||||||
f"[phase-memory] {feature} called — phase-memory not yet connected. "
|
f"[phase-memory] {feature} called — phase-memory not yet connected. "
|
||||||
"This is a no-op placeholder. Real implementation will come from the "
|
"Falling back to local T02 json store (real phase graph in later slice). "
|
||||||
"phase-memory package. See T05 in workplan CYA-WP-0001."
|
"See T02 in workplan CYA-WP-0002 and MemoryVision contract."
|
||||||
)
|
)
|
||||||
print(msg, file=sys.stderr)
|
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)
|
# Explicit ports (the four capabilities from the workplan)
|
||||||
# Refined in T01 per phase-memory architecture + interop + lifecycle.
|
# 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".
|
# 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:
|
) -> None:
|
||||||
"""Remember a user preference or workflow pattern (preference kind).
|
"""Remember a user preference or workflow pattern (preference kind).
|
||||||
|
|
||||||
Delegates (T02+) to phase-memory profile execution / graph store.
|
Real T02: persists to user-controlled json (scoped).
|
||||||
Dry-run plans and policy checks come from phase-memory lifecycle.
|
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(
|
try:
|
||||||
f"remember_preference({key!r}, scope={scope}, profile={profile})"
|
items = _load(scope)
|
||||||
)
|
item = {
|
||||||
# No-op by design (T01 complete; real in T02)
|
"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(
|
def recall_preferences(
|
||||||
@@ -70,39 +118,77 @@ def recall_preferences(
|
|||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Recall relevant history / preferences for cwd + task (preference + context).
|
"""Recall relevant history / preferences for cwd + task (preference + context).
|
||||||
|
|
||||||
Returns structured payload with items, provenance, dry_run_plan, phase.
|
Real T02: loads from user-controlled scoped json.
|
||||||
Enables explainability in orchestrator / --explain-context.
|
Returns structured payload with items, provenance, phase for explain.
|
||||||
Will be replaced by real phase-memory retrieval + planner.
|
Future: real phase-memory retrieval + planner + dry_run_plan.
|
||||||
"""
|
"""
|
||||||
_warn_not_connected(
|
try:
|
||||||
f"recall_preferences(scope={scope}, task={task_class}, profile={profile})"
|
items = _load(scope)
|
||||||
)
|
if kinds:
|
||||||
return {}
|
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:
|
def forget(scope: str = "cwd", keys: list[str] | None = None, *, profile: str | None = None) -> None:
|
||||||
"""Forget / reset memory (scoped).
|
"""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})")
|
try:
|
||||||
# No-op
|
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]:
|
def export_memory(scope: str = "cwd", *, profile: str | None = None) -> dict[str, Any]:
|
||||||
"""Inspect / export current memory for this project or user.
|
"""Inspect / export current memory for this project or user.
|
||||||
|
|
||||||
Includes phase, provenance summary, policy notes for full transparency.
|
Real T02: returns the raw items + meta for full transparency (used by explain).
|
||||||
Used by CLI explain paths.
|
Includes phase, provenance summary, policy notes placeholder.
|
||||||
"""
|
"""
|
||||||
_warn_not_connected(f"export_memory(scope={scope}, profile={profile})")
|
try:
|
||||||
return {
|
items = _load(scope)
|
||||||
"status": "phase-memory not connected (T05 no-op; T01 contract complete)",
|
return {
|
||||||
"scope": scope,
|
"status": "real (T02 local json; phase ready)",
|
||||||
"profile": profile,
|
"scope": scope,
|
||||||
"note": "Replace this module with real phase-memory ports (see MemoryVision contract).",
|
"profile": profile,
|
||||||
"phases": ["ephemeral", "fluid", "stabilized", "rigid"],
|
"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__ = [
|
__all__ = [
|
||||||
|
|||||||
Reference in New Issue
Block a user