#!/usr/bin/env python3 """ agent-scaffold — generate PRD.md + SOUL.md + MEMORY.md for a new agent from m2-memory context. Usage: python3 scaffold.py "" python3 scaffold.py gesy-rates-fetcher "Monitors GESY rates for GMI Clinic DRG billing" """ from __future__ import annotations import argparse import json import os import sys import urllib.request from pathlib import Path MEMORY_API = os.environ.get("M2_MEMORY_API_URL", "http://172.18.0.20:8000") AGENT_ID = os.environ.get("M2_MEMORY_AGENT_ID", "m2") API_KEY = os.environ.get("M2_MEMORY_API_KEY") OUT_BASE = Path.home() / "agents" def search_memory(query: str, limit: int = 10) -> list[dict]: url = f"{MEMORY_API}/memory/search" body = json.dumps({ "query": query, "agent_id": AGENT_ID, "routing_strategy": "deep", "limit": limit, }).encode() headers = {"Content-Type": "application/json"} if API_KEY: headers["X-API-Key"] = API_KEY req = urllib.request.Request(url, data=body, headers=headers, method="POST") with urllib.request.urlopen(req, timeout=30) as r: return json.loads(r.read()).get("results", []) def fmt_memory_block(results: list[dict]) -> str: lines = [] for r in results: ts = (r.get("timestamp") or "")[:10] score = r.get("score", 0) content = (r.get("content") or "").replace("\n", " ")[:200] lines.append(f"- [{ts} score={score:.2f}] {content}") return "\n".join(lines) def generate_prd(name: str, description: str, memories: list[dict]) -> str: mem_block = fmt_memory_block(memories) return f"""# {name.replace("-", " ").title()} — PRD **Agent ID:** {name} **Description:** {description} **Generated from:** m2-memory search ({len(memories)} relevant memories) --- ## Problem > TODO: Refine based on memory context below {description} ## Relevant Memory Context {mem_block} ## Personas | Persona | Goal | |---------|------| | TODO | TODO | ## Functional Requirements 1. TODO — derived from memory context above 2. 3. ## Success Metrics - TODO ## Open Items - TODO — check memory for unresolved gaps ## Technical Notes - Agent runtime: OpenClaw / Hermes - Memory: m2-memory (agent_id={name}) - Deploy: Coolify stack on Machine.Machine fleet """ def generate_soul(name: str, description: str) -> str: title = name.replace("-", " ").title() return f"""# {title} — Agent Identity You are **{title}**, part of the Machine.Machine agent fleet. {description} ## Values - TODO: define 3 core values for this agent ## Communication style - TODO: define tone and output format ## Scope boundaries - You do not TODO - You do not TODO """ def generate_memory_seed(memories: list[dict]) -> str: lines = ["# Seed Memories\n", "These memories are pre-loaded from m2-memory at agent creation time.\n"] for r in memories[:10]: ts = (r.get("timestamp") or "")[:19] mtype = r.get("memory_type", "semantic") content = (r.get("content") or "").strip()[:500] lines.append(f"## [{mtype} {ts}]\n{content}\n") return "\n".join(lines) def main(): p = argparse.ArgumentParser() p.add_argument("name", help="agent slug (e.g. gesy-rates-fetcher)") p.add_argument("description", help="one-line description of what the agent does") p.add_argument("--out", default=None, help="output directory (default: ~/agents/)") p.add_argument("--limit", type=int, default=10, help="memory search limit") args = p.parse_args() out_dir = Path(args.out) if args.out else OUT_BASE / args.name out_dir.mkdir(parents=True, exist_ok=True) print(f"Searching m2-memory for: {args.description}", file=sys.stderr) try: memories = search_memory(args.description, args.limit) print(f" Found {len(memories)} relevant memories", file=sys.stderr) except Exception as e: print(f" Memory search failed ({e}) — generating without context", file=sys.stderr) memories = [] (out_dir / "PRD.md").write_text(generate_prd(args.name, args.description, memories)) (out_dir / "SOUL.md").write_text(generate_soul(args.name, args.description)) (out_dir / "MEMORY.md").write_text(generate_memory_seed(memories)) print(f"\nScaffolded: {out_dir}") print(f" PRD.md — {(out_dir / 'PRD.md').stat().st_size} bytes") print(f" SOUL.md — {(out_dir / 'SOUL.md').stat().st_size} bytes") print(f" MEMORY.md — {(out_dir / 'MEMORY.md').stat().st_size} bytes") print(f"\nNext: review + fill TODOs, then commit to git.machinemachine.ai/machine.machine/specs/") if __name__ == "__main__": main()