← All articles

I Stopped Writing Code. Now I Write Tickets Agents Build on Their Own.

My job is now crafting tickets that a team of agents can build on their own. Each agent picks up a slice, builds it, and proves it end to end, with nobody stepping in. Here are the rules a ticket has to follow for that to work, and what the reviewers caught that I wouldn't have.

Topics: Claude Code · AI · Engineering Process · Developer Tools

On this page

TL;DR: For the last few weeks I’ve written almost no code on the product I’m building. My job is now crafting tickets that a team of agents can build on their own, in parallel, with nobody stepping in. That only works if a ticket settles every decision and says what “done” looks like in a form an agent can prove end to end. This post covers the rules I ended up with and one real session where they paid off.

For the last few weeks I’ve written almost no code.

The agents didn’t get good enough to guess what I want. I stopped asking them to guess, and my job became the ticket. A team of agents picks the tickets up, builds them in parallel, proves each one, and opens the merge requests. Nobody steps in along the way.

The deal I used to have with my CTO

When I wrote the code myself, my CTO and CEO never asked how I’d built something. They wanted to see that it did what the ticket asked. The how was my call.

I have the same deal with the agents now, except I’m on the other side of it. I write the tickets and make the system design calls. The agents handle the rest: the code, the tests, the merge request.

Each merge request also comes with a short video. It shows an agent walking through the user journey from the ticket, step by step, in the real product. I watch it the way my CTO used to watch my demos. If the journey works on screen and the tests prove every success line, I don’t need to read every line of code to trust it.

That deal only holds if the ticket is clear. If it’s vague, no one is there to clear it up. The agent fills the gap with something plausible. Plausible code passes the demo and breaks a month later, when nobody remembers why it was written that way.

Here’s what a ticket has to carry for that not to happen.

A ticket is a contract, not a reminder

The ticket I used to write was a note to myself or a colleague: “Add expiry to invite links, see Slack thread.” It worked because the reader shared my context.

An agent shares none of it, and no one is there to answer its questions. So a ticket I call ready has to settle every decision it owns. The agent plans the code and decides nothing else. The ticket carries only what the agent can’t cheaply recover from the repo:

  • the problem, who has it, and why now
  • the appetite: how many days this is worth
  • the decisions already taken
  • what will be true for the user once it ships
  • what is out
  • what is still open

Anything left open means the ticket isn’t ready. Readiness is computed from the open questions, not something I pick. A runner refuses an epic until every slice in it is ready.

Three rules for success lines

Most of the work goes into the “Done when” lines. Three rules carry them.

Rule 1: name who sees it. Every line starts with the person who observes the result. “A customer cannot redeem an invite link past its expiry”, not “an invite link past its expiry cannot be redeemed.” That sounds pedantic until you notice the actor is who the agent plays in the test. With no actor named, the agent tests whatever is easiest to reach, usually a function.

Rule 2: the wording test. For every line I ask: would this wording change if the implementation changed? If it would, the line doesn’t belong in the ticket.

  • “The account owner sees who has accepted an invite without opening each one.” Passes.
  • “The list endpoint returns a accepted_count.” Fails.

The second one makes a design decision and passes it off as a requirement. The agent then builds exactly that, even when a better design exists. Routes, selectors, file paths and test framework names all fail the same way. Domain nouns stay (“invite” versus “member”, “web app” versus “mobile”) because they are product facts.

Rule 3: the proof is part of the ticket. Every ticket ends with the same sentence:

Every Done when line is proven by an integration test that walks its journey step through the product’s real entry point. The test asserts the line’s observable result as written. Unit tests cover pure logic only. No test mocks the thing it proves.

It’s in every ticket word for word because a posted ticket has to stand on its own. The journey beside it has a happy path, plus the failure paths a user can actually hit: declined, expired, empty, offline.

I also write the bodies in a controlled language, roughly Simplified Technical English: one term per concept, one fact per sentence, a named subject. It reads a bit stiff. It also reads one way only, which is the point.

The process: two gates and a graph check

