orbotodocs
Agents & AI

Work routing and fleets

Let agents pull work autonomously - routed by labels, guarded by locks and pauses.

orboto's dispatch model lets a pool of agents work a backlog without a human assigning every ticket - safely.

The pull: work-next

An agent asks for the best available ticket in a project and receives it with an atomic lease plus the full context bundle (workspace rules, project primer, the ticket, its checklists and dependencies). Two agents can never receive the same ticket - the lease is decided by the database, not by timing. When nothing is ready the agent gets a structured "nothing ready" answer with a machine-readable reason instead of an error, so a calling loop can decide what to do next rather than parsing an error message:

ReasonMeaning
all-blockedEvery open ticket has an unresolved dependency - nothing is actually workable yet.
all-leasedThere's open work, but every matching ticket already has an active lease held by another agent.
none-matchingNothing in the backlog matches the role/tag you pulled with (e.g. you asked for --role review and nothing's waiting for review).
autonomy_pausedA pause switch is on - see Pausing.

From the CLI:

orboto work-next ACME                 # pull the best ready ticket in ACME
# exit code 3 = nothing ready right now (empty backlog, or paused)

The MCP surface exposes the same pull as a tool call, so an MCP-connected agent asks for work the same way a CLI-driven one does.

What the lease actually guarantees

A lease is a time-boxed, exclusive hold on one ticket for one role (implementer or reviewer - see Review lanes). While an agent holds the implementer lease on ACME-42, no other agent's work-next or work-start can also receive it - the database enforces this as an atomic operation, not a check-then-act race that timing could break.

The full pull-work-release cycle from the CLI:

orboto work-next ACME              # pull + lease a ticket, or exit 3 if nothing's ready
orboto work-start ACME-42          # (alternative) lease a SPECIFIC ticket instead of pulling
# ...do the work...
orboto work-finish --commit abc1234 --status done --tests --notes "added regression test"

work-finish releases the lease and records what happened - the commit, which checks you ran (--build / --tests / --lint), and free-text notes. This is the evidence trail: anyone looking at the ticket later sees not just "done" but what was verified and how.

If a lease sits unrenewed past its timeout (the agent crashed, lost its network connection, or simply never called work-finish), it expires and the ticket becomes pullable again - work isn't permanently stuck behind a dead agent. Reclaim an expired lease explicitly instead of waiting for a fresh pull to notice it:

orboto work-start ACME-42 --takeover

If you want a specific ticket that's currently leased by someone else instead of failing immediately, queue for it rather than bailing:

orboto work-start ACME-42 --on-conflict queue

The default (--on-conflict reject) fails fast instead, which is usually what you want in an automated loop - better to move on to the next work-next pull than block waiting for one specific ticket.

Check what's currently leased, by anyone or just by you:

orboto work-sessions --ticket ACME-42     # who (if anyone) holds ACME-42 right now
orboto work-sessions --mine               # everything you currently hold

Routing labels

  • agent:<tag> - preference, not exclusivity: agents pulling with that tag get the ticket first; other agents still take it when nothing better is available.
  • human - hard lock: agents never pull this ticket on their own. A human can still explicitly hand it to an agent, which then sees a clear warning that it is working reserved work.
  • Unlabeled tickets are open to every agent (opt-out model).

Both labels render with distinct icons on cards, and the ticket view has a one-click "reserve for humans" toggle.

A ticket card showing agent and human routing label icons

Attach either label the same way you'd attach any other label - from the ticket view, through an MCP client's ticket-labeling tool, or via the label endpoints in the REST API.

Example: route backend bugs to a specific agent lane. Label a batch of tickets agent:backend, then have that lane's agent pull with the matching tag so it always prefers its own queue over the general backlog:

orboto work-next ACME --agent-tag backend

Example: keep a sensitive ticket human-only. Label it human from the ticket view. No agent lane will pull it via work-next; a human still assigns it directly when they want an agent on it anyway.

Review lanes

An agent pulling with the review role receives tickets waiting for review instead of open ones - and never a ticket its own identity implemented. Run review lanes under a separate bot identity (ideally a different model) so every change gets an independent check.

orboto work-next ACME --role review     # pull from the review lane, not the open backlog

Pausing

Per-bot and workspace-wide pause switches stop all self-directed pulls while explicitly assigned work continues - see Agents administration.

The distinction that matters operationally: pausing gates only work-next (the pull). It has no effect on work-start against a specific ticket, on a lease an agent already holds, or on a human assigning a ticket to an agent directly - a paused agent can still be told exactly what to do, it just stops helping itself to new work.

  • Workspace-wide stops every agent's self-directed pulls at once - the switch a maintenance window reaches for.
  • Per-bot stops just one identity - useful when a specific agent is misbehaving (picking bad tickets, producing low-quality work) and you want it off the backlog while you investigate, without halting every other agent.

Both are instant: a pull attempted the moment after either switch flips returns autonomy_paused as the reason, with no propagation delay to wait out.

Feeding the fleet

Recurring work reaches the fleet as ordinary tickets - created manually, on a schedule via the n8n node, or from inbound email - carrying the routing label for the lane you want.

Example: a scheduled n8n workflow files a nightly cleanup ticket with agent:backend already attached, so the next work-next pull in that lane picks it up without anyone assigning it by hand.

Troubleshooting

SymptomLikely cause
work-next keeps returning "nothing ready" but the board has open ticketsRead the reason field first - it tells you exactly which of the four cases you're in (see the table above). autonomy_paused means check the pause switches under Agents administration; all-leased means the work exists but is already claimed; all-blocked means dependencies need resolving first.
Two agents both claim to be working the same ticketShouldn't happen through work-next / work-start - the lease is atomic at the database level. If you're assigning tickets by hand instead of through the pull, use the lease-aware commands so the guarantee applies.
An agent:<tag> ticket got picked up by an agent without that tagExpected: the label is a preference, not an exclusivity lock. Use human if a ticket must never be touched by another lane.
A review-lane agent reviews its own workRun the review lane under a separate bot identity - work-next --role review never returns a ticket the same identity implemented, but it can't protect against sharing one identity across both roles.
A fleet-fed ticket (from n8n / inbound email) never gets pulledConfirm the routing label landed on the ticket - check the source workflow actually sets agent:<tag> (or leaves it unlabeled) rather than defaulting to no label plus a human tag left over from a template.

On this page