Build a Coding Agent
Create an engineering assistant that helps developers understand, write, and improve code.
Overview
You'll build a coding agent that:
- Explains how code works
- Generates code from natural language descriptions
- Refactors code for readability and performance
- Identifies and fixes bugs
- Stores code snippets in memory for future reference
Expected outcome: A deployed coding assistant accessible via API.
Architecture
Developer
↓
Coding Agent
↓
Memory (code snippets, patterns, explanations)
↓
Workflow (command → agent → memory → response)
↓
Observability
Prerequisites
- Complete Build an AI Assistant first
- API key set as
NOVIUM_API_KEY
Step 1: Create Coding Agent
novium agent create coding-agent
Step 2: Implement Logic
Open agents/coding-agent/agent.ts:
type Action = "explain" | "generate" | "refactor" | "debug";
interface CodeRequest {
action: Action;
userId: string;
code: string;
language: string;
instruction?: string;
}
export default async function agent(input: CodeRequest) {
switch (input.action) {
case "explain":
return explain(input);
case "generate":
return generate(input);
case "refactor":
return refactor(input);
case "debug":
return debug(input);
default:
return { error: `Unknown action: ${(input as any).action}` };
}
}
async function explain(input: CodeRequest) {
const lines = input.code.split("\n").length;
const explanation = generateExplanation(input.code, input.language);
// Store in memory for future reference
await memory.save({
key: `code:${input.userId}:${Date.now()}`,
value: { action: "explain", code: input.code, explanation, language: input.language },
});
return {
action: "explain",
language: input.language,
linesOfCode: lines,
explanation,
summary: `This is a ${input.language} snippet with ${lines} lines.`,
suggestions: ["Review variable naming", "Consider adding type annotations"],
};
}
async function generate(input: CodeRequest) {
const template = input.language === "typescript"
? `function solution(): string {\n // ${input.instruction ?? "Implement logic"}\n return "result";\n}`
: `def solution():\n # ${input.instruction ?? "Implement logic"}\n return "result"`;
const generated = generateCode(input.instruction ?? "", input.language);
await memory.save({
key: `code:${input.userId}:${Date.now()}`,
value: { action: "generate", code: generated, language: input.language },
});
return {
action: "generate",
language: input.language,
code: generated,
tests: generateTests(generated, input.language),
explanation: `Generated a ${input.language} function based on your description.`,
};
}
async function refactor(input: CodeRequest) {
const improvements: string[] = [];
let refactored = input.code;
if (input.code.includes("var ")) {
refactored = refactored.replace(/var /g, "const ");
improvements.push("Replaced 'var' with 'const'");
}
if (input.code.includes("function(") && !input.code.includes("=>")) {
improvements.push("Consider using arrow functions for conciseness");
}
const beforeLines = input.code.split("\n").length;
const afterLines = refactored.split("\n").length;
await memory.save({
key: `code:${input.userId}:${Date.now()}`,
value: { action: "refactor", before: input.code, after: refactored, improvements },
});
return {
action: "refactor",
language: input.language,
originalLines: beforeLines,
refactoredLines: afterLines,
code: refactored,
improvements,
summary: `Applied ${improvements.length} improvements.`,
};
}
async function debug(input: CodeRequest) {
const issues: Array<{ line: number; severity: string; message: string; fix: string }> = [];
let lineNum = 0;
for (const line of input.code.split("\n")) {
lineNum++;
if (line.includes("==") && !line.includes("===")) {
issues.push({ line: lineNum, severity: "warning", message: "Use === instead of ==", fix: line.replace("==", "===") });
}
if (line.includes("console.log")) {
issues.push({ line: lineNum, severity: "info", message: "Remove debug logging before commit", fix: "" });
}
if (line.includes(": any")) {
issues.push({ line: lineNum, severity: "warning", message: "Avoid 'any' type", fix: line.replace(": any", ": unknown") });
}
}
return {
action: "debug",
language: input.language,
issueCount: issues.length,
issues,
summary: issues.length > 0
? `Found ${issues.length} issue(s).`
: "No issues detected.",
grade: issues.length === 0 ? "A" : issues.length <= 2 ? "B" : "C",
};
}
function generateExplanation(code: string, language: string): string {
const lines = code.split("\n").length;
const hasFunction = code.includes("function") || code.includes("def ");
return `This ${language} code consists of ${lines} lines. ` +
(hasFunction ? "It defines a function. " : "") +
`The code appears to process data and return a result.`;
}
function generateCode(instruction: string, language: string): string {
const words = instruction.toLowerCase().split(" ");
const includesReturn = instruction.includes("return");
const includesArray = instruction.includes("array") || instruction.includes("list");
const includesAsync = instruction.includes("async") || instruction.includes("promise");
if (language === "typescript" || language === "javascript") {
return [
`${includesAsync ? "async " : ""}function process${words.length > 2 ? capitalize(words[0]) : "Data"}(${includesArray ? "items: string[]" : "input: string"}): ${includesReturn ? "string" : "void"} {`,
` ${includesAsync ? "const result = await " : ""}${includesArray ? "items.map(i => i.toUpperCase())" : `\`Processed: \${input}\``};`,
` ${includesReturn ? "return result;" : "console.log(result);"}`,
`}`,
].join("\n");
}
return [
`def process_${includesArray ? "list" : "data"}(data):`,
` # ${instruction}`,
` return f"Result: {data}"`,
].join("\n");
}
function generateTests(code: string, language: string): string[] {
return language === "typescript"
? [
`import { describe, it, expect } from "vitest";`,
``,
`describe("Generated function", () => {`,
` it("should handle basic input", () => {`,
` expect(processData("test")).toBeDefined();`,
` });`,
` it("should handle empty input", () => {`,
` expect(processData("")).toBeDefined();`,
` });`,
`});`,
]
: [
`def test_basic():`,
` assert process_data("test") is not None`,
``,
`def test_empty():`,
` assert process_data("") is not None`,
];
}
function capitalize(s: string): string {
return s.charAt(0).toUpperCase() + s.slice(1);
}
| Action | Description |
| ------ | ----------- |
| explain | Counts lines, detects functions, returns an overview explanation |
| generate | Creates function boilerplate from description |
| refactor | Replaces var with const, suggests arrow functions |
| debug | Checks for == vs ===, console.log, : any |
Step 3: Add Memory
The agent already stores all code operations. Let's add a retrieval endpoint.
Add to agent.ts:
async function history(userId: string) {
const keys = (await memory.get(`user:${userId}:code_keys`)) ?? [];
const snippets: any[] = [];
for (const key of keys.slice(-10)) {
const snippet = await memory.get(key);
if (snippet) {
snippets.push({ key, action: snippet.action, language: snippet.language });
}
}
return {
action: "history",
snippetCount: snippets.length,
recentSnippets: snippets.reverse(),
};
}
Register the action in the switch:
case "history":
return history(input.userId);
Step 4: Add Workflow
novium workflow create code-pipeline
Open workflows/code-pipeline/workflow.ts:
export default {
trigger: { type: "http", method: "POST", path: "/code" },
steps: [
{
id: "process-code",
agent: "coding-agent",
input: {
action: "$input.action",
userId: "$input.userId",
code: "$input.code",
language: "$input.language",
instruction: "$input.instruction",
},
},
{
id: "track-usage",
action: "log",
message: "Code action: $input.action in $input.language by $input.userId",
},
],
};
Step 5: Run Locally
novium agent dev
Explain code:
curl -X POST http://localhost:3000 \
-H "Content-Type: application/json" \
-d '{
"action": "explain",
"userId": "dev-1",
"code": "function add(a: number, b: number): number { return a + b; }",
"language": "typescript"
}'
{
"action": "explain",
"language": "typescript",
"linesOfCode": 1,
"explanation": "This typescript code consists of 1 lines. It defines a function.",
"suggestions": ["Review variable naming", "Consider adding type annotations"]
}
Debug code:
curl -X POST http://localhost:3000 \
-H "Content-Type: application/json" \
-d '{
"action": "debug",
"userId": "dev-1",
"code": "function test(x: any) {\n if (x == null) {\n console.log(x)\n return x\n }\n return x\n}",
"language": "typescript"
}'
{
"action": "debug",
"language": "typescript",
"issueCount": 3,
"issues": [
{ "line": 1, "severity": "warning", "message": "Avoid 'any' type" },
{ "line": 2, "severity": "warning", "message": "Use === instead of ==" },
{ "line": 3, "severity": "info", "message": "Remove debug logging before commit" }
],
"grade": "C"
}
Step 6: Deploy
novium deploy
✓ Deployed
Agent "coding-agent":
Endpoint: https://ai-assistant.novium.cloud/coding-agent
Workflow "code-pipeline":
Endpoint: https://ai-assistant.novium.cloud/code
Final Result
A coding agent that explains, generates, refactors, and debugs code, with memory persistence and workflow orchestration.
What You Learned
- ✅ Multi-action agent (explain, generate, refactor, debug)
- ✅ Static code analysis patterns
- ✅ Code generation from natural language
- ✅ Memory-backed snippet history
- ✅ Workflows for code pipeline automation
- ✅ Test generation