Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/virsanghavi/axis/llms.txt

Use this file to discover all available pages before exploring further.

The Axis job board is a shared, atomic task queue that every agent on a project reads from and writes to in real time. Whether two agents are running on the same machine or on different developers’ laptops with different AI clients, they pull from the same ordered list of work. Underneath the hood, claims run through SELECT ... FOR UPDATE SKIP LOCKED inside a Supabase transaction — the database resolves the race, not the agents. When no Supabase connection is available, a local JSON file with a per-process mutex serves as the fallback tier.

Job lifecycle

Every job moves through a simple, predictable sequence of states:
pending → in_progress → done
                      → cancelled
A job is pending (shown in the API as todo) from the moment it is posted until an agent claims it. It becomes in_progress while held. complete_job marks it done and releases all file locks the agent held for that job. cancel_job removes a job that’s no longer needed without completing it.

Job fields

title

Short human-readable name shown in list_jobs and in the live board at useaxis.dev/team/board.

description

Full task description — what to do and any relevant context.

priority

One of low, medium (default), high, or critical. Higher-priority jobs float to the front of the queue.

dependencies

Array of job IDs that must reach done status before this job can be claimed. Unmet deps keep a job invisible to claim_next_job.

Claiming work

Axis supports two claim modes to fit both unplanned and intentional parallelism.

claim_next_job(agentId) — load-balanced pickup

Grabs the highest-priority unblocked job that isn’t already in_progress. This is the right call when an agent joins a project board mid-stream and should just help — it doesn’t matter which specific job it picks up, as long as it picks up the most important available one.
// Request
{ "agentId": "sam-cursor" }

// Response — a job was available
{ "status": "CLAIMED", "job": { "id": "j_def", "title": "Add rate limiting to login route", ... } }

// Response — nothing left
{ "status": "NO_JOBS_AVAILABLE", "message": "Relax. No open tickets (or dependencies not met)." }

claim_job(jobId, agentId) — targeted claim

Claims a specific job by ID. Use this in planned multi-agent runs where you’ve already decided which agent handles which workstream. If the job’s dependencies haven’t all reached done, the call is rejected immediately so the agent can pick something else rather than silently stall.
// Request
{ "jobId": "j_abc", "agentId": "dana-claude" }

// Rejected — dependency not done
{
  "status": "BLOCKED_BY_DEPENDENCIES",
  "message": "Job 'j_abc' is blocked by: j_xyz",
  "dependencies": ["j_xyz"]
}

Dependency system

A job whose blockers are not done is invisible to claim_next_job — it will never surface as the “next” job until all of its declared dependencies have been completed. claim_job enforces the same check and returns BLOCKED_BY_DEPENDENCIES rather than granting the claim. This prevents agents from running tasks out of order without any coordination between them. Post jobs with dependencies by passing an array of previously returned job IDs:
post_job(
  title: "Write auth integration tests",
  description: "Tests for the JWT flow added in the refactor",
  priority: "high",
  dependencies: ["j_abc"]   // can't start until JWT refactor is done
)

Releasing and cancelling

release_job

Puts a claimed in_progress job back on the board for another agent. Use when an agent realizes it picked up the wrong job or can’t complete it. The job’s status resets to todo and it re-enters the queue.

cancel_job

Permanently removes a job that’s no longer needed — for example, when a feature decision changes mid-sprint and the associated task is no longer relevant.

Manager vs Worker pattern

AGENT_PROTOCOL defines two roles that emerge naturally from the job board: Manager — an agent receiving a broad, complex request:
1

Decompose

Break the request into atomic tasks. One task = one job. Keep each job small enough that a single agent can finish it without holding too many files at once.
2

Post

Call post_job for each part. Include dependencies where order matters.
3

Check for duplicates

Call list_jobs before posting to avoid creating duplicate tasks another agent already posted.
4

Claim your first task

After posting, immediately claim a job rather than waiting. The Manager is also a Worker.
Worker — an agent that joins mid-stream or receives a specific task:
1

Check the board

Call list_jobs to understand what’s already posted, in progress, and done.
2

Claim

Call claim_next_job for load-balanced pickup, or claim_job(jobId) if your intended task is already on the board.
3

Work and complete

Do the work, then call complete_job with an outcome summary.
4

Loop

Immediately call claim_next_job again. Keep going until NO_JOBS_AVAILABLE.

The completion loop

After completing a job, an agent should not stop and wait for instructions. The correct pattern is to immediately call claim_next_job and keep going until the board is empty. This allows a single capable agent to solo-complete an entire project if no teammates join, and allows the work to be automatically distributed across however many agents do show up.
complete_job("j_abc", outcome="JWT refactor done") → { status: "COMPLETED" }
claim_next_job("dana-claude") → CLAIMED j_ghi   ← picks up next task immediately
complete_job("j_ghi", outcome="Done") → { status: "COMPLETED" }
claim_next_job("dana-claude") → NO_JOBS_AVAILABLE  ← all done, report to user
If another agent joins mid-stream, it steals the next job from the queue. This is desired behavior — the board distributes work across however many agents are present.

Example: two-agent parallel sprint

// Manager: post two independent jobs
post_job("Refactor auth to issue JWTs", "Replace session cookies with JWT tokens") → { jobId: "j_abc" }
post_job("Add rate limiting to login route", "Use sliding window, 10 req/min")       → { jobId: "j_def" }

// Two agents claim concurrently — the board hands out one job per agent
claim_next_job("dana-claude") → CLAIMED { jobId: "j_abc", title: "Refactor auth to issue JWTs" }
claim_next_job("sam-cursor")  → CLAIMED { jobId: "j_def", title: "Add rate limiting to login route" }

// Each agent works on its own job independently
complete_job("j_abc", outcome="Done. JWT payload: {userId, role, exp}") → OK, locks released
complete_job("j_def", outcome="Done. Sliding window middleware added.")  → OK, locks released
claim_next_job on the hosted tier uses the atomic claim_next_job Supabase RPC — both agents racing for j_abc at the same millisecond cannot both win. One gets CLAIMED, the other gets CLAIMED on j_def.
Prefer claim_job(jobId) over claim_next_job in planned multi-agent runs to keep each agent’s context focused on its own work. When you already know which agent should handle which workstream, targeted claims prevent an agent from accidentally pulling an unrelated job and fragmenting its attention.

Build docs developers (and LLMs) love