DEV Community

Miguel Miranda de Mattos
Miguel Miranda de Mattos

Posted on

From Code to Systems: Engineering Autonomous Full-Stack Applications with Agentic Principles

Agentic Coding - Full Stack

From Backend Generation to Full System Generation

Introduction

In the previous article, we explored agentic coding for backend generation.

This article takes the next step:

Generating a complete, validated, full-stack system (Go + React) using an autonomous agent.

This is not scaffolding.

This is:

  • system generation
  • execution
  • validation
  • self-healing
  • System Architecture
project_agent_coded_go_fullstack/
├── backend/
│   ├── cmd/server/main.go
│   ├── internal/domain/
│   ├── internal/application/
│   ├── internal/infrastructure/
│   ├── tests/
│   └── go.mod
│
├── frontend/
│   ├── src/
│   ├── package.json
│   └── vite.config.js
│
└── README.md
Enter fullscreen mode Exit fullscreen mode

Requirements

  • Python 3.10+
  • Go 1.20+
  • Node.js (18+)
  • Ports 8000 and 5173 available

Running the Agent

python -m venv .venv
source .venv/bin/activate
pip install openai requests
python -m agent.main
Enter fullscreen mode Exit fullscreen mode

Running Generated Backend

cd backend
go mod init backend
go mod tidy
go run ./cmd/server
Running Generated Frontend
cd frontend
npm install
npm run dev
Backend Unit Tests (Go)
cd backend
go test ./...
Enter fullscreen mode Exit fullscreen mode

Runtime Validation

The agent validates full CRUD:

  • POST /notes
  • GET /notes
  • PUT /notes/{id}
  • DELETE /notes/{id}

Agent Loop

generate → run → test → validate → fix

Key Principles

  1. Tests as Source of Truth
    System is correct only when tests pass.

  2. Validation Ensures Behavior
    Tests alone are insufficient — runtime validation ensures usability.

  3. System > Code
    The output is a working system, not snippets.

Appendix A — Agent (Complete)

main.py

Handles orchestration.

import json
import os

from .config import WORKDIR, MAX_ATTEMPTS, MAX_STAGNATION
from .generator import generate_repo
from .executor import run_backend, stop_backend
from .tester import run_tests
from .validator import validate_api
from .installer import install_missing
from .utils import write_files


def main():
    error = None
    previous_error = None
    stagnation = 0

    os.makedirs(WORKDIR, exist_ok=True)

    for attempt in range(MAX_ATTEMPTS):
        print(f"\n--- Attempt {attempt+1} ---")

        # --------------------------------------------------
        # GENERATE
        # --------------------------------------------------
        raw = generate_repo(error)

        try:
            file_map = json.loads(raw)
        except Exception:
            error = "Invalid JSON from generator"
            continue

        # --------------------------------------------------
        # WRITE FILES
        # --------------------------------------------------
        write_files(WORKDIR, file_map)

        # --------------------------------------------------
        # RUN BACKEND
        # --------------------------------------------------
        ok, process, run_error = run_backend(WORKDIR)

        if not ok:
            print("[executor] Failed to run backend")

            if install_missing(run_error, WORKDIR):
                error = run_error
                continue

            error = run_error
            continue

        # --------------------------------------------------
        # RUN TESTS
        # --------------------------------------------------
        test_ok, test_err = run_tests(WORKDIR)

        if not test_ok:
            print("[tester] Tests failed")

            if install_missing(test_err, WORKDIR):
                error = test_err
                stop_backend(process)
                continue

        # --------------------------------------------------
        # VALIDATE API
        # --------------------------------------------------
        valid, val_err = validate_api()

        stop_backend(process)

        if not valid:
            print("[validator] API validation failed")

            if install_missing(val_err, WORKDIR):
                error = val_err
                continue

        # --------------------------------------------------
        # SUCCESS
        # --------------------------------------------------
        if test_ok and valid:
            print("SUCCESS: System validated")
            return

        # --------------------------------------------------
        # ERROR HANDLING
        # --------------------------------------------------
        current_error = test_err or val_err or run_error or "Unknown error"

        print(f"[agent] Error: {current_error}")

        if current_error == previous_error:
            stagnation += 1
        else:
            stagnation = 0

        if stagnation >= MAX_STAGNATION:
            print("Stopping: no further progress")
            return

        previous_error = current_error
        error = current_error

    print("FAILED: Max attempts reached")


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

