feat(protocols): add protocols artifact convention, sys-medic protocol + CLI (WP-0002 T17-T24)

- ADR-003: protocols artifact convention (location, structure, lifecycle)
- agents/protocols/README.md: directory-level index and usage guide
- agents/protocols/sys-medic/k3s-node-health-assessment.md: full structured
  k3s node health assessment protocol (8 steps: OS baseline, process hygiene,
  memory, CPU, disk, network, k3s node state, runtime services)
- agent-sys-medic.md: add memory: enabled frontmatter, session-start/close
  protocols, node-profile memory template extensions, protocol reference in
  Default Task
- cli.py: add protocols command group (list, show); extend memory init to hint
  protocol commands for agents that have protocols

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-18 23:48:09 +00:00
parent 15f4cce238
commit 53dfd55916
5 changed files with 593 additions and 0 deletions

View File

@@ -820,6 +820,16 @@ session_count: 0
memory_path.write_text(content)
click.echo(f"Initialized memory for '{agent_name}': {memory_path}")
# For agents with protocols, note the protocol location
registry = _get_registry()
protocols_dir = registry.agents_dir / "protocols" / agent_name
if protocols_dir.exists():
slugs = [f.stem for f in sorted(protocols_dir.glob("*.md")) if f.name != "README.md"]
if slugs:
click.echo(f" Protocols available for '{agent_name}':")
for slug in slugs:
click.echo(f" kaizen-agentic protocols show {agent_name} {slug}")
@memory.command("brief")
@click.argument("agent_name")
@@ -918,6 +928,79 @@ def memory_clear(agent_name: str, target: str):
memory_path.parent.rmdir()
@cli.group()
def protocols():
"""Browse agent protocol runbooks (agents/protocols/<agent>/<slug>.md)."""
pass
@protocols.command("list")
@click.argument("agent_name", required=False)
def protocols_list(agent_name: Optional[str]):
"""List available protocols, optionally filtered by agent."""
registry = _get_registry()
protocols_dir = registry.agents_dir / "protocols"
if not protocols_dir.exists():
click.echo("No protocols directory found.")
return
found = []
agent_dirs = (
[protocols_dir / agent_name] if agent_name else sorted(protocols_dir.iterdir())
)
for agent_dir in agent_dirs:
if not agent_dir.is_dir() or agent_dir.name == "__pycache__":
continue
for protocol_file in sorted(agent_dir.glob("*.md")):
if protocol_file.name == "README.md":
continue
# Try to read title from frontmatter
title = protocol_file.stem.replace("-", " ").title()
try:
content = protocol_file.read_text()
for line in content.splitlines():
if line.startswith("title:"):
title = line.split(":", 1)[1].strip().strip('"')
break
except Exception:
pass
found.append((agent_dir.name, protocol_file.stem, title))
if not found:
if agent_name:
click.echo(f"No protocols found for agent '{agent_name}'.")
else:
click.echo("No protocols found.")
return
click.echo("Available Protocols:")
click.echo("=" * 40)
current_agent = None
for agent, slug, title in found:
if agent != current_agent:
click.echo(f"\n {agent}:")
current_agent = agent
click.echo(f"{slug}: {title}")
@protocols.command("show")
@click.argument("agent_name")
@click.argument("slug")
def protocols_show(agent_name: str, slug: str):
"""Print a protocol runbook."""
registry = _get_registry()
protocol_path = registry.agents_dir / "protocols" / agent_name / f"{slug}.md"
if not protocol_path.exists():
click.echo(f"Protocol not found: {agent_name}/{slug}")
click.echo(f" Expected: {protocol_path}")
click.echo(f" Run: kaizen-agentic protocols list {agent_name}")
return
click.echo(protocol_path.read_text())
def _memory_path(target: str, agent_name: str) -> Path:
return Path(target).resolve() / ".kaizen" / "agents" / agent_name / "memory.md"