diff --git a/docs/adr/ADR-001-workplan-convention.md b/docs/adr/ADR-001-workplan-convention.md new file mode 100644 index 0000000..11333fa --- /dev/null +++ b/docs/adr/ADR-001-workplan-convention.md @@ -0,0 +1,57 @@ +--- +id: ADR-001 +title: Workplan Convention +status: accepted +date: "2026-03-18" +--- + +# ADR-001 — Workplan Convention + +## Status + +Accepted + +## Context + +kaizen-agentic needs a way to track planned work that is version-controlled, +visible to the state-hub, and authoritative when the two diverge. + +## Decision + +Work items originate as Markdown files in `workplans/` **before** being +registered in the state-hub DB. The file is always authoritative; the DB is +a read/query model derived from it. + +**File naming:** `workplans/kaizen-agentic-WP-NNNN-.md` +**ID prefix:** `KAIZEN-WP` + +### Required YAML frontmatter + +```yaml +--- +id: KAIZEN-WP-NNNN +type: workplan +title: "..." +domain: custodian +repo: kaizen-agentic +status: active | completed | archived +owner: kaizen-agentic +topic_slug: custodian +state_hub_workstream_id: +created: "YYYY-MM-DD" +updated: "YYYY-MM-DD" +--- +``` + +### Task tracking + +Tasks use `- [ ]` / `- [x]` checkboxes with a `T##` code prefix. A +`## State Hub Task IDs` table at the end of each workplan maps codes to +DB UUIDs so status can be synced without a list_tasks lookup. + +## Consequences + +- File is the source of truth; DB drift is auto-fixable via + `check_repo_consistency(fix=True)`. +- Tasks must be created in the file first, then registered in the hub. +- C-12 warnings are expected when the DB host has not yet seen local changes. diff --git a/docs/adr/ADR-002-project-memory-convention.md b/docs/adr/ADR-002-project-memory-convention.md new file mode 100644 index 0000000..0e96712 --- /dev/null +++ b/docs/adr/ADR-002-project-memory-convention.md @@ -0,0 +1,119 @@ +--- +id: ADR-002 +title: Project Memory Convention +status: accepted +date: "2026-03-18" +--- + +# ADR-002 — Project Memory Convention + +## Status + +Accepted + +## Context + +kaizen-agentic agents are stateless by default — each session starts from +scratch with no knowledge of what has been tried, what worked, or what the +project's recurring patterns are. This makes agents less useful over time +and forces the operator to re-supply context that the agent itself +accumulated. + +## Decision + +Each agent deployed into a project may maintain a **project-scoped memory +file**. Memory files are written at session close and read at session start. + +### File location + +``` +/.kaizen/agents//memory.md +``` + +The `.kaizen/` directory is the kaizen-agentic ecosystem's project-level +state directory, analogous to `.claude/` for Claude Code. + +### Memory file structure + +```markdown +--- +agent: +project: +last_updated: +session_count: +--- + +## Project Context + + +## Accumulated Findings + + +## What Worked + + +## Watch Points + + +## Open Threads + + +## Session Log + +``` + +### Session-start protocol (all memory-enabled agents) + +1. Check for `.kaizen/agents//memory.md` in the project root. +2. If present, read it before beginning work. +3. Acknowledge the memory in the opening brief. + +### Session-close protocol (all memory-enabled agents) + +1. Update `## Accumulated Findings`, `## What Worked`, `## Watch Points` + as needed. +2. Append one line to `## Session Log`. +3. Bump `last_updated` and `session_count`. + +### Agent opt-out + +An agent may declare `memory: disabled` in its YAML frontmatter to opt out. +Default is enabled. Stateless utility agents (e.g. `keepaTodofile`) should +opt out. + +### CLI interface + +``` +kaizen-agentic memory show # Print agent memory for current project +kaizen-agentic memory init # Scaffold empty memory file +kaizen-agentic memory brief # Run coach, print orientation for agent +kaizen-agentic memory clear # Wipe memory (with confirmation prompt) +``` + +`memory init` creates the `.kaizen/agents//memory.md` file with the +standard structure and populates the frontmatter. + +### Coaching meta-agent + +A dedicated `agent-coach.md` (category: `meta`) reads across all +`.kaizen/agents/*/memory.md` files in a project and: + +- Synthesises a cross-agent brief (shared patterns, cross-domain risks) +- Produces a new-agent orientation targeted at a specific agent about to + be deployed for the first time +- Maintains its own memory covering meta-level fleet observations + +`kaizen-agentic memory brief ` invokes the coach to produce this +orientation. + +## Consequences + +- Agents accumulate project-specific knowledge and arrive in later sessions + informed rather than blank. +- The `.kaizen/` directory should be added to `.gitignore` by default; + teams may choose to commit it for shared context. +- Memory files are human-readable and can be manually edited or reviewed. +- The coach agent provides a single synthesised view across all agent + memories — reducing the operator's burden of re-supplying context. +- Agents with `memory: disabled` remain fully stateless and require no + `.kaizen/` setup. diff --git a/src/kaizen_agentic/cli.py b/src/kaizen_agentic/cli.py index 7e91bc5..15835be 100644 --- a/src/kaizen_agentic/cli.py +++ b/src/kaizen_agentic/cli.py @@ -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//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 + + +## Accumulated Findings + + +## What Worked + + +## Watch Points + + +## Open Threads + + +## Session Log + +""" + 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 ' 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 diff --git a/workplans/kaizen-agentic-WP-0002-agency-framework.md b/workplans/kaizen-agentic-WP-0002-agency-framework.md index 410c823..6126531 100644 --- a/workplans/kaizen-agentic-WP-0002-agency-framework.md +++ b/workplans/kaizen-agentic-WP-0002-agency-framework.md @@ -138,8 +138,8 @@ kaizen-agentic memory clear # Wipe memory (with confirmation) ### Tasks **Memory convention and tooling** -- [ ] T07 — Write ADR: project memory convention (file location, structure, lifecycle) -- [ ] T08 — Implement `memory` CLI command group (show, init, brief, clear) +- [x] T07 — Write ADR: project memory convention (file location, structure, lifecycle) +- [x] T08 — Implement `memory` CLI command group (show, init, brief, clear) - [ ] T09 — Add session-start and session-close protocol sections to agent template / contributor guide