Skip to content

Commit 1074e34

Browse files
committed
Add SDK features to all Ralph loop recipes
- Add WorkingDirectory/working_directory to pin sessions to project root - Add OnPermissionRequest/on_permission_request for unattended operation - Add tool execution event logging for visibility - Add See Also cross-links to error-handling and persisting-sessions - Add best practices for WorkingDirectory and permission auto-approval - Consistent across all 4 languages (Node.js, Python, .NET, Go)
1 parent 92df16d commit 1074e34

9 files changed

Lines changed: 139 additions & 18 deletions

File tree

cookbook/copilot-sdk/dotnet/ralph-loop.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,24 @@ try
119119
120120
// Fresh session — each task gets full context budget
121121
var session = await client.CreateSessionAsync(
122-
new SessionConfig { Model = "claude-sonnet-4.5" });
122+
new SessionConfig
123+
{
124+
Model = "claude-sonnet-4.5",
125+
// Pin the agent to the project directory
126+
WorkingDirectory = Environment.CurrentDirectory,
127+
// Auto-approve tool calls for unattended operation
128+
OnPermissionRequest = (_, _) => Task.FromResult(
129+
new PermissionRequestResult { Kind = "approved" }),
130+
});
123131
try
124132
{
125133
var done = new TaskCompletionSource<string>();
126134
session.On(evt =>
127135
{
128-
if (evt is AssistantMessageEvent msg)
136+
// Log tool usage for visibility
137+
if (evt is ToolExecutionStartEvent toolStart)
138+
Console.WriteLine($" ⚙ {toolStart.Data.ToolName}");
139+
else if (evt is AssistantMessageEvent msg)
129140
done.TrySetResult(msg.Data.Content);
130141
});
131142
@@ -224,6 +235,8 @@ dotnet build
224235
6. **The plan is disposable**: If the agent goes off track, delete `IMPLEMENTATION_PLAN.md` and re-plan
225236
7. **Keep `AGENTS.md` brief**: It's loaded every iteration — operational info only, no progress notes
226237
8. **Use a sandbox**: The agent runs autonomously with full tool access — isolate it
238+
9. **Set `WorkingDirectory`**: Pin the session to your project root so tool operations resolve paths correctly
239+
10. **Auto-approve permissions**: Use `OnPermissionRequest` to allow tool calls without interrupting the loop
227240
228241
## When to Use a Ralph Loop
229242
@@ -238,3 +251,8 @@ dotnet build
238251
- One-shot operations that don't benefit from iteration
239252
- Vague requirements without testable acceptance criteria
240253
- Exploratory prototyping where direction isn't clear
254+
255+
## See Also
256+
257+
- [Error Handling](error-handling.md) — timeout patterns and graceful shutdown for long-running sessions
258+
- [Persisting Sessions](persisting-sessions.md) — save and resume sessions across restarts

cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,25 @@
4343

4444
// Fresh session — each task gets full context budget
4545
var session = await client.CreateSessionAsync(
46-
new SessionConfig { Model = "claude-sonnet-4.5" });
46+
new SessionConfig
47+
{
48+
Model = "claude-sonnet-4.5",
49+
// Pin the agent to the project directory
50+
WorkingDirectory = Environment.CurrentDirectory,
51+
// Auto-approve tool calls for unattended operation
52+
OnPermissionRequest = (_, _) => Task.FromResult(
53+
new PermissionRequestResult { Kind = "approved" }),
54+
});
4755

