diff --git a/cookbook/copilot-sdk/README.md b/cookbook/copilot-sdk/README.md index 53c91a70..55981302 100644 --- a/cookbook/copilot-sdk/README.md +++ b/cookbook/copilot-sdk/README.md @@ -6,6 +6,7 @@ This cookbook collects small, focused recipes showing how to accomplish common t ### .NET (C#) +- [Ralph Loop](dotnet/ralph-loop.md): Build autonomous AI coding loops with fresh context per iteration, planning/building modes, and backpressure. - [Error Handling](dotnet/error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. - [Multiple Sessions](dotnet/multiple-sessions.md): Manage multiple independent conversations simultaneously. - [Managing Local Files](dotnet/managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. @@ -14,6 +15,7 @@ This cookbook collects small, focused recipes showing how to accomplish common t ### Node.js / TypeScript +- [Ralph Loop](nodejs/ralph-loop.md): Build autonomous AI coding loops with fresh context per iteration, planning/building modes, and backpressure. - [Error Handling](nodejs/error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. - [Multiple Sessions](nodejs/multiple-sessions.md): Manage multiple independent conversations simultaneously. - [Managing Local Files](nodejs/managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. @@ -22,6 +24,7 @@ This cookbook collects small, focused recipes showing how to accomplish common t ### Python +- [Ralph Loop](python/ralph-loop.md): Build autonomous AI coding loops with fresh context per iteration, planning/building modes, and backpressure. - [Error Handling](python/error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. - [Multiple Sessions](python/multiple-sessions.md): Manage multiple independent conversations simultaneously. - [Managing Local Files](python/managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. @@ -30,6 +33,7 @@ This cookbook collects small, focused recipes showing how to accomplish common t ### Go +- [Ralph Loop](go/ralph-loop.md): Build autonomous AI coding loops with fresh context per iteration, planning/building modes, and backpressure. - [Error Handling](go/error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. - [Multiple Sessions](go/multiple-sessions.md): Manage multiple independent conversations simultaneously. - [Managing Local Files](go/managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. @@ -83,4 +87,4 @@ go run .go ## Status -Cookbook structure is complete with 4 recipes across all 4 supported languages. Each recipe includes both markdown documentation and runnable examples. +Cookbook structure is complete with 6 recipes across all 4 supported languages. Each recipe includes both markdown documentation and runnable examples. diff --git a/cookbook/copilot-sdk/dotnet/ralph-loop.md b/cookbook/copilot-sdk/dotnet/ralph-loop.md new file mode 100644 index 00000000..8ff85246 --- /dev/null +++ b/cookbook/copilot-sdk/dotnet/ralph-loop.md @@ -0,0 +1,260 @@ +# Ralph Loop: Autonomous AI Task Loops + +Build autonomous coding loops where an AI agent picks tasks, implements them, validates against backpressure (tests, builds), commits, and repeats — each iteration in a fresh context window. + +> **Runnable example:** [recipe/ralph-loop.cs](recipe/ralph-loop.cs) +> +> ```bash +> cd dotnet +> dotnet run recipe/ralph-loop.cs +> ``` + +## What is a Ralph Loop? + +A [Ralph loop](https://ghuntley.com/ralph/) is an autonomous development workflow where an AI agent iterates through tasks in isolated context windows. The key insight: **state lives on disk, not in the model's context**. Each iteration starts fresh, reads the current state from files, does one task, writes results back to disk, and exits. + +``` +┌─────────────────────────────────────────────────┐ +│ loop.sh │ +│ while true: │ +│ ┌─────────────────────────────────────────┐ │ +│ │ Fresh session (isolated context) │ │ +│ │ │ │ +│ │ 1. Read PROMPT.md + AGENTS.md │ │ +│ │ 2. Study specs/* and code │ │ +│ │ 3. Pick next task from plan │ │ +│ │ 4. Implement + run tests │ │ +│ │ 5. Update plan, commit, exit │ │ +│ └─────────────────────────────────────────┘ │ +│ ↻ next iteration (fresh context) │ +└─────────────────────────────────────────────────┘ +``` + +**Core principles:** + +- **Fresh context per iteration**: Each loop creates a new session — no context accumulation, always in the "smart zone" +- **Disk as shared state**: `IMPLEMENTATION_PLAN.md` persists between iterations and acts as the coordination mechanism +- **Backpressure steers quality**: Tests, builds, and lints reject bad work — the agent must fix issues before committing +- **Two modes**: PLANNING (gap analysis → generate plan) and BUILDING (implement from plan) + +## Simple Version + +The minimal Ralph loop — the SDK equivalent of `while :; do cat PROMPT.md | copilot ; done`: + +```csharp +using GitHub.Copilot.SDK; + +var client = new CopilotClient(); +await client.StartAsync(); + +try +{ + var prompt = await File.ReadAllTextAsync("PROMPT.md"); + var maxIterations = 50; + + for (var i = 1; i <= maxIterations; i++) + { + Console.WriteLine($"\n=== Iteration {i}/{maxIterations} ==="); + + // Fresh session each iteration — context isolation is the point + var session = await client.CreateSessionAsync( + new SessionConfig { Model = "gpt-5.1-codex-mini" }); + try + { + var done = new TaskCompletionSource(); + session.On(evt => + { + if (evt is AssistantMessageEvent msg) + done.TrySetResult(msg.Data.Content); + }); + + await session.SendAsync(new MessageOptions { Prompt = prompt }); + await done.Task; + } + finally + { + await session.DisposeAsync(); + } + + Console.WriteLine($"Iteration {i} complete."); + } +} +finally +{ + await client.StopAsync(); +} +``` + +This is all you need to get started. The prompt file tells the agent what to do; the agent reads project files, does work, commits, and exits. The loop restarts with a clean slate. + +## Ideal Version + +The full Ralph pattern with planning and building modes, matching the [Ralph Playbook](https://github.com/ClaytonFarr/ralph-playbook) architecture: + +```csharp +using GitHub.Copilot.SDK; + +// Parse args: dotnet run [plan] [max_iterations] +var mode = args.Contains("plan") ? "plan" : "build"; +var maxArg = args.FirstOrDefault(a => int.TryParse(a, out _)); +var maxIterations = maxArg != null ? int.Parse(maxArg) : 50; +var promptFile = mode == "plan" ? "PROMPT_plan.md" : "PROMPT_build.md"; + +var client = new CopilotClient(); +await client.StartAsync(); + +Console.WriteLine(new string('━', 40)); +Console.WriteLine($"Mode: {mode}"); +Console.WriteLine($"Prompt: {promptFile}"); +Console.WriteLine($"Max: {maxIterations} iterations"); +Console.WriteLine(new string('━', 40)); + +try +{ + var prompt = await File.ReadAllTextAsync(promptFile); + + for (var i = 1; i <= maxIterations; i++) + { + Console.WriteLine($"\n=== Iteration {i}/{maxIterations} ==="); + + // Fresh session — each task gets full context budget + var session = await client.CreateSessionAsync( + new SessionConfig + { + Model = "gpt-5.1-codex-mini", + // Pin the agent to the project directory + WorkingDirectory = Environment.CurrentDirectory, + // Auto-approve tool calls for unattended operation + OnPermissionRequest = (_, _) => Task.FromResult( + new PermissionRequestResult { Kind = "approved" }), + }); + try + { + var done = new TaskCompletionSource(); + session.On(evt => + { + // Log tool usage for visibility + if (evt is ToolExecutionStartEvent toolStart) + Console.WriteLine($" ⚙ {toolStart.Data.ToolName}"); + else if (evt is AssistantMessageEvent msg) + done.TrySetResult(msg.Data.Content); + }); + + await session.SendAsync(new MessageOptions { Prompt = prompt }); + await done.Task; + } + finally + { + await session.DisposeAsync(); + } + + Console.WriteLine($"\nIteration {i} complete."); + } + + Console.WriteLine($"\nReached max iterations: {maxIterations}"); +} +finally +{ + await client.StopAsync(); +} +``` + +### Required Project Files + +The ideal version expects this file structure in your project: + +``` +project-root/ +├── PROMPT_plan.md # Planning mode instructions +├── PROMPT_build.md # Building mode instructions +├── AGENTS.md # Operational guide (build/test commands) +├── IMPLEMENTATION_PLAN.md # Task list (generated by planning mode) +├── specs/ # Requirement specs (one per topic) +│ ├── auth.md +│ └── data-pipeline.md +└── src/ # Your source code +``` + +### Example `PROMPT_plan.md` + +```markdown +0a. Study `specs/*` to learn the application specifications. +0b. Study IMPLEMENTATION_PLAN.md (if present) to understand the plan so far. +0c. Study `src/` to understand existing code and shared utilities. + +1. Compare specs against code (gap analysis). Create or update + IMPLEMENTATION_PLAN.md as a prioritized bullet-point list of tasks + yet to be implemented. Do NOT implement anything. + +IMPORTANT: Do NOT assume functionality is missing — search the +codebase first to confirm. Prefer updating existing utilities over +creating ad-hoc copies. +``` + +### Example `PROMPT_build.md` + +```markdown +0a. Study `specs/*` to learn the application specifications. +0b. Study IMPLEMENTATION_PLAN.md. +0c. Study `src/` for reference. + +1. Choose the most important item from IMPLEMENTATION_PLAN.md. Before + making changes, search the codebase (don't assume not implemented). +2. After implementing, run the tests. If functionality is missing, add it. +3. When you discover issues, update IMPLEMENTATION_PLAN.md immediately. +4. When tests pass, update IMPLEMENTATION_PLAN.md, then `git add -A` + then `git commit` with a descriptive message. + +5. When authoring documentation, capture the why. +6. Implement completely. No placeholders or stubs. +7. Keep IMPLEMENTATION_PLAN.md current — future iterations depend on it. +``` + +### Example `AGENTS.md` + +Keep this brief (~60 lines). It's loaded every iteration, so bloat wastes context. + +```markdown +## Build & Run + +dotnet build + +## Validation + +- Tests: `dotnet test` +- Build: `dotnet build --no-restore` +``` + +## Best Practices + +1. **Fresh context per iteration**: Never accumulate context across iterations — that's the whole point +2. **Disk is your database**: `IMPLEMENTATION_PLAN.md` is shared state between isolated sessions +3. **Backpressure is essential**: Tests, builds, lints in `AGENTS.md` — the agent must pass them before committing +4. **Start with PLANNING mode**: Generate the plan first, then switch to BUILDING +5. **Observe and tune**: Watch early iterations, add guardrails to prompts when the agent fails in specific ways +6. **The plan is disposable**: If the agent goes off track, delete `IMPLEMENTATION_PLAN.md` and re-plan +7. **Keep `AGENTS.md` brief**: It's loaded every iteration — operational info only, no progress notes +8. **Use a sandbox**: The agent runs autonomously with full tool access — isolate it +9. **Set `WorkingDirectory`**: Pin the session to your project root so tool operations resolve paths correctly +10. **Auto-approve permissions**: Use `OnPermissionRequest` to allow tool calls without interrupting the loop + +## When to Use a Ralph Loop + +**Good for:** + +- Implementing features from specs with test-driven validation +- Large refactors broken into many small tasks +- Unattended, long-running development with clear requirements +- Any work where backpressure (tests/builds) can verify correctness + +**Not good for:** + +- Tasks requiring human judgment mid-loop +- One-shot operations that don't benefit from iteration +- Vague requirements without testable acceptance criteria +- Exploratory prototyping where direction isn't clear + +## See Also + +- [Error Handling](error-handling.md) — timeout patterns and graceful shutdown for long-running sessions +- [Persisting Sessions](persisting-sessions.md) — save and resume sessions across restarts diff --git a/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs b/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs new file mode 100644 index 00000000..9f153324 --- /dev/null +++ b/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs @@ -0,0 +1,83 @@ +#:package GitHub.Copilot.SDK@* + +using GitHub.Copilot.SDK; + +// 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: +// dotnet run # build mode, 50 iterations +// dotnet run plan # planning mode +// dotnet run 20 # build mode, 20 iterations +// dotnet run plan 5 # planning mode, 5 iterations + +var mode = args.Contains("plan") ? "plan" : "build"; +var maxArg = args.FirstOrDefault(a => int.TryParse(a, out _)); +var maxIterations = maxArg != null ? int.Parse(maxArg) : 50; +var promptFile = mode == "plan" ? "PROMPT_plan.md" : "PROMPT_build.md"; + +var client = new CopilotClient(); +await client.StartAsync(); + +Console.WriteLine(new string('━', 40)); +Console.WriteLine($"Mode: {mode}"); +Console.WriteLine($"Prompt: {promptFile}"); +Console.WriteLine($"Max: {maxIterations} iterations"); +Console.WriteLine(new string('━', 40)); + +try +{ + var prompt = await File.ReadAllTextAsync(promptFile); + + for (var i = 1; i <= maxIterations; i++) + { + Console.WriteLine($"\n=== Iteration {i}/{maxIterations} ==="); + + // Fresh session — each task gets full context budget + var session = await client.CreateSessionAsync( + new SessionConfig + { + Model = "gpt-5.1-codex-mini", + // Pin the agent to the project directory + WorkingDirectory = Environment.CurrentDirectory, + // Auto-approve tool calls for unattended operation + OnPermissionRequest = (_, _) => Task.FromResult( + new PermissionRequestResult { Kind = "approved" }), + }); + + try + { + var done = new TaskCompletionSource(); + session.On(evt => + { + // Log tool usage for visibility + if (evt is ToolExecutionStartEvent toolStart) + Console.WriteLine($" ⚙ {toolStart.Data.ToolName}"); + else if (evt is AssistantMessageEvent msg) + done.TrySetResult(msg.Data.Content); + }); + + await session.SendAsync(new MessageOptions { Prompt = prompt }); + await done.Task; + } + finally + { + await session.DisposeAsync(); + } + + Console.WriteLine($"\nIteration {i} complete."); + } + + Console.WriteLine($"\nReached max iterations: {maxIterations}"); +} +finally +{ + await client.StopAsync(); +} diff --git a/cookbook/copilot-sdk/go/ralph-loop.md b/cookbook/copilot-sdk/go/ralph-loop.md new file mode 100644 index 00000000..f8462c3d --- /dev/null +++ b/cookbook/copilot-sdk/go/ralph-loop.md @@ -0,0 +1,296 @@ +# Ralph Loop: Autonomous AI Task Loops + +Build autonomous coding loops where an AI agent picks tasks, implements them, validates against backpressure (tests, builds), commits, and repeats — each iteration in a fresh context window. + +> **Runnable example:** [recipe/ralph-loop.go](recipe/ralph-loop.go) +> +> ```bash +> cd go +> go run recipe/ralph-loop.go +> ``` + +## What is a Ralph Loop? + +A [Ralph loop](https://ghuntley.com/ralph/) is an autonomous development workflow where an AI agent iterates through tasks in isolated context windows. The key insight: **state lives on disk, not in the model's context**. Each iteration starts fresh, reads the current state from files, does one task, writes results back to disk, and exits. + +``` +┌─────────────────────────────────────────────────┐ +│ loop.sh │ +│ while true: │ +│ ┌─────────────────────────────────────────┐ │ +│ │ Fresh session (isolated context) │ │ +│ │ │ │ +│ │ 1. Read PROMPT.md + AGENTS.md │ │ +│ │ 2. Study specs/* and code │ │ +│ │ 3. Pick next task from plan │ │ +│ │ 4. Implement + run tests │ │ +│ │ 5. Update plan, commit, exit │ │ +│ └─────────────────────────────────────────┘ │ +│ ↻ next iteration (fresh context) │ +└─────────────────────────────────────────────────┘ +``` + +**Core principles:** + +- **Fresh context per iteration**: Each loop creates a new session — no context accumulation, always in the "smart zone" +- **Disk as shared state**: `IMPLEMENTATION_PLAN.md` persists between iterations and acts as the coordination mechanism +- **Backpressure steers quality**: Tests, builds, and lints reject bad work — the agent must fix issues before committing +- **Two modes**: PLANNING (gap analysis → generate plan) and BUILDING (implement from plan) + +## Simple Version + +The minimal Ralph loop — the SDK equivalent of `while :; do cat PROMPT.md | copilot ; done`: + +```go +package main + +import ( + "context" + "fmt" + "log" + "os" + + copilot "github.com/github/copilot-sdk/go" +) + +func ralphLoop(ctx context.Context, promptFile string, maxIterations int) error { + client := copilot.NewClient(nil) + if err := client.Start(ctx); err != nil { + return err + } + defer client.Stop() + + prompt, err := os.ReadFile(promptFile) + if err != nil { + return err + } + + for i := 1; i <= maxIterations; i++ { + fmt.Printf("\n=== Iteration %d/%d ===\n", i, maxIterations) + + // Fresh session each iteration — context isolation is the point + session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + Model: "gpt-5.1-codex-mini", + }) + if err != nil { + return err + } + + _, err = session.SendAndWait(ctx, copilot.MessageOptions{ + Prompt: string(prompt), + }) + session.Destroy() + if err != nil { + return err + } + + fmt.Printf("Iteration %d complete.\n", i) + } + return nil +} + +func main() { + if err := ralphLoop(context.Background(), "PROMPT.md", 20); err != nil { + log.Fatal(err) + } +} +``` + +This is all you need to get started. The prompt file tells the agent what to do; the agent reads project files, does work, commits, and exits. The loop restarts with a clean slate. + +## Ideal Version + +The full Ralph pattern with planning and building modes, matching the [Ralph Playbook](https://github.com/ClaytonFarr/ralph-playbook) architecture: + +```go +package main + +import ( + "context" + "fmt" + "log" + "os" + "strconv" + "strings" + + copilot "github.com/github/copilot-sdk/go" +) + +func ralphLoop(ctx context.Context, mode string, maxIterations int) error { + promptFile := "PROMPT_build.md" + if mode == "plan" { + promptFile = "PROMPT_plan.md" + } + + client := copilot.NewClient(nil) + if err := client.Start(ctx); err != nil { + return err + } + defer client.Stop() + + cwd, _ := os.Getwd() + + fmt.Println(strings.Repeat("━", 40)) + fmt.Printf("Mode: %s\n", mode) + fmt.Printf("Prompt: %s\n", promptFile) + fmt.Printf("Max: %d iterations\n", maxIterations) + fmt.Println(strings.Repeat("━", 40)) + + prompt, err := os.ReadFile(promptFile) + if err != nil { + return err + } + + for i := 1; i <= maxIterations; i++ { + fmt.Printf("\n=== Iteration %d/%d ===\n", i, maxIterations) + + session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + Model: "gpt-5.1-codex-mini", + WorkingDirectory: cwd, + OnPermissionRequest: func(_ copilot.PermissionRequest, _ map[string]string) copilot.PermissionRequestResult { + return copilot.PermissionRequestResult{Kind: "approved"} + }, + }) + if err != nil { + return err + } + + // Log tool usage for visibility + session.On(func(event copilot.Event) { + if toolExecution, ok := event.(copilot.ToolExecutionStartEvent); ok { + fmt.Printf(" ⚙ %s\n", toolExecution.Data.ToolName) + } + }) + + _, err = session.SendAndWait(ctx, copilot.MessageOptions{ + Prompt: string(prompt), + }) + session.Destroy() + if err != nil { + return err + } + + fmt.Printf("\nIteration %d complete.\n", i) + } + + fmt.Printf("\nReached max iterations: %d\n", maxIterations) + return nil +} + +func main() { + mode := "build" + maxIterations := 50 + + for _, arg := range os.Args[1:] { + if arg == "plan" { + mode = "plan" + } else if n, err := strconv.Atoi(arg); err == nil { + maxIterations = n + } + } + + if err := ralphLoop(context.Background(), mode, maxIterations); err != nil { + log.Fatal(err) + } +} +``` + +### Required Project Files + +The ideal version expects this file structure in your project: + +``` +project-root/ +├── PROMPT_plan.md # Planning mode instructions +├── PROMPT_build.md # Building mode instructions +├── AGENTS.md # Operational guide (build/test commands) +├── IMPLEMENTATION_PLAN.md # Task list (generated by planning mode) +├── specs/ # Requirement specs (one per topic) +│ ├── auth.md +│ └── data-pipeline.md +└── src/ # Your source code +``` + +### Example `PROMPT_plan.md` + +```markdown +0a. Study `specs/*` to learn the application specifications. +0b. Study IMPLEMENTATION_PLAN.md (if present) to understand the plan so far. +0c. Study `src/` to understand existing code and shared utilities. + +1. Compare specs against code (gap analysis). Create or update + IMPLEMENTATION_PLAN.md as a prioritized bullet-point list of tasks + yet to be implemented. Do NOT implement anything. + +IMPORTANT: Do NOT assume functionality is missing — search the +codebase first to confirm. Prefer updating existing utilities over +creating ad-hoc copies. +``` + +### Example `PROMPT_build.md` + +```markdown +0a. Study `specs/*` to learn the application specifications. +0b. Study IMPLEMENTATION_PLAN.md. +0c. Study `src/` for reference. + +1. Choose the most important item from IMPLEMENTATION_PLAN.md. Before + making changes, search the codebase (don't assume not implemented). +2. After implementing, run the tests. If functionality is missing, add it. +3. When you discover issues, update IMPLEMENTATION_PLAN.md immediately. +4. When tests pass, update IMPLEMENTATION_PLAN.md, then `git add -A` + then `git commit` with a descriptive message. + +5. When authoring documentation, capture the why. +6. Implement completely. No placeholders or stubs. +7. Keep IMPLEMENTATION_PLAN.md current — future iterations depend on it. +``` + +### Example `AGENTS.md` + +Keep this brief (~60 lines). It's loaded every iteration, so bloat wastes context. + +```markdown +## Build & Run + +go build ./... + +## Validation + +- Tests: `go test ./...` +- Vet: `go vet ./...` +``` + +## Best Practices + +1. **Fresh context per iteration**: Never accumulate context across iterations — that's the whole point +2. **Disk is your database**: `IMPLEMENTATION_PLAN.md` is shared state between isolated sessions +3. **Backpressure is essential**: Tests, builds, lints in `AGENTS.md` — the agent must pass them before committing +4. **Start with PLANNING mode**: Generate the plan first, then switch to BUILDING +5. **Observe and tune**: Watch early iterations, add guardrails to prompts when the agent fails in specific ways +6. **The plan is disposable**: If the agent goes off track, delete `IMPLEMENTATION_PLAN.md` and re-plan +7. **Keep `AGENTS.md` brief**: It's loaded every iteration — operational info only, no progress notes +8. **Use a sandbox**: The agent runs autonomously with full tool access — isolate it +9. **Set `WorkingDirectory`**: Pin the session to your project root so tool operations resolve paths correctly +10. **Auto-approve permissions**: Use `OnPermissionRequest` to allow tool calls without interrupting the loop + +## When to Use a Ralph Loop + +**Good for:** + +- Implementing features from specs with test-driven validation +- Large refactors broken into many small tasks +- Unattended, long-running development with clear requirements +- Any work where backpressure (tests/builds) can verify correctness + +**Not good for:** + +- Tasks requiring human judgment mid-loop +- One-shot operations that don't benefit from iteration +- Vague requirements without testable acceptance criteria +- Exploratory prototyping where direction isn't clear + +## See Also + +- [Error Handling](error-handling.md) — timeout patterns and graceful shutdown for long-running sessions +- [Persisting Sessions](persisting-sessions.md) — save and resume sessions across restarts diff --git a/cookbook/copilot-sdk/go/recipe/ralph-loop.go b/cookbook/copilot-sdk/go/recipe/ralph-loop.go new file mode 100644 index 00000000..03d99987 --- /dev/null +++ b/cookbook/copilot-sdk/go/recipe/ralph-loop.go @@ -0,0 +1,111 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strconv" + "strings" + + copilot "github.com/github/copilot-sdk/go" +) + +// 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: +// go run ralph-loop.go # build mode, 50 iterations +// go run ralph-loop.go plan # planning mode +// go run ralph-loop.go 20 # build mode, 20 iterations +// go run ralph-loop.go plan 5 # planning mode, 5 iterations + +func ralphLoop(ctx context.Context, mode string, maxIterations int) error { + promptFile := "PROMPT_build.md" + if mode == "plan" { + promptFile = "PROMPT_plan.md" + } + + client := copilot.NewClient(nil) + if err := client.Start(ctx); err != nil { + return fmt.Errorf("failed to start client: %w", err) + } + defer client.Stop() + + cwd, err := os.Getwd() + if err != nil { + return fmt.Errorf("failed to get working directory: %w", err) + } + + fmt.Println(strings.Repeat("━", 40)) + fmt.Printf("Mode: %s\n", mode) + fmt.Printf("Prompt: %s\n", promptFile) + fmt.Printf("Max: %d iterations\n", maxIterations) + fmt.Println(strings.Repeat("━", 40)) + + prompt, err := os.ReadFile(promptFile) + if err != nil { + return fmt.Errorf("failed to read %s: %w", promptFile, err) + } + + for i := 1; i <= maxIterations; i++ { + fmt.Printf("\n=== Iteration %d/%d ===\n", i, maxIterations) + + session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + Model: "gpt-5.1-codex-mini", + WorkingDirectory: cwd, + OnPermissionRequest: func(_ copilot.PermissionRequest, _ map[string]string) copilot.PermissionRequestResult { + return copilot.PermissionRequestResult{Kind: "approved"} + }, + }) + if err != nil { + return fmt.Errorf("failed to create session: %w", err) + } + + // Log tool usage for visibility + session.On(func(event copilot.Event) { + if toolExecution, ok := event.(copilot.ToolExecutionStartEvent); ok { + fmt.Printf(" ⚙ %s\n", toolExecution.Data.ToolName) + } + }) + + _, err = session.SendAndWait(ctx, copilot.MessageOptions{ + Prompt: string(prompt), + }) + if destroyErr := session.Destroy(); destroyErr != nil { + log.Printf("failed to destroy session on iteration %d: %v", i, destroyErr) + } + if err != nil { + return fmt.Errorf("send failed on iteration %d: %w", i, err) + } + + fmt.Printf("\nIteration %d complete.\n", i) + } + + fmt.Printf("\nReached max iterations: %d\n", maxIterations) + return nil +} + +func main() { + mode := "build" + maxIterations := 50 + + for _, arg := range os.Args[1:] { + if arg == "plan" { + mode = "plan" + } else if n, err := strconv.Atoi(arg); err == nil { + maxIterations = n + } + } + + if err := ralphLoop(context.Background(), mode, maxIterations); err != nil { + log.Fatal(err) + } +} diff --git a/cookbook/copilot-sdk/nodejs/ralph-loop.md b/cookbook/copilot-sdk/nodejs/ralph-loop.md new file mode 100644 index 00000000..87c5225f --- /dev/null +++ b/cookbook/copilot-sdk/nodejs/ralph-loop.md @@ -0,0 +1,238 @@ +# Ralph Loop: Autonomous AI Task Loops + +Build autonomous coding loops where an AI agent picks tasks, implements them, validates against backpressure (tests, builds), commits, and repeats — each iteration in a fresh context window. + +> **Runnable example:** [recipe/ralph-loop.ts](recipe/ralph-loop.ts) +> +> ```bash +> npm install +> npx tsx recipe/ralph-loop.ts +> ``` + +## What is a Ralph Loop? + +A [Ralph loop](https://ghuntley.com/ralph/) is an autonomous development workflow where an AI agent iterates through tasks in isolated context windows. The key insight: **state lives on disk, not in the model's context**. Each iteration starts fresh, reads the current state from files, does one task, writes results back to disk, and exits. + +``` +┌─────────────────────────────────────────────────┐ +│ loop.sh │ +│ while true: │ +│ ┌─────────────────────────────────────────┐ │ +│ │ Fresh session (isolated context) │ │ +│ │ │ │ +│ │ 1. Read PROMPT.md + AGENTS.md │ │ +│ │ 2. Study specs/* and code │ │ +│ │ 3. Pick next task from plan │ │ +│ │ 4. Implement + run tests │ │ +│ │ 5. Update plan, commit, exit │ │ +│ └─────────────────────────────────────────┘ │ +│ ↻ next iteration (fresh context) │ +└─────────────────────────────────────────────────┘ +``` + +**Core principles:** + +- **Fresh context per iteration**: Each loop creates a new session — no context accumulation, always in the "smart zone" +- **Disk as shared state**: `IMPLEMENTATION_PLAN.md` persists between iterations and acts as the coordination mechanism +- **Backpressure steers quality**: Tests, builds, and lints reject bad work — the agent must fix issues before committing +- **Two modes**: PLANNING (gap analysis → generate plan) and BUILDING (implement from plan) + +## Simple Version + +The minimal Ralph loop — the SDK equivalent of `while :; do cat PROMPT.md | copilot ; done`: + +```typescript +import { readFile } from "fs/promises"; +import { CopilotClient } from "@github/copilot-sdk"; + +async function ralphLoop(promptFile: string, maxIterations: number = 50) { + const client = new CopilotClient(); + await client.start(); + + try { + const prompt = await readFile(promptFile, "utf-8"); + + for (let i = 1; i <= maxIterations; i++) { + console.log(`\n=== Iteration ${i}/${maxIterations} ===`); + + // Fresh session each iteration — context isolation is the point + const session = await client.createSession({ model: "gpt-5.1-codex-mini" }); + try { + await session.sendAndWait({ prompt }, 600_000); + } finally { + await session.destroy(); + } + + console.log(`Iteration ${i} complete.`); + } + } finally { + await client.stop(); + } +} + +// Usage: point at your PROMPT.md +ralphLoop("PROMPT.md", 20); +``` + +This is all you need to get started. The prompt file tells the agent what to do; the agent reads project files, does work, commits, and exits. The loop restarts with a clean slate. + +## Ideal Version + +The full Ralph pattern with planning and building modes, matching the [Ralph Playbook](https://github.com/ClaytonFarr/ralph-playbook) architecture: + +```typescript +import { readFile } from "fs/promises"; +import { CopilotClient } from "@github/copilot-sdk"; + +type Mode = "plan" | "build"; + +async function ralphLoop(mode: Mode, maxIterations: number = 50) { + const promptFile = mode === "plan" ? "PROMPT_plan.md" : "PROMPT_build.md"; + const client = new CopilotClient(); + await client.start(); + + console.log(`Mode: ${mode} | Prompt: ${promptFile}`); + + try { + const prompt = await readFile(promptFile, "utf-8"); + + for (let i = 1; i <= maxIterations; i++) { + console.log(`\n=== Iteration ${i}/${maxIterations} ===`); + + const session = await client.createSession({ + model: "gpt-5.1-codex-mini", + // Pin the agent to the project directory + workingDirectory: process.cwd(), + // Auto-approve tool calls for unattended operation + onPermissionRequest: async () => ({ allow: true }), + }); + + // Log tool usage for visibility + session.on((event) => { + if (event.type === "tool.execution_start") { + console.log(` ⚙ ${event.data.toolName}`); + } + }); + + try { + await session.sendAndWait({ prompt }, 600_000); + } finally { + await session.destroy(); + } + + console.log(`Iteration ${i} complete.`); + } + } finally { + await client.stop(); + } +} + +// Parse CLI args: npx tsx ralph-loop.ts [plan] [max_iterations] +const args = process.argv.slice(2); +const mode: Mode = args.includes("plan") ? "plan" : "build"; +const maxArg = args.find((a) => /^\d+$/.test(a)); +const maxIterations = maxArg ? parseInt(maxArg) : 50; + +ralphLoop(mode, maxIterations); +``` + +### Required Project Files + +The ideal version expects this file structure in your project: + +``` +project-root/ +├── PROMPT_plan.md # Planning mode instructions +├── PROMPT_build.md # Building mode instructions +├── AGENTS.md # Operational guide (build/test commands) +├── IMPLEMENTATION_PLAN.md # Task list (generated by planning mode) +├── specs/ # Requirement specs (one per topic) +│ ├── auth.md +│ └── data-pipeline.md +└── src/ # Your source code +``` + +### Example `PROMPT_plan.md` + +```markdown +0a. Study `specs/*` to learn the application specifications. +0b. Study IMPLEMENTATION_PLAN.md (if present) to understand the plan so far. +0c. Study `src/` to understand existing code and shared utilities. + +1. Compare specs against code (gap analysis). Create or update + IMPLEMENTATION_PLAN.md as a prioritized bullet-point list of tasks + yet to be implemented. Do NOT implement anything. + +IMPORTANT: Do NOT assume functionality is missing — search the +codebase first to confirm. Prefer updating existing utilities over +creating ad-hoc copies. +``` + +### Example `PROMPT_build.md` + +```markdown +0a. Study `specs/*` to learn the application specifications. +0b. Study IMPLEMENTATION_PLAN.md. +0c. Study `src/` for reference. + +1. Choose the most important item from IMPLEMENTATION_PLAN.md. Before + making changes, search the codebase (don't assume not implemented). +2. After implementing, run the tests. If functionality is missing, add it. +3. When you discover issues, update IMPLEMENTATION_PLAN.md immediately. +4. When tests pass, update IMPLEMENTATION_PLAN.md, then `git add -A` + then `git commit` with a descriptive message. + +5. When authoring documentation, capture the why. +6. Implement completely. No placeholders or stubs. +7. Keep IMPLEMENTATION_PLAN.md current — future iterations depend on it. +``` + +### Example `AGENTS.md` + +Keep this brief (~60 lines). It's loaded every iteration, so bloat wastes context. + +```markdown +## Build & Run + +npm run build + +## Validation + +- Tests: `npm test` +- Typecheck: `npx tsc --noEmit` +- Lint: `npm run lint` +``` + +## Best Practices + +1. **Fresh context per iteration**: Never accumulate context across iterations — that's the whole point +2. **Disk is your database**: `IMPLEMENTATION_PLAN.md` is shared state between isolated sessions +3. **Backpressure is essential**: Tests, builds, lints in `AGENTS.md` — the agent must pass them before committing +4. **Start with PLANNING mode**: Generate the plan first, then switch to BUILDING +5. **Observe and tune**: Watch early iterations, add guardrails to prompts when the agent fails in specific ways +6. **The plan is disposable**: If the agent goes off track, delete `IMPLEMENTATION_PLAN.md` and re-plan +7. **Keep `AGENTS.md` brief**: It's loaded every iteration — operational info only, no progress notes +8. **Use a sandbox**: The agent runs autonomously with full tool access — isolate it +9. **Set `workingDirectory`**: Pin the session to your project root so tool operations resolve paths correctly +10. **Auto-approve permissions**: Use `onPermissionRequest` to allow tool calls without interrupting the loop + +## When to Use a Ralph Loop + +**Good for:** + +- Implementing features from specs with test-driven validation +- Large refactors broken into many small tasks +- Unattended, long-running development with clear requirements +- Any work where backpressure (tests/builds) can verify correctness + +**Not good for:** + +- Tasks requiring human judgment mid-loop +- One-shot operations that don't benefit from iteration +- Vague requirements without testable acceptance criteria +- Exploratory prototyping where direction isn't clear + +## See Also + +- [Error Handling](error-handling.md) — timeout patterns and graceful shutdown for long-running sessions +- [Persisting Sessions](persisting-sessions.md) — save and resume sessions across restarts diff --git a/cookbook/copilot-sdk/nodejs/recipe/package-lock.json b/cookbook/copilot-sdk/nodejs/recipe/package-lock.json index a5a8fea5..47e85e5a 100644 --- a/cookbook/copilot-sdk/nodejs/recipe/package-lock.json +++ b/cookbook/copilot-sdk/nodejs/recipe/package-lock.json @@ -8,7 +8,7 @@ "name": "copilot-sdk-cookbook-recipes", "version": "1.0.0", "dependencies": { - "@github/copilot-sdk": "file:../../src" + "@github/copilot-sdk": "*" }, "devDependencies": { "@types/node": "^22.19.7", diff --git a/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts b/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts new file mode 100644 index 00000000..fb0fbe45 --- /dev/null +++ b/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts @@ -0,0 +1,78 @@ +import { readFile } from "fs/promises"; +import { CopilotClient } from "@github/copilot-sdk"; + +/** + * 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: + * npx tsx ralph-loop.ts # build mode, 50 iterations + * npx tsx ralph-loop.ts plan # planning mode + * npx tsx ralph-loop.ts 20 # build mode, 20 iterations + * npx tsx ralph-loop.ts plan 5 # planning mode, 5 iterations + */ + +type Mode = "plan" | "build"; + +async function ralphLoop(mode: Mode, maxIterations: number) { + const promptFile = mode === "plan" ? "PROMPT_plan.md" : "PROMPT_build.md"; + + const client = new CopilotClient(); + await client.start(); + + console.log("━".repeat(40)); + console.log(`Mode: ${mode}`); + console.log(`Prompt: ${promptFile}`); + console.log(`Max: ${maxIterations} iterations`); + console.log("━".repeat(40)); + + try { + const prompt = await readFile(promptFile, "utf-8"); + + for (let i = 1; i <= maxIterations; i++) { + console.log(`\n=== Iteration ${i}/${maxIterations} ===`); + + const session = await client.createSession({ + model: "gpt-5.1-codex-mini", + // Pin the agent to the project directory + workingDirectory: process.cwd(), + // Auto-approve tool calls for unattended operation + onPermissionRequest: async () => ({ allow: true }), + }); + + // Log tool usage for visibility + session.on((event) => { + if (event.type === "tool.execution_start") { + console.log(` ⚙ ${event.data.toolName}`); + } + }); + + try { + await session.sendAndWait({ prompt }, 600_000); + } finally { + await session.destroy(); + } + + console.log(`\nIteration ${i} complete.`); + } + + console.log(`\nReached max iterations: ${maxIterations}`); + } finally { + await client.stop(); + } +} + +// Parse CLI args +const args = process.argv.slice(2); +const mode: Mode = args.includes("plan") ? "plan" : "build"; +const maxArg = args.find((a) => /^\d+$/.test(a)); +const maxIterations = maxArg ? parseInt(maxArg) : 50; + +ralphLoop(mode, maxIterations).catch(console.error); diff --git a/cookbook/copilot-sdk/python/ralph-loop.md b/cookbook/copilot-sdk/python/ralph-loop.md new file mode 100644 index 00000000..b0d1c4b6 --- /dev/null +++ b/cookbook/copilot-sdk/python/ralph-loop.md @@ -0,0 +1,252 @@ +# Ralph Loop: Autonomous AI Task Loops + +Build autonomous coding loops where an AI agent picks tasks, implements them, validates against backpressure (tests, builds), commits, and repeats — each iteration in a fresh context window. + +> **Runnable example:** [recipe/ralph_loop.py](recipe/ralph_loop.py) +> +> From the repository root, install dependencies and run: +> +> ```bash +> pip install -r cookbook/copilot-sdk/python/recipe/requirements.txt +> python cookbook/copilot-sdk/python/recipe/ralph_loop.py +> ``` +> +> Make sure `PROMPT_build.md` and `PROMPT_plan.md` exist in your current working directory before running the loop. + +## What is a Ralph Loop? + +A [Ralph loop](https://ghuntley.com/ralph/) is an autonomous development workflow where an AI agent iterates through tasks in isolated context windows. The key insight: **state lives on disk, not in the model's context**. Each iteration starts fresh, reads the current state from files, does one task, writes results back to disk, and exits. + +``` +┌─────────────────────────────────────────────────┐ +│ loop.sh │ +│ while true: │ +│ ┌─────────────────────────────────────────┐ │ +│ │ Fresh session (isolated context) │ │ +│ │ │ │ +│ │ 1. Read PROMPT.md + AGENTS.md │ │ +│ │ 2. Study specs/* and code │ │ +│ │ 3. Pick next task from plan │ │ +│ │ 4. Implement + run tests │ │ +│ │ 5. Update plan, commit, exit │ │ +│ └─────────────────────────────────────────┘ │ +│ ↻ next iteration (fresh context) │ +└─────────────────────────────────────────────────┘ +``` + +**Core principles:** + +- **Fresh context per iteration**: Each loop creates a new session — no context accumulation, always in the "smart zone" +- **Disk as shared state**: `IMPLEMENTATION_PLAN.md` persists between iterations and acts as the coordination mechanism +- **Backpressure steers quality**: Tests, builds, and lints reject bad work — the agent must fix issues before committing +- **Two modes**: PLANNING (gap analysis → generate plan) and BUILDING (implement from plan) + +## Simple Version + +The minimal Ralph loop — the SDK equivalent of `while :; do cat PROMPT.md | copilot ; done`: + +```python +import asyncio +from pathlib import Path +from copilot import CopilotClient, MessageOptions, SessionConfig + + +async def ralph_loop(prompt_file: str, max_iterations: int = 50): + client = CopilotClient() + await client.start() + + 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 iteration — context isolation is the point + session = await client.create_session( + SessionConfig(model="gpt-5.1-codex-mini") + ) + try: + await session.send_and_wait( + MessageOptions(prompt=prompt), timeout=600 + ) + finally: + await session.destroy() + + print(f"Iteration {i} complete.") + finally: + await client.stop() + + +# Usage: point at your PROMPT.md +asyncio.run(ralph_loop("PROMPT.md", 20)) +``` + +This is all you need to get started. The prompt file tells the agent what to do; the agent reads project files, does work, commits, and exits. The loop restarts with a clean slate. + +## Ideal Version + +The full Ralph pattern with planning and building modes, matching the [Ralph Playbook](https://github.com/ClaytonFarr/ralph-playbook) architecture: + +```python +import asyncio +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() + + print("━" * 40) + print(f"Mode: {mode}") + print(f"Prompt: {prompt_file}") + 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} ===") + + session = await client.create_session(SessionConfig( + model="gpt-5.1-codex-mini", + # Pin the agent to the project directory + working_directory=str(Path.cwd()), + # Auto-approve tool calls for unattended operation + on_permission_request=lambda _req, _ctx: { + "kind": "approved", "rules": [] + }, + )) + + # Log tool usage for visibility + def log_tool_event(event): + if event.type.value == "tool.execution_start": + print(f" ⚙ {event.data.tool_name}") + + session.on(log_tool_event) + + try: + await session.send_and_wait( + MessageOptions(prompt=prompt), timeout=600 + ) + finally: + await session.destroy() + + 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)) +``` + +### Required Project Files + +The ideal version expects this file structure in your project: + +``` +project-root/ +├── PROMPT_plan.md # Planning mode instructions +├── PROMPT_build.md # Building mode instructions +├── AGENTS.md # Operational guide (build/test commands) +├── IMPLEMENTATION_PLAN.md # Task list (generated by planning mode) +├── specs/ # Requirement specs (one per topic) +│ ├── auth.md +│ └── data-pipeline.md +└── src/ # Your source code +``` + +### Example `PROMPT_plan.md` + +```markdown +0a. Study `specs/*` to learn the application specifications. +0b. Study IMPLEMENTATION_PLAN.md (if present) to understand the plan so far. +0c. Study `src/` to understand existing code and shared utilities. + +1. Compare specs against code (gap analysis). Create or update + IMPLEMENTATION_PLAN.md as a prioritized bullet-point list of tasks + yet to be implemented. Do NOT implement anything. + +IMPORTANT: Do NOT assume functionality is missing — search the +codebase first to confirm. Prefer updating existing utilities over +creating ad-hoc copies. +``` + +### Example `PROMPT_build.md` + +```markdown +0a. Study `specs/*` to learn the application specifications. +0b. Study IMPLEMENTATION_PLAN.md. +0c. Study `src/` for reference. + +1. Choose the most important item from IMPLEMENTATION_PLAN.md. Before + making changes, search the codebase (don't assume not implemented). +2. After implementing, run the tests. If functionality is missing, add it. +3. When you discover issues, update IMPLEMENTATION_PLAN.md immediately. +4. When tests pass, update IMPLEMENTATION_PLAN.md, then `git add -A` + then `git commit` with a descriptive message. + +5. When authoring documentation, capture the why. +6. Implement completely. No placeholders or stubs. +7. Keep IMPLEMENTATION_PLAN.md current — future iterations depend on it. +``` + +### Example `AGENTS.md` + +Keep this brief (~60 lines). It's loaded every iteration, so bloat wastes context. + +```markdown +## Build & Run + +python -m pytest + +## Validation + +- Tests: `pytest` +- Typecheck: `mypy src/` +- Lint: `ruff check src/` +``` + +## Best Practices + +1. **Fresh context per iteration**: Never accumulate context across iterations — that's the whole point +2. **Disk is your database**: `IMPLEMENTATION_PLAN.md` is shared state between isolated sessions +3. **Backpressure is essential**: Tests, builds, lints in `AGENTS.md` — the agent must pass them before committing +4. **Start with PLANNING mode**: Generate the plan first, then switch to BUILDING +5. **Observe and tune**: Watch early iterations, add guardrails to prompts when the agent fails in specific ways +6. **The plan is disposable**: If the agent goes off track, delete `IMPLEMENTATION_PLAN.md` and re-plan +7. **Keep `AGENTS.md` brief**: It's loaded every iteration — operational info only, no progress notes +8. **Use a sandbox**: The agent runs autonomously with full tool access — isolate it +9. **Set `working_directory`**: Pin the session to your project root so tool operations resolve paths correctly +10. **Auto-approve permissions**: Use `on_permission_request` to allow tool calls without interrupting the loop + +## When to Use a Ralph Loop + +**Good for:** + +- Implementing features from specs with test-driven validation +- Large refactors broken into many small tasks +- Unattended, long-running development with clear requirements +- Any work where backpressure (tests/builds) can verify correctness + +**Not good for:** + +- Tasks requiring human judgment mid-loop +- One-shot operations that don't benefit from iteration +- Vague requirements without testable acceptance criteria +- Exploratory prototyping where direction isn't clear + +## See Also + +- [Error Handling](error-handling.md) — timeout patterns and graceful shutdown for long-running sessions +- [Persisting Sessions](persisting-sessions.md) — save and resume sessions across restarts diff --git a/cookbook/copilot-sdk/python/recipe/ralph_loop.py b/cookbook/copilot-sdk/python/recipe/ralph_loop.py new file mode 100644 index 00000000..918e8c66 --- /dev/null +++ b/cookbook/copilot-sdk/python/recipe/ralph_loop.py @@ -0,0 +1,81 @@ +#!/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 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() + + print("━" * 40) + print(f"Mode: {mode}") + print(f"Prompt: {prompt_file}") + 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} ===") + + session = await client.create_session(SessionConfig( + model="gpt-5.1-codex-mini", + # Pin the agent to the project directory + working_directory=str(Path.cwd()), + # Auto-approve tool calls for unattended operation + on_permission_request=lambda _req, _ctx: { + "kind": "approved", + "rules": [], + }, + )) + + # Log tool usage for visibility + def log_tool_event(event): + if event.type.value == "tool.execution_start": + print(f" ⚙ {event.data.tool_name}") + + session.on(log_tool_event) + try: + await session.send_and_wait( + MessageOptions(prompt=prompt), timeout=600 + ) + finally: + await session.destroy() + + 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))