Who gets the work
A queue nobody looks at is a queue nobody works, and the promise a borrower is waiting on is the item least able to survive being overlooked — so work is offered rather than left to be found.
Work reaches a named person or AI agent by being offered to them individually, with the reason they were chosen, rather than being listed for whoever claims it first. How a case moves explains how a case decides it needs work done; this page explains how that work reaches somebody, what they can do with it, and what happens when nobody can take it.
Two decisions, not one
People usually describe this as “assigning a task”, as though it were one act. It is two. They are made at different moments by different machinery, and both are written down separately. Keeping them apart is what lets you answer two very different questions later: why is this in the bankruptcy queue? and why was it offered to me?
What a task is
A task is one unit of work the platform has to place — with an AI agent, which is where most of it goes, or with a person where policy reserves that decision for one. Everything else on this page is about getting each task to the right holder.
| What it carries | Why it matters |
|---|---|
| Status | Enforced by the database, not just by code, so an illegal move is refused rather than trusted. |
| Queue, and why | The queue plus the rule that put it there, the reason in English, and the policy version. Recorded at creation and never re-derived, so the placement can be explained later without re-running the rules against facts that have since changed. |
| Two possible holders | The person who has accepted it, and separately the person it is currently offered to. Different columns, because they are different states. |
| Where it came from | A source type and a source reference. That reference is load-bearing: it decides whether the task can be cancelled, whether something else owns its lifecycle, and whether it is the current step’s own work. |
| Which visit it answers for | Not the step, the visit. A flow can return to the same step, and the second visit’s rule must not be satisfied by the first visit’s work. |
| When it becomes actionable, and when it is late | Two separate times. For a promised callback they are the agreed window’s start and end. |
| Sensitivity | A sensitive task is invisible to anyone without the specific permission — in every view, and at reservation. |
| Its outcome | Written only on completion, from a catalog. |
Where tasks come from
Five sources, and the difference matters because it decides who is allowed to finish the task.
- A workflow step asking for the work its rules describe. This is most of them.
- A standing timer on a monitoring step, raising its review on a cadence.
- A service that owns something and needs a person: a failed effect raises exactly one review, a reconciliation drift raises its own, a governed action request needing manual execution raises its own.
- The channel gateway, when a call is answered. That path creates the task already accepted by the agent who answered, bypassing the offer entirely, and records a system decision so the bypass is auditable.
- A person, creating follow-up work or a checklist item on a manual case.
Work owned by a parent — an action request, a recovery review — cannot be completed through the ordinary task command. It completes inside the parent’s own transaction, and the platform first transfers the task to the person resolving it, so the record still names one accountable person rather than closing under somebody who did not do it.
Choosing the queue
Eight levels, tried in order, first match wins. This runs once, when the task is created.
Two things follow from that ladder that are easy to miss. A workflow step’s queue hint is weak, not strong — a stage rule is consulted before it, and a case-type team rule matches every task on that case, so the hint often never gets a look-in. And the compliance queue cannot be named, only earned: a step hint or an escalation pointing at it is rejected outright unless a governed process supplied a closed reason and a source. Public and template input cannot select that queue or manufacture its reason.
Queues and the people in them
A queue is pure configuration: a code, a name, a type, an enabled flag. Membership is a join with an active flag, and removing somebody deactivates the row rather than deleting it, so history stays answerable.
| Queue | What lands there |
|---|---|
SUPPORT |
General borrower support. |
COLLECTIONS |
Delinquency review and outreach. |
BANKRUPTCY |
Specialist evidence and restriction work. |
CEASE_COMMUNICATIONS |
Cease and representation requests. |
COMPLIANCE_REVIEW |
Second-line work. Every task needs a closed reason and source. |
ACTION_REVIEW |
Failed, manual-execution and approval-required action requests. |
AI_REVIEW |
AI quality and low-confidence review. |
UNKNOWN_INTAKE |
Inbound nobody has identified yet. |
PAYMENT_OPERATIONS |
Refunds and payment investigations. Deliberately never falls back to collections. |
HARDSHIP |
Assistance programmes, including disaster relief. |
ESCALATION |
Routes to the selected person or team only, and overrides ordinary routing. |
LANGUAGE_SPECIALIST · MILITARY_SPECIALIST |
Specialist callbacks. |
SANCTIONS_REVIEW · ESTATE_HANDLING |
Sanctions decisions, and deceased-borrower and estate work. |
IT_ONCALL |
Infrastructure failures, which are an IT concern rather than a servicing one. |
DEFAULT |
The catch-all. |
Workers
One registry holds three kinds. A human has a real platform login and a capacity. An AI worker has no login at all: it has an adapter and a contract version, and its registration states maxima rather than authority. A system actor is a closed, code-defined set of job identities that never reserve ordinary queue work.
Presence is intent, not workload. A worker on a call stays available and is withheld the second call by voice occupancy, not by a status change, so provider events never quietly mark anyone busy.
Capacity is the smaller of the person’s own configured number and the platform-wide ceiling. A person configured to carry less than the platform allows is respected; nobody carries more. Reserved work counts against capacity, because an unanswered offer is being held for that person and nobody else can have it. A callback scheduled for Thursday does not, because a promise for Thursday is not something anyone is carrying today.
Who is allowed to take it
One contract answers this, and every caller asks it: the views, the pull command, and the dispatcher. That is deliberate. A view that offered work reservation would refuse sends an operator to a locked door, and a dispatcher that pushed work reservation would refuse is the same failure arriving uninvited.
Authority is read from the worker being considered, never from whatever triggered the dispatch, because the dispatcher acts for a worker rather than as them. And voice exclusivity is declared on the task type rather than inferred from a queue or a channel, so a new kind of conversation has to declare itself.
Two routes to a worker
A worker comes to hold a task in exactly two ways, and both ask the same eligibility question.
The offer
When the dispatcher has a choice to make, it makes it in a stated order rather than by scoring — because an operator asking why they were given something deserves a sentence.
An offer has a deadline, two minutes by default. Three things can end it.
- Accepted. The full eligibility contract is re-run at this moment, not assumed, so a worker whose circumstances changed between the offer and the answer is refused here rather than discovered later.
- Declined, optionally with a reason. That worker is then skipped when the work is offered again. Declining is not the same as saying the work cannot be done; it says nothing about the work at all.
- Expired. A sweep runs every fifteen seconds, returns the task to the queue, and immediately offers it again. It is faster than the other sweeps because the cost of being slow here is a person waiting.
Marking yourself busy, away or offline returns any unanswered offer and re-offers it, but never touches work already accepted.
Taking the next task
The implemented route derives the worker, queue membership, capacity, and reservation duration from the authenticated workforce record and queue policy:
curl -X POST "$BASE/v1/servicing/tasks/reserve-next" \
-H "Authorization: Bearer $TOKEN" \
-H "LendEasy-Tenant: demo-lender" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: reserve-next-ana-4-2027-05-01T1000" \
-d '{
"queueCode": "COLLECTIONS",
"taskTypeCodes": ["COLLECTIONS_OUTREACH"]
}'
{
"taskId": 2001,
"reservationExpiresAt": "2027-05-01T10:02:00-07:00"
}
Selection and reservation happen in one step, so two workers cannot receive the same task.
A task’s whole life
What a worker sees
Six views, and they exist because “the queue” was answering the wrong question. A queue listing shows what is in a queue. An operator needs to know what they can start now, which is a different set.
| View | Shows |
|---|---|
| Ready for me | Work this person can pick up right now. Every clause mirrors a check the reservation would make, in the same order, because a view that shows work reservation would refuse is worse than no view at all — it sends someone to a locked door. |
| My work | Everything they are holding: offered, accepted, in progress, waiting. |
| Waiting | Their work that is parked on somebody else. |
| Team backlog | Unassigned human-eligible work across their queues, whether or not they personally qualify. A supervisor needs to see the item nobody on shift can take. |
| AI work | Work an AI holds or has run, including finished and failed runs — because a run that failed is the point of the view. |
| All work | The unrestricted inventory. |
Two properties matter more than the list. The caller’s identity is resolved on the server for every view, so a client cannot ask for somebody else’s ready for me. And a view narrows and is never widened: asking for ready-for-me plus completed still returns only available work.
Two self-serve reads keep the workspace grounded in the authenticated worker’s real eligibility instead of client-side assumptions. GET /v1/servicing/me returns the worker’s own context in one call — identity, queue memberships, task-type eligibility, presence, the live reservation if one exists, active assignments, and whether the worker is at capacity:
{
"workerId": 4101,
"displayName": "Maria Agent",
"active": true,
"capacity": 8,
"taskTypeCodes": ["COLLECTIONS_OUTREACH"],
"queueCodes": ["COLLECTIONS"],
"presence": { "state": "AVAILABLE", "channels": ["PHONE", "GENERAL"], "since": "2027-05-05T18:00:00Z", "setBy": "AGENT_TOGGLE" },
"activeReservation": { "taskId": 2001, "reservationExpiresAt": "2027-05-05T18:40:00Z" },
"activeAssignments": [
{ "taskId": 2002, "status": "IN_PROGRESS", "taskTypeCode": "COLLECTIONS_OUTREACH", "queueCode": "COLLECTIONS", "priority": "HIGH", "dueAt": "2027-05-06T17:00:00Z" }
],
"activeAssignmentCount": 1,
"atCapacity": false
}
GET /v1/servicing/task-types returns the presentation-safe task-type catalog for enabled queues — code, display name, default queue, and default priority — without exposing AI-eligibility or worker-type internals.
Work an AI is holding disappears from human pickup views, and not as a special case: a task an AI holds has a holder, and the readiness rule excludes anything with a holder. Holding is holding.
Completing a task
Completing a task is not a status update. It is a recorded decision, and the platform is strict about it in three separate ways.
The outcome comes from a catalog. Each task type declares its legal outcomes, each with its own requirements: a note, a document, named fields, whether a follow-up is required or forbidden. An outcome that is not in the catalog is not a way to finish.
The catalog is narrowed again by the case’s own flow version. An outcome the pinned version cannot route is refused, and the worker is told which ones it can route, or offered the recovery path instead.
And some outcomes require proof.
None of them can be satisfied by the completion request itself, which is the whole point: the person completing the task cannot also supply the proof that they did it.
An outcome can declare that it claims a real operation — a message actually sent, a promise actually recorded, a governed command the Lending Core actually executed, a compliance decision actually taken, an identity actually verified. Completion then reads the state that other part of the platform wrote as a side effect of the work being done. If it is not there, the completion is refused with a sentence a person can act on: the Lending Core has not reported executing a governed command for this case. Do the work, then complete the task.
The catalog is careful about where it does and does not demand proof. Submitting a refund proves nothing, because submitting is not refunding. Telling a borrower their refund is on its way has an outcome for the case where nothing was sent, and that one proves nothing because nothing happened.
Completion writes the stored fields and evidence, spends the collections treatment decision first, and then re-evaluates the workflow inline. That re-evaluation can walk the case all the way to its ending and close it in the same transaction — which is why any follow-up work is created before the completion rather than after. A follow-up created afterwards would be newly active work on a closed case.
The five commands
Reservation is only the offer. Five commands carry a task the rest of the way, and each records the decision that ended it. The two that look alike are the two most worth keeping apart: release says someone else should do this; cancel says this no longer needs doing. Only release returns the task to queue ranking.
| Command | Body | Permission |
|---|---|---|
POST /tasks/{taskId}/accept |
None — it accepts the caller’s own live reservation. | ACCEPT_SERVICING_TASK |
POST /tasks/{taskId}/decline |
Turns down the caller’s own live offer so it requeues immediately. | DECLINE_SERVICING_TASK |
POST /tasks/{taskId}/complete |
outcomeCode required; note, fields, followUp optional. |
COMPLETE_SERVICING_TASK |
POST /tasks/{taskId}/release |
The full unable/requeue decision. | ACCEPT_SERVICING_TASK |
POST /tasks/{taskId}/cancel |
reason required. |
CANCEL_SERVICING_TASK |
Completion carries the evidence the task type demands:
curl -X POST "$BASE/v1/servicing/tasks/2001/complete" \
-H "Authorization: Bearer $TOKEN" \
-H "LendEasy-Tenant: demo-lender" \
-H "Content-Type: application/json" \
-d '{
"outcomeCode": "CUSTOMER_REACHED",
"note": "Customer agreed to a promise to pay.",
"fields": { "identity_verified": true, "contact_channel": "PHONE" },
"followUp": { "taskTypeCode": "REVIEW_PROMISE_OUTCOME", "dueDate": "2026-08-02" }
}'
fields must satisfy the task type’s configured completion-field definitions, so “record whether identity was verified” is enforced by the platform rather than by a client-side form — see Servicing configuration. followUp creates the next task only where policy allows it; it is not a way to invent arbitrary work.
Releasing is not a silent undo. Every field is required, because returning work to a queue is an operational decision that has to be attributable:
{
"unableReason": "SHIFT_ENDED",
"note": "Returning unstarted work to the queue.",
"requeueDecision": "RETURN_TO_QUEUE",
"createReplacementTask": false,
"managerReviewRequired": false
}
Cancel is different again: it ends work that has become obsolete — usually because the owning case resolved. Where the workflow required that task, cancelling blocks case progression or creates replacement work rather than quietly removing an obligation.
When it cannot be done
A worker who cannot finish the current step’s work has one honest thing to say, and saying it must not strand the case. So the release is treated as a request for recovery rather than as an ending.
The task is held: it leaves them, nobody else is offered it, and the step still owes the work. Exactly one recovery review is raised, deliberately sent somewhere other than the team that could not finish the job. No replacement is created, because a second copy of work nobody could do is not recovery.
| The reviewer says | The held work | The case |
|---|---|---|
| Resume | Back to its queue, unassigned | Unchanged. The step still owes it. |
| Hand it on | To a named worker or queue | Unchanged |
| Skip the step | Withdrawn as superseded | Moves to a step this case’s own flow version already routes to, and that step’s entry effects run exactly as the engine would have run them |
| Close the case | Withdrawn | Closes through the ordinary guards |
| Close and continue by hand | Withdrawn | Closes, and a manual case opens in its place, linked back to it, carrying the supervisor’s checklist |
Two properties make this safe to rely on. It is all or nothing: a closure guard that refuses, or a step the flow does not route to, leaves the review and the held work exactly as they were. And destinations are never arbitrary — being present in a definition does not make a step safe to enter from where the case is standing, so the console is handed the legal list up front.
If the world moves past held work, the engine withdraws it and takes its review with it. A review whose subject has moved on is a question nobody can answer.
Moving work to somebody else
Reassignment records source and destination, actor, reason, eligibility result, and service-level impact. GET /v1/servicing/tasks/{taskId}/reassignment-options gives the manager valid destinations first: eligible queues for the task, and under-capacity eligible workers in its current queue. The read requires the same reassign authority as the command, and terminal tasks return empty lists.
POST /v1/servicing/tasks/{taskId}/reassign then moves the work under REASSIGN_SERVICING_TASK:
{
"targetType": "QUEUE",
"targetRef": "HARDSHIP",
"reason": "Specialist queue handling is required."
}
{
"resourceId": 2001,
"changes": { "queueCode": "HARDSHIP", "status": "AVAILABLE" }
}
The command revalidates the chosen target rather than trusting the option list — a stale option fails closed. Reassigning to a queue returns the task to AVAILABLE for ranking; it does not hand ownership to a worker who has not reserved it. Escalation is a case control that can create a higher-priority specialist task; it is not a way to bypass permissions or review requirements.
Promised callbacks
These behave differently enough to be worth their own section, and the difference starts with how the promise is understood.
A promise is a window, not an appointment. A borrower who agrees to “around eight tomorrow morning” has agreed to a half hour.
So the task is created immediately, in a scheduled state, with the window’s start as the moment it becomes actionable and the window’s end as the moment the promise is broken. It exists straight away so the case can show it, but it is held out of every pickup view and every capacity count until the window opens.
A window must be between five minutes and two hours, must be in the future, must be within the configured horizon, and requires the borrower’s own time zone — because a promise made without it is a promise made in somebody else’s morning.
A sweep activates due callbacks each minute, flips them to available, and hands them straight to the dispatcher, because the minutes between the window opening and somebody dialling are the borrower’s minutes. The sweep evaluates nothing else: compliance belongs at scheduling, where the proposed window is judged, and at dialling, where the actual moment is. A borrower who asks to be called in the next half hour does not wait for the next tick; that one activates inline.
When nobody can take it
Nothing new is created. The task stays where it is, and the reason nobody could take it is written onto it.
No second escalation task is created. Work nobody can take does not become better staffed by growing an item beside it, and the supervisor who needs to act on it needs the reason, not another row.
The reasons are ranked so that the one naming a staffing or configuration gap wins over the one that merely says people are busy, because that is the one somebody can act on.
| Recorded reason | What it means |
|---|---|
| No queue members | A configuration defect. Nobody belongs to this queue at all. |
| Missing permission, or missing sensitive access | Also a configuration gap: the people are there but not authorised. |
| All on a call | Voice work, everyone occupied. |
| All at capacity | A staffing decision, not a defect. |
| All holding an offer | Everyone already has something unanswered. |
| None available | Nobody is marked available right now. |
It is visible in three places: the team backlog view, a queue coverage report that warns most-actionable-first, and the promise scorecard. The coverage view exists so an unstaffed specialist queue is visible before it quietly holds promises nobody will keep, rather than afterwards. A queue that nothing routes to and nothing sits in reads as unused rather than unstaffed.
Specialist work never falls back to a general queue when its own queue is unstaffed. Falling back would place the work somewhere it would be answered badly.
Escalation is a separate, deliberate act rather than an automatic consequence of being unstaffed. It creates a review at high priority with the target queue as the strongest routing input, and it refuses a queue target that does not exist, is disabled, is the compliance queue, or has nobody eligible in it.
What to watch
Time-to-first-action, deadline misses, reassignments, expired reservations, workload by worker, AI/human fall-through, ranking drift, and subgroup outcomes. Throughput alone rises when the easiest work is taken first, so a queue can look healthier in the same week it starts missing the regulated deadlines that carry real consequences.
Queue position is transient. The lasting proof is the task lifecycle, the decisions, the actions and the case outcome — not a screenshot of a ranked list.