#!/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 -c " import json, sys, time, os try: data = json.loads('''$INPUT''') except: sys.exit(0) tool_name = data.get('tool_name', '') 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') " 2>/dev/null # Check budget if BUDGET.md exists if [ -f "$BUDGET_FILE" ] && [ -f "$COST_LOG" ]; then BUDGET_RESULT=$(BUDGET_FILE="$BUDGET_FILE" COST_LOG="$COST_LOG" PAUSED_FLAG="$PAUSED_FLAG" python3 -c " 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', '') 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('Budget exceeded: ' + str(estimated_cents) + '/' + str(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