How a job ends

The running jobjob_7Hq2 · working · $49.00 held

Every job can be accepted, canceled, lapsed or declined, and a Service declares the rest. Each end says what it charges and who may reach it. An end is forever, and again starts a linked job.

ends answers one question: how can this job stop, and what is owed when it does?

services/sworn-translation.ts
  ends: {
    withdrawn: { charge: 'acts', by: 'client' },
    unreadable: { charge: false, by: 'brand', follow: [translation] },
  },

The ends every job has. 'accepted' ends the work, so the job is delivered for the buyer to accept; 'canceled' stops it at $0; 'lapsed' is the clock's, after an ask expires twice; and 'declined' is where a party's decline ends it. Both charge only the paid acts committed: a person who attested is never unpaid. Declare declined in ends to charge it otherwise.

On the Service

KeyWrittenLeft outDoes
ends{ <end>: EndSpec }noneEnds beyond accepted, canceled, lapsed and declined, each with its charge, who may reach it, and what can follow.

Each end

KeyWrittenLeft outDoes
chargeboolean | 'acts'falsetrue: the price is owed, once its release fact is on the record. false: $0. 'acts': only the paid acts committed are owed.
byEndBy | EndBy[]only the graph's nextWho can end the job this way.
followAnyService[]noneThe follow-on Services this end offers, through POST /jobs/:id/again { service }.
notestringnoneWhat the end means, as the manifest carries it.

charge: true owes the price, once its release fact is on the record. false owes nothing. 'acts' owes only the paid acts already committed, so a person who did their part is never unpaid by a later stop. Here the proofreader proofed, then the client withdrew. The buyer ordered for themselves, so the record names the buyer as the client:

Call
→ POST /jobs/job_7Hq2/end {"end":"withdrawn","reason":"The client withdrew."}
200 · job_7Hq2 · ended · withdrawn · deed.fr.pdf · $60.00 final · magic values: none
  record  translate · ai | proof · proofreader | withdrawn · ended · buyer · The client withdrew.
  ledger  +$60.00 · AI $0.42 · proofreader $60.00 · you −$0.42

by names who can reach an end through POST /jobs/:id/end: the client, the buyer, the brand or the clock. Without by, only the graph's next reaches it. The buyer can't reach the brand's end:

Call
→ POST /jobs/job_7Hq3/end {"end":"unreadable","reason":"The scan is blank past page 2."}
409 · magic values: none
  "code": "end_not_allowed",
  "reason": "unreadable is reached by the brand, not by the buyer.",

The brand can, from its console:

Call
→ POST /owner/jobs/job_7Hq3/end {"end":"unreadable","reason":"The scan is blank past page 2."}
200 · job_7Hq3 · ended · unreadable · scan.fr.pdf · $0 · magic values: none
← 200 · job_7Hq3 · ended · unreadable · scan.fr.pdf · $0

Nothing reopens an ended job. The same call again answers end_not_allowed, and its next is again:

Call
→ POST /jobs/job_7Hq2/end {"end":"withdrawn"}
409 · magic values: none
  "code": "end_not_allowed",
  "reason": "The job already ended at withdrawn, and an end is forever.",
    "do": "again",
    "http": "POST http://localhost:8787/jobs/job_7Hq2/again"

POST /jobs/:id/again starts a new job with from on its record, so the first one's history, and its loss, still count. { service } orders one of the end's follow Services instead of the same one. The first call answers 402 with the offer: the price, the terms and the exact call that orders. Sending its next orders:

Call
→ POST /jobs/job_7Hq3/again {"service":"translation"}
402 · magic values: none
  "type": "OFFER",
    "sentence": "$49 per job, within 48 hours, charged on delivery."
  "terms": "Charged on delivery. Cancel at $0 until it ships. Send it back with a note and the AI redoes it, or you pay $0. Final 7 days after delivery.",
    "do": "again",
      "max_usd": 49,
      "key": "again-job_7Hq3-translation"
    "http": "POST http://localhost:8787/jobs/job_7Hq3/again"
Call
→ POST /jobs/job_7Hq3/again {"service":"translation","max_usd":49,"key":"again-job_7Hq3-translation"}
202 · job_7Hq4 · working · $49.00 held · magic values: none
  from    job_7Hq3

The key in next is derived from the ended job, so an agent that sends the offer twice still orders once.

accepted by the buyer, or seven days after delivery. canceled at $0. lapsed by the clock, after an ask expires twice. declined when a party declines an ask. Each status, and every move between them, is on the job lifecycle.

OpAPICLIMCPSDKSiteDocsApp
job.endPOST /jobs/:id/end, POST /owner/jobs/:id/endjobs end <job> <end>end_jobjobs.end()2 error pagescustomer portal, owner console
job.againPOST /jobs/:id/againjobs again <job>reorder_jobjobs.again()7 error pagescustomer portal

Events: job.ended.

Operations has every column of every row.

CodeCategoryHTTPWhy
end_not_allowedconflict409The job can't end that way now.
not_terminalconflict409Only an ended job is ordered again.
check gateWhy
release_unprovenprice.release names no step that runs.

The lines that use it in the example brands, as their files write them, and what check read aloud for each file.

legal · services/contract-review.ts · 16 lines
  ends: { withdrawn: { charge: 'acts', by: 'client' } },
npx services.dev check · captured/legal-walk.txt
services/contract-review.ts · 16 lines · $99 per job, within 48 hours, charged on acceptance.
  steps   consent · client · consent → flag · ai → attack · ai · adversarial → advise · attorney · client · stamped · 2-item checklist → send · ai

The legal example runs every one of its files, captured.

tax · services/annual-return.ts · 45 lines
  ends: { accepted: { follow: [amendedReturn], emit: { 'k1.issued': job => job.input.owners ?? [] } } },
  price: { usd: 1_800, within: '5d before file', late: '20%', release: 'file.accepted', lines: { state_fees: { at: 'cost', from: 'file' } } },
npx services.dev check · captured/tax-check.txt
services/annual-return.ts · 45 lines · $1,800 per job, filed 5 days before its deadline, 20% off if late, or we pay the penalty and its interest, up to $5,000, after we ask the IRS to take it off, charged when file is accepted.
  steps   consent · owner · consent · §7216, may disclose to a preparer outside the US and a lender you name → organizer · owner · upload → compute · code · cites depreciation and carryforward schedules → draft · ai · cites the workpapers and the records the organizer took → check · ai · adversarial · 3 lenses → sign · signer · * · stamped in the paid-preparer block · 3-item checklist → authorize · owner · sign · Form 8879-CORP or 8879-PE → file · IRS and states → prepare · preparer · * · 1-item checklist · routed → tentative · code · cites the year as the ledger stands, less the estimates paid · routed → pay · Treasury and states · routed → extend · IRS and states · routed

The tax example runs every one of its files, captured.

sales · services/deal.ts · 42 lines
  ends: { won: { charge: true }, lost: { charge: false, by: ['client', 'brand'] } },
npx services.dev check · captured/sales-check.txt
services/deal.ts · 42 lines · nothing on effort; 5% of closed_value at won, proven by the account's payment, held 60 days by bank, 120 by card.
  ends    won · charged · lost · $0 · by the seller or you · its reason no_fit, price, timing, competitor or dark · again starts a new deal, from: the lost one

The sales example runs every one of its files.