diff --git a/README.md b/README.md index db570cf..89c38be 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ Run a guided reflection session to review how memory was used and explicitly set cya retrospect ``` -During the session `cya` will: +During the session `cya` will (Profile 1 spike in T05 adds optional verbal lesson capture at the end): - Show recent memory items that were activated. - Help you reflect on what worked or didn't. - Let you record new **interaction goals** (e.g. "be more concise", "always show one safe alternative for destructive commands"). diff --git a/src/cya/memory/__init__.py b/src/cya/memory/__init__.py index 49b6a47..7926fdc 100644 --- a/src/cya/memory/__init__.py +++ b/src/cya/memory/__init__.py @@ -38,6 +38,7 @@ from typing import Any KIND_PREFERENCE = "preference" KIND_RETROSPECTION = "retrospection" KIND_INTERACTION_GOAL = "interaction_goal" +KIND_REFLECTION = "reflection" # Profile 1 (Reflexion-style verbal lessons) — T05 minimal spike def _warn_not_connected(feature: str) -> None: @@ -257,14 +258,31 @@ def remember_retrospection_outcome( remember_preference(key, value, scope=scope, profile=profile, kind=kind) +def remember_reflection( + key: str, + value: Any, + scope: str = "cwd", + *, + profile: str | None = None, +) -> None: + """Convenience helper for Profile 1 (Reflexion-style verbal self-improvement). + + Stores verbal lessons/reflections with kind="reflection" for preferential + activation in future turns. Thin wrapper for the T05 minimal spike. + """ + remember_preference(key, value, scope=scope, profile=profile, kind=KIND_REFLECTION) + + __all__ = [ "remember_preference", "recall_preferences", "forget", "export_memory", "remember_retrospection_outcome", + "remember_reflection", "KIND_PREFERENCE", "KIND_RETROSPECTION", "KIND_INTERACTION_GOAL", + "KIND_REFLECTION", ] diff --git a/src/cya/orchestrator.py b/src/cya/orchestrator.py index 17d46fa..938fa72 100644 --- a/src/cya/orchestrator.py +++ b/src/cya/orchestrator.py @@ -32,8 +32,10 @@ from cya.context.collector import collect, render_explanation from cya.memory import ( recall_preferences, remember_retrospection_outcome, + remember_reflection, KIND_RETROSPECTION, KIND_INTERACTION_GOAL, + KIND_REFLECTION, ) from cya.safety.risk import classify, get_user_confirmation from cya.llm.adapter import AssistanceRequest, FakeLLMAdapter @@ -85,7 +87,11 @@ def handle_request( git_info = envelope.git or {} if git_info.get("workdir"): act_ctx["git_root"] = git_info["workdir"] - memory = recall_preferences(".", activation_context=act_ctx) + memory = recall_preferences( + ".", + activation_context=act_ctx, + kinds=[KIND_REFLECTION, KIND_RETROSPECTION, KIND_INTERACTION_GOAL, "preference"], + ) except Exception: memory = {"error": "recall failed (graceful degradation)"} @@ -150,6 +156,12 @@ def handle_request( mem_line = "" if memory.get("items"): mem_line = f"\n[dim]Memory activated: {len(memory.get('items', []))} items (phase {memory.get('phase')})[/dim]" + # Minimal Profile 1 surface (T05 spike) + reflections = [i for i in memory.get("items", []) if i.get("kind") == KIND_REFLECTION] + if reflections: + refl_text = "; ".join(str(i.get("value", ""))[:60] for i in reflections[:2]) + mem_line += f"\n[cyan]Verbal reflections activated: {len(reflections)} — {refl_text}[/cyan]" + console.print( Panel( f"[bold]Suggestion:[/bold]\n{llm_response.suggestion}\n\n" @@ -251,6 +263,24 @@ def run_retrospection(scope: str = ".", limit: int = 8) -> None: ) console.print("[green]Recorded as safety preference.[/green]") + # Minimal Profile 1 spike (T05): optional verbal reflection / lesson capture + capture_lesson = typer.prompt( + "Capture any verbal lessons or reflections from this session? (y/n or short text)", + default="n", + show_default=False, + ) + if capture_lesson and capture_lesson.lower() not in ("n", "no", "skip", "s", ""): + lesson_text = capture_lesson if len(capture_lesson) > 3 else typer.prompt( + "What is the key lesson? (1-2 sentences)", + default="", + show_default=False, + ) + if lesson_text: + remember_reflection( + "verbal_lesson", lesson_text, scope=scope + ) + console.print("[green]Recorded as verbal reflection (Profile 1).[/green]") + console.print( Panel( "Thank you. Your reflections have been stored as retrospection memory.\n" diff --git a/tests/test_memory.py b/tests/test_memory.py index 4691c98..b4d26e7 100644 --- a/tests/test_memory.py +++ b/tests/test_memory.py @@ -21,8 +21,10 @@ from cya.memory import ( forget, export_memory, remember_retrospection_outcome, + remember_reflection, KIND_RETROSPECTION, KIND_INTERACTION_GOAL, + KIND_REFLECTION, ) from cya.safety.risk import classify, RiskLevel @@ -167,6 +169,23 @@ def test_recall_with_kinds_and_activation_context(isolated_memory): assert KIND_INTERACTION_GOAL in kinds or "retrospection" in kinds +def test_profile_1_reflection_helper_and_activation(isolated_memory): + """Minimal T05 Profile 1 spike: remember_reflection + preferential recall by kind.""" + remember_reflection("lesson_rust", "Always run cargo clippy before suggesting fixes", scope="proj-rust") + + data = recall_preferences( + scope="proj-rust", + kinds=[KIND_REFLECTION], + activation_context={"cwd": "proj-rust"}, + ) + + assert len(data.get("items", [])) >= 1 + kinds = [i.get("kind") for i in data.get("items", [])] + assert KIND_REFLECTION in kinds + # The helper stored it correctly + assert any("cargo clippy" in str(i.get("value", "")) for i in data.get("items", [])) + + # --------------------------------------------------------------------------- # CYA-WP-0005 T02 — Explicit Profile 0 baseline assertions # --------------------------------------------------------------------------- diff --git a/workplans/CYA-WP-0005-agentic-memory-profiles-and-phase-memory-feedback.md b/workplans/CYA-WP-0005-agentic-memory-profiles-and-phase-memory-feedback.md index 00a9fbe..e639734 100644 --- a/workplans/CYA-WP-0005-agentic-memory-profiles-and-phase-memory-feedback.md +++ b/workplans/CYA-WP-0005-agentic-memory-profiles-and-phase-memory-feedback.md @@ -137,9 +137,11 @@ completed: "2026-05-28 ralph iter 4" ```task id: CYA-WP-0005-T05 -status: todo +status: done priority: medium state_hub_task_id: "4b59d4f4-d067-470b-b6c3-30c64cbc1010" +started: "2026-05-28 ralph iter 5 (optional spike)" +completed: "2026-05-28 ralph iter 5" ``` **Description**: If time and review allow, implement a thin but real end-to-end slice of Profile 1 on top of Profile 0: new `kind="reflection"` support (or reuse retrospection), enhancement to `cya retrospect` (or new `cya reflect`) to capture verbal lessons, preferential activation for reflection kinds, rendering in `--explain-context` and final output, tests, and docs. All changes must be small, inspectable, and preserve safety invariants.