All tutorials

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


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

Next Tutorial

Build a Research Agent →