Incidents
Four controls for when something breaks, run first by the response your files name. Hold one step, reroute it to people, drain a Service of new orders, or stop the whole brand. Then find every job the cause touched, redo it at no charge, lift without sending a job back to the cause, and close the incident on its record, cost included.
An incident answers one question: what stops, and what keeps going? A hold never moves a date the world sets, never touches pay a member has earned, and never charges a buyer for work that didn't happen.
| Control | CLI | What stops | What keeps going |
|---|---|---|---|
| Hold a step | hold <service>.<step> | every job reaching the step waits there, with job.held | every other step, every ask and clock |
| Reroute it to people | hold <service>.<step> --to <role> [--pay <usd>] | the AI at that step: each job posts a Task to the role instead | the job, which goes on once the person's act commits |
| Drain a Service | hold <service> --drain | new orders, which answer not_live with the waitlist | every job already ordered, to its end |
| Stop the brand | stop --live [--except <step>] | every AI call, outside send, capture, new Task and new order | clocks and their notices, accrued pay and payouts, sends already out, every read, and each step --except names |
A hold can narrow to the jobs it names, such as --where jurisdiction=US-NJ, so one bad leg stops while the rest file.
Each alarm can run the control you wrote for it, in seconds, before anyone reads it. respond in brand.ts maps an alarm to its hold:
respond: {
'quality.alarm': { hold: 'draft', to: 'attorney', pay: 120, expires: '8h' },
'spend.alarm': { hold: 'draft' },
'pool.alarm:no_eligible': { raise: 'sooner' },
},A response records hold.placed { by: 'respond', alarm } and lists under the console's done for you, with its lift. check refuses a response that reroutes a reserved act to a role without the act's credential. Responses run on Team.
At 09:40 on a Monday, signers are sending back nearly a third of api.lawyer's morning trademark responses, all at the AI's draft. By the time the owner opens the console, the file has already rerouted the draft:
open https://api.lawyer/console # 9 Nov, 09:40 alarm quality.alarm · trademark-response · send-back budget burning 14x over the last hour and 5 minutes · signers sent back 9 of 31 responses since 06:00 (29%), against an objective of 5%
every one at draft · since 06:12 decision.made names the family claude-x at a new build
done 09:40:07 · respond in brand.ts held the draft and rerouted it to attorneys, $120.00 an act, for up to 8h · hld_2Lp4 · hold.placed { by: 'respond', alarm: 'quality.alarm' }
controls lift the hold · change its pay or its hours · drain the Service · stop the brandnpx services.dev holdsapi.lawyer · live · 1 hold
hld_2Lp4 trademark-response.draft → attorney since 09:40 by respond 22 drafts posted, 22 claimed, p50 41m to claim expires 17:40The draft named only the family claude-x, and the provider moved its build at 06:12. A rollback wouldn't help, because both versions named the same family. With the draft pinned to a build, a new build reaches buyers only through a deploy, and a staged one shows it in the first cohort.
| The cause | The control |
|---|---|
| your own bundle | rollback, or jobs repin the jobs in flight: Versions |
| the model, under a family pin | reroute the step, then deploy a build pin |
| an outside system | none: the submit step's fallback runs with no hold |
| the Pool | Pool health's moves |
| a key that may have leaked | keys rotate --all --live first; stop only if you can't tell what the key signed |
npx services.dev jobs affected --where decision.model_build=claude-x@2026-11-09 --since 06:12 --livetrademark-response · 31 matters drafted on claude-x@2026-11-09 since 06:12
sent back 9 by their signers at respond · each redrafts at no charge
in flight 14 held at draft · drafted by attorneys under hld_2Lp4
awaiting 2 signed, filed and delivered · the client hasn't accepted
accepted 6 signed, filed and accepted
run it npx services.dev jobs send-back --affected "decision.model_build=claude-x@2026-11-09 status=delivered,accepted" --from draftnpx services.dev jobs send-back --affected "decision.model_build=claude-x@2026-11-09 status=delivered,accepted" --from draft --reason incident← 8 matters back at draft · by: 'incident' · each redrafts on the fixed build, then goes to its attorney again
clients pay nothing more · each matter's timeline says "redone: we found a problem in our draft"
attorneys each second act is paid again, $175.00 from your line: $1,400.00 · a supplemental response files where one is owed
event job.sent_back { by: 'incident' } on eachA deploy at 13:55 pins the draft to the build from the day before. Matters still pinned to the old version would draft on the moved build again, so the lift's dry run sorts them first:
npx services.dev hold lift hld_2Lp4 --dry-runhld_2Lp4 · trademark-response.draft → attorney · dry run, nothing changes
release 4 matters on 2026-11-09.1, ordered since 13:55 · AI drafts each on claude-x@2026-11-08
kept 6 matters on 2026-11-02.1, not yet claimed · they stay with attorneys: 2026-11-02.1 names only the family
finish 3 matters claimed by attorneys · each finishes on the terms it was claimed under
run it npx services.dev hold lift hld_2Lp4 --repin-to 2026-11-09.1npx services.dev hold lift hld_2Lp4 --repin-to 2026-11-09.1← lifted 14:20 · after 4h 40m · 22 drafts by attorneys, $2,640.00 · hold.lifted
repinned 6 matters to 2026-11-09.1, at draft · job.repinned { by: 'hold.lift' } on each
released 10 matters to AI on claude-x@2026-11-08 · 3 finish with their attorneysThe incident opens on its first alarm or hold and gathers every alarm, hold, deploy, repin, redo and refund until its last hold lifts. Its record is the postmortem's source:
npx services.dev incidents get inc_4Lp1inc_4Lp1 · trademark-response · opened 09:40 by quality.alarm · closed 14:20 when hld_2Lp4 lifted · 4h 40m
cause the draft named the family claude-x, and its build moved at 06:12 · a rollback wouldn't have helped: 2026-10-19.3 named the same family
timeline 09:40 quality.alarm · 09:40 hld_2Lp4 by respond · 13:41 evl_3Lq7, no lens finds fewer · 13:55 2026-11-09.1 live, draft pinned to claude-x@2026-11-08 · 14:20 lifted, 6 repinned
touched 31 matters · 9 sent back by signers · 8 sent back by: 'incident' · 14 drafted by attorneys · 0 dates missed
cost $2,640.00 reroute · $1,400.00 second acts · AI $5.68 on 8 redrafts · $0.00 to clients · $4,045.68 in all
status no buyer-facing delay: every matter kept its stated time · nothing posted on api.lawyer/status
report npx services.dev incidents report inc_4Lp1 writes the postmortem from this recordEvery brand's Site serves /status. A drain, a stop, or a hold that pushes a job past its stated time shows there and on the job's timeline, in the buyer's words, never naming the model or the cause.
Draining keeps the Service visible and takes no new orders, the way a Service with status: 'waitlist' does:
npx services.dev hold trademark-response --drain --reason "the model's new build"← hld_2Lp5 · placed 09:46 · trademark-response drains · live
orders answer not_live with the waitlist, as a Service with status: 'waitlist' does · no buyer is charged
in flight 38 matters run to their ends on the version and the holds they have
status api.lawyer/status: "New trademark responses are paused. Matters already ordered keep their dates."
lifted each waitlisted buyer gets the link to orderThe stop is for the day something is wrong everywhere. It prints what it costs if it stands, and --except keeps a dated step running:
npx services.dev stop --live --except respond --reason "every AI step answering out of shape"← api.lawyer · stopped 16 Nov, 10:02 · live
stops every AI call, outside send, capture, new review and new order · 214 matters wait where they are
keeps respond, as --except asks: 9 office dates inside 30 days still reach their attorneys · clocks and their notices · accrued pay and payouts on schedule · sends already out · every read
expires 11 card authorizations lapse inside 7 days if the stop stands · each renews on start, or the buyer is asked again
asks a party's link says the matter is paused, and keeps what they already did
status api.lawyer/status: "We've paused new work. Every date you were given still holds."
start npx services.dev start --liveYour files first, then your coding agent within the ceilings you set, then a staff role your custom roles allow to hold, then you. Every hold, lift, stop and start is on the audit log with who placed it and the reason. Every control runs on every plan.
| Call (CLI) | API | Event | On the console | Plan |
|---|---|---|---|---|
hold <service>.<step> [--where <filter>] | POST /owner/holds { at, where } | hold.placed, job.held | /console/holds | every plan |
hold <service>.<step> --to <role> [--pay <usd>] | POST /owner/holds { at, to, pay } | hold.placed | /console/holds | every plan |
hold <service> --drain | POST /owner/holds { at, drain: true } | hold.placed | /console/holds | every plan |
holds | GET /owner/holds | none: it reads | /console/holds | every plan |
jobs affected --where <filter> --since <time> | GET /owner/jobs?decision.model_build=&since= | none: it reads | /console/incidents | every plan |
jobs send-back --affected <filter> --from <step> | POST /owner/jobs/send-back { where, from } | job.sent_back | /console/incidents | every plan |
hold lift <hld> [--dry-run] [--repin-to <version>] | POST /owner/holds/:id/lift { repin_to, dry_run } | hold.lifted, job.repinned | /console/holds | every plan |
incidents get <inc> | GET /owner/incidents/:id | incident.opened, incident.closed | /console/incidents | every plan |
incidents report <inc> | GET /owner/incidents/:id/report | none: it reads | /console/incidents | every plan |
stop --live [--except <step>] [--to <role>] | POST /owner/stop { except, to } | brand.stopped | /console | every plan |
start --live | POST /owner/start | brand.started | /console | every plan |
keys rotate --all --live | POST /owner/keys/rotate { all: true } | key.rotated | /console/keys | every plan |