I packaged all of this into a Claude Code skill called writing-tickets. The flow:

  1. Classify. One ticket, an epic with slices, or several epics. Plus a term list, so “invite” means one thing all the way through.
  2. Gate 1. The shape and the titles, then it stops until I type yes. This is my cheapest point of control, before any bodies exist.
  3. Bodies, written to the rules above.
  4. Graph check. A script reads the drafts and checks dependencies, waves and readiness. Slices in the same wave run in parallel, so every pair in a wave needs evidence that the two really are independent. The script prints something like ok: 15 slices, 5 waves or one failure line per problem.
  5. Review. Five independent reviewers, each with one lens (below), for two rounds.
  6. Gate 2. The final drafts, then a second typed yes, then it posts to the tracker.

Five reviewers, one lens each

One reviewer asked to “check everything” checks the obvious things. So each reviewer gets one question:

  1. Implementation leak. Does any line describe the how instead of the what?
  2. Slice integrity. Is each slice independently shippable, and is the dependency graph honest?
  3. Completeness and split. Is something missing, or is one slice really two?
  4. Code overlap. Checked against the real repo: does this already exist, or collide with something that does?
  5. Adversarial. A model from a different family attacks the whole set. Same-family reviewers tend to share blind spots, so this one is deliberately an outsider.

One real session

The product has an epic about protecting customer data at rest. Agents shipped the first two waves, seven slices. The review notes came back with 54 findings.

Here’s what the session looked like from my chair:

  • The agent deduplicated and sorted the 54 findings: 27 needed a ticket, 6 needed a decision from me, and the rest were already covered or needed nothing.
  • I made the 6 decisions.
  • It proposed 15 tickets. I said that was too many. It regrouped them by theme into 4 slices.
  • I wanted those slices done before the next planned wave, so they went in as a new wave 3.
  • The reviewers ran. Gate 2. Posted.

The epic ended at 5 waves and 15 slices, 14 of them ready for agents to pick up and build unattended. My part was the decisions and a lot of “no”.

The reviewers caught things I would have shipped:

  • A hidden dependency. One slice quietly relied on another slice in the same wave. Run in parallel, they would have collided.
  • A contradiction with a locked decision. One slice said “delete stored tokens.” The epic had a locked decision that no slice migrates or backfills data. Rewording it to “stop storing tokens” fixed the conflict, and it also removed the only irreversible step in the whole plan.
  • A test that proved the wrong thing. A webhook line was proven by checking that a status changed. That proves a status, not a delivery.
  • A flaky-test proof that could miss. “Run it 200 times” doesn’t help when the failure sits on a time boundary that 200 fast runs may never cross.
  • A target with no baseline. “10% faster” than what? It’s now pinned to a specific commit, a fixed seed, and the median of five runs.

These are ordinary mistakes. They slip through because the person writing the ticket already knows what it means, so they read what they meant instead of what they wrote.

What I got wrong

In that same session, my agent reported that an access token had leaked and that I should rotate it. It hadn’t. A scan of the session transcripts found no leak. The finding was false.

The rule I took from it: verify before telling a human to act. A false alarm that stays inside the agent loop costs a minute. A human who rotates credentials in a panic because of one loses an afternoon.

What this costs

This isn’t free, and I don’t want to oversell it.

  • It’s slower up front. Writing actor-led, implementation-free lines takes real thought. A one-line ticket is quicker to write. It just isn’t quicker to finish.
  • Review rounds cost tokens and wall-clock time. Five reviewers over two rounds is real spend. For a one-line bug fix I don’t run all of it.
  • Slice boundaries are provisional. The agent that picks up a slice may re-cut it once it sees the code, and the process has to allow that.
  • You still have to know the domain. The six decisions in that session were mine. No reviewer could have made them.

Where the bottleneck went

I used to think the constraint on shipping was writing code. Agents removed most of that. What’s left is the part that was always hard: knowing what “done” means, precisely enough that a team of agents with no context can build it and check it on their own.

That’s the job now: the tickets and the system design. The agents handle the rest. Some days it feels more like writing contracts than engineering, and I’m not sure yet how I feel about that. For now it’s where my time does the most good.