The Experiment

A few weeks ago I asked myself a question: how far can an AI coding agent actually go on a non-trivial software project?

Not "write me a function" or "explain this code" — I wanted to test full vertical ownership. Design → code → tests → build system → publishing pipeline → documentation. Everything a platform engineer would own on a real SDK project.

So I wrote a detailed technical design document for an Android analytics library, handed it to Claude Code — Anthropic's agentic coding tool — and stepped back.

This is that story. Part 2 will cover something equally interesting: using only the generated documentation (and the agent) to integrate the library into a separate Android app, without ever looking at the library source code.


The Brief

The library is called EventTracker — an offline-first, pluggable analytics SDK for Android. The design doc covered:

  • A five-layer architecture (public API → core → persistence → adapters → scheduling)
  • Room database schema with two tables: a live events queue and a dead-letter queue
  • Batched HTTP delivery with gzip compression and full HTTP status handling
  • Truncated exponential backoff with full jitter for retries
  • WorkManager-based periodic flush scheduling
  • A pluggable EventAdapter interface for third-party destinations (Firebase, custom backend)
  • Session management, opt-out / GDPR controls, and per-event sampling
  • A complete NFR list: no main-thread I/O, no wake-locks, APK size < 250 KB, crash safety

The document was 33 pages, ~1,810 lines. I fed it to Claude Code with a single instruction: "Build this."

The design document itself was generated with Claude — so the entire chain from spec to implementation ran through the same model. You can read the full spec here.


What Got Built

In 12 minutes of wall-clock time, the agent produced a complete, compilable Android library:

34 files · 2,010 lines of Kotlin · 47 tool calls · ~12 minutes

The output spanned all five layers from the design:

  • Public APIEventTracker singleton with track, identify, reset, flush, setOptOut, wipeLocalData, and a full dead-letter queue API
  • CoreEventDispatcher running on a single-threaded coroutine scope, SamplingFilter, SessionManager, OptOutGuard
  • Persistence — Room database with WAL mode, two tables, 8 DAO methods on the events queue and 6 on the DLQ
  • AdaptersBackendBatchAdapter (HTTP batch delivery, 426 lines), FirebaseAdapter, LoggingAdapter (auto-disabled in release builds)
  • SchedulingFlushScheduler via WorkManager, connectivity callbacks, lifecycle observer

The most complex single file — BackendBatchAdapter.kt at 426 lines — was written in one shot. It includes gzip compression, 200/207/400/401/403/429/5xx HTTP status handling, the retry policy, and atomic Room transactions for moving events to the DLQ. No back-and-forth, no incremental patches.

The library is published on GitHub: singhangadin/EventTracker


The Process

The agent followed a bottom-up file ordering strategy without being asked to — data classes and utilities first, then DAOs, then the database, then adapters, then the dispatcher, and finally the public API. This meant every file it wrote only referenced things that already existed, avoiding forward references entirely.

Multiple files were written in parallel within a single turn. Independent files — say, RetryPolicy.kt and SamplingFilter.kt — were batched into one step, cutting wall-clock time roughly in half compared to sequential generation.

Design decisions the agent made on its own

A few things in the output weren't in the spec at all:

1. The configure() pattern for adapters. The spec puts batchSize and maxRetries on EventTrackerConfig, but BackendBatchAdapter's constructor only takes an endpoint URL. The agent added an internal configure(batchSize, maxRetries) method called by EventTracker.initialize() so both stay in sync without leaking config into the constructor.

2. WorkManager 15-minute floor. The spec says "schedule a periodic flush every batchIntervalMs / 2". WorkManager silently ignores intervals shorter than 15 minutes. The agent added maxOf(batchIntervalMs / 2, 15 * 60 * 1000L) — a constraint the spec didn't mention.

3. SENDING → QUEUED recovery. If the process is killed mid-send, rows remain stuck in a SENDING state forever. The agent added a resetSendingToQueued() call during initialize() to recover them on the next launch.

4. Bottom-up build order. No instruction to write files in dependency order — it just did it.


The Rough Edges

It wasn't all smooth. After the initial generation, there was a back-and-forth debugging phase that's worth being honest about.

Lint failures. Android's lint flagged 10 forEach calls across three files as requiring API 24, even though they were Kotlin extension functions and perfectly valid on minSdk 23. The root cause is that the lint checker conflates Kotlin's forEach extension with java.lang.Iterable#forEach (a Java 8 default method). The fix was mechanical — replace every forEach with a for loop — but it took a few CI cycles to catch all of them across both modules.

Dokka parsing bug. The module.md file I asked the agent to write for API documentation had sub-headers (##) that caused a cryptic "Wrong AST Tree" error in Dokka 1.9.20. The offset in the error message didn't match a byte offset — it turned out Dokka counts relative to the end of the module header line. The fix: prose-only content in includes files, no nested headers.

ASM9 compatibility. Kotlin 2.x compiles sealed classes with a PermittedSubclasses bytecode attribute that requires ASM9. The version of ASM bundled inside AGP's javaDocReleaseGeneration task predates this. The fix was to drop withJavadocJar() from the publishing config and rely on the separate Dokka task for docs.

None of these were logic bugs in the generated library itself — all the issues were in tooling and CI configuration. The agent fixed every one of them when shown the error output.


Publishing

Once the library compiled cleanly, the agent set up the full publishing pipeline:

  • JitPack via jitpack.yml — any git tag triggers a build and publishes both modules to Maven
  • GitHub Actions CI — lint + unit tests on every push and PR
  • GitHub Actions release workflow — on a v* tag: build, run dokkaHtml, deploy versioned docs to GitHub Pages via JamesIves/github-pages-deploy-action

The library is available on JitPack right now:

// settings.gradle.kts
repositories { maven("https://jitpack.io") }

// build.gradle.kts
implementation("com.github.singhangadin.EventTracker:eventtracker:1.0.0")
implementation("com.github.singhangadin.EventTracker:eventtracker-adapter-firebase:1.0.0")

API documentation is live at singhangadin.github.io/EventTracker/docs/latest/.


What Surprised Me

A few things stood out from the session:

Spec fidelity was remarkably high. The retry formula (random(0, min(maxDelayMs, baseDelayMs × 2ⁿ))), the HTTP status code mapping, the exact wire format fields — every detail from the 33-page spec landed exactly as written, without me having to repeat or re-specify anything.

The whole spec stayed in context. The design doc alone was ~26,000 tokens and was held in context for the entire session. The agent never had to re-read sections or ask clarifying questions about things it had already processed.

The architecture layering was respected. Internal types like EventEntity and EventDao are internal in Kotlin and never appear in the public API surface. The five-layer separation from the design was maintained without being asked.


What Still Needs a Human

To be fair, a few things are not finished:

  • encryptAtRest = true in the config correctly wires the flag but doesn't yet open the database with SQLCipher — the spec marked it as optional and the integration wasn't completed
  • The MoEngage adapter was removed in favour of the separate Firebase module; anyone wanting a MoEngage destination would need to add it
  • The library has unit tests and Room DAO tests, but integration tests against a real backend endpoint don't exist yet

These are honest gaps — not failures in generation, but things that need product decisions before they can be finished.


Up Next

So that's Part 1: design document → working, published, documented library, almost entirely agent-driven.

Part 2 is the more interesting question: can the agent integrate this library into a real Android app using only the published documentation — without ever reading the library source code?

The app will call EventTracker.initialize(), track real user events, send them to a backend, and display diagnostics — all driven by the agent reading singhangadin.github.io/EventTracker/docs/latest/ the same way a developer new to the library would.

Stay tuned.