12 min

Auditing a 12-Hour AI Go Code-Generation Marathon

AI Agents Go Architecture MCP Automation Code Generation Engineering

Evidence note after auditing the repository: the full operator-observed marathon ran for 12 hours. Within it, the independently reproducible P82–P113 slice contains 32 feature commits between 07:18 and 10:48 on 31 May. Counting literal mcp.NewTool( constructors at those Git boundaries gives 116 before P82 and 185 after P113: a net increase of 69. The 12-hour duration and the 3h30 audited slice describe different boundaries.

The wider day contained 69 commits and may explain how the working notes arrived at a larger number. But a backlink-worthy engineering case study should publish the count another person can reproduce. This article therefore focuses on the 32-commit window, what the code proves, and what raw throughput concealed.

Repository measurementResult
Audited feature rangeP82 → P113
Feature commits32
Commit timestamps07:18:32 → 10:48:28 CEST
Literal tool constructors116 → 185 (+69)
Server constructor parameters at P11397
Recorded repair inside the rangeOne explicit variable-name fix commit

Why We Ran the Marathon

I'm building an open-source MCP server called a2a-negotiation-mcp — a headless server that negotiates SaaS pricing between AI agents. It's a Go service built on the mark3labs/mcp-go framework with a SQLite backend, 75+ internal packages, and a growing list of MCP tool definitions.

At the start of this session, 116 tools were implemented. The GitHub roadmap had 250 issues. At our existing pace — one hand-crafted tool per day — that's a 4-month backlog.

So I made a bet: what if I serialized the whole thing? One task at a time, back-to-back, no breaks. Pre-write the specs, fire them into an AI coding agent (Qwen via OpenClaw ACP), validate, push, repeat. Like an assembly line.


The Assembly Line

The dispatch loop ended up being dead simple:

  1. Pre-write the spec — Types, methods, tests, integration instructions. Save as .pXX-spec.md.
  2. Fire to Qwen — Use an isolated ACP session for each bounded task.
  3. Use the wait time — Pre-write the next spec, review the previous task's code, or fix defects caught by parallel verification.
  4. Validatego build ./cmd/server/... (not just go test ./... — we learned that the hard way).
  5. Checkpoint — Commit the bounded change and record the next task outside conversational context.
  6. Repeat.

Every spec followed the same structure:

TYPES: exact struct definitions (copy-paste ready)
ENGINE: function signatures, validation rules
TESTS: 3-5 test cases per method
INTEGRATION: 7-step checklist (import → struct → constructor → registration → handlers → main.go → tests)
QUALITY: build + test + self-certification

Operator throughput improved as the template stabilised. That observation was not instrumented, so the Git audit treats it as a retrospective report rather than a measured result.


Experimental Configuration

Before we go further — the specifics of the setup, because reproducibility matters:

ParameterValue
ModelQwen 3 (via OpenClaw ACP, Alibaba Cloud API)
InterfaceOpenClaw ACP runtime (isolated sessions per task)
LanguageGo 1.26.3
Frameworkmark3labs/mcp-go v0.54.1
DatabaseSQLite via modernc.org/sqlite (pure Go, no CGo)
Context per taskFresh isolated session (no conversation history)
Audited task count32 isolated feature tasks (P82–P113)
TemperatureDefault (provider-controlled, not overridden)
Spec templatePre-written .pXX-spec.md files
Validationgo build ./cmd/server/... + go test -count=1 ./...

Every task ran in a brand-new ACP session — no context carried between tasks. This means the model had no memory of previous tasks. The flywheel effect I'll describe came purely from my spec-writing speed, not from model state.


What Went Right

1. The Spec Template Eliminated Interpretation Errors

By locking down the structure upfront — "here's your types.go, here's your engine.go, here's your test.go — fill in the bodies" — I eliminated the model's biggest failure mode: creative interpretation. When the spec says "type Foo struct { ID int }", Qwen writes type Foo struct { ID int }. No extra fields, no renamed types, no surprises.

This sounds obvious, but it's the opposite of how most people prompt code generation. "Write a data retention policy engine" invites creativity. The template says: here are the exact files, here are the exact types, write the implementation.

2. State Persistence Made the Session Crash-Safe

The working method used a state file and resume script to track the last task, tool count, in-flight work, and commit. Neither artifact is present at the audited P113 commit, so their exact behavior cannot be independently reviewed.

The operator notes report two session crashes and rapid recovery. The durable Git history supports restartability at commit boundaries, but not the reported recovery latency or “zero context lost” claim.

3. Go's Type System Caught Nearly Every Mistake

Qwen makes mistakes — wrong method names, missing imports, type mismatches. But Go's compiler catches nearly all of them at go build time. The errors are clear and actionable: "undefined: Foo", "cannot use X as Y", "too many arguments in call."

Go's "fail fast at compile time" philosophy is an excellent safety net for AI-generated code. You can't "almost compile" in Go. Every error maps directly to a line in the spec.

4. Parallel Verification Agents Used Qwen's Idle Time Productively

While Qwen ran each task, I dispatched three verification passes in parallel: code quality, issue relevance, and architecture. This is the right shape, but a review report is evidence only when its findings become enforced gates.

By the time Qwen finished its fifth task, I had deep analysis of the first four tasks' quality. This async pattern — dispatch while waiting — is the only way to keep pace with a model that finishes in minutes.

AgentWhat it checksAvg time
Code QualityError handling, Go idioms, concurrency, testing depth3.5 min
Issue RelevanceIdea quality, duplication, priority ranking3.3 min
ArchitectureStructural health, coupling, constructor bloat, risks3.2 min

What Went Wrong

1. The Tab-Ate-First-Letter Bug (the most specific failure)

About 15 tasks in, go build exploded with dozens of "undefined variable" errors. The culprit was subtle: Qwen consistently dropped the first character of variable names when writing Go code that started with a tab character.

The model wrote this in its simulated head:

\tcoEng := tco.NewEngine(pricingStore)

The file ended up with:

tcoEng := tco.NewEngine(pricingStore)

Wait, no — it ended up with the leading t missing:

        coEng := tco.NewEngine(pricingStore)    // missing 't'

Seven variable declarations across main.go were silently corrupted. The pattern was so consistent I could predict which variable would be broken by which task:

Intended nameFile outputTask #
trendsStorerendsStoreP104
toolStatsEngoolStatsEngP106
tcoEngcoEngP107
toolStatsStoreoolStatsStoreP106
trainingEngrainingEngP108
translationStoreranslationStoreP108
toolBillingStoreoolBillingStoreP109
triggerStoreriggerStoreP110

Fix: After every Qwen dispatch, grep for all recent variable declarations and verify the first letter is intact. The fix is mechanical and can be automated.

2. The Build Cache Time Bomb

The working notes report a period in which package tests stayed green while the executable wiring was broken. The surviving repository proves the relevant mechanism—a package can compile and test without being registered in the server—but does not independently establish the claimed eight-commit duration.

But cmd/server was silently broken for eight commits. Each commit added one more variable with a missing first letter, and the Go test cache happily reported "ok (cached)" because main.go changes don't invalidate the test cache for internal/server/.

The root issue is broader than caching: package-level success does not prove that the binary builds, boots, enumerates the expected tools, or routes a request through each new handler.

Fix: go build ./cmd/server/... after every Qwen dispatch. No exceptions. And use go test -count=1 ./... in any review loop — never trust cached results when wiring changes.

3. The 97-Positional-Parameter Constructor

The NegotiationServer constructor started at ~60 positional parameters before the marathon. By P113, it hit 97. Each new tool adds one more parameter. The call site in main.go is a 97-argument line that wraps across half the file.

Every new tool requires synchronized edits to:

- The struct definition (add field)

- The constructor signature (add positional param)

- The struct literal (add assignment)

- The constructor call site in main.go (add argument)

- Two test files (add nil)

Five edits for one new feature. The probability of a mistake approaches certainty as the list grows. This is the single biggest architectural debt, and it's compounding.

Fix: split registration into cohesive modules and inject a small dependency container per module. A functional-options API can reduce positional ordering mistakes, but simply moving 97 dependencies behind opts... hides rather than removes coupling. No options.go artifact exists at the audited commit.

4. Qwen Occasionally Skips the Wiring

The working notes estimate that integration was skipped in roughly one in five tasks. That rate is not reconstructable from Git alone. The failure class is real, however: an unused Go package can compile while adding zero runtime capability.

This happened because Qwen's output window was consumed by the package code, leaving no room for the integration steps. The pass/fail signal (go build ./...) passed because the package compiles even without integration.

Fix: A mandatory post-dispatch checklist: grep for the package name in all four integration files (tools.go, main.go, handler_test.go, bench_test.go).


Contemporaneous Task Log

The following table is retained as a contemporaneous operator log. Package names and commit order match the repository. Per-task runtimes and retry counts were not recorded in Git or a durable benchmark artifact, so they should not be treated as independently reproducible measurements.

Show the original 32-task working log
P#PackageToolsQwen runtimeRetries
P82training16m36s0
P83industryreports35m55s0
P84aiperformance13m53s0
P85prompts45m46s0
P86modelabtesting15m28s0
P87vendorknowledge35m28s0
P88summarizer10
P89sentiment10
P90translation312m56s0
P91compliance14m23s0
P92contractclauses48m51s0
P93esignature34m17s0
P94datresidency34m23s0
P95dashboard44m56s0
P96chartexport25m43s0
P97monitordash17m27s0
P98webhooklog44m29s0
P99toolbilling40
P100sandbox44m35s0
P101scheduler44m23s0
P102autotrigger34m23s0
P103workflow412m56s0
P104batchcsv18m51s0
P105strategymarket44m23s0
P106vendorreviews34m29s0
P107communitybench37m27s0
P108pushnotif39m14s0
P109qrshare14m29s0
P110ratelimitmgr34m29s0
P111auditreports34m29s0
P112serverconfig44m29s0
P113backupmgr49m14s0

The defensible elapsed time for the audited Git window is 3 hours 30 minutes between the first and last feature-commit timestamps. Git also contains an explicit repair commit—fix: correct variable name typos in main.go—between P104 and P105. It would therefore be misleading to call the sequence “zero retry” or “first-attempt success.”


Five Lessons

1. AI Code Generation Reaches Escape Velocity at Task 10

The commit cadence accelerated once package patterns repeated, but the audit does not isolate model time from specification, review, repair, and commit overhead. Measure those phases separately in the next run.

Caveat: This is as much about operator learning as model learning. I got faster at writing specs. The model saw no previous tasks (fresh session each time). The flywheel is real but it's partly a human-skill flywheel, not an AI-skill flywheel.

2. Compile-Time Safety Is an AI Force Multiplier

Every Qwen mistake — wrong type, missing import, bad function signature — was caught at compile time with a clear, actionable error message. Go's "make it compile or don't ship" philosophy is a perfect fit for AI-first development.

Caveat: This claim is from one project in one language. It's a hypothesis, not a universal finding. Rust's type system would catch more errors; TypeScript's would catch fewer; Python's would catch none at compile time.

3. Don't Trust the Test Cache

Fresh tests are useful, but -count=1 is not a substitute for building and exercising the executable. CI should run both.

4. Pre-Written Specs Beat Real-Time Prompting

Pre-written specs reduced ambiguity, but the repository contains an integration repair inside the audited window. Treat the template as an input control, not proof of correctness. The release gate must still test runtime registration and behavior.

5. You Need a Crash Plan

The commit sequence made the work restartable even when an agent session ended. For the next run, persist and test the checkpoint artifact in the repository so recovery time and state loss become measurable rather than anecdotal.


How We Restructured to Handle the Load

PATTERNS.md — The Qwen Cookbook

A PATTERNS.md file in the project root contains five complete code patterns: in-memory engine, SQLite store, tool registration + handler, struct field + constructor param, and table-driven tests. Each pattern includes complete code examples from the codebase. Qwen reads this before every task.

Checkpoint state — make it an auditable artifact

Record the implemented task, expected runtime tool count, last verified commit, in-flight task, and gate results. Commit a schema and test the resume path. A recovery mechanism that exists only in operator notes cannot support a reliability claim.

The Quality Triad

Three verification agents run asynchronously during each Qwen task:

- Code quality: scores each package on error handling, Go idioms, concurrency, testing depth

- Issue relevance: classifies each P# issue as HIGH/MEDIUM/LOW/DUPLICATE

- Architecture: assesses structural health and identifies accumulating debt

These can run in parallel with implementation, provided one authoritative gate reconciles their findings before merge.


Constraints & Limitations

This experiment is n=1. Every finding here is a hypothesis for others to test, not a definitive claim:

- One model tested — Qwen 3 via OpenClaw ACP. Results likely differ with Claude Code, GitHub Copilot, GPT-4 Code, or CodeGemma.

- One language — Go's compile-time guarantees were critical. These patterns may not transfer to Python, JavaScript, or Ruby.

- One project — a headless MCP server with a uniform package structure. A web app with mixed UI/business logic would behave differently.

- One developer — operator skill level affects spec quality, error detection, and overall throughput.

- Short audited window — the 32-commit slice spans 3 hours 30 minutes. The 97-parameter constructor already shows the throughput/debt tension.


The Numbers

MetricValue
Full session duration12 hours (operator-confirmed)
Audited commit window3h 30m (reproducible Git timestamps)
P# tasks completed32 (P82 → P113)
Tool-constructor delta+69 (mcp.NewTool( source count)
Tool constructors at P113185
Feature commits32 (P82–P113)
Runtime per taskNot reproducibly recorded
RepairsAt least one explicit integration repair commit
Architecture debt items3 (constructor, tools.go, main.go)
Junk issues closed10 (P241-P250, duplicates)

The release gate this run needed

  1. Build the exact production command, not only library packages.
  2. Start the MCP server in an isolated process and enumerate its tools.
  3. Compare runtime enumeration with the reviewed manifest.
  4. Invoke at least one happy path and one invalid-input path per new tool.
  5. Run uncached unit, integration, race, and static-analysis checks.
  6. Fail when a package exists but has no registration, handler, or contract test.
  7. Measure constructor fan-in and block further growth beyond the agreed limit.
  8. Retain model, spec, diff, test evidence, repair count, and reviewer decision.

Download the AI code-generation integration gate

The repository supports a useful conclusion, just not the original headline: structured specs can create extraordinary implementation throughput, while executable-level verification and architecture limits determine whether that throughput becomes a product or a pile of disconnected packages.


Scaling AI-assisted delivery without scaling integration debt?

Review the Delivery Gate