Auditing a 12-Hour AI Go Code-Generation Marathon
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 measurement | Result |
|---|---|
| Audited feature range | P82 → P113 |
| Feature commits | 32 |
| Commit timestamps | 07:18:32 → 10:48:28 CEST |
| Literal tool constructors | 116 → 185 (+69) |
| Server constructor parameters at P113 | 97 |
| Recorded repair inside the range | One 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:
- Pre-write the spec — Types, methods, tests, integration instructions. Save as
.pXX-spec.md. - Fire to Qwen — Use an isolated ACP session for each bounded task.
- Use the wait time — Pre-write the next spec, review the previous task's code, or fix defects caught by parallel verification.
- Validate —
go build ./cmd/server/...(not justgo test ./...— we learned that the hard way). - Checkpoint — Commit the bounded change and record the next task outside conversational context.
- 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:
| Parameter | Value |
|---|---|
| Model | Qwen 3 (via OpenClaw ACP, Alibaba Cloud API) |
| Interface | OpenClaw ACP runtime (isolated sessions per task) |
| Language | Go 1.26.3 |
| Framework | mark3labs/mcp-go v0.54.1 |
| Database | SQLite via modernc.org/sqlite (pure Go, no CGo) |
| Context per task | Fresh isolated session (no conversation history) |
| Audited task count | 32 isolated feature tasks (P82–P113) |
| Temperature | Default (provider-controlled, not overridden) |
| Spec template | Pre-written .pXX-spec.md files |
| Validation | go 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.
| Agent | What it checks | Avg time |
|---|---|---|
| Code Quality | Error handling, Go idioms, concurrency, testing depth | 3.5 min |
| Issue Relevance | Idea quality, duplication, priority ranking | 3.3 min |
| Architecture | Structural health, coupling, constructor bloat, risks | 3.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 name | File output | Task # |
|---|---|---|
trendsStore | rendsStore | P104 |
toolStatsEng | oolStatsEng | P106 |
tcoEng | coEng | P107 |
toolStatsStore | oolStatsStore | P106 |
trainingEng | rainingEng | P108 |
translationStore | ranslationStore | P108 |
toolBillingStore | oolBillingStore | P109 |
triggerStore | riggerStore | P110 |
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# | Package | Tools | Qwen runtime | Retries |
|---|---|---|---|---|
| P82 | training | 1 | 6m36s | 0 |
| P83 | industryreports | 3 | 5m55s | 0 |
| P84 | aiperformance | 1 | 3m53s | 0 |
| P85 | prompts | 4 | 5m46s | 0 |
| P86 | modelabtesting | 1 | 5m28s | 0 |
| P87 | vendorknowledge | 3 | 5m28s | 0 |
| P88 | summarizer | 1 | — | 0 |
| P89 | sentiment | 1 | — | 0 |
| P90 | translation | 3 | 12m56s | 0 |
| P91 | compliance | 1 | 4m23s | 0 |
| P92 | contractclauses | 4 | 8m51s | 0 |
| P93 | esignature | 3 | 4m17s | 0 |
| P94 | datresidency | 3 | 4m23s | 0 |
| P95 | dashboard | 4 | 4m56s | 0 |
| P96 | chartexport | 2 | 5m43s | 0 |
| P97 | monitordash | 1 | 7m27s | 0 |
| P98 | webhooklog | 4 | 4m29s | 0 |
| P99 | toolbilling | 4 | — | 0 |
| P100 | sandbox | 4 | 4m35s | 0 |
| P101 | scheduler | 4 | 4m23s | 0 |
| P102 | autotrigger | 3 | 4m23s | 0 |
| P103 | workflow | 4 | 12m56s | 0 |
| P104 | batchcsv | 1 | 8m51s | 0 |
| P105 | strategymarket | 4 | 4m23s | 0 |
| P106 | vendorreviews | 3 | 4m29s | 0 |
| P107 | communitybench | 3 | 7m27s | 0 |
| P108 | pushnotif | 3 | 9m14s | 0 |
| P109 | qrshare | 1 | 4m29s | 0 |
| P110 | ratelimitmgr | 3 | 4m29s | 0 |
| P111 | auditreports | 3 | 4m29s | 0 |
| P112 | serverconfig | 4 | 4m29s | 0 |
| P113 | backupmgr | 4 | 9m14s | 0 |
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
| Metric | Value |
|---|---|
| Full session duration | 12 hours (operator-confirmed) |
| Audited commit window | 3h 30m (reproducible Git timestamps) |
| P# tasks completed | 32 (P82 → P113) |
| Tool-constructor delta | +69 (mcp.NewTool( source count) |
| Tool constructors at P113 | 185 |
| Feature commits | 32 (P82–P113) |
| Runtime per task | Not reproducibly recorded |
| Repairs | At least one explicit integration repair commit |
| Architecture debt items | 3 (constructor, tools.go, main.go) |
| Junk issues closed | 10 (P241-P250, duplicates) |
The release gate this run needed
- Build the exact production command, not only library packages.
- Start the MCP server in an isolated process and enumerate its tools.
- Compare runtime enumeration with the reviewed manifest.
- Invoke at least one happy path and one invalid-input path per new tool.
- Run uncached unit, integration, race, and static-analysis checks.
- Fail when a package exists but has no registration, handler, or contract test.
- Measure constructor fan-in and block further growth beyond the agreed limit.
- 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.
- Design a verifiable ACP handoff
- Select gates by change risk
- Compile workflow specifications into controlled execution
Scaling AI-assisted delivery without scaling integration debt?
Review the Delivery Gate