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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user