generator.py

Generates repository via LLM.

def generate_repo(error=None):
    return """
{
  "backend/go.mod": "module backend\\n\\ngo 1.20\\n",

  "backend/cmd/server/main.go": "package main\\n\\nimport (\\n\\t\\"encoding/json\\"\\n\\t\\"fmt\\"\\n\\t\\"net/http\\"\\n\\t\\"strconv\\"\\n\\t\\"strings\\"\\n)\\n\\ntype Note struct {\\n\\tID int `json:\\"id\\"`\\n\\tContent string `json:\\"content\\"`\\n}\\n\\nvar notes = []Note{}\\nvar nextID = 1\\n\\nfunc enableCORS(w http.ResponseWriter) {\\n\\tw.Header().Set(\\"Access-Control-Allow-Origin\\", \\"*\\")\\n\\tw.Header().Set(\\"Access-Control-Allow-Methods\\", \\"GET, POST, PUT, DELETE, OPTIONS\\")\\n\\tw.Header().Set(\\"Access-Control-Allow-Headers\\", \\"Content-Type\\")\\n}\\n\\nfunc notesHandler(w http.ResponseWriter, r *http.Request) {\\n\\tenableCORS(w)\\n\\tif r.Method == http.MethodOptions { return }\\n\\n\\tswitch r.Method {\\n\\tcase http.MethodGet:\\n\\t\\tjson.NewEncoder(w).Encode(notes)\\n\\n\\tcase http.MethodPost:\\n\\t\\tvar n Note\\n\\t\\tjson.NewDecoder(r.Body).Decode(&n)\\n\\t\\tn.ID = nextID\\n\\t\\tnextID++\\n\\t\\tnotes = append(notes, n)\\n\\t\\tjson.NewEncoder(w).Encode(n)\\n\\t}\\n}\\n\\nfunc noteByIDHandler(w http.ResponseWriter, r *http.Request) {\\n\\tenableCORS(w)\\n\\tidStr := strings.TrimPrefix(r.URL.Path, \\"/notes/\\")\\n\\tid, _ := strconv.Atoi(idStr)\\n\\n\\tfor i, n := range notes {\\n\\t\\tif n.ID == id {\\n\\t\\t\\tswitch r.Method {\\n\\t\\t\\tcase http.MethodPut:\\n\\t\\t\\t\\tvar updated Note\\n\\t\\t\\t\\tjson.NewDecoder(r.Body).Decode(&updated)\\n\\t\\t\\t\\tnotes[i].Content = updated.Content\\n\\t\\t\\t\\tjson.NewEncoder(w).Encode(notes[i])\\n\\n\\t\\t\\tcase http.MethodDelete:\\n\\t\\t\\t\\tnotes = append(notes[:i], notes[i+1:]...)\\n\\t\\t\\t\\tw.WriteHeader(http.StatusOK)\\n\\t\\t\\t}\\n\\t\\t\\treturn\\n\\t\\t}\\n\\t}\\n\\tw.WriteHeader(http.StatusNotFound)\\n}\\n\\nfunc main() {\\n\\thttp.HandleFunc(\\"/notes\\", notesHandler)\\n\\thttp.HandleFunc(\\"/notes/\\", noteByIDHandler)\\n\\n\\tfmt.Println(\\"Running on :8000\\")\\n\\thttp.ListenAndServe(\\":8000\\", nil)\\n}\\n",

  "backend/cmd/server/main_test.go": "package main\\n\\nimport (\\n\\t\\"net/http\\"\\n\\t\\"net/http/httptest\\"\\n\\t\\"strings\\"\\n\\t\\"testing\\"\\n)\\n\\nfunc TestCreateNote(t *testing.T) {\\n\\treq := httptest.NewRequest(\\"POST\\", \\"/notes\\", strings.NewReader(`{\\"content\\":\\"test\\"}`))\\n\\trr := httptest.NewRecorder()\\n\\n\\thandler := http.HandlerFunc(notesHandler)\\n\\thandler.ServeHTTP(rr, req)\\n\\n\\tif rr.Code != http.StatusOK {\\n\\t\\tt.Errorf(\\"expected 200 got %d\\", rr.Code)\\n\\t}\\n}\\n",

  "frontend/package.json": "{\\"name\\":\\"frontend\\",\\"private\\":true,\\"version\\":\\"0.0.0\\",\\"type\\":\\"module\\",\\"scripts\\":{\\"dev\\":\\"vite\\"},\\"dependencies\\":{\\"react\\":\\"^18.2.0\\",\\"react-dom\\":\\"^18.2.0\\"},\\"devDependencies\\":{\\"vite\\":\\"^5.0.0\\",\\"@vitejs/plugin-react\\":\\"^4.0.0\\"}}",

  "frontend/vite.config.js": "import { defineConfig } from 'vite';import react from '@vitejs/plugin-react';export default defineConfig({plugins:[react()]});",

  "frontend/index.html": "<!doctype html><html><head><meta charset='UTF-8'><title>Notes</title></head><body><div id='root'></div><script type='module' src='/src/main.jsx'></script></body></html>",

  "frontend/src/main.jsx": "import React from 'react';import ReactDOM from 'react-dom/client';import App from './App.jsx';ReactDOM.createRoot(document.getElementById('root')).render(<React.StrictMode><App /></React.StrictMode>);",

  "frontend/src/App.jsx": "import { useEffect, useState } from 'react';export default function App(){const [notes,setNotes]=useState([]);const [text,setText]=useState('');const fetchNotes=async()=>{try{const r=await fetch('http://localhost:8000/notes');const d=await r.json();setNotes(Array.isArray(d)?d:[]);}catch(e){console.error(e);setNotes([]);}};useEffect(()=>{fetchNotes();},[]);const add=async()=>{try{await fetch('http://localhost:8000/notes',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({content:text})});setText('');fetchNotes();}catch(e){console.error(e);}};const update=async(id)=>{const newText=prompt('Update note');if(!newText)return;try{await fetch(`http://localhost:8000/notes/${id}`,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({content:newText})});fetchNotes();}catch(e){console.error(e);}};const del=async(id)=>{try{await fetch(`http://localhost:8000/notes/${id}`,{method:'DELETE'});fetchNotes();}catch(e){console.error(e);}};return(<div style={{padding:20}}><h1>Notes</h1><input value={text} onChange={e=>setText(e.target.value)}/><button onClick={add}>Add</button><ul>{notes.map(n=>(<li key={n.id}>{n.content} <button onClick={()=>update(n.id)}>Edit</button> <button onClick={()=>del(n.id)}>Delete</button></li>))}</ul></div>);}"
}
"""
Enter fullscreen mode Exit fullscreen mode

