# Session 4: Paperclip Orchestration Patterns > Steps 14, 15, 16, 17, 18 | Wave 1 | Depends on: none ## Dependencies Entry condition: none (creates new template directories only, plus 2 new files in heartbeat/) ## Scope Fence **Touch:** - `scripts/templates/heartbeat/context-packet.md` (new) - `scripts/templates/heartbeat/wake-prompt.md` (new) - `scripts/templates/goals/GOALS.md` (new) - `scripts/templates/goals/goal-tracker.sh` (new) - `scripts/templates/goals/README.md` (new) - `scripts/templates/budget/budget-hook.sh` (new) - `scripts/templates/budget/BUDGET.md` (new) - `scripts/templates/budget/budget-report.sh` (new) - `scripts/templates/budget/README.md` (new) - `scripts/templates/governance/GOVERNANCE.md` (new) - `scripts/templates/governance/approval-gate.sh` (new) - `scripts/templates/governance/README.md` (new) - `scripts/templates/org-chart/ORG-CHART.md` (new) - `scripts/templates/org-chart/org-manager.sh` (new) - `scripts/templates/org-chart/README.md` (new) **Never touch:** - `commands/` - `agents/` - `skills/` - `scripts/templates/heartbeat/HEARTBEAT.md` (Session 3) - `scripts/templates/heartbeat/heartbeat-runner.sh` (Session 3) - `scripts/templates/heartbeat/README.md` (Session 3) - `scripts/templates/memory/` - `scripts/templates/proactive/` - `scripts/templates/cron/` - `.claude-plugin/`, `CLAUDE.md`, `README.md` --- ## Step 14: Create heartbeat context injection templates ### Files to create **`scripts/templates/heartbeat/context-packet.md`** — Paperclip's "Memento Man" pattern: ```markdown # Context Packet: {{AGENT_NAME}} Generated: {{TIMESTAMP}} ## Identity {{AGENT_IDENTITY}} ## Current Goals {{ACTIVE_GOALS}} ## Memory State {{MEMORY_SUMMARY}} ## Task Queue {{PENDING_TASKS}} ## Recent Events (last 24h) {{RECENT_EVENTS}} ## Wake Reason {{WAKE_REASON}} ## Budget Status Spent: {{BUDGET_SPENT}} / {{BUDGET_LIMIT}} ({{BUDGET_PERCENT}}%) {{BUDGET_WARNING}} ``` **`scripts/templates/heartbeat/wake-prompt.md`** — Prompt template for each heartbeat wakeup: ```markdown You are {{AGENT_NAME}}. You are waking up for a scheduled heartbeat. Read the context packet below carefully. It contains everything you need to know about your current state and pending work. {{CONTEXT_PACKET}} Your task for this beat: {{WAKE_REASON}} Rules: - Do NOT infer tasks from prior conversations - Only work on what is in the context packet and wake reason - If nothing needs attention, respond with HEARTBEAT_OK - Update SESSION-STATE.md before finishing ``` ### Verify ```bash ls /Users/ktg/repos/agent-builder/scripts/templates/heartbeat/ | wc -l ``` Expected: `5` (3 from Session 3 + 2 from this step) ### On failure: revert ### Checkpoint ```bash git commit -m "feat(templates): add context injection templates (Paperclip heartbeat pattern)" ``` --- ## Step 15: Create goal hierarchy templates ### Files to create **`scripts/templates/goals/GOALS.md`**: ```markdown # Goals: {{PROJECT_NAME}} ## Company Goals - [G1] {{COMPANY_GOAL_1}} - [G2] {{COMPANY_GOAL_2}} ## Project Goals - [G1.1] {{PROJECT_GOAL_1}} (parent: G1) - [G1.2] {{PROJECT_GOAL_2}} (parent: G1) - [G2.1] {{PROJECT_GOAL_3}} (parent: G2) ## Task Goals - [G1.1.1] {{TASK_GOAL_1}} (parent: G1.1, owner: {{AGENT_NAME}}, status: active) - [G1.1.2] {{TASK_GOAL_2}} (parent: G1.1, owner: {{AGENT_NAME}}, status: pending) ## Notes Goal IDs use hierarchical dot notation. Each goal has: - ID: unique identifier (e.g., G1.1.1) - Description: what the goal is - Parent: which goal this supports (simple parent reference, not recursive) - Owner: which agent is responsible (task goals only) - Status: active | pending | complete | blocked ``` **`scripts/templates/goals/goal-tracker.sh`**: ```bash #!/bin/bash # Goal tracker: parse and manage GOALS.md # Bash 3.2 compatible. Uses python3 for parsing. # # Usage: # ./goal-tracker.sh # Show goal summary # ./goal-tracker.sh complete G1.1.1 # Mark goal as complete # ./goal-tracker.sh status # Show status counts # ./goal-tracker.sh context # Generate context for heartbeat injection # # Placeholders: # {{WORKING_DIR}} - absolute path to project directory WORKING_DIR="{{WORKING_DIR}}" GOALS_FILE="$WORKING_DIR/GOALS.md" ACTION="${1:-summary}" GOAL_ID="${2:-}" if [ ! -f "$GOALS_FILE" ]; then echo "Error: $GOALS_FILE not found" exit 1 fi case "$ACTION" in summary|status) python3 << PYEOF import re goals = [] with open("$GOALS_FILE") as f: for line in f: m = re.match(r'-\s*\[(\S+)\]\s+(.+)', line.strip()) if m: gid = m.group(1) rest = m.group(2) status_m = re.search(r'status:\s*(\w+)', rest) parent_m = re.search(r'parent:\s*(\S+)', rest) owner_m = re.search(r'owner:\s*(\S+)', rest) status = status_m.group(1) if status_m else 'active' parent = parent_m.group(1).rstrip(',)') if parent_m else None owner = owner_m.group(1).rstrip(',)') if owner_m else None desc = re.sub(r'\(.*\)', '', rest).strip() goals.append({'id': gid, 'desc': desc, 'status': status, 'parent': parent, 'owner': owner}) # Status counts counts = {} for g in goals: counts[g['status']] = counts.get(g['status'], 0) + 1 print("Goal Status Summary") print("=" * 40) for status, count in sorted(counts.items()): print(f" {status}: {count}") print(f" Total: {len(goals)}") # Check for orphans all_ids = set(g['id'] for g in goals) orphans = [g for g in goals if g['parent'] and g['parent'] not in all_ids] if orphans: print(f"\nOrphaned goals (parent not found):") for g in orphans: print(f" [{g['id']}] parent: {g['parent']}") # Goals without owners at task level unowned = [g for g in goals if '.' in g['id'] and g['id'].count('.') >= 2 and not g['owner']] if unowned: print(f"\nTask goals without owners:") for g in unowned: print(f" [{g['id']}] {g['desc']}") PYEOF ;; complete) if [ -z "$GOAL_ID" ]; then echo "Usage: $0 complete " exit 1 fi python3 -c " import re goal_id = '$GOAL_ID' with open('$GOALS_FILE') as f: content = f.read() # Replace status for the specific goal pattern = r'(\[' + re.escape(goal_id) + r'\][^)]*status:\s*)\w+' if re.search(pattern, content): content = re.sub(pattern, r'\1complete', content) with open('$GOALS_FILE', 'w') as f: f.write(content) print(f'Goal {goal_id} marked as complete') else: print(f'Goal {goal_id} not found or has no status field') " ;; context) # Generate a goal summary for heartbeat context injection python3 << PYEOF import re goals = [] with open("$GOALS_FILE") as f: for line in f: m = re.match(r'-\s*\[(\S+)\]\s+(.+)', line.strip()) if m: gid = m.group(1) rest = m.group(2) status_m = re.search(r'status:\s*(\w+)', rest) status = status_m.group(1) if status_m else 'active' desc = re.sub(r'\(.*\)', '', rest).strip() goals.append({'id': gid, 'desc': desc, 'status': status}) active = [g for g in goals if g['status'] == 'active'] if active: print("Active goals:") for g in active: print(f" [{g['id']}] {g['desc']}") else: print("No active goals.") PYEOF ;; *) echo "Usage: $0 [summary|complete |status|context]" exit 1 ;; esac ``` **`scripts/templates/goals/README.md`**: ```markdown # Goal Hierarchy File-based goal hierarchy inspired by Paperclip's goal system. ## Design decisions - **Simple parent_id, not recursive**: Each goal references its parent by ID. No recursive traversal at runtime — matching Paperclip's actual implementation (not their aspirational docs which describe "full ancestry"). - **Dot notation for hierarchy**: G1 → G1.1 → G1.1.1 makes the hierarchy visible in the ID itself. - **File-based, not database**: Human-editable, version-controlled, no dependencies. ## Goal levels | Level | ID pattern | Example | Description | |-------|-----------|---------|-------------| | Company | G1, G2 | G1 | Top-level organizational goals | | Project | G1.1, G1.2 | G1.1 | Goals that support a company goal | | Task | G1.1.1 | G1.1.1 | Specific tasks assigned to agents | ## Usage ```bash # View goal summary ./goal-tracker.sh # Mark a task as complete ./goal-tracker.sh complete G1.1.1 # Generate context for heartbeat injection ./goal-tracker.sh context ``` ## Integration with heartbeat The `context` command produces a summary suitable for injection into the heartbeat context packet (see `scripts/templates/heartbeat/context-packet.md`). The heartbeat runner can call `./goal-tracker.sh context` and inject the output as `{{ACTIVE_GOALS}}`. ``` ### Verify ```bash bash -n /Users/ktg/repos/agent-builder/scripts/templates/goals/goal-tracker.sh && echo "VALID" ``` Expected: `VALID` ### On failure: retry — fix bash syntax, then revert ### Checkpoint ```bash git commit -m "feat(templates): add goal hierarchy templates (Paperclip pattern)" ``` --- ## Step 16: Create budget tracking templates ### Files to create **`scripts/templates/budget/BUDGET.md`**: ```markdown # Budget Policy: {{PROJECT_NAME}} ## Company Budget - window: {{BUDGET_WINDOW}} - limit: {{BUDGET_LIMIT_CENTS}} cents - warn_percent: 80 - hard_stop: true ## Agent Budgets - {{AGENT_NAME}}: {{AGENT_BUDGET_CENTS}} cents/{{BUDGET_WINDOW}} ## Notification - on_warn: log - on_hard_stop: pause ## Notes Budget enforcement is POST-HOC (checked after each run, not before). This matches Paperclip's proven approach: check SUM(cost) after run, pause if exceeded. No pre-run reservation needed. Cost estimation uses token counts × published pricing. For accurate cost data, organizations can use the Admin API: `/v1/organizations/cost_report` (requires Admin API key: sk-ant-admin...). For headless runs, use `claude -p --max-budget-usd N` as a per-run cap. ``` **`scripts/templates/budget/budget-hook.sh`**: ```bash #!/bin/bash # PostToolUse hook: Log cost events and enforce budget. # Bash 3.2 compatible. Uses python3 for JSON parsing. # # Follows Paperclip's post-hoc enforcement pattern: # 1. Log cost event after each tool call # 2. Check cumulative cost against budget policy # 3. Warn at soft threshold, pause at hard threshold # # Placeholders: # {{WORKING_DIR}} - absolute path to project directory WORKING_DIR="{{WORKING_DIR}}" BUDGET_DIR="$WORKING_DIR/budget" COST_LOG="$BUDGET_DIR/cost-events.jsonl" BUDGET_FILE="$WORKING_DIR/BUDGET.md" PAUSED_FLAG="$BUDGET_DIR/PAUSED" mkdir -p "$BUDGET_DIR" # Read hook input INPUT=$(cat) TOOL_NAME=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_name',''))" 2>/dev/null) # Log cost event python3 << PYEOF import json, sys, time, os try: data = json.loads('''$INPUT''') except: sys.exit(0) tool_name = data.get('tool_name', '') # Estimate cost from token counts if available in tool result # This is a rough estimate; actual costs come from the Admin API event = { 'timestamp': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), 'tool_name': tool_name, 'agent': os.environ.get('AGENT_NAME', 'unknown'), 'estimated_tokens': 0 } cost_log = "$COST_LOG" with open(cost_log, 'a') as f: f.write(json.dumps(event) + '\n') PYEOF # Check budget if BUDGET.md exists if [ -f "$BUDGET_FILE" ] && [ -f "$COST_LOG" ]; then BUDGET_CHECK=$(python3 << 'PYEOF' import re, json, os budget_file = os.environ.get('BUDGET_FILE', '') cost_log = os.environ.get('COST_LOG', '') paused_flag = os.environ.get('PAUSED_FLAG', '') if not budget_file or not cost_log: print("ok") exit(0) # Parse budget try: content = open(budget_file).read() limit_m = re.search(r'limit:\s*(\d+)\s*cents', content) warn_m = re.search(r'warn_percent:\s*(\d+)', content) hard_m = re.search(r'hard_stop:\s*(\w+)', content) if not limit_m: print("ok") exit(0) limit = int(limit_m.group(1)) warn_pct = int(warn_m.group(1)) if warn_m else 80 hard_stop = hard_m.group(1).lower() == 'true' if hard_m else True except: print("ok") exit(0) # Sum cost events (count events as rough proxy — actual cost tracking # requires Admin API or token counting from responses) try: event_count = sum(1 for _ in open(cost_log)) except: event_count = 0 # Rough estimate: each event ~ 1 cent (placeholder — customize per model) estimated_cents = event_count pct = (estimated_cents / limit * 100) if limit > 0 else 0 if pct >= 100 and hard_stop: open(paused_flag, 'w').write(f'Budget exceeded: {estimated_cents}/{limit} cents') print("hard_stop") elif pct >= warn_pct: print("warn") else: print("ok") PYEOF ) BUDGET_FILE="$BUDGET_FILE" COST_LOG="$COST_LOG" PAUSED_FLAG="$PAUSED_FLAG" \ BUDGET_RESULT=$(BUDGET_FILE="$BUDGET_FILE" COST_LOG="$COST_LOG" PAUSED_FLAG="$PAUSED_FLAG" python3 -c " import re, json, os budget_file = '$BUDGET_FILE' cost_log = '$COST_LOG' paused_flag = '$PAUSED_FLAG' try: content = open(budget_file).read() limit_m = re.search(r'limit:\s*(\d+)\s*cents', content) if not limit_m: print('ok'); exit(0) limit = int(limit_m.group(1)) warn_m = re.search(r'warn_percent:\s*(\d+)', content) warn_pct = int(warn_m.group(1)) if warn_m else 80 hard_m = re.search(r'hard_stop:\s*(\w+)', content) hard_stop = hard_m.group(1).lower() == 'true' if hard_m else True event_count = sum(1 for _ in open(cost_log)) estimated_cents = event_count pct = (estimated_cents / limit * 100) if limit > 0 else 0 if pct >= 100 and hard_stop: open(paused_flag, 'w').write(f'Budget exceeded: {estimated_cents}/{limit} cents') print('hard_stop') elif pct >= warn_pct: print('warn') else: print('ok') except Exception as e: print('ok') " 2>/dev/null) if [ "$BUDGET_RESULT" = "hard_stop" ]; then echo "BUDGET EXCEEDED — agent paused. Check $PAUSED_FLAG" >&2 elif [ "$BUDGET_RESULT" = "warn" ]; then echo "BUDGET WARNING — approaching limit" >&2 fi fi # Check if agent is paused if [ -f "$PAUSED_FLAG" ]; then echo '{"decision": "block", "reason": "Agent paused: budget exceeded. Remove '"$PAUSED_FLAG"' to resume."}' exit 2 fi exit 0 ``` **`scripts/templates/budget/budget-report.sh`**: ```bash #!/bin/bash # Budget report: summarize cost events and compare against policy. # Bash 3.2 compatible. Uses python3 for aggregation. # # Usage: ./budget-report.sh # # Placeholders: # {{WORKING_DIR}} - absolute path to project directory WORKING_DIR="{{WORKING_DIR}}" COST_LOG="$WORKING_DIR/budget/cost-events.jsonl" BUDGET_FILE="$WORKING_DIR/BUDGET.md" PAUSED_FLAG="$WORKING_DIR/budget/PAUSED" if [ ! -f "$COST_LOG" ]; then echo "No cost events recorded yet." exit 0 fi python3 << PYEOF import json, re, os from collections import defaultdict cost_log = "$COST_LOG" budget_file = "$BUDGET_FILE" paused_flag = "$PAUSED_FLAG" # Read events events = [] with open(cost_log) as f: for line in f: line = line.strip() if line: try: events.append(json.loads(line)) except: pass # Aggregate by_agent = defaultdict(int) by_day = defaultdict(int) by_tool = defaultdict(int) for e in events: agent = e.get('agent', 'unknown') day = e.get('timestamp', '')[:10] tool = e.get('tool_name', 'unknown') by_agent[agent] += 1 by_day[day] += 1 by_tool[tool] += 1 print("BUDGET REPORT") print("=" * 50) print(f"Total events: {len(events)}") print() # Per-agent breakdown print("By Agent:") for agent, count in sorted(by_agent.items(), key=lambda x: -x[1]): print(f" {agent}: {count} events") print() # Per-day breakdown (last 7 days) print("By Day (last 7):") for day, count in sorted(by_day.items())[-7:]: print(f" {day}: {count} events") print() # Budget comparison if os.path.exists(budget_file): content = open(budget_file).read() limit_m = re.search(r'limit:\s*(\d+)\s*cents', content) if limit_m: limit = int(limit_m.group(1)) est_cents = len(events) # rough proxy pct = (est_cents / limit * 100) if limit > 0 else 0 print(f"Budget: ~{est_cents}/{limit} cents ({pct:.0f}%)") # Paused status if os.path.exists(paused_flag): print(f"\n!! AGENT PAUSED: {open(paused_flag).read().strip()}") print(f" Remove {paused_flag} to resume") PYEOF ``` **`scripts/templates/budget/README.md`**: ```markdown # Budget Tracking Post-hoc budget enforcement inspired by Paperclip's budget system. ## How it works 1. `budget-hook.sh` runs as a PostToolUse hook after every tool call 2. Each call is logged to `budget/cost-events.jsonl` 3. After logging, cumulative cost is compared against `BUDGET.md` policy 4. If soft threshold (default 80%) exceeded: warning to stderr 5. If hard threshold (100%) exceeded and hard_stop=true: creates `budget/PAUSED` flag file, subsequent tool calls are blocked (exit 2) ## Why post-hoc, not pre-run? Paperclip uses the same approach. Pre-run budget reservation requires a persistent service or lock file coordination. Post-hoc checking is simpler and robust enough in practice — the worst case is one extra run before pause. ## Cost estimation The current implementation counts events as a rough proxy for cost. For accurate cost tracking, you have two options: 1. **Admin API** (org accounts only): Query `/v1/organizations/cost_report` with an Admin API key (`sk-ant-admin...`). This gives actual USD costs. 2. **Token estimation**: Parse token counts from Claude's responses and multiply by published per-token prices. More accurate than event counting but still an estimate. For headless runs, `claude -p --max-budget-usd N` provides a per-run budget cap directly in the CLI. ## Integration Add to `.claude/settings.json`: ```json { "hooks": { "PostToolUse": [{ "matcher": "*", "hooks": [{"type": "command", "command": "bash budget/budget-hook.sh"}] }] } } ``` ``` ### Verify ```bash bash -n /Users/ktg/repos/agent-builder/scripts/templates/budget/budget-hook.sh && bash -n /Users/ktg/repos/agent-builder/scripts/templates/budget/budget-report.sh && echo "VALID" ``` Expected: `VALID` ### On failure: retry — fix bash syntax, then revert ### Checkpoint ```bash git commit -m "feat(templates): add budget tracking templates (Paperclip pattern)" ``` --- ## Step 17: Create governance and approval gate templates ### Files to create **`scripts/templates/governance/GOVERNANCE.md`** — See plan Step 17 for full content. Key sections: - Autonomy Levels (0-4 scale from full manual to full autonomy) - Approval Gates with `{{GATE_NAME}}` and `{{GATE_CONDITION}}` placeholders - Escalation Rules (budget exceeded, error threshold, unknown tool, scope violation) - Audit Requirements (tool calls, budget events, approvals, retention) **`scripts/templates/governance/approval-gate.sh`** — PreToolUse hook implementing approval gates. Bash 3.2 compatible. Key behavior: 1. Reads GOVERNANCE.md for current autonomy level 2. Based on level, auto-approves or requires approval 3. For gated operations: writes request to `governance/pending-approvals.jsonl` 4. Checks `governance/approval-responses.jsonl` for matching response 5. No response within timeout → block (exit 2) **`scripts/templates/governance/README.md`** — Explains the governance model, autonomy levels, Paperclip's "autonomy is a privilege" philosophy. ### Verify ```bash bash -n /Users/ktg/repos/agent-builder/scripts/templates/governance/approval-gate.sh && echo "VALID" ``` Expected: `VALID` ### On failure: retry — fix bash syntax, then revert ### Checkpoint ```bash git commit -m "feat(templates): add governance and approval gate templates (Paperclip pattern)" ``` --- ## Step 18: Create org-chart template ### Files to create **`scripts/templates/org-chart/ORG-CHART.md`** — Markdown table with columns: Agent, Role, Reports To, Status, Budget. Uses `reportsTo` pattern from Paperclip. Includes delegation rules and human override section. **`scripts/templates/org-chart/org-manager.sh`** — Bash 3.2 script that: - Parses ORG-CHART.md table with python3 - Validates: agents exist in `.claude/agents/`, no circular chains - Can add/remove agents from the chart - Generates text-based org tree visualization **`scripts/templates/org-chart/README.md`** — Explains the simple `reportsTo` pattern, delegation flows, cross-team routing. ### Verify ```bash bash -n /Users/ktg/repos/agent-builder/scripts/templates/org-chart/org-manager.sh && echo "VALID" ``` Expected: `VALID` ### On failure: retry — fix bash syntax, then revert ### Checkpoint ```bash git commit -m "feat(templates): add org-chart template (Paperclip pattern)" ``` --- ## Exit Condition - [ ] `ls /Users/ktg/repos/agent-builder/scripts/templates/heartbeat/ | wc -l` → 5 - [ ] `ls /Users/ktg/repos/agent-builder/scripts/templates/goals/ | wc -l` → 3 - [ ] `ls /Users/ktg/repos/agent-builder/scripts/templates/budget/ | wc -l` → 4 - [ ] `ls /Users/ktg/repos/agent-builder/scripts/templates/governance/ | wc -l` → 3 - [ ] `ls /Users/ktg/repos/agent-builder/scripts/templates/org-chart/ | wc -l` → 3 - [ ] All shell scripts pass `bash -n`: `find /Users/ktg/repos/agent-builder/scripts/templates/goals /Users/ktg/repos/agent-builder/scripts/templates/budget /Users/ktg/repos/agent-builder/scripts/templates/governance /Users/ktg/repos/agent-builder/scripts/templates/org-chart -name "*.sh" -exec bash -n {} \;` → no errors - [ ] context-packet.md contains `{{WAKE_REASON}}` placeholder - [ ] budget-hook.sh contains reference to PAUSED flag file ## Quality Criteria - Context packet follows Paperclip's "Memento Man" pattern with all sections - Wake prompt includes rules about not inferring from prior conversations - Goal hierarchy uses simple parent_id (not recursive) matching Paperclip's actual code - Budget enforcement is post-hoc matching Paperclip's pattern - Budget README honestly documents Admin API limitation (org-only, admin key) - Governance has 5 autonomy levels (0-4) with clear descriptions - Org chart uses simple reportsTo pattern with human override authority - All bash scripts are 3.2 compatible