From abc54989e56a29248976394f3b3766170ea77eec Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 26 May 2026 15:15:20 +0200 Subject: [PATCH] =?UTF-8?q?feat(memory)=20+=20chore(workplan):=20complete?= =?UTF-8?q?=20T02=20for=20CYA-WP-0003=20=E2=80=94=20port=20extensions=20fo?= =?UTF-8?q?r=20activation,=20kinds,=20and=20retrospection=20records=20(bac?= =?UTF-8?q?kward=20compatible)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cya/memory/__init__.py | 103 ++++++++++++++---- ...ual-memory-activation-and-retrospection.md | 24 ++-- 2 files changed, 96 insertions(+), 31 deletions(-) diff --git a/src/cya/memory/__init__.py b/src/cya/memory/__init__.py index 5d6120d..91f160d 100644 --- a/src/cya/memory/__init__.py +++ b/src/cya/memory/__init__.py @@ -23,6 +23,11 @@ import time from pathlib import Path from typing import Any +# Standard memory kinds used by cya (especially after CYA-WP-0003) +KIND_PREFERENCE = "preference" +KIND_RETROSPECTION = "retrospection" +KIND_INTERACTION_GOAL = "interaction_goal" + def _warn_not_connected(feature: str) -> None: """Loud, visible marker that phase-memory is not yet wired (fallback path).""" @@ -67,10 +72,10 @@ def _save(scope: str, items: list[dict[str, Any]]) -> None: # --------------------------------------------------------------------------- # Explicit ports (the four capabilities from the workplan) -# Refined in T01 per phase-memory architecture + interop + lifecycle. -# 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". +# Refined in T01 (0003) per phase-memory architecture + interop + lifecycle. +# Real (non-no-op) implementation added in T02 (0002): actual persist + recall. +# Extended in T02 (0003) for contextual activation and retrospection support. +# See docs/cya-memory-activation-and-retrospection-concept.md and MemoryVision.md. # --------------------------------------------------------------------------- @@ -81,12 +86,15 @@ def remember_preference( *, profile: str | None = None, ttl: str | None = None, + kind: str = KIND_PREFERENCE, ) -> None: - """Remember a user preference or workflow pattern (preference kind). + """Remember a user preference, workflow pattern, retrospection outcome, or goal. - 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. + `kind` defaults to "preference" for backward compatibility. + Special kinds (e.g. "retrospection", "interaction_goal") are supported for + higher-order memory from retrospection sessions (see CYA-WP-0003). + + Real T02 + 0003: persists to user-controlled json (scoped). """ try: items = _load(scope) @@ -96,7 +104,7 @@ def remember_preference( "ts": time.time(), "scope": scope, "profile": profile, - "kind": "preference", + "kind": kind, } # avoid exact dups for same key in small stores items = [i for i in items if i.get("key") != key] @@ -115,30 +123,52 @@ def recall_preferences( kinds: list[str] | None = None, profile: str | None = None, limit: int = 50, + activation_context: dict | None = None, ) -> dict[str, Any]: - """Recall relevant history / preferences for cwd + task (preference + context). + """Recall relevant memory for the current context. - 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. + Supports: + - `kinds`: filter or boost specific kinds (e.g. ["interaction_goal", "retrospection"]) + - `activation_context`: hints for smarter activation (e.g. {"cwd": "...", "project": "..."}) + - Backward compatible with all previous call sites. + + Real T02 + 0003 extensions for contextual activation and retrospection support. """ try: items = _load(scope) + + # Basic kind filtering (existing behavior + 0003 enhancement) if kinds: items = [i for i in items if i.get("kind") in kinds or not i.get("kind")] - items = items[-limit:] # most recent + + # Simple activation boost: prefer items whose scope or profile matches context + if activation_context: + ctx_cwd = activation_context.get("cwd") or activation_context.get("scope") + ctx_profile = activation_context.get("profile") + boosted = [] + normal = [] + for item in items: + if (ctx_cwd and item.get("scope") == ctx_cwd) or (ctx_profile and item.get("profile") == ctx_profile): + boosted.append(item) + else: + normal.append(item) + items = boosted + normal + + items = items[-limit:] # most recent first (after boosting) + return { "items": items, "provenance": [ { - "source": "cya-local-memory-json (T02; phase models ready)", + "source": "cya-local-memory-json (T02+0003; activation + retrospection support)", "scope": scope, "count": len(items), + "activation_context": activation_context, } ], "phase": "fluid", "profile": profile, - "note": "real persist across invocations; full phase-memory graph/planner in later T02 slice", + "note": "real persist + contextual activation; full phase-memory in later slices", } except Exception as e: _warn_not_connected( @@ -164,23 +194,32 @@ def forget(scope: str = "cwd", keys: list[str] | None = None, *, profile: str | _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, kinds: list[str] | None = None) -> dict[str, Any]: """Inspect / export current memory for this project or user. - Real T02: returns the raw items + meta for full transparency (used by explain). - Includes phase, provenance summary, policy notes placeholder. + Supports optional `kinds` filter (e.g. to export only retrospection records). + Real T02 + 0003: improved transparency for different memory kinds. """ try: items = _load(scope) + if kinds: + items = [i for i in items if i.get("kind") in kinds] + + by_kind = {} + for item in items: + k = item.get("kind", "unknown") + by_kind.setdefault(k, []).append(item) + return { - "status": "real (T02 local json; phase ready)", + "status": "real (T02+0003 local json; activation + retrospection ready)", "scope": scope, "profile": profile, "count": len(items), "items": items, + "by_kind": {k: len(v) for k, v in by_kind.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.", + "provenance_summary": f"{len(items)} records from ~/.config/cya/memory/ (kinds: {list(by_kind.keys())})", + "note": "User-controlled. Replace with real phase-memory when available.", "phases": ["ephemeral", "fluid", "stabilized", "rigid"], } except Exception as e: @@ -191,10 +230,30 @@ def export_memory(scope: str = "cwd", *, profile: str | None = None) -> dict[str } +def remember_retrospection_outcome( + key: str, + value: Any, + scope: str = "cwd", + *, + profile: str | None = None, +) -> None: + """Convenience helper to record outcomes from a retrospection session. + + Stores with kind="retrospection" or "interaction_goal" (caller decides via key/value). + This is a thin wrapper around remember_preference for clarity in T04+. + """ + kind = KIND_RETROSPECTION if "retrospection" in str(key).lower() else KIND_INTERACTION_GOAL + remember_preference(key, value, scope=scope, profile=profile, kind=kind) + + __all__ = [ "remember_preference", "recall_preferences", "forget", "export_memory", + "remember_retrospection_outcome", + "KIND_PREFERENCE", + "KIND_RETROSPECTION", + "KIND_INTERACTION_GOAL", ] diff --git a/workplans/CYA-WP-0003-contextual-memory-activation-and-retrospection.md b/workplans/CYA-WP-0003-contextual-memory-activation-and-retrospection.md index 1dff7b9..208fb9b 100644 --- a/workplans/CYA-WP-0003-contextual-memory-activation-and-retrospection.md +++ b/workplans/CYA-WP-0003-contextual-memory-activation-and-retrospection.md @@ -73,20 +73,26 @@ completed: "2026-05-27" ```task id: CYA-WP-0003-T02 -status: todo +status: done priority: high state_hub_task_id: "6f50bdf4-6252-4b93-9697-407ef432cd90" +started: "2026-05-27 ralph continuation (after T01 review)" +completed: "2026-05-27" ``` -- Evolve the four ports (or add minimal new ones) to better support: - - Richer scoping and activation hints (e.g., directory + project identity + optional profile). - - Storing and retrieving "retrospection outcomes" and "interaction goals" as first-class memory. -- Define lightweight structures for retrospection records and activated context packages. -- Maintain full backward compatibility and user inspectability. +**Done** — implemented in `src/cya/memory/__init__.py`. -**Acceptance criteria**: -- Ports and data model support the concepts from T01 without breaking existing usage. -- All new memory remains fully user-visible and editable. +- Added `kind` parameter to `remember_preference` (defaults to "preference" for full backward compat). +- Added `KIND_*` constants and `remember_retrospection_outcome()` convenience helper. +- Enhanced `recall_preferences` with better `kinds` filtering + new `activation_context` parameter for smarter directory/project-aware activation. +- Improved `export_memory` with optional `kinds` filter and `by_kind` summary. +- All changes maintain 100% backward compatibility with existing call sites. +- Verified manually (roundtrips for preferences, retrospection records, kind filtering, and activation hints all work). + +**Acceptance criteria met**: +- Ports and data model now support the T01 concepts. +- Everything remains fully user-visible/editable (still plain JSON under `~/.config/cya/memory/`). +- No breaking changes. ### T03 — Implement directory- and project-bound memory activation