4856
try
4957
{
5058
var done = new TaskCompletionSource<string>();
5159
session.On(evt =>
5260
{
53-
if (evt is AssistantMessageEvent msg)
61+
// Log tool usage for visibility
62+
if (evt is ToolExecutionStartEvent toolStart)
63+
Console.WriteLine($" ⚙ {toolStart.Data.ToolName}");
64+
else if (evt is AssistantMessageEvent msg)
5465
done.TrySetResult(msg.Data.Content);
5566
});
5667

cookbook/copilot-sdk/go/ralph-loop.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ import (
111111
"log"
112112
"os"
113113
"strconv"
114+
"strings"
114115
115116
copilot "github.com/github/copilot-sdk/go"
116117
)
@@ -127,6 +128,8 @@ func ralphLoop(ctx context.Context, mode string, maxIterations int) error {
127128
}
128129
defer client.Stop()
129130
131+
cwd, _ := os.Getwd()
132+
130133
fmt.Println(strings.Repeat("━", 40))
131134
fmt.Printf("Mode: %s\n", mode)
132135
fmt.Printf("Prompt: %s\n", promptFile)
@@ -141,14 +144,24 @@ func ralphLoop(ctx context.Context, mode string, maxIterations int) error {
141144
for i := 1; i <= maxIterations; i++ {
142145
fmt.Printf("\n=== Iteration %d/%d ===\n", i, maxIterations)
143146
144-
// Fresh session — each task gets full context budget
145147
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
146-
Model: "claude-sonnet-4.5",
148+
Model: "claude-sonnet-4.5",
149+
WorkingDirectory: cwd,
150+
OnPermissionRequest: func(_ copilot.PermissionRequest, _ map[string]string) copilot.PermissionRequestResult {
151+
return copilot.PermissionRequestResult{Kind: "approved"}
152+
},
147153
})
148154
if err != nil {
149155
return err
150156
}
151157
158+
// Log tool usage for visibility
159+
session.On(func(event copilot.Event) {
160+
if te, ok := event.(copilot.ToolExecutionStartEvent); ok {
161+
fmt.Printf(" ⚙ %s\n", te.Data.ToolName)
162+
}
163+
})
164+
152165
_, err = session.SendAndWait(ctx, copilot.MessageOptions{
153166
Prompt: string(prompt),
154167
})
@@ -258,6 +271,8 @@ go build ./...
258271
6. **The plan is disposable**: If the agent goes off track, delete `IMPLEMENTATION_PLAN.md` and re-plan
259272
7. **Keep `AGENTS.md` brief**: It's loaded every iteration — operational info only, no progress notes
260273
8. **Use a sandbox**: The agent runs autonomously with full tool access — isolate it
274+
9. **Set `WorkingDirectory`**: Pin the session to your project root so tool operations resolve paths correctly
275+
10. **Auto-approve permissions**: Use `OnPermissionRequest` to allow tool calls without interrupting the loop
261276
262277
## When to Use a Ralph Loop
263278
@@ -272,3 +287,8 @@ go build ./...
272287
- One-shot operations that don't benefit from iteration
273288
- Vague requirements without testable acceptance criteria
274289
- Exploratory prototyping where direction isn't clear
290+
291+
## See Also
292+
293+
- [Error Handling](error-handling.md) — timeout patterns and graceful shutdown for long-running sessions
294+
- [Persisting Sessions](persisting-sessions.md) — save and resume sessions across restarts

cookbook/copilot-sdk/go/recipe/ralph-loop.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ func ralphLoop(ctx context.Context, mode string, maxIterations int) error {
3939
}
4040
defer client.Stop()
4141

42+
cwd, _ := os.Getwd()
43+
4244
fmt.Println(strings.Repeat("━", 40))
4345
fmt.Printf("Mode: %s\n", mode)
4446
fmt.Printf("Prompt: %s\n", promptFile)
@@ -53,14 +55,24 @@ func ralphLoop(ctx context.Context, mode string, maxIterations int) error {
5355
for i := 1; i <= maxIterations; i++ {
5456
fmt.Printf("\n=== Iteration %d/%d ===\n", i, maxIterations)
5557

56-
// Fresh session — each task gets full context budget
5758
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
58-
Model: "claude-sonnet-4.5",
59+
Model: "claude-sonnet-4.5",
60+
WorkingDirectory: cwd,
61+
OnPermissionRequest: func(_ copilot.PermissionRequest, _ map[string]string) copilot.PermissionRequestResult {
62+
return copilot.PermissionRequestResult{Kind: "approved"}
63+
},
5964
})
6065
if err != nil {
6166
return fmt.Errorf("failed to create session: %w", err)
6267
}
6368

69+
// Log tool usage for visibility
70+
session.On(func(event copilot.Event) {
71+
if te, ok := event.(copilot.ToolExecutionStartEvent); ok {
72+
fmt.Printf(" ⚙ %s\n", te.Data.ToolName)
73+
}
74+
})
75+
6476
_, err = session.SendAndWait(ctx, copilot.MessageOptions{
6577
Prompt: string(prompt),
6678
})

cookbook/copilot-sdk/nodejs/ralph-loop.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,21 @@ async function ralphLoop(mode: Mode, maxIterations: number = 50) {
9999
for (let i = 1; i <= maxIterations; i++) {
100100
console.log(`\n=== Iteration ${i}/${maxIterations} ===`);
101101
102-
// Fresh session — each task gets full context budget
103-
const session = await client.createSession({ model: "claude-sonnet-4.5" });
102+
const session = await client.createSession({
103+
model: "claude-sonnet-4.5",
104+
// Pin the agent to the project directory
105+
workingDirectory: process.cwd(),
106+
// Auto-approve tool calls for unattended operation
107+
onPermissionRequest: async () => ({ allow: true }),
108+
});
109+
110+
// Log tool usage for visibility
111+
session.on((event) => {
112+
if (event.type === "tool.execution_start") {
113+
console.log(` ⚙ ${event.data.toolName}`);
114+
}
115+
});
116+
104117
try {
105118
await session.sendAndWait({ prompt }, 600_000);
106119
} finally {
@@ -200,6 +213,8 @@ npm run build
200213
6. **The plan is disposable**: If the agent goes off track, delete `IMPLEMENTATION_PLAN.md` and re-plan
201214
7. **Keep `AGENTS.md` brief**: It's loaded every iteration — operational info only, no progress notes
202215
8. **Use a sandbox**: The agent runs autonomously with full tool access — isolate it
216+
9. **Set `workingDirectory`**: Pin the session to your project root so tool operations resolve paths correctly
217+
10. **Auto-approve permissions**: Use `onPermissionRequest` to allow tool calls without interrupting the loop
203218
204219
## When to Use a Ralph Loop
205220
@@ -214,3 +229,8 @@ npm run build
214229
- One-shot operations that don't benefit from iteration
215230
- Vague requirements without testable acceptance criteria
216231
- Exploratory prototyping where direction isn't clear
232+
233+
## See Also
234+
235+
- [Error Handling](error-handling.md) — timeout patterns and graceful shutdown for long-running sessions
236+
- [Persisting Sessions](persisting-sessions.md) — save and resume sessions across restarts

cookbook/copilot-sdk/nodejs/recipe/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,19 @@ async function ralphLoop(mode: Mode, maxIterations: number) {
3939
for (let i = 1; i <= maxIterations; i++) {
4040
console.log(`\n=== Iteration ${i}/${maxIterations} ===`);
4141

42-
// Fresh session — each task gets full context budget
4342
const session = await client.createSession({
4443
model: "claude-sonnet-4.5",
44+
// Pin the agent to the project directory
45+
workingDirectory: process.cwd(),
46+
// Auto-approve tool calls for unattended operation
47+
onPermissionRequest: async () => ({ allow: true }),
48+
});
49+
50+
// Log tool usage for visibility
51+
session.on((event) => {
52+
if (event.type === "tool.execution_start") {
53+
console.log(` ⚙ ${event.data.toolName}`);
54+
}
4555
});
4656

4757
try {

cookbook/copilot-sdk/python/ralph-loop.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,20 @@ async def ralph_loop(mode: str = "build", max_iterations: int = 50):
108108
for i in range(1, max_iterations + 1):
109109
print(f"\n=== Iteration {i}/{max_iterations} ===")
110110
111-
# Fresh session — each task gets full context budget
112-
session = await client.create_session(
113-
SessionConfig(model="claude-sonnet-4.5")
111+
session = await client.create_session(SessionConfig(
112+
model="claude-sonnet-4.5",
113+
# Pin the agent to the project directory
114+
working_directory=str(Path.cwd()),
115+
# Auto-approve tool calls for unattended operation
116+
on_permission_request=lambda _req, _ctx: {"kind": "approved", "rules": []},
117+
))
118+
119+
# Log tool usage for visibility
120+
session.on(lambda event:
121+
print(f" ⚙ {event.data.tool_name}")
122+
if event.type.value == "tool.execution_start" else None
114123
)
124+
115125
try:
116126
await session.send_and_wait(
117127
MessageOptions(prompt=prompt), timeout=600
@@ -210,6 +220,8 @@ python -m pytest
210220
6. **The plan is disposable**: If the agent goes off track, delete `IMPLEMENTATION_PLAN.md` and re-plan
211221
7. **Keep `AGENTS.md` brief**: It's loaded every iteration — operational info only, no progress notes
212222
8. **Use a sandbox**: The agent runs autonomously with full tool access — isolate it
223+
9. **Set `working_directory`**: Pin the session to your project root so tool operations resolve paths correctly
224+
10. **Auto-approve permissions**: Use `on_permission_request` to allow tool calls without interrupting the loop
213225
214226
## When to Use a Ralph Loop
215227
@@ -224,3 +236,8 @@ python -m pytest
224236
- One-shot operations that don't benefit from iteration
225237
- Vague requirements without testable acceptance criteria
226238
- Exploratory prototyping where direction isn't clear
239+
240+
## See Also
241+
242+
- [Error Handling](error-handling.md) — timeout patterns and graceful shutdown for long-running sessions
243+
- [Persisting Sessions](persisting-sessions.md) — save and resume sessions across restarts

cookbook/copilot-sdk/python/recipe/ralph_loop.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,23 @@ async def ralph_loop(mode: str = "build", max_iterations: int = 50):
4343
for i in range(1, max_iterations + 1):
4444
print(f"\n=== Iteration {i}/{max_iterations} ===")
4545

46-
# Fresh session — each task gets full context budget
47-
session = await client.create_session(
48-
SessionConfig(model="claude-sonnet-4.5")
46+
session = await client.create_session(SessionConfig(
47+
model="claude-sonnet-4.5",
48+
# Pin the agent to the project directory
49+
working_directory=str(Path.cwd()),
50+
# Auto-approve tool calls for unattended operation
51+
on_permission_request=lambda _req, _ctx: {
52+
"kind": "approved",
53+
"rules": [],
54+
},
55+
))
56+
57+
# Log tool usage for visibility
58+
session.on(lambda event:
59+
print(f" ⚙ {event.data.tool_name}")
60+
if event.type.value == "tool.execution_start" else None
4961
)
62+
5063
try:
5164
await session.send_and_wait(
5265
MessageOptions(prompt=prompt), timeout=600

0 commit comments

Comments
 (0)