Overview

Most “AI coding agents” stop at code generation. This one starts at the ticket and stops at the PR.

The platform is a five-service system that owns the round-trip: a story is drafted with you, decomposed into a plan, executed against the right repo, reviewed by a separate quality agent, validated against tests and coverage, and finally landed as a PR — with the human approving the plan and stepping in only on escalations.

The Five Services

ServiceRoleRuntime
Story AgentConversational ticket authoring, story generation, ideationFastAPI :8001
Dev AgentHeadless poller + planner + executorPoller process + Dashboard :8011
Critic AgentNon-interventionist scoring gate before PR creationFastAPI :8020
Context RetrieverHybrid RAG over wiki, tickets, docs, and codeFastAPI :8000
FrontendReact SPA — chat, dashboards, execution monitor, searchStatic, served by story agent

Each service is independently deployable. They communicate over HTTP — no message queue, no shared database.

Why No Message Queue

A queue would have made the architecture diagram neater. It would also have introduced new infra to operate, a new failure mode (lag, dead letters, redelivery), and a divergence point between ticket state and queue state.

The ticket system already supports the things a queue would do here: ordered work, attachments, transitions, audit trail. The poller polls. The critic is called synchronously. The state lives in one place, visible to humans.

Workflow

READY FOR PLANNING
  → validate (lane label, dev prompt, AC)
IN PLANNING
  → plan workflow (static analysis + LLM) → attach plan
PLAN APPROVED                                  ← human approval
  → development gate (download artifacts, derive branch)
IN DEVELOPMENT
  → execute LLM in repo workspace → verify file changes
[Critic Gate]                                  ← scored rubric, up to 3 retries
IN TESTING
  → build / test / coverage / static analysis
CODE REVIEW
  → PR workflow → GitHub PR + check polling

Ticket selection is strictly priority-ordered: CODE REVIEW > PLAN APPROVED > IN DEVELOPMENT > IN TESTING > IN PLANNING > READY FOR PLANNING. The closer a ticket is to merging, the higher its priority. A ticket already in flight is never starved by a fresh one.

Lanes

Every ticket carries one of three labels: api, db, or ui. The label is the routing key for everything downstream:

  • which repo to check out
  • which validation script to run
  • which branch naming convention to use
  • which test runner (pytest vs vitest)

This kept one platform serving three very different stacks without per-language branching scattered through the code. Adding a new lane was a config block, not a code change.

Human-in-the-Loop, But Cheap

The platform is autonomous, not unsupervised. Three explicit gates require a human:

  1. Plan approval — the implementation plan must be approved before code executes
  2. Critic escalation — after repeated failed reviews, the ticket waits for human input
  3. PR review — created PRs still go through normal review

Between those gates, the agents run without nudging. The bet was that cheap humans-in-the-loop (a one-click approval) beat expensive ones (writing the plan yourself).

What Worked

  • Treating the ticket system as the state store removed an entire class of distributed-state bugs.
  • Critic-as-separate-service kept evaluation honest — the agent that wrote the code never grades its own work.
  • Lane labels were the single most useful abstraction in the codebase.
  • Idempotent stage workflows meant a poller crash never lost state — the next cycle picked up from the ticket alone.

What Was Hard

  • LLM CLI subprocess management. Timeouts, partial output, prompt-boundary stripping, cost logging — things that don’t matter at small scale and matter enormously at production scale.
  • Diff scoping on shared epic branches. Naively reviewing “the working tree” surfaces other tickets’ commits. The fix took three iterations.
  • Observability for an LLM-driven system. Every LLM call now writes structured logs with cost, tokens, and a reasoning trace — otherwise the system is unfixable when it misbehaves.

Takeaway

The interesting work wasn’t the LLM calls. It was the boring scaffolding around them: state machines, idempotent retries, scoped diffs, lane routing, observability — and the patience to make the human’s role one click instead of one hour.