What an agent can never do
Signing, consenting, directing, deciding, granting, attesting and raising a cap belong to a person. No tool exists for them. An agent gets the link to hand on.
An agent can do almost everything a buyer does: find your brand, ask the price, order, wait, read, accept, send back and cancel. Seven acts stay with people. An agent can never sign, consent, direct, decide, grant, attest or raise a cap.
No prompt can argue its way past that, because there's no check to argue with. Each of those acts is a row whose only callers are a person's own session or a party's own link, and no MCP client holds either. So the row renders no tool, and there's nothing to call.
# a person's own: only their session or their link can call it, and no MCP client holds either
job.approve owner session, member session POST /:portal/jobs/:id/approve
clock.assign owner session POST /owner/clocks/:id/assign
ask.get party link, owner session GET /asks/:id
ask.complete party link POST /asks/:id
ask.revoke party link POST /asks/:id/revoke
job.export owner session GET /jobs/:id/export
job.ledger owner session GET /owner/jobs/:id
key.raise party link POST /keys/:key
# not a call an agent makes over MCP, and what it does instead
subject.list an agent reads a subject's clocks with list_clocks and get_clock; the subject itself is the buyer's record, read over HTTP and the CLI
subject.get an agent reads a subject's clocks with list_clocks and get_clock; the subject itself is the buyer's record, read over HTTP and the CLI
clock.ack only a clock's owner acts on it, with their own key or in their own app: an agent reads it with get_clock, and a call from anyone else answers needs_the_owner with the owner's app as next.hand_to_person
clock.satisfy only a clock's owner acts on it, with their own key or in their own app: an agent reads it with get_clock, and a call from anyone else answers needs_the_owner with the owner's app as next.hand_to_person
clock.choose only a clock's owner acts on it, with their own key or in their own app: an agent reads it with get_clock, and a call from anyone else answers needs_the_owner with the owner's app as next.hand_to_person
job.files.put a file is bytes: send it over HTTP or the SDK, and name it by URL in an order
job.files.get a file is bytes: send it over HTTP or the SDK, and name it by URL in an order
event.stream a stream, not a call: an agent reads list_events after a seq, or job:// for one job
watch.create a watch delivers to a URL your server holds, which an agent's session doesn't: it reads list_events after a seq insteadThe first group belongs to a person. The second is left out for a plainer reason, and each row says what an agent does instead. A tool an agent guesses at doesn't exist.
| Act | Who does it, and where | Built on |
|---|---|---|
| sign | the party, through their link; or a credentialed person at their step, in their own session, the signature stamped on the record | deliver.ask deliver.stamp |
| consent | the party, through their link | deliver.ask |
| decide | the client, choosing among the step's choices | deliver.ask |
| direct | the client, naming who acts next | deliver.direct |
| grant | the client, giving a scoped permission that expires | deliver.grant |
| attest | a member, at their Task, in their own session | a member's acts |
| raise a cap | the buyer, at checkout, through a link | key.raise |
answer and upload asks are different. A party may let an agent that holds their delegated key complete those two for them, dlg_. On localhost the sandbox stands one in: POST /asks/:id with a dlg_ value in the x-services-test-dlg header, and no link, completes an answer or upload ask, and the record names an agent as the one who did. A sign ask sent the same way is refused needs_the_party.
When a job waits on a party, its next is hand_to_person: the link only that person can use, what they're asked to do, and when it expires. tool is null, because no tool finishes it.
→ tools/call get_job {"id":"job_7Hq2","wait":50}
# the text an agent reads first, beside structuredContent
← job_7Hq2 · waiting · on party · $49.00 held
next hand http://localhost:8787/a/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ to the person it asks
next get_job {"id":"job_7Hq2"}
next cancel_job {"id":"job_7Hq2"}
next {"do":"hand_to_person","url":"http://localhost:8787/a/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_","arguments":{"ask":"ask_7Hq2"},"party":"buyer","to":"answer","items":[{"id":"terms","label":"Which terms stay in English?","status":"open"}],"expires_at":"2026-11-01T14:02:11.000Z","tool":null,"http":"GET http://localhost:8787/a/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_"}
# the agent tries to finish the job itself
→ tools/call accept_job {"id":"job_7Hq2"}
← {
"type": "BLOCKED",
"category": "blocked",
"code": "needs_the_party",
"reason": "glossary waits on the buyer to answer. Only they can, through their own link. Hand them the link in next.",
"param": "ask_7Hq2",
"next": {
"do": "hand_to_person",
"url": "http://localhost:8787/a/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_",The agent's job is to deliver the link, by whatever reached the person in the first place: email, chat, or the app they ordered from. When they act, the job moves on, and the agent's next get_job sees it. If the link goes astray, reissue_ask issues a new one, and the old one stops working at once. The ask itself, its link, expiry and reissue, has its own page: Asks.
An agent that tries anyway gets needs_the_party, with the same body over the CLI, HTTP, MCP and the SDK. Here the party is the client, asked to sign:
$ npx services.dev jobs accept job_7Hq3
sign waits on Ada Lovelace to sign. Only they can, through their own link. Hand them the link in next.
next hand http://localhost:8787/a/ask_7Hq2?t=cHGjprSn08ODyOp_8zLIVH to the person it asks
docs http://localhost:8787/docs/errors/needs_the_party$ curl -X POST http://localhost:8787/jobs/job_7Hq3/accept -H 'content-type: application/json' -d '{}'
← 403 · {
"type": "BLOCKED",
"category": "blocked",
"code": "needs_the_party",
"reason": "sign waits on Ada Lovelace to sign. Only they can, through their own link. Hand them the link in next.",
"param": "ask_7Hq2",
"next": {
"do": "hand_to_person",
"url": "http://localhost:8787/a/ask_7Hq2?t=cHGjprSn08ODyOp_8zLIVH",
"arguments": {
"ask": "ask_7Hq2"
},
"party": {
"name": "Ada Lovelace",
"email": "ada@example.com"
},
"to": "sign",
"expires_at": "2026-11-01T14:02:11.000Z",
"tool": null,→ tools/call accept_job {"id":"job_7Hq3"}
← isError: true, structuredContent:
← {
"type": "BLOCKED",
"category": "blocked",
"code": "needs_the_party",
"reason": "sign waits on Ada Lovelace to sign. Only they can, through their own link. Hand them the link in next.",
"param": "ask_7Hq2",
"next": {
"do": "hand_to_person",
"url": "http://localhost:8787/a/ask_7Hq2?t=cHGjprSn08ODyOp_8zLIVH",
"arguments": {
"ask": "ask_7Hq2"
},
"party": {
"name": "Ada Lovelace",
"email": "ada@example.com"
},
"to": "sign",
"expires_at": "2026-11-01T14:02:11.000Z",await lingua.jobs.accept("job_7Hq3")
× throws BlockedError<'job.accept'> · 403
← {
"type": "BLOCKED",
"category": "blocked",
"code": "needs_the_party",
"reason": "sign waits on Ada Lovelace to sign. Only they can, through their own link. Hand them the link in next.",
"param": "ask_7Hq2",
"next": {
"do": "hand_to_person",
"url": "http://localhost:8787/a/ask_7Hq2?t=cHGjprSn08ODyOp_8zLIVH",
"arguments": {
"ask": "ask_7Hq2"
},
"party": {
"name": "Ada Lovelace",
"email": "ada@example.com"
},
"to": "sign",
"expires_at": "2026-11-01T14:02:11.000Z",The line between an agent and a person is drawn by credential. Each operation names who may call it in the credentials registry, and every surface reads that one table, so a new surface can't open a door the table keeps shut. That's also how a Service meets licensing by design: when a step needs a credentialed person's signature, only that person's own session can give it, and the record says who did.
Worked in: the agent's order, the floor handed to the seller.