Talk

The running jobjob_7Hq2 · working · $49.00 held

Every job has one thread. The AI answers the client in it by default, advice your rules reserve goes out only under a member's name and credential, and a member reaches the client only through it.

talk answers one question: who may say what to the client, and under whose name?

There is nothing to write. Every job has a thread, and the AI answers in it. The four-line Translation does:

Run
cat services/translation.ts
magic values: none
import { Service } from 'services.dev'

export default Service({
  name: 'Translation',
  input: { document: 'file', to: 'language: a BCP 47 tag, such as fr-CA' },
  output: { document: 'file' },
  price: 49,
})
Call
→ POST /jobs/job_7Hq2/messages {"text":"Will it read formal or informal?"}
magic values: none
← 200 msg_7Hq3 · AI · answer · sent: Simulated: what the record shows about "Will it read formal or informal?". The job is delivered.

With talk: false, every call to the thread answers talk_off:

Run
grep talk services/translation.ts
magic values: none
  talk: false,
Call
→ POST /jobs/job_7Hq2/messages {"text":"Will it read formal or informal?"}
magic values: none
← 403 talk_off: Translation has no thread: its talk is off, so nothing reaches the client through job_7Hq2.

On the Service

KeyWrittenLeft outDoes
talkTalk | falseon, the AI answering the clientThe job's thread: the AI answers the client in it, and holds an answer in an advice class the rules reserve until a member of adopt adopts it. false turns it off.

talk, written out

KeyWrittenLeft outDoes
with'client' | 'buyer' | <party input>the clientWho the AI talks with: the client, the buyer, or a party input.
by'ai' | RoleWord'ai'Who answers: 'ai', or a role whose member sends every answer the AI drafts.
adoptRoleWord | AdoptnoneWho adopts held advice before it goes out: a role, or { role, same, pay, max }. By default it goes to the member who claimed the reserved step of the same act class, included in their act fee, up to max answers (3), after which the client is offered a paid follow-up; advice that comes before the step is claimed waits in its packet. check needs it when the rules have an advice row.

Three voices use the thread: the AI, a member with a claimed Task, and the party the job is for. Here the client asks and the AI answers; a member's voice comes below. The record names each message by the hash of its words, and the words stay in the thread, which is client file.

Call
→ GET /matters/job_7Hq2/messages
200 · magic values: client.email = signs@example.test, metadata.sandbox.talk = open
  msg_7Hq2 · the client · message · sent: When will my review be ready?
  msg_7Hq3 · AI · answer · sent: Simulated: what the record shows about "When will my review be ready?". The job waits at advise.

Each AI answer is classified against the rules rows whose kind is 'advice'. An answer in an open class goes out at once. An answer in a reserved-advice class is held as a proposal. The client reads that a professional is reviewing it, and nothing else:

Call
→ POST /matters/job_7Hq2/messages {"text":"Should I sign this lease?","metadata":{"sandbox":{"talk":"advice"}}}
magic values: client.email = signs@example.test, metadata.sandbox.talk = advice
← 200 msg_7Hq3 · AI · answer · held · advice: A professional is reviewing this answer.
  14:02 · task.posted · tsk_7Hq3 · $0.00 · dispatch direct · to mbr_7Hq2
  14:02 · task.offered · tsk_7Hq3 · mbr_7Hq2 · client

The held answer is a Task for the role in adopt: by default the member on the reserved step, included in their fee. They read the draft in the packet and adopt it, as written or edited. It goes out under their name, credential and region, and the record says so:

Call
→ POST /reviews/tsk_7Hq3/attest {}   # as mbr_7Hq2: adopts it as written
magic values: client.email = signs@example.test, metadata.sandbox.talk = advice
← 200 tsk_7Hq3 · done · attorney · advise · US-CA · $0.00 · attested · no estimate · due 2026-10-27 14:02
  14:02 · message.adopted · msg_7Hq3 · tsk_7Hq3 · mbr_7Hq2 · bar admission · US-CA · rules 2026-10 · sha256:dbcc5338…
  14:02 · message.sent · msg_7Hq3 · advice advise · re msg_7Hq2 · mbr_7Hq2 · bar admission · US-CA · sha256:dbcc5338…

An agent can send a message over MCP and read the held status, but no tool adopts: adopting is a member's act, in their own session.

With no advice rows, talk is plain AI support. The rules table reserves the classes, so a Service meets licensing by design: advice reaches the client only from someone credentialed to give it.

Under posture self the client performs the reserved acts, so no member adopts. The AI answers with facts only, and the decision is theirs. Under full the same question is held. When the client does it themselves has the posture.

Call
→ POST /matters/job_7Hq2/messages {"text":"Should I file?","metadata":{"sandbox":{"talk":"advice"}}}
magic values: client.email = signs@example.test, metadata.sandbox.talk = advice
← 200 msg_7Hq3 · AI · answer · sent · facts: Simulated, facts only: what the record shows about "Should I file?". You perform file yourself, so the decision is yours.
  held for no one: no member adopts under self
Call
→ POST /matters/job_7Hq3/messages {"text":"Should I file?","metadata":{"sandbox":{"talk":"advice"}}}
magic values: client.email = signs@example.test, metadata.sandbox.talk = advice
← 200 msg_7Hq5 · AI · answer · held · advice: A professional is reviewing this answer.
  held for the attorney

