refactor: remove auto-continue from codex runner
Auto-continue was punishing the model for thinking — text-only turns were treated as failures and re-prompted up to 5 times with aggressive "execute now" messages. This made Codex overly compliant and suppressed critical thinking. Now each turn result is delivered directly. The model decides whether to think, push back, or execute.
This commit is contained in:
@@ -62,8 +62,6 @@ const IPC_INPUT_DIR = path.join(IPC_DIR, 'input');
|
||||
const IPC_INPUT_CLOSE_SENTINEL = path.join(IPC_INPUT_DIR, '_close');
|
||||
const IPC_POLL_MS = 500;
|
||||
const MAX_TURNS = 100;
|
||||
const MAX_AUTO_CONTINUES = 5;
|
||||
const AUTO_CONTINUE_PROMPT = 'Continue. Execute the task — don\'t just describe what you\'ll do.';
|
||||
|
||||
const OUTPUT_START_MARKER = '---NANOCLAW_OUTPUT_START---';
|
||||
const OUTPUT_END_MARKER = '---NANOCLAW_OUTPUT_END---';
|
||||
@@ -414,12 +412,11 @@ async function executeTurn(
|
||||
server: CodexAppServer,
|
||||
threadId: string,
|
||||
prompt: string,
|
||||
): Promise<{ result: string; error?: string; turnId: string; hadToolExecution: boolean }> {
|
||||
): Promise<{ result: string; error?: string; turnId: string }> {
|
||||
return new Promise((resolve) => {
|
||||
let agentText = '';
|
||||
let turnId = '';
|
||||
let resolved = false;
|
||||
let hadToolExecution = false;
|
||||
let ipcPollTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const cleanup = () => {
|
||||
@@ -441,7 +438,6 @@ async function executeTurn(
|
||||
result: agentText || '',
|
||||
error: 'Turn timed out after 5 minutes',
|
||||
turnId,
|
||||
hadToolExecution,
|
||||
});
|
||||
}
|
||||
}, 5 * 60 * 1000);
|
||||
@@ -504,9 +500,9 @@ async function executeTurn(
|
||||
|
||||
if (status === 'failed') {
|
||||
const errMsg = (error?.message as string) || 'Turn failed';
|
||||
resolve({ result: agentText || '', error: errMsg, turnId, hadToolExecution });
|
||||
resolve({ result: agentText || '', error: errMsg, turnId });
|
||||
} else {
|
||||
resolve({ result: agentText, turnId, hadToolExecution });
|
||||
resolve({ result: agentText, turnId });
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -526,7 +522,6 @@ async function executeTurn(
|
||||
if (msg.method === 'item/commandExecution/requestApproval' ||
|
||||
msg.method === 'item/fileChange/requestApproval' ||
|
||||
msg.method === 'item/permissions/requestApproval') {
|
||||
hadToolExecution = true;
|
||||
server.respond(msg.id, { decision: 'accept' });
|
||||
return;
|
||||
}
|
||||
@@ -550,7 +545,6 @@ async function executeTurn(
|
||||
result: '',
|
||||
error: `Failed to start turn: ${err.message}`,
|
||||
turnId: '',
|
||||
hadToolExecution: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -601,7 +595,6 @@ async function main(): Promise<void> {
|
||||
: await server.startThread();
|
||||
|
||||
let turnCount = 0;
|
||||
let autoContinueCount = 0;
|
||||
|
||||
// Main turn loop
|
||||
while (true) {
|
||||
@@ -616,13 +609,12 @@ async function main(): Promise<void> {
|
||||
break;
|
||||
}
|
||||
|
||||
log(`Starting turn ${turnCount}/${MAX_TURNS} (auto-continue: ${autoContinueCount}/${MAX_AUTO_CONTINUES})...`);
|
||||
log(`Starting turn ${turnCount}/${MAX_TURNS}...`);
|
||||
|
||||
const { result, error, hadToolExecution } = await executeTurn(server, threadId, prompt);
|
||||
const { result, error } = await executeTurn(server, threadId, prompt);
|
||||
|
||||
// Check close sentinel
|
||||
if (shouldClose()) {
|
||||
// Flush any pending output before exiting
|
||||
if (result) {
|
||||
writeOutput({ status: 'success', result, newSessionId: threadId });
|
||||
}
|
||||
@@ -630,23 +622,6 @@ async function main(): Promise<void> {
|
||||
break;
|
||||
}
|
||||
|
||||
// Auto-continue: if the turn produced only text (no tool execution),
|
||||
// nudge Codex to actually execute instead of just describing plans.
|
||||
// This mimics `codex exec --full-auto` behavior.
|
||||
if (!error && !hadToolExecution && result && autoContinueCount < MAX_AUTO_CONTINUES) {
|
||||
autoContinueCount++;
|
||||
log(`Turn had no tool execution, auto-continuing (${autoContinueCount}/${MAX_AUTO_CONTINUES})`);
|
||||
// Still emit the intermediate text so user sees progress
|
||||
writeOutput({ status: 'success', result, newSessionId: threadId });
|
||||
prompt = AUTO_CONTINUE_PROMPT;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reset auto-continue counter when tools were actually executed
|
||||
if (hadToolExecution) {
|
||||
autoContinueCount = 0;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
log(`Turn error: ${error}`);
|
||||
writeOutput({
|
||||
@@ -673,7 +648,6 @@ async function main(): Promise<void> {
|
||||
|
||||
log(`Got new message (${nextMessage.length} chars)`);
|
||||
prompt = nextMessage;
|
||||
autoContinueCount = 0; // Reset on new user message
|
||||
}
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
|
||||
Reference in New Issue
Block a user