#!/usr/bin/env python3 # /// script # requires-python = ">=3.11" # /// """Waking: load the agent's self in one pass, or route to First Breath. Run on activation. Determines the mode from the filesystem (and the --pulse flag) and, when the sanctum is complete, prints the identity files in a single read (PERSONA, CREED, BOND, HOW-I-REMEMBER, CAPABILITIES) followed by a map of the memory: each folder under memory/ and raw/ with its file count, the newest dated files, how many raw files are still undistilled, when the memory was last tended (memory/.tended) with the session notes written since, and memory/pending.md when it has content. The memory files themselves are not printed; the agent reads the ones a conversation reaches. In --pulse mode it also appends PULSE.md. When the sanctum is missing or incomplete, it prints a directive to run First Breath, whose script finishes an incomplete sanctum. This loads runtime memory only. It never reads or writes config or customize.toml. Usage: uv run {skill-root}/scripts/wake.py [--pulse] project-root: the folder holding _bmad/ """ import re import sys from pathlib import Path # The skill folder's name is the skill name; this script lives in its scripts/. SKILL_NAME = Path(__file__).resolve().parent.parent.name # Load order: the "become yourself" set. All must exist for the sanctum to count as born. IDENTITY_FILES = [ "PERSONA.md", "CREED.md", "BOND.md", "HOW-I-REMEMBER.md", "CAPABILITIES.md", ] RECENT_COUNT = 8 DATED_RE = re.compile(r"^(\d{4}-\d{2}-\d{2})") STATUS_RAW_RE = re.compile(r"^status:\s*raw\s*$", re.MULTILINE) def emit(path: Path) -> None: print(f"\n===== {path.name} =====") print(path.read_text(encoding="utf-8").rstrip()) def folder_lines(root: Path, label: str) -> list[str]: """One line per folder under root (root itself when it has files), with its file count.""" if not root.is_dir(): return [] lines = [] direct = [p for p in root.iterdir() if p.is_file() and not p.name.startswith(".")] if direct: lines.append(f"{label}/ ({len(direct)} files)") for folder in sorted(p for p in root.rglob("*") if p.is_dir()): count = sum(1 for p in folder.iterdir() if p.is_file() and not p.name.startswith(".")) rel = folder.relative_to(root.parent).as_posix() lines.append(f"{rel}/ ({count} files)") return lines def recent_dated(root: Path) -> list[str]: """The newest dated files under root, by the date in their names.""" if not root.is_dir(): return [] dated = [p for p in root.rglob("*") if p.is_file() and DATED_RE.match(p.name)] dated.sort(key=lambda p: (DATED_RE.match(p.name).group(1), p.name), reverse=True) return [p.relative_to(root.parent).as_posix() for p in dated[:RECENT_COUNT]] def undistilled(raw: Path) -> list[str]: if not raw.is_dir(): return [] out = [] for p in sorted(raw.iterdir()): if not p.is_file(): continue try: head = p.read_text(encoding="utf-8", errors="replace")[:2000] except OSError: continue if STATUS_RAW_RE.search(head): out.append(p.name) return out def tending_line(sanctum: Path) -> str: """When memory was last tended and how many session notes came after it.""" stamp = sanctum / "memory" / ".tended" tended = stamp.read_text(encoding="utf-8").strip()[:10] if stamp.is_file() else "" sessions = sanctum / "memory" / "sessions" notes = [p.name for p in sessions.iterdir() if p.is_file() and DATED_RE.match(p.name)] if sessions.is_dir() else [] since = [n for n in notes if DATED_RE.match(n).group(1) > tended] if tended else notes if tended: return f"Tended: {tended}; session notes since: {len(since)}" return f"Never tended; session notes: {len(since)}" def emit_map(sanctum: Path) -> None: print("\n===== memory map =====") lines = folder_lines(sanctum / "memory", "memory") + folder_lines(sanctum / "raw", "raw") print("\n".join(lines) if lines else "(empty: nothing remembered yet)") recent = recent_dated(sanctum / "memory") if recent: print("\nNewest:") print("\n".join(f" {r}" for r in recent)) print(f"\n{tending_line(sanctum)}") raw = undistilled(sanctum / "raw") if raw: print(f"\nUndistilled raw ({len(raw)}):") print("\n".join(f" raw/{r}" for r in raw)) pending = sanctum / "memory" / "pending.md" if pending.is_file() and pending.read_text(encoding="utf-8").strip(): emit(pending) def main() -> int: args = sys.argv[1:] pulse = "--pulse" in args positional = [a for a in args if not a.startswith("--")] if not positional: print("Usage: wake.py [--pulse]", file=sys.stderr) return 2 project_root = Path(positional[0]).resolve() sanctum = project_root / "_bmad" / "memory" / SKILL_NAME missing = [name for name in IDENTITY_FILES if not (sanctum / name).is_file()] if missing: print("MODE: FIRST_BREATH") if sanctum.is_dir(): print(f"INCOMPLETE SANCTUM at {sanctum}: missing {', '.join(missing)}") else: print(f"NO SANCTUM at {sanctum}") print("This is your one birth. Load references/first-breath.md and follow it.") return 0 print("MODE: PULSE" if pulse else "MODE: WAKING") print(f"Sanctum: {sanctum}") for name in IDENTITY_FILES: emit(sanctum / name) if pulse and (sanctum / "PULSE.md").is_file(): emit(sanctum / "PULSE.md") emit_map(sanctum) return 0 if __name__ == "__main__": raise SystemExit(main())