A member with a claimed Task can ask the party a question, an answer ask that pauses the step's deadline and the Task's hold until the party replies through their own link:

Call
→ POST /reviews/tsk_7Hq2/messages {"kind":"question","text":"Is the lease you sent the signed copy? Write me at grace@firm.example."}   # as mbr_7Hq2
magic values: none
← 200 msg_7Hq2 · the attorney · question · open: Is the lease you sent the signed copy? Write me at [email withheld: reply in this thread].
  14:02 · job.paused · on the client's reply · advise · tsk_7Hq2

The party's reply lands in the same thread, and the hold moves on by the time it waited:

Call
→ POST /asks/ask_7Hq3?t=… {"answer":"Yes, signed on 1 March. Text me at (415) 555-0199."}
magic values: none
← 200 done
  20:02 · thread.posted · msg_7Hq3 · reply · re msg_7Hq2 · ask_7Hq3 · sha256:07bc91c3…

A note expects no reply and pauses nothing. A flag names a fact, such as a missed one, and raises it as a signal for you. It never blocks the job:

Call
→ POST /reviews/tsk_7Hq2/messages {"kind":"note","text":"Drafting your memo now. Call me on (415) 555-0100 if anything changes."}   # as mbr_7Hq2
magic values: client.email = signs@example.test
← 200 msg_7Hq2 · the attorney · note · sent: Drafting your memo now. Call me on [phone withheld: reply in this thread] if anything changes.
  14:02 · thread.posted · msg_7Hq2 · note · tsk_7Hq2 · mbr_7Hq2 · sha256:91b14d90…
Call
→ POST /reviews/tsk_7Hq2/messages {"kind":"flag","signal":"missed_fact","text":"The lease names no end date."}   # as mbr_7Hq2
magic values: client.email = signs@example.test
← 200 msg_7Hq3 · the attorney · flag · sent · missed_fact: The lease names no end date.
  14:02 · thread.posted · msg_7Hq3 · flag · tsk_7Hq2 · mbr_7Hq2 · missed_fact · sha256:79e32216…
  14:02 · signal.raised · missed_fact · person · flagged by the attorney on tsk_7Hq2

Contact is relayed only through the thread: an email address or a phone number is withheld before anyone reads it, either way. A member without the Task can't post:

Call
→ POST /reviews/tsk_7Hq2/messages {"kind":"note","text":"Hello from another attorney."}   # as mbr_eligible, without the Task
magic values: client.email = signs@example.test
← 403 not_cleared: Only the signer who claimed and cleared tsk_7Hq2 reaches its client, through the thread.
OpAPICLIMCPSDKSiteDocsApp
task.messagePOST /tasks/:id/messagestasks message <task> <kind> <text>never: a person actsnever: a person acts6 error pagesdelivery portal
job.messagePOST /jobs/:id/messagesjobs message <job> <text>send_job_messagejobs.message()4 error pagescustomer portal
job.threadGET /jobs/:id/messagesjobs messages <job>list_job_messagesjobs.messages()2 error pagescustomer portal

Objects: msg_. Events: thread.posted, message.sent.

Operations has every column of every row.

The client's message, four ways:

npx services.dev matters message job_7Hq2 "When will my review be ready?"
msg_7Hq3 · AI · answer · sent: Simulated: what the record shows about "When will my review be ready?". The job waits at advise.
curl -X POST http://localhost:8787/matters/job_7Hq2/messages -H 'content-type: application/json' -d '{"text":"When will my review be ready?"}'
← 200 · msg_7Hq3 · AI · answer · sent: Simulated: what the record shows about "When will my review be ready?". The job waits at advise.
→ tools/call send_matter_message {"id":"job_7Hq2","text":"When will my review be ready?"}
← msg_7Hq3 · AI · answer · sent: Simulated: what the record shows about "When will my review be ready?". The job waits at advise.
await legal.matters.message("job_7Hq2", {"text":"When will my review be ready?"})
← msg_7Hq3 · AI · answer · sent: Simulated: what the record shows about "When will my review be ready?". The job waits at advise.

The customer portal shows the thread beside the job, and the delivery portal's "Ask the client" screen posts a member's question: The customer portal and The delivery portal.

CodeCategoryHTTPWhy
talk_offblocked403This Service's jobs have no thread: its talk is off.
not_clearedblocked403The packet opens only to the member who claimed the Task and cleared it.
question_openconflict409A question to the client is out already, and their reply comes through the thread.
needs_the_partyblocked403Only the party can do this, through their own link.
job_endedconflict409The job has ended, for good.
check gateWhy
advice_without_adoptThe rules reserve an advice class for the Service, and its talk names no role from a Pool that holds the row's credential to adopt held answers.

Each legal Service whose rules reserve advice names its adopter in one line:

Run
grep talk services/contract-review.ts
magic values: client.email = signs@example.test, metadata.sandbox.talk = advice
  talk: { adopt: 'attorney' },

check reads the line back for each, with the advice classes held and who adopts them. Contract review and company formation reserve advice; the other three Services have plain AI support:

Run
npx services.dev check
magic values: member = mbr_eligible
  talk    the AI answers the client · advice (advise, rules 2026-10) held for the attorney on advise, included, up to 3 answers · contact relays through the thread
  talk    the AI answers the client · advice (advise_entity_choice and advise_83b, rules 2026-10) held for the attorney on advise_entity and advise_83b, included, up to 3 answers · contact relays through the thread