Project: Autonomous Coding Agent with Self-Correction
In this project, we will build an Autonomous Coding Agent. The agent generates a JavaScript function, executes it in a virtual sandbox container, captures any console run errors, and loops back to self-correct and rewrite the code until it runs successfully.
1. Agent Workflow
graph TD
A[Start: User coding task] --> B[Node: GenerateCode]
B --> C[Node: RunCodeSandbox]
C -->|Conditional Edge| D{Did it throw errors?}
D -->|Yes: Feed error log| B
D -->|No: Successful execution| E[__end__]2. Implementing the Self-Correction Agent
Create the full StateGraph setup:
// src/services/debuggerAgent.ts
import { ChatOpenAI } from "@langchain/openai";
import { StateGraph, Annotation } from "@langchain/langgraph";
import { StringOutputParser } from "@langchain/core/output_parsers";
import * as vm from "vm";
// 1. Define Shared Graph State
const CodeStateAnnotation = Annotation.Root({
taskDescription: Annotation<string>(),
generatedCode: Annotation<string>(),
errorLog: Annotation<string>(),
runsSuccessfully: Annotation<boolean>(),
});
// 2. Node: Generate or fix code based on errorLog status
async function generateCodeNode(state: typeof CodeStateAnnotation.State) {
const model = new ChatOpenAI({ modelName: "gpt-4o-mini", temperature: 0.1 });
const parser = new StringOutputParser();
let prompt = `Write a JavaScript function resolving this task: ${state.taskDescription}.
You must output ONLY raw executable JavaScript code inside standard backticks. No markdown.`;
if (state.errorLog) {
prompt += `\n\nYour previous code failed with this error: ${state.errorLog}. Please fix the code and return the corrected version.`;
}
const codeReply = await model.pipe(parser).invoke(prompt);
// Clean potential markdown wrap strings
const cleanedCode = codeReply.replace(/```javascript/g, "").replace(/```/g, "").trim();
return {
generatedCode: cleanedCode,
};
}
// 3. Node: Run code in Node Node.js VM Sandbox
async function runCodeNode(state: typeof CodeStateAnnotation.State) {
console.log("Running generated code in sandbox...");
try {
const sandboxContext = { console };
vm.createContext(sandboxContext);
// Execute code block in sandbox
vm.runInContext(state.generatedCode, sandboxContext, { timeout: 1000 });
return {
runsSuccessfully: true,
errorLog: "",
};
} catch (error: any) {
console.log("Sandbox error caught:", error.message);
return {
runsSuccessfully: false,
errorLog: error.message,
};
}
}
// 4. Compose the StateGraph workflow
const workflow = new StateGraph(CodeStateAnnotation)
.addNode("generate", generateCodeNode)
.addNode("run", runCodeNode)
.setEntryPoint("generate")
.addEdge("generate", "run")
// 5. Add conditional routing edges
.addConditionalEdges("run", (state) => {
if (state.runsSuccessfully) {
return "success";
}
return "fix";
}, {
success: "__end__",
fix: "generate", // Loop back to generate node for debugging rewrite
});
export const debuggerApp = workflow.compile();3. Verifying the Loop
Invoke the debugger agent with a coding task:
const result = await debuggerApp.invoke({
taskDescription: "Create a function returning the sum of numbers, but intentionally divide by zero first.",
});
console.log("Successfully generated code:", result.generatedCode);Published on Last updated: