Files
awesome-copilot/cookbook/copilot-sdk/python/recipe/ralph_loop.py
Anthony Shaw 952372c1ec Rewrite Ralph loop recipes: split into simple vs ideal versions
Align all 4 language recipes (Node.js, Python, .NET, Go) with the
Ralph Playbook architecture:

- Simple version: minimal outer loop with fresh session per iteration
- Ideal version: planning/building modes, backpressure, git integration
- Fresh context isolation instead of in-session context accumulation
- Disk-based shared state via IMPLEMENTATION_PLAN.md
- Example prompt templates (PROMPT_plan.md, PROMPT_build.md, AGENTS.md)
- Updated cookbook README descriptions
2026-02-11 11:28:41 -08:00

85 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""
Ralph loop: autonomous AI task loop with fresh context per iteration.
Two modes:
- "plan": reads PROMPT_plan.md, generates/updates IMPLEMENTATION_PLAN.md
- "build": reads PROMPT_build.md, implements tasks, runs tests, commits
Each iteration creates a fresh session so the agent always operates in
the "smart zone" of its context window. State is shared between
iterations via files on disk (IMPLEMENTATION_PLAN.md, AGENTS.md, specs/*).
Usage:
python ralph_loop.py # build mode, 50 iterations
python ralph_loop.py plan # planning mode
python ralph_loop.py 20 # build mode, 20 iterations
python ralph_loop.py plan 5 # planning mode, 5 iterations
"""
import asyncio
import subprocess
import sys
from pathlib import Path
from copilot import CopilotClient, MessageOptions, SessionConfig
async def ralph_loop(mode: str = "build", max_iterations: int = 50):
prompt_file = "PROMPT_plan.md" if mode == "plan" else "PROMPT_build.md"
client = CopilotClient()
await client.start()
branch = subprocess.check_output(
["git", "branch", "--show-current"], text=True
).strip()
print("" * 40)
print(f"Mode: {mode}")
print(f"Prompt: {prompt_file}")
print(f"Branch: {branch}")
print(f"Max: {max_iterations} iterations")
print("" * 40)
try:
prompt = Path(prompt_file).read_text()
for i in range(1, max_iterations + 1):
print(f"\n=== Iteration {i}/{max_iterations} ===")
# Fresh session — each task gets full context budget
session = await client.create_session(
SessionConfig(model="claude-sonnet-4.5")
)
try:
await session.send_and_wait(
MessageOptions(prompt=prompt), timeout=600
)
finally:
await session.destroy()
# Push changes after each iteration
try:
subprocess.run(
["git", "push", "origin", branch], check=True
)
except subprocess.CalledProcessError:
subprocess.run(
["git", "push", "-u", "origin", branch], check=True
)
print(f"\nIteration {i} complete.")
print(f"\nReached max iterations: {max_iterations}")
finally:
await client.stop()
if __name__ == "__main__":
args = sys.argv[1:]
mode = "plan" if "plan" in args else "build"
max_iter = next((int(a) for a in args if a.isdigit()), 50)
asyncio.run(ralph_loop(mode, max_iter))