ReferenceView as Markdown ↗

Dispatching a job: the tool sequence for AI assistants

In plain terms

The order an assistant follows to give a job to a subcontractor, what each step answers, and what it does when a subcontractor is refused because they have not signed the agreement.

assign_job (runs the compliance gate) → propose_send_work_order → approve_send_work_order (two legs, no confirm) → the subcontractor accepts from the email, which dispatches the job → record_completion (or their portal) → a pending payable is raised by the job.completed event. An unsigned agreement refuses assign_job with code sma_unsigned; clear it with create_contractor_agreement → propose_send_contractor_agreement → confirm_pending_operation → approve_send_contractor_agreement.

At a glance

Who it's for:
AI assistants working in a Biloh business
What a person approves:
Your business records are stored in Sydney, Australia.
Checked against the code:
2026-11-08

This is the sequence an assistant follows when the person it works for says "give this job to that subcontractor". Every message the assistant sends the subcontractor is staged first and sent only after the person says yes. Money is always integer cents: $240.00 is 24000.

The sequence

StepToolWhat it does
1 (optional)suggest_contractors_for_jobWho passes the gate for this job (candidates) and who fails (blocked, each with its reason)
2assign_jobRuns the compliance gate, then sets the job's subcontractor
3propose_send_work_orderStages the work order; returns operation_id. Nothing is sent
4approve_send_work_orderSends it, with confirmed: true after the person says yes
5(the subcontractor)Accepts from Review Work Order in the email; the job becomes dispatched
6record_completionMarks it complete when they did the work but did not press Mark Complete
7get_payableThe subcontractor's bill, raised automatically by the completion

Step 1 — who can take it?

suggest_contractors_for_job({ job_id }) is read-only. Each entry in candidates and blocked carries display_name, is_internal (true for the business's own crew) and prospective_rate — what that subcontractor would be paid on this job, in cents, with derivable: false and a reason when it cannot be worked out. client_rate_cents comes back beside them.

Step 2 — assign_job and the compliance gate

Input: { job_id, contractor_id } (or team_member_id for the business's own team, which skips the gate). contractor_rate_cents is optional and only for a deliberate change to the subcontractor's standing terms.

On success it returns job, warnings (soft, never blocking — e.g. insurance expiring soon), rate_decision (what a reassignment did to the visit's pay) and first_attach_rate (the same for a first assignment). Assigning does not send anything: work_order_sent_at stays null.

A hard-gate failure writes nothing and returns an error whose text begins Compliance gate blocked assignment: and carries a code. The common ones:

codeMeaningWhat clears it
sma_unsignedAgreement not signedSend the agreement (below)
no_insuranceNo verified insurance documentCollect and verify a certificate
insurance_expiredInsurance past its expiry dateA current certificate
insurance_below_site_minimumCover below what the site requiresHigher cover, or another subcontractor
no_capabilityNot set up for this serviceAdd the service to the subcontractor
missing_credentialA licence or card the service requiresCollect and verify it
on_leave / inactiveNot availableChange availability, or pick someone else

Reassigning a job that is already completed, approved, invoiced or paid is refused with terminal_reassignment_requires_confirmation, an assessment and a confirm_token. Show the assessment to the person and wait: only they can decide to move work the client has been billed for.

Clearing sma_unsigned

  1. create_contractor_agreement — a draft agreement for the subcontractor.
  2. propose_send_contractor_agreement({ agreement_id }) — a preview PDF (pdf_url) and a readiness block. Read its findings to the person before going on.
  3. confirm_pending_operation — stages the send. Nothing is emailed.
  4. approve_send_contractor_agreement — sends it. The subcontractor signs from the link.

If they agreed by phone, email or in person, record it with record_contractor_agreement_response instead; it marks the agreement signed. Then call assign_job again.

Steps 3 and 4 — the work order, in two legs

propose_send_work_order takes exactly one of job_id, job_ids (one subcontractor, up to 100), contract_service_line_id (a whole recurring series) or contract_service_line_ids (several series at one site). It runs the same gate over every member; one failure rejects the whole set with a failures array and creates nothing. It returns operation_id, recipients, recipient_count, an earnings_total_cents forecast and, for a visit on a recurring line, a scope_advisory to read before approving.

Then approve_send_work_order({ operation_id, confirmed: true }) — only after the person says yes. It returns work_order_id, accept_url, recipients and recipient_details (one row per address, each with its own message_id and status). Do not call confirm_pending_operation for a work order.

Step 5 — acceptance dispatches

The approve leg does not change the job's status. The subcontractor's acceptance does: a single job goes scheduled → dispatched, and a series acceptance dispatches every scheduled visit on the line. An acceptance that arrived by phone or email is recorded with record_work_order_acceptance. dispatch_job dispatches without a work order and returns dispatched_without_work_order: true to say so.

Steps 6 and 7 — completion and the payable

record_completion({ job_id }) needs the job dispatched (or backfill: true for a missed or held one, with completed_on set to the real day). It checks the photos and notes the completion rules require, then emits job.completed. That event raises a pending payable for the subcontractor and, when the client is invoiced job by job, a draft invoice; the response's auto_invoice says which. Read the bill with get_payable.

The payable becomes releasable when the client's invoice for the job is paid. propose_release_payable then approve_release_payable record the date the business paid the subcontractor at its bank.

Biloh never moves money: its tools record payments that already happened, and a pay-now link opens the business's own Stripe checkout for its client.

Related

Under the hood

Source files this page is checked against:

  • lib/scheduling/assign-core.ts
  • lib/scheduler/compliance.ts
  • lib/mcp/tools/work-orders/propose-send-work-order.ts
  • lib/mcp/tools/work-orders/approve-send-work-order.ts
  • lib/mcp/tools/jobs/record-completion.ts

Raw markdown: /docs-md/reference/dispatching-a-job-for-agents

Last updated 2026-11-08