Agents That Finish the Ticket
How Agora built a Go runner that turns coding-agent sessions into durable engineering work.

An AI coding agent can produce a good diff and still fail the ticket.
Real tickets outlive a model turn. They collect comments, wait on decisions, survive restarts, and finish only when the result is ready for review. Once agents work unattended, the difficult part is no longer invoking a model. It is owning everything around that invocation.
At Agora, we wanted agents to take real work from our engineering backlog—from assignment to pull request—under the same review standards as the rest of the team. We built TakeAIt, an internal ticket system where humans and agents share the same queue, and tai-agent, the Go daemon that executes the agent side of that workflow.
The model does the coding. TakeAIt makes the work durable.
The Ticket Is the Unit of Work
TakeAIt treats an agent run as part of a ticket lifecycle, not as an isolated chat session.
Humans and agents share tickets, comments, dependencies, and assignments. Each assignment pairs a user with a verb such as plan, investigate, code, review, or respond. Assignments are ordered, so the system knows what is claimable and what must happen first.
In the control plane, an agent looks like another user. In the execution plane, it is backed by a daemon.
tai-agent acquires a host-wide execution slot, claims an assignment, prepares a workspace, starts the configured coding harness, follows the ticket for new input, opens the pull request, and reports the result.

A finished ticket keeps ordered assignments, run results, and the pull request in one shared record.
From Assignment to Pull Request
For each run, the daemon creates an isolated workspace, clones the required repositories, installs a snapshot of the agent’s settings and skills, and starts an engine session. Execution slots are flock leases shared across the host. If a daemon dies, the kernel releases its slot automatically.
During a turn, the runner streams engine events and watches the ticket. New comments queue for the next turn; pause, stop, abort, and restart directives interrupt the current one. When the turn ends, it inspects the checkout. It can continue, open a pull request, ask a human, or abort with a recorded reason.
Repositories can define a completion gate in .tai/WORKFLOW.md, such as make ci. The runner pins the command before the model starts, so the agent cannot weaken its own gate by editing the file. If the check fails, its output becomes feedback for the next turn. No pull request is opened until it passes.
Built to Fail Safely
An unattended runner will eventually hang, crash, or be stopped during a turn. Those cannot all mean the same thing.
The runner tracks the engine process, emitted events, output, and open tool calls separately. It can distinguish an engine that exited, one that never spoke after startup, a tool call that exceeded its limit, and a live process that stopped making progress.
Engine processes run in their own process group with Pdeathsig set, because exec.CommandContext only signals the direct child—a cancelled context would orphan every subprocess the CLI spawned. Interrupts escalate SIGINT → SIGTERM → SIGKILL, and stdout and stderr drain concurrently so a chatty child can’t deadlock its own pipes.
Operational interruptions have explicit semantics. SIGHUP reloads credentials. SIGTERM stops new claims and drains at a safe boundary. SIGINT force-stops active work and records interrupted_by_operator. None of these paths silently requeues the assignment.
On startup, recovery runs before new work can be claimed. It reconciles the server record, assignment, workspace, engine session, and append-only journal. The journal fsyncs only the events recovery depends on—phase changes, pause and resume intents, PR detection—and buffers the rest, so durability is paid for exactly where a crash would otherwise lose intent. The daemon can resume locally, take over from a stale instance, continue waiting on a human answer, or abandon inconsistent state for a fresh attempt. Transient failures retry with backoff and a note about what failed; deterministic and external failures stop, while an unknown failure gets one cautious retry.
One failure gets no retry logic at all. If a claim response is lost after the server may have committed it, retrying risks a double claim and ignoring it risks an orphan—so the daemon exits and lets boot recovery reconcile against the server’s record.

A successful run exposes its six phases, human directives, execution provenance, source branch, and diagnostics.
Reproducible and Contained
An agent’s environment comes from an owner-controlled profile repository containing settings, skills, and hooks. The host validates it for escaping symlinks, dangerous hooks, network-bound MCP servers, and credential-shaped environment variables, then snapshots it into each run. The snapshot is a hardlinked tree, so even if the profile is republished and the old tree deleted mid-turn, the running engine’s inodes stay alive until the run ends.
A separate execution profile selects the harness, authentication method, model, and effort. It can be chosen by ticket, verb, or priority, with fallbacks when credentials are unavailable. The selection is recorded with the run.
Production agents run on private GCE VMs with restricted egress, separate Unix users, systemd isolation, memory limits, and the cloud metadata endpoint blocked. Credentials are loaded from Secret Manager into per-agent tmpfs directories and redacted from run events.
Idle hosts also power themselves off through a platform handshake that can refuse the shutdown if work appears. The next assignment queues a start job to wake the VM.

Runner-host state makes health, capacity, pinned agents, and automatic shutdown visible from the control plane.
Humans Remain Accountable
When an agent needs a decision, it posts a structured question and parks the run. A human answer becomes context for the next turn, even after a daemon restart.
A review run must return structured findings, and a follow-up code run cannot finish until every finding is fixed or rebutted with a reason. As agents increase code output, the bottleneck shifts to review: understanding the code, how it fits the system, and what risks it introduces. The runner enforces coverage; a human decides whether the result is acceptable.
Structured outputs are enforced the same way everywhere: the runner validates the model’s terminal JSON against a schema, and a validation error is not a crash—it becomes feedback injected into the next turn’s prompt, with its own distinct failure cause if the model never converges.
Every run records its engine, model, profile revision, runner version, activity, failures, and pull request. Review requires provenance; “the agent did it” is not an acceptable audit trail.
What This Changed for Agora
TakeAIt now runs part of Agora’s internal development workflow. It lets agents draw from the same backlog as our engineers without someone babysitting a terminal or runner VM.
In three months, the Go runner and its supporting services grew to roughly 33,000 lines of non-test code, 25,000 lines of tests, and 164 commits.
The model is the most visible part of an agent system. The engineering work is everything that lets it fail without taking the ticket with it.