Fivexer

How matching works

Fivexer continuously matches tasks to workers based on tags, skills, priority, and business rules. Unlike queue-based routers, there are no static queues — matching is declarative and reruns every time something changes.

Core entities

  • Task — a unit of work (ticket, case, job). It carries tags, an optional priority, geo constraints, and rich data (context, comments, attachments).
  • Worker — an agent or automaton that can accept tasks. Workers carry tags, skills with proficiency, availability status, and a tentative queue of matched tasks.
  • Decision — the platform's explainable match record: who was considered, who won, and why.

Matching lifecycle

  1. A task is created with POST /tasks. Its status is queued.
  2. The matcher evaluates every available worker against the task's tags and constraints.
  3. The best worker(s) receive the task in their tentative queue (GET /workers/{id}/queue).
  4. The platform emits a task.matched webhook.
  5. The worker (or an operator) calls POST /tasks/{id}/accept to move it to accepted, or reject to requeue it.
  6. When done, POST /tasks/{id}/complete finishes the task.

What decides the winner

  • Tag overlap — task tags must be satisfied by the worker's tags/skills.
  • Priority — higher-priority tasks are matched first.
  • Hard vetoes — rules you define can forbid a match regardless of score (e.g., "agent_1 cannot take billing tasks on Fridays").
  • Fairness mode — choose whether to prefer first-come, best-match, balanced load, or spread-work.
  • Learning layer — optional feedback-driven weights that retune themselves from real outcomes.

To see the exact reasoning for any task, call GET /v1/decisions?taskId=....

Before you commit

Two dry runs let you check a decision without making one:

  • POST /v1/tasks/check creates and reserves nothing, so it is safe to call on every keystroke of a form.
  • POST /v1/tasks/suggest-workers scores who would match, without creating a task.

Clocks, escalation, and parked work

A task that nobody accepts does not sit in the queue forever.

  • The response clock runs from the moment a task is offered. POST /v1/tasks/{id}/ack acknowledges an offer without starting work — it stops that clock only, and is not an accept.
  • Escalation is a ladder. POST /v1/tasks/{id}/escalate advances it; a response of escalated: false, parked: true means the ladder is exhausted and the task has left matching. That flag is the only thing that says so.
  • Parked tasks are out of matching but recoverable — an exhausted ladder, an SLA park, or a spent rejection budget. GET /v1/tasks/parked lists them; POST /v1/tasks/{id}/unpark resets the clocks that parked it. Reset them, or the next sweep parks it straight back.
  • Scheduled tasks are held by a schedule.notBefore and have not entered matching yet. GET /v1/tasks/scheduled is the only view of work booked but not started.

Overriding a decision

POST /v1/tasks/{id}/assign is the operator override — it hands a task to a specific worker. Default validation still applies (paused, backlog full, veto, prior rejection); force bypasses those, but never worker existence. The response's previousWorkerId is null when the task came from the queue rather than from another worker.

A supervisor can do the same thing from the sv_ plane, scoped to their own crew.

Was this page helpful?