executor.py

Runs backend.

import subprocess
import os
import time
import requests


def run_backend(workdir):
    backend_path = os.path.join(workdir, "backend")

    # Ensure Go modules are ready
    subprocess.run(["go", "mod", "tidy"], cwd=backend_path)

    process = subprocess.Popen(
        ["go", "run", "./cmd/server"],
        cwd=backend_path,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True
    )

    # --------------------------------------------------
    # WAIT FOR SERVER TO BE READY
    # --------------------------------------------------
    for _ in range(10):
        try:
            r = requests.get("http://localhost:8000/notes")
            if r.status_code == 200:
                return True, process, ""
        except Exception:
            pass

        time.sleep(0.5)

    # --------------------------------------------------
    # IF NOT READY → REAL ERROR
    # --------------------------------------------------
    out, err = process.communicate()

    print("\nBACKEND ERROR:")
    print(err or out)

    return False, None, err or out


def stop_backend(process):
    if process:
        process.terminate()
        try:
            process.wait(timeout=2)
        except Exception:
            process.kill()
tester.py
Tests

import subprocess
import os


def run_tests(workdir):
    backend_path = os.path.join(workdir, "backend")

    result = subprocess.run(
        ["go", "test", "./..."],
        cwd=backend_path,
        capture_output=True,
        text=True
    )

    if result.returncode == 0:
        return True, ""

    return False, result.stdout + result.stderr
Enter fullscreen mode Exit fullscreen mode

validator.py

Performs HTTP validation.

import requests

BASE = "http://localhost:8000"


def validate_api():
    try:
        r = requests.post(f"{BASE}/notes", json={"content": "hello"})
        note = r.json()
        note_id = note["id"]

        requests.get(f"{BASE}/notes")
        requests.put(f"{BASE}/notes/{note_id}", json={"content": "updated"})
        requests.delete(f"{BASE}/notes/{note_id}")

        return True, ""

    except Exception as e:
        return False, str(e)
Enter fullscreen mode Exit fullscreen mode

installer.py

Handles environment fixes.

