Orkestia
Blog
Staff & Agents

Run a ticket end to end

How to use the coding agent — write a ticket it can implement, launch the delivery, watch it code, publish, review and merge on its own, and close the loop when it lands

Coding agents explains what the pieces are. This page is the operator's path through one ticket: what to write, which calls launch it, what "normal" looks like while it runs, and what to do when it stalls.

Every call on this page is an Orkestia workflow. Start it from the MCP server, the Staff console, or the API — the names and inputs are the same. Per-workflow contracts live in the reference catalog.

The loop you are launching

ticket ──▶ git work ──▶ delivery ──▶ coding session (hosted runner)
                                          │ commit, run required commands
                                          ▼
                          trusted broker publishes the exact head
                                          ▼
                    reviewer actor opens the pull request (labels, provenance)
                                          ▼
                  approval gate: CI green ▸ AI code review ▸ receipt
                                          ▼
                    merge lane squashes, deletes the work branch
                                          ▼
                  release operator advances post-merge, resolves the ticket

Every arrow is driven by the software-delivery controller, a Staff actor that runs every 30 seconds and claims one actionable delivery at a time. You never push, open, review or merge anything yourself. The coding session never holds a git credential; the broker publishes only the exact object the session acknowledged.

Before you start

  • The repository is ready for hosted delivery: platform registration, GitHub binding, delivery policy, execution profile, and manifest files on the prepared branch. See Wire a repository for coding agents. For local-only work, Coding agents is enough. The execution profile's dependency kind must be one the runner implements; a python-uv profile needs a committed uv.lock. See also Runner groups.
  • The controller actor's schedule is enabled. Check it in Staff → Staff tree, or with staff.get-actor.
  • You know the repository's repository_uuid, the policy's policy_uuid, and the target branch the policy allows (usually refs/heads/master).

1. Write the ticket as a specification

The coding agent treats the ticket body as the contract and implements nothing outside it. Tickets that merge on the first attempt share one shape:

  • the exact files to change, by path;
  • numbered rules, each verifiable inside the workspace;
  • schema rules spelled out when the change touches a workflow — an undeclared output field fails every call of that workflow, so "declare it in OUTPUT_SCHEMA" is part of the change;
  • the tests to add, by file and by case;
  • a done-when line and "no other files".

Keep the diff inside the policy's approval thresholds (changed files, additions, deletions); the gate will not approve a change that exceeds them.

ticket.open
{
  "kind": "task",
  "title": "github.pulls.get_exact_pr: expose head_repository and is_fork",
  "body": "<the specification>",
  "labels": ["pipeline:coding"],
  "source_type": "human", "owner_type": "human", "owner_uuid": "<your principal>",
  "priority": "routine", "severity": "low"
}

The label marks intent; it does not launch anything. Step 2 does.

2. Bind the work and open the delivery

Delivery policies are serial per target branch: one active piece of work per branch. If a previous delivery on the same branch has merged, close its work first so the lane is free:

ticket.git-work.complete
{ "work_uuid": "<previous work>", "outcome": "merged", "merge_oid": "<its squash oid>" }

ticket.git-work.concurrency reports the lane without claiming anything.

Then bind the ticket to the repository and open the delivery. Pass the current head of the target branch as base_oid; the derived default can be stale.

ticket.git-work.begin
{
  "ticket_uuid": "<ticket>",
  "repository_uuid": "<repository>",
  "runner_group_uuid": "<hosted coding runner group>",
  "target_ref": "refs/heads/master",
  "base_oid": "<current head of master>"
}

ticket.software-delivery.begin
{ "work_uuid": "<work from above>", "idempotency_key": "delivery-<work8>-r1", "policy_uuid": "<policy>" }

You get a work item in active with a codex/<slug>-<id> branch ref and a delivery in coding. Within 30 seconds ticket.software-delivery.controller-list shows the delivery on start_coding_session, first with the claim available, then leased. The runner group named here is advisory; the coding actor's own config decides where the session runs.

3. Watch it

All reads, no side effects:

QuestionCall
Which step is the controller on, how many attemptsticket.software-delivery.controller-list
Is the coding session alive, on which runnerticket.git-work.get → workspace_leases (heartbeat, session id)
What is the agent doing right nowdata.agents.session-trace with the session id
Did the runner capture the committicket.coding-artifact.list → an artifact in available
Is publication movingticket.git-delivery.list → attempt claimed → pushed → verified
The full ledgerticket.software-delivery.get with transitions and evidence
The provider sidethe pull request, its checks, and the review carrying the orkestia-verdict marker

What a healthy run looks like on a hosted runner, measured from the delivery's creation:

StageElapsedSignal
Runner claims the sessionabout 1 minlease running
Commitabout 20 mingit.commit in the session trace
Publication requestedabout 30 minan attempt appears
Branch on the provider, attempt verifiedabout 35 mindelivery published
Pull request opened with labels and provenanceabout 70 mindelivery checks_pending
AI review postedabout 80 minreview with the verdict marker
merge_authorizedabout 95 mingate receipt recorded as approval evidence
Merged, delivery completedabout 105 minsquash oid on the merge transition

A coding turn takes roughly a minute of model time. Reviewer actions run on your agent runner group and take four to eight minutes each. After a session releases its claim, the controller re-dispatches that step about five minutes later.

4. Close the loop

After the merge the fabric resolves the ticket and deletes the work branch. Verify:

  • ticket resolved; delivery completed; git work merged;
  • the pull request merged with its model:*, tool:* and category:* labels;
  • the work branch gone; the repository's own release pipeline picked up the merge.

If the ticket is still open, move it yourself. The lifecycle refuses open → resolved; start it first:

ticket.transition { "ticket_uuid": "<ticket>", "intent": "start", "actor_type": "human", "actor_uuid": "<you>" }
ticket.transition { "ticket_uuid": "<ticket>", "intent": "resolve", "resolution": "Merged as PR #<n> (<oid>)", "actor_type": "human", "actor_uuid": "<you>" }

A pass is autonomous when the delivery's transitions show no actor_type: human entry between coding and merged.

When it stalls

Read the ledger before naming a cause: ticket.software-delivery.get with evidence tells you what actually happened; the agent's narration does not.

SymptomWhat it usually isWhat to do
controller-list is empty right after software-delivery.begincontroller schedule disabledstaff.manage-actor-schedule with enable on the controller actor
start_coding_session attempts climb, no lease ever runsno warm runner, or the execution profile's requirements do not match a warm runnerdata.runner.pool-status on the group; adopt a universal profile
Delivery sits in publish_requested, git-delivery.list is emptythe session transitioned outside publish-requestticket.software-delivery.recovery-inspect, then publish-request for the acknowledged head, then resume or abandon
Attempt claimed, branch already at the right oid on the providerpush recorded on the provider, not yet in the ledgerwait for the lease to expire; the broker re-claims and verifies by reading the ref back
checks_pending for a long time while CI is greena reviewer session judged CI from a stale snapshotthe next dispatch runs the gate; the gate waits for CI itself
Reviewer reports a tool call failed with Unknown workflow type: virtual.…a composition was re-versioned after the session launchednothing; the next dispatch carries the new version
Session dies mid-step with a connection resettransient control-plane connectivitythe controller re-dispatches within about 90 seconds and resumes the retained worktree

To stop a delivery: ticket.software-delivery.recovery-inspect returns a snapshot digest and says whether recovery-abandon is allowed (only while no controller claim is live; release it with ticket.software-delivery.controller-claim-release first). Abandon with the cleanup acknowledgement, then ticket.git-work.complete with outcome: abandoned. Nothing merged can be undone from here; revert in the repository.

Where to go next