Address review feedback: fix event handler leak, error handling, model alignment

- Move session.On handler outside loop to prevent handler accumulation (C#)
- Use TrySetResult instead of SetResult to avoid duplicate-set exceptions (C#)
- Wrap CreateSessionAsync in broader try/finally so client always stops (C#)
- Fix PersistentRalphLoop to use maxIterations parameter instead of hardcoded 10
- Align model name to gpt-5.1-codex-mini across all doc snippets
- Fix completion promise DONE -> COMPLETE in usage snippet
- Replace Claude references with generic model terminology
This commit is contained in:
Anthony Shaw
2026-02-11 06:35:14 -08:00
parent bb9f63a899
commit ab82accc08
3 changed files with 119 additions and 88 deletions

View File

@@ -20,13 +20,13 @@ RALPH-loop is a development methodology for iterative AI-powered task completion
## Example Scenario ## Example Scenario
You need to iteratively improve code until all tests pass. Instead of asking Claude to "write perfect code," you use RALPH-loop to: You need to iteratively improve code until all tests pass. Instead of asking the model to "write perfect code," you use RALPH-loop to:
1. Send the initial prompt with clear success criteria 1. Send the initial prompt with clear success criteria
2. Claude writes code and tests 2. The model writes code and tests
3. Claude runs tests and sees failures 3. The model runs tests and sees failures
4. Loop automatically re-sends the prompt 4. Loop automatically re-sends the prompt
5. Claude reads test output and previous code, fixes issues 5. The model reads test output and previous code, fixes issues
6. Repeat until all tests pass and completion promise is output 6. Repeat until all tests pass and completion promise is output
## Basic Implementation ## Basic Implementation
@@ -52,56 +52,66 @@ public class RalphLoop
public async Task<string> RunAsync(string prompt) public async Task<string> RunAsync(string prompt)
{ {
await _client.StartAsync(); await _client.StartAsync();
var session = await _client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" });
try try
{ {
while (_iteration < _maxIterations) var session = await _client.CreateSessionAsync(
{ new SessionConfig { Model = "gpt-5.1-codex-mini" });
_iteration++;
Console.WriteLine($"\n--- Iteration {_iteration} ---");
try
{
var done = new TaskCompletionSource<string>(); var done = new TaskCompletionSource<string>();
session.On(evt => session.On(evt =>
{ {
if (evt is AssistantMessageEvent msg) if (evt is AssistantMessageEvent msg)
{ {
_lastResponse = msg.Data.Content; _lastResponse = msg.Data.Content;
done.SetResult(msg.Data.Content); done.TrySetResult(msg.Data.Content);
} }
}); });
// Send prompt (on first iteration) or continuation while (_iteration < _maxIterations)
var messagePrompt = _iteration == 1
? prompt
: $"{prompt}\n\nPrevious attempt:\n{_lastResponse}\n\nContinue iterating...";
await session.SendAsync(new MessageOptions { Prompt = messagePrompt });
var response = await done.Task;
// Check for completion promise
if (response.Contains(_completionPromise))
{ {
Console.WriteLine($"✓ Completion promise detected: {_completionPromise}"); _iteration++;
return response; Console.WriteLine($"\n--- Iteration {_iteration} ---");
done = new TaskCompletionSource<string>();
// Send prompt (on first iteration) or continuation
var messagePrompt = _iteration == 1
? prompt
: $"{prompt}\n\nPrevious attempt:\n{_lastResponse}\n\nContinue iterating...";
await session.SendAsync(new MessageOptions { Prompt = messagePrompt });
var response = await done.Task;
// Check for completion promise
if (response.Contains(_completionPromise))
{
Console.WriteLine($"✓ Completion promise detected: {_completionPromise}");
return response;
}
Console.WriteLine($"Iteration {_iteration} complete. Continuing...");
} }
Console.WriteLine($"Iteration {_iteration} complete. Continuing..."); throw new InvalidOperationException(
$"Max iterations ({_maxIterations}) reached without completion promise");
}
finally
{
await session.DisposeAsync();
} }
throw new InvalidOperationException(
$"Max iterations ({_maxIterations}) reached without completion promise");
} }
finally finally
{ {
await session.DisposeAsync();
await _client.StopAsync(); await _client.StopAsync();
} }
} }
} }
// Usage // Usage
var loop = new RalphLoop(maxIterations: 5, completionPromise: "DONE"); var loop = new RalphLoop(maxIterations: 5, completionPromise: "COMPLETE");
var result = await loop.RunAsync("Your task here"); var result = await loop.RunAsync("Your task here");
Console.WriteLine(result); Console.WriteLine(result);
``` ```
@@ -115,11 +125,13 @@ public class PersistentRalphLoop
{ {
private readonly string _workDir; private readonly string _workDir;
private readonly CopilotClient _client; private readonly CopilotClient _client;
private readonly int _maxIterations;
private int _iteration = 0; private int _iteration = 0;
public PersistentRalphLoop(string workDir, int maxIterations = 10) public PersistentRalphLoop(string workDir, int maxIterations = 10)
{ {
_workDir = workDir; _workDir = workDir;
_maxIterations = maxIterations;
Directory.CreateDirectory(_workDir); Directory.CreateDirectory(_workDir);
_client = new CopilotClient(); _client = new CopilotClient();
} }
@@ -127,26 +139,17 @@ public class PersistentRalphLoop
public async Task<string> RunAsync(string prompt) public async Task<string> RunAsync(string prompt)
{ {
await _client.StartAsync(); await _client.StartAsync();
var session = await _client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" });
try try
{ {
// Store initial prompt var session = await _client.CreateSessionAsync(
var promptFile = Path.Combine(_workDir, "prompt.md"); new SessionConfig { Model = "gpt-5.1-codex-mini" });
await File.WriteAllTextAsync(promptFile, prompt);
while (_iteration < 10) try
{ {
_iteration++; // Store initial prompt
Console.WriteLine($"\n--- Iteration {_iteration} ---"); var promptFile = Path.Combine(_workDir, "prompt.md");
await File.WriteAllTextAsync(promptFile, prompt);
// Build context including previous work
var contextBuilder = new StringBuilder(prompt);
var previousOutput = Path.Combine(_workDir, $"output-{_iteration - 1}.txt");
if (File.Exists(previousOutput))
{
contextBuilder.AppendLine($"\nPrevious iteration output:\n{await File.ReadAllTextAsync(previousOutput)}");
}
var done = new TaskCompletionSource<string>(); var done = new TaskCompletionSource<string>();
string response = ""; string response = "";
@@ -155,29 +158,48 @@ public class PersistentRalphLoop
if (evt is AssistantMessageEvent msg) if (evt is AssistantMessageEvent msg)
{ {
response = msg.Data.Content; response = msg.Data.Content;
done.SetResult(msg.Data.Content); done.TrySetResult(msg.Data.Content);
} }
}); });
await session.SendAsync(new MessageOptions { Prompt = contextBuilder.ToString() }); while (_iteration < _maxIterations)
await done.Task;
// Persist output
await File.WriteAllTextAsync(
Path.Combine(_workDir, $"output-{_iteration}.txt"),
response);
if (response.Contains("COMPLETE"))
{ {
return response; _iteration++;
} Console.WriteLine($"\n--- Iteration {_iteration} ---");
}
throw new InvalidOperationException("Max iterations reached"); done = new TaskCompletionSource<string>();
// Build context including previous work
var contextBuilder = new StringBuilder(prompt);
var previousOutput = Path.Combine(_workDir, $"output-{_iteration - 1}.txt");
if (File.Exists(previousOutput))
{
contextBuilder.AppendLine($"\nPrevious iteration output:\n{await File.ReadAllTextAsync(previousOutput)}");
}
await session.SendAsync(new MessageOptions { Prompt = contextBuilder.ToString() });
await done.Task;
// Persist output
await File.WriteAllTextAsync(
Path.Combine(_workDir, $"output-{_iteration}.txt"),
response);
if (response.Contains("COMPLETE"))
{
return response;
}
}
throw new InvalidOperationException("Max iterations reached");
}
finally
{
await session.DisposeAsync();
}
} }
finally finally
{ {
await session.DisposeAsync();
await _client.StopAsync(); await _client.StopAsync();
} }
} }