import subprocess
import sys
import os
import re


def install_missing(error: str | None, workdir: str) -> bool:
    """
    Attempts to fix environment/dependency issues based on error output.

    Returns:
        True  -> something was installed/fixed
        False -> nothing actionable
    """

    if not error:
        return False

    error_lower = error.lower()

    backend_path = os.path.join(workdir, "backend")
    frontend_path = os.path.join(workdir, "frontend")

    # --------------------------------------------------------
    # GO MODULE ISSUES
    # --------------------------------------------------------
    if "go.mod file not found" in error_lower:
        print("[installer] Initializing go module")

        subprocess.run(
            ["go", "mod", "init", "backend"],
            cwd=backend_path
        )

        subprocess.run(
            ["go", "mod", "tidy"],
            cwd=backend_path
        )

        return True

    if "cannot find module" in error_lower or "no required module provides package" in error_lower:
        print("[installer] Running go mod tidy")

        subprocess.run(
            ["go", "mod", "tidy"],
            cwd=backend_path
        )

        return True

    # --------------------------------------------------------
    # NPM / FRONTEND
    # --------------------------------------------------------
    if "npm" in error_lower or "node_modules" in error_lower:
        if os.path.exists(frontend_path):
            print("[installer] Installing npm dependencies")

            subprocess.run(
                ["npm", "install"],
                cwd=frontend_path
            )
            return True

    # --------------------------------------------------------
    # PYTHON MODULE (requests etc.)
    # --------------------------------------------------------
    if "no module named" in error_lower:
        try:
            pkg = re.findall(r"No module named ['\"](.+?)['\"]", error)[0]
        except:
            return False

        # Avoid installing internal modules
        if pkg.startswith(("agent", "backend", "frontend")):
            print(f"[installer] Skipping internal module: {pkg}")
            return False

        print(f"[installer] Installing python package: {pkg}")

        subprocess.run(
            [sys.executable, "-m", "pip", "install", pkg]
        )

        return True

    return False
Enter fullscreen mode Exit fullscreen mode

utils.py

Writes files.

import os
import shutil


def write_files(base, file_map: dict):
    # CLEAN previous generation
    if os.path.exists(base):
        shutil.rmtree(base)

    os.makedirs(base, exist_ok=True)

    # Write fresh files
    for path, content in file_map.items():
        full_path = os.path.join(base, path)

        os.makedirs(os.path.dirname(full_path), exist_ok=True)

        with open(full_path, "w") as f:
            f.write(content)
Enter fullscreen mode Exit fullscreen mode

config.py

import os

WORKDIR = "project_agentic_coded_full_system_go"

MAX_ATTEMPTS = 5
MAX_STAGNATION = 2

SERVER_START_DELAY = 2
PORT = 8000
Enter fullscreen mode Exit fullscreen mode

.gitignore

What should not get into the Git repository

# Python
__pycache__/
*.pyc
*.pyo
*.pyd

# Virtual env
.venv/
venv/

# Generated projects (ignore agent output)
project_agentic_coded_full_system_go/

# OS
.DS_Store
Enter fullscreen mode Exit fullscreen mode

Appendix B — Backend Generated Example (Go)

main.go (simplified)

http.HandleFunc("/notes", notesHandler)
http.HandleFunc("/notes/", noteByIDHandler)
Enter fullscreen mode Exit fullscreen mode

Supports:

  • GET
  • POST
  • PUT
  • DELETE

Includes:

  • in-memory store
  • JSON encoding
  • CORS headers

Appendix C — Frontend Example (React)
Features:

fetch notes
create note
update note
delete note
Uses fetch API.

Appendix D — Iteration Strategy

Problem

Fixed loops:

  • stop too early
  • waste iterations

Solution

MAX_ATTEMPTS = 10
MAX_STAGNATION = 2
Enter fullscreen mode Exit fullscreen mode

Behavior

  • stop on success
  • stop on stagnation
  • bounded retries

Final Takeaway

This approach moves development from:
code generation → system generation

The agent becomes:

  • builder
  • tester
  • validator
  • debugger

Full Modular System Example Git Repository

In this Git repository you can clone the implentation of this Agent.

GitHub - mmmattos/agentic-coding-full-system-go: Agentic Coding for full system in Go: Backend and…
Agentic Coding for full system in Go: Backend and Frontend - mmmattos/agentic-coding-full-system-go
github.com

Top comments (0)