Why Self-Correction is Essential for Agency§
When human developers encounter a compiler error or an API response error, they do not give up immediately. They read the error traceback, adjust function arguments, fix missing syntax, and re-execute.
Conversely, naive LLM tool-calling implementations crash as soon as a tool invocation throws an exception. To build resilient autonomous agents, developers must construct Self-Correction Loops that treat runtime errors as feedback signals rather than fatal crashes.
---
Architecture of a Self-Correction Execution Engine§
[ Agent Prompt ] ➔ [ LLM Tool Call Generation ]
➔ [ Execute Tool ]
│
┌──────────┴──────────┐
▼ ▼
( Success ) ( Exception )
│ │
▼ ▼
[ Return Result ] [ Format Error Stack Trace ]
│
▼
[ Re-Prompt LLM (Attempt N+1) ]TypeScript Self-Correction Middleware Example§
export async function executeToolWithSelfCorrection(
llmClient: any,
toolMap: Record<string, Function>,
initialMessages: any[],
maxRetries = 3
) {
let messages = [...initialMessages];
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const response = await llmClient.chat.completions.create({
model: "gpt-4o",
messages,
tools: Object.values(toolMap).map(t => t.schema),
});
const choice = response.choices[0].message;
if (!choice.tool_calls || choice.tool_calls.length === 0) {
return choice.content;
}
messages.push(choice);
for (const call of choice.tool_calls) {
const tool = toolMap[call.function.name];
try {
const args = JSON.parse(call.function.arguments);
const result = await tool.execute(args);
messages.push({
role: "tool",
tool_call_id: call.id,
content: JSON.stringify(result),
});
} catch (err: any) {
console.warn(`Tool ${call.function.name} failed: ${err.message}`);
messages.push({
role: "tool",
tool_call_id: call.id,
content: JSON.stringify({
error: true,
message: err.message,
hint: "Review parameter schema and correct argument values.",
}),
});
}
}
}
throw new Error(`Agent failed to self-correct after ${maxRetries} attempts.`);
}