feat(memory): add memory CLI command group and project memory ADRs

- Add docs/adr/ADR-001-workplan-convention.md (formalises existing convention)
- Add docs/adr/ADR-002-project-memory-convention.md (file location, structure,
  session protocols, opt-out, CLI interface)
- Implement `kaizen-agentic memory` command group: show, init, brief, clear
  - Memory stored at .kaizen/agents/<name>/memory.md in project root
  - `init` scaffolds the standard memory template with YAML frontmatter
  - `brief` lists all agent memories + note that coach synthesis is pending T13
  - `clear` deletes with confirmation prompt

WP-0002 T07 and T08 done.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-18 23:25:48 +00:00
parent eff77973a1
commit 4b4b1ff1f1
4 changed files with 312 additions and 2 deletions

View File

@@ -756,6 +756,140 @@ def remove(name: str, target: str):
click.echo(f"❌ Extension not found: {name}")
@cli.group()
def memory():
"""Manage project-scoped agent memory (.kaizen/agents/<name>/memory.md)."""
pass
@memory.command("show")
@click.argument("agent_name")
@click.option("--target", "-t", default=".", help="Project root (default: current)")
def memory_show(agent_name: str, target: str):
"""Print agent memory for the current project."""
memory_path = _memory_path(target, agent_name)
if not memory_path.exists():
click.echo(f"No memory found for agent '{agent_name}'.")
click.echo(f" Expected: {memory_path}")
click.echo(f" Run: kaizen-agentic memory init {agent_name}")
return
click.echo(memory_path.read_text())
@memory.command("init")
@click.argument("agent_name")
@click.option("--target", "-t", default=".", help="Project root (default: current)")
def memory_init(agent_name: str, target: str):
"""Scaffold an empty memory file for an agent."""
memory_path = _memory_path(target, agent_name)
if memory_path.exists():
click.echo(f"Memory file already exists: {memory_path}")
return
memory_path.parent.mkdir(parents=True, exist_ok=True)
project_name = Path(target).resolve().name
content = f"""---
agent: {agent_name}
project: {project_name}
last_updated: {_today()}
session_count: 0
---
## Project Context
<!-- What this agent knows about the project it works in -->
## Accumulated Findings
<!-- Patterns, recurring issues, key decisions encountered -->
## What Worked
<!-- Approaches that produced good results in this project -->
## Watch Points
<!-- Recurring risks, traps, or areas requiring extra care -->
## Open Threads
<!-- Things noticed but not yet acted on -->
## Session Log
<!-- One-line entry per session: date · summary · outcome -->
"""
memory_path.write_text(content)
click.echo(f"Initialized memory for '{agent_name}': {memory_path}")
@memory.command("brief")
@click.argument("agent_name")
@click.option("--target", "-t", default=".", help="Project root (default: current)")
def memory_brief(agent_name: str, target: str):
"""Print a coach-synthesised orientation for an agent.
Reads this agent's memory and all other agent memories in the project.
Coach agent integration is pending (T13 — see KAIZEN-WP-0002).
Currently prints the agent's own memory as a starting orientation.
"""
memory_path = _memory_path(target, agent_name)
kaizen_dir = Path(target).resolve() / ".kaizen" / "agents"
click.echo(f"=== Agent Brief: {agent_name} ===\n")
# Show own memory
if memory_path.exists():
click.echo(f"--- Memory: {agent_name} ---")
click.echo(memory_path.read_text())
else:
click.echo(f"No memory file found for '{agent_name}'. Run: memory init {agent_name}")
# List other agents with memory in this project
other_agents = []
if kaizen_dir.exists():
for agent_dir in kaizen_dir.iterdir():
if agent_dir.is_dir() and agent_dir.name != agent_name:
mf = agent_dir / "memory.md"
if mf.exists():
other_agents.append(agent_dir.name)
if other_agents:
click.echo(f"\n--- Other agents with memory in this project: {', '.join(other_agents)} ---")
click.echo("(Coach synthesis not yet available — use 'memory show <agent>' to read each)")
else:
click.echo("\nNo other agent memories found in this project.")
click.echo("\nNote: Full coach synthesis will be available once agent-coach is implemented (KAIZEN-WP-0002 T12-T13).")
@memory.command("clear")
@click.argument("agent_name")
@click.option("--target", "-t", default=".", help="Project root (default: current)")
@click.confirmation_option(prompt="This will permanently delete the agent memory. Continue?")
def memory_clear(agent_name: str, target: str):
"""Wipe agent memory for the current project."""
memory_path = _memory_path(target, agent_name)
if not memory_path.exists():
click.echo(f"No memory found for agent '{agent_name}' — nothing to clear.")
return
memory_path.unlink()
click.echo(f"Cleared memory for '{agent_name}': {memory_path}")
# Remove empty parent directory
if not any(memory_path.parent.iterdir()):
memory_path.parent.rmdir()
def _memory_path(target: str, agent_name: str) -> Path:
return Path(target).resolve() / ".kaizen" / "agents" / agent_name / "memory.md"
def _today() -> str:
from datetime import date
return date.today().isoformat()
def _get_registry() -> AgentRegistry:
"""Get the agent registry."""
# Try to find agents directory