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
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
2. Claude writes code and tests
3. Claude runs tests and sees failures
2. The model writes code and tests
3. The model runs tests and sees failures
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
## Basic Implementation
@@ -52,56 +52,66 @@ public class RalphLoop
public async Task<string> RunAsync(string prompt)
{
await _client.StartAsync();
var session = await _client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" });
try
{
while (_iteration < _maxIterations)
{
_iteration++;
Console.WriteLine($"\n--- Iteration {_iteration} ---");
var session = await _client.CreateSessionAsync(
new SessionConfig { Model = "gpt-5.1-codex-mini" });
try
{
var done = new TaskCompletionSource<string>();
session.On(evt =>
{
if (evt is AssistantMessageEvent msg)
{
_lastResponse = msg.Data.Content;
done.SetResult(msg.Data.Content);
done.TrySetResult(msg.Data.Content);
}
});
// 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))
while (_iteration < _maxIterations)
{
Console.WriteLine($"✓ Completion promise detected: {_completionPromise}");
return response;
_iteration++;
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
{
await session.DisposeAsync();
await _client.StopAsync();
}
}
}
// 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");
Console.WriteLine(result);
```
@@ -115,11 +125,13 @@ public class PersistentRalphLoop
{
private readonly string _workDir;
private readonly CopilotClient _client;
private readonly int _maxIterations;
private int _iteration = 0;
public PersistentRalphLoop(string workDir, int maxIterations = 10)
{
_workDir = workDir;
_maxIterations = maxIterations;
Directory.CreateDirectory(_workDir);
_client = new CopilotClient();
}
@@ -127,26 +139,17 @@ public class PersistentRalphLoop
public async Task<string> RunAsync(string prompt)
{
await _client.StartAsync();
var session = await _client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" });
try
{
// Store initial prompt
var promptFile = Path.Combine(_workDir, "prompt.md");
await File.WriteAllTextAsync(promptFile, prompt);
var session = await _client.CreateSessionAsync(
new SessionConfig { Model = "gpt-5.1-codex-mini" });
while (_iteration < 10)
try
{
_iteration++;
Console.WriteLine($"\n--- Iteration {_iteration} ---");
// 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)}");
}
// Store initial prompt
var promptFile = Path.Combine(_workDir, "prompt.md");
await File.WriteAllTextAsync(promptFile, prompt);
var done = new TaskCompletionSource<string>();
string response = "";
@@ -155,29 +158,48 @@ public class PersistentRalphLoop
if (evt is AssistantMessageEvent msg)
{
response = msg.Data.Content;
done.SetResult(msg.Data.Content);
done.TrySetResult(msg.Data.Content);
}
});
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"))
while (_iteration < _maxIterations)
{
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
{
await session.DisposeAsync();
await _client.StopAsync();
}
}

View File

@@ -62,54 +62,63 @@ public class RalphLoop
public async Task<string> RunAsync(string initialPrompt)
{
await _client.StartAsync();
var session = await _client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-5.1-codex-mini"
});
try
{
while (_iteration < _maxIterations)
{
_iteration++;
Console.WriteLine($"\n=== Iteration {_iteration}/{_maxIterations} ===");
var session = await _client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-5.1-codex-mini"
});
try
{
var done = new TaskCompletionSource<string>();
session.On(evt =>
{
if (evt is AssistantMessageEvent msg)
{
_lastResponse = msg.Data.Content;
done.SetResult(msg.Data.Content);
done.TrySetResult(msg.Data.Content);
}
});
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))
while (_iteration < _maxIterations)
{
Console.WriteLine($"\n✓ Completion promise detected: '{_completionPromise}'");
return response;
_iteration++;
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
{
await session.DisposeAsync();
await _client.StopAsync();
}
}

View File

@@ -49,7 +49,7 @@ class RalphLoop {
async run(initialPrompt: string): Promise<string> {
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 {
while (this.iteration < this.maxIterations) {
@@ -115,7 +115,7 @@ class PersistentRalphLoop {
async run(initialPrompt: string): Promise<string> {
await fs.mkdir(this.workDir, { recursive: true });
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 {
// Store initial prompt