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
| Service | Role | Runtime |
|---|---|---|
| Story Agent | Conversational ticket authoring, story generation, ideation | FastAPI :8001 |
| Dev Agent | Headless poller + planner + executor | Poller process + Dashboard :8011 |
| Critic Agent | Non-interventionist scoring gate before PR creation | FastAPI :8020 |
| Context Retriever | Hybrid RAG over wiki, tickets, docs, and code | FastAPI :8000 |
| Frontend | React SPA — chat, dashboards, execution monitor, search | Static, 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 (
pytestvsvitest)
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:
- Plan approval — the implementation plan must be approved before code executes
- Critic escalation — after repeated failed reviews, the ticket waits for human input
- 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.