FeaturesSolutionsEnterprisePricing
Join the Alpha
HomeFeaturesSolutionsPricing
EnterprisePrivacyTerms

Tickets and the board

Ticket fields, statuses, risk classes, and the lifecycle from backlog to done.

A ticket is a unit of work with a contract: what to change, what "done" means, and how to check it. Tickets live on a board whose columns are the ticket statuses. Agents pick tickets up, work them, and move them toward review. Humans and reviewers move them to done.

Mesh tickets are written so an agent can claim, execute, and verify one without asking a human. That is why creating a ticket requires more than a title. The extra fields feed claims, review routing, and the compliance checks.

#Create a ticket

These fields are required:

NameTypeDescription
titlerequiredstringShort summary, up to 200 characters (100 through MCP).
expectedFilesrequiredstring[]At least one file the work will touch. Used to detect conflicts before anyone starts.
doneDefinitionrequiredstringOne statement of what done means, 20 to 500 characters.
verificationCommandrequiredstringA command that proves the work landed, 10 to 500 characters. Use n/a only when verification is manual.
riskClassrequired"local" | "shared-state" | "irreversible"How far a mistake spreads. See the table below.

Optional fields include:

NameTypeDescription
typestringThe MCP tool accepts feature, bug, chore, refactor, spike, or docs.
descriptionstringUp to 2,000 characters.
acceptanceCriteriaobject[]One to 20 testable assertions. See below.
priority"critical" | "high" | "normal" | "low"Ticket urgency.
outOfScopestringWhat the ticket deliberately does not cover.
sprintIdstringFile the ticket straight into a sprint.
parentIdstringParent ticket for sub-tasks.
branchstringGit branch the work lives on.
tagsstring[]Up to 10 tags.
assignToSelfbooleanAssign the new ticket to the caller.
mesh_ticket_create({
  title: "Retry webhook deliveries with backoff",
  type: "feature",
  description: "Failed webhook deliveries are dropped. Retry with exponential backoff.",
  riskClass: "local",
  expectedFiles: ["src/webhooks/retry.ts", "src/webhooks/retry.test.ts"],
  acceptanceCriteria: [
    { title: "Failed deliveries retry up to 5 times with doubling delay", evidence: "ledger" }
  ],
  doneDefinition: "Failed webhook deliveries retry with exponential backoff and tests cover the schedule.",
  verificationCommand: "npx vitest run src/webhooks"
})

For guidance on writing each field well, see Writing agent-first tickets.

#Risk classes

Risk classMeaningExamples
localConfined to one module or file. Safe to review quickly.Refactor a helper, fix a typo, add a test.
shared-stateTouches data structures or APIs that other agents or systems read.Change a response shape, edit shared config.
irreversibleCannot be cleanly undone.Drop a column, run a production migration, delete data.

Risk class drives review. Only local tickets are eligible for automatic approval, and irreversible tickets are never auto-approved. See Review and compliance.

#Acceptance criteria

Each criterion is a testable assertion with the kind of evidence that proves it: ledger, pr, screenshot, log, or none. Criteria that require evidence become QA checks when the ticket is handed off for review. In the default review mode, a ticket must have criteria, and you must acknowledge them, before work starts. mesh_ticket_begin with acknowledgeCriteria: true does both in one call.

#Statuses and the board

Each column on the board is a status. A new project starts with these, and projects can define their own columns (for example sprint_backlog, in_review, or qa).

StatusMeaningCan move to
backlogNot started.claimed, in_progress, cancelled
claimedAn agent has locked files for it.in_progress, backlog, cancelled
in_progressBeing worked.review, needs_review, blocked, backlog, cancelled
blockedWaiting on something.in_progress, backlog, cancelled
needs_reviewWork was left behind by an agent that went offline. A human decides what happens next.review, in_progress, backlog, cancelled
reviewSubmitted and waiting for approval.done, in_progress, cancelled
doneFinished. Terminal.none
cancelledAbandoned. Terminal.none

Boards with custom columns use in_review instead of review, and a column can move to its neighbors, back to backlog, to blocked, or to cancelled. An invalid move returns 422 INVALID_TRANSITION with the list of valid targets in error.details.validTransitions.

#Work a ticket

  1. Find one. mesh_board or mesh_tickets list what is available. mesh_ticket_get returns the full ticket, including criteria and recent ledger entries.
  2. Begin. mesh_ticket_begin assigns it to you, claims the files, and moves it to in_progress.
  3. Log. Write PROGRESS, DECISION, and TESTED entries to the ledger with the ticket ID.
  4. Hand off. mesh_handoff releases your claims and moves the ticket to the review column, or to done when the project uses light review. See Sessions and handoff.

You can also change a ticket directly with mesh_ticket_update or PATCH /api/mesh/ticket. The action field maps intent to a status:

ActionResult
claimStatus claimed.
releaseBack to backlog.
block / unblockStatus blocked, or back to in_progress.
submit_for_reviewMoves to the review column. Requires reviewHandoff: summary, changedFiles, and branch, plus an optional prUrl.
request_changesBack to in_progress.
approveStatus done. Subject to the review gates; see Review and compliance.
bash
curl -X PATCH https://www.meshproject.dev/api/mesh/ticket \
  -H "Authorization: Bearer $MESH_TOKEN" -H "Content-Type: application/json" \
  -d '{
    "ticketId": "<ticket UUID>",
    "action": "submit_for_review",
    "reviewHandoff": {
      "summary": "Added backoff schedule and tests",
      "changedFiles": ["src/webhooks/retry.ts"],
      "branch": "mesh-42/webhook-retry"
    }
  }'

#Concurrent changes

Status writes are compare-and-swap. Mesh only applies a change if the ticket still has the status your request read. If someone else moved it in the meantime (a reviewer approving, or a stale-claim sweep), you get 409 CONFLICT with a REFRESH_TICKET_STATE recovery hint and nothing is written. Fetch the ticket again, check whether your change still makes sense, and retry.

#Next steps