View File

@@ -62,54 +62,63 @@ public class RalphLoop
public async Task<string> RunAsync(string initialPrompt) public async Task<string> RunAsync(string initialPrompt)
{ {
await _client.StartAsync(); await _client.StartAsync();
var session = await _client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-5.1-codex-mini"
});
try try
{ {
while (_iteration < _maxIterations) var session = await _client.CreateSessionAsync(new SessionConfig
{ {
_iteration++; Model = "gpt-5.1-codex-mini"
Console.WriteLine($"\n=== Iteration {_iteration}/{_maxIterations} ==="); });
try
{
var done = new TaskCompletionSource<string>(); var done = new TaskCompletionSource<string>();
session.On(evt => session.On(evt =>
{ {
if (evt is AssistantMessageEvent msg) if (evt is AssistantMessageEvent msg)
{ {
_lastResponse = msg.Data.Content; _lastResponse = msg.Data.Content;
done.SetResult(msg.Data.Content); done.TrySetResult(msg.Data.Content);
} }
}); });
var currentPrompt = BuildIterationPrompt(initialPrompt); while (_iteration < _maxIterations)
Console.WriteLine($"Sending prompt (length: {currentPrompt.Length})...");
await session.SendAsync(new MessageOptions { Prompt = currentPrompt });
var response = await done.Task;
var summary = response.Length > 200
? response.Substring(0, 200) + "..."
: response;
Console.WriteLine($"Response: {summary}");
if (response.Contains(_completionPromise))
{ {
Console.WriteLine($"\n✓ Completion promise detected: '{_completionPromise}'"); _iteration++;
return response; Console.WriteLine($"\n=== Iteration {_iteration}/{_maxIterations} ===");
done = new TaskCompletionSource<string>();
var currentPrompt = BuildIterationPrompt(initialPrompt);
Console.WriteLine($"Sending prompt (length: {currentPrompt.Length})...");
await session.SendAsync(new MessageOptions { Prompt = currentPrompt });
var response = await done.Task;
var summary = response.Length > 200
? response.Substring(0, 200) + "..."
: response;
Console.WriteLine($"Response: {summary}");
if (response.Contains(_completionPromise))
{
Console.WriteLine($"\n✓ Completion promise detected: '{_completionPromise}'");
return response;
}
Console.WriteLine($"Iteration {_iteration} complete. Continuing...");
} }
Console.WriteLine($"Iteration {_iteration} complete. Continuing..."); throw new InvalidOperationException(
$"Max iterations ({_maxIterations}) reached without completion promise: '{_completionPromise}'");
}
finally
{
await session.DisposeAsync();
} }
throw new InvalidOperationException(
$"Max iterations ({_maxIterations}) reached without completion promise: '{_completionPromise}'");
} }
finally finally
{ {
await session.DisposeAsync();
await _client.StopAsync(); await _client.StopAsync();
} }
} }

View File

@@ -49,7 +49,7 @@ class RalphLoop {
async run(initialPrompt: string): Promise<string> { async run(initialPrompt: string): Promise<string> {
await this.client.start(); await this.client.start();
const session = await this.client.createSession({ model: "gpt-5" }); const session = await this.client.createSession({ model: "gpt-5.1-codex-mini" });
try { try {
while (this.iteration < this.maxIterations) { while (this.iteration < this.maxIterations) {
@@ -115,7 +115,7 @@ class PersistentRalphLoop {
async run(initialPrompt: string): Promise<string> { async run(initialPrompt: string): Promise<string> {
await fs.mkdir(this.workDir, { recursive: true }); await fs.mkdir(this.workDir, { recursive: true });
await this.client.start(); await this.client.start();
const session = await this.client.createSession({ model: "gpt-5" }); const session = await this.client.createSession({ model: "gpt-5.1-codex-mini" });
try { try {
// Store initial prompt // Store initial prompt