---
title: "Talk"
url: https://services.dev/docs/service/talk
captured: [ask-the-client.txt, legal-walk.txt, talk-default.txt, talk-held.txt, talk-member.txt, talk-self.txt, talk-tabs.txt, talk.txt]
codes: [job_ended, needs_the_party, not_cleared, question_open, talk_off]
---
# Talk

URL: https://services.dev/docs/service/talk

> 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?

## Smallest form

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

```bash
cat services/translation.ts
```

```txt title="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,
})
```

```txt
→ POST /jobs/job_7Hq2/messages {"text":"Will it read formal or informal?"}
```

```txt title="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`:

```bash
grep talk services/translation.ts
```

```txt title="magic values: none"
  talk: false,
```

```txt
→ POST /jobs/job_7Hq2/messages {"text":"Will it read formal or informal?"}
```

```txt title="magic values: none"
← 403 talk_off: Translation has no thread: its talk is off, so nothing reaches the client through job_7Hq2.
```

## Every form

**On the Service**

| Key | Written | Left out | Does |
|---|---|---|---|
| `talk` | `Talk \| false` | on, the AI answering the client | The 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**

| Key | Written | Left out | Does |
|---|---|---|---|
| `with` | `'client' \| 'buyer' \| <party input>` | the client | Who 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. |
| `adopt` | `RoleWord \| Adopt` | none | Who 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, one thread

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](#a-members-question-note-and-flag). The record names each message by the hash of its words, and the words stay in the thread, which is client file.

```txt
→ GET /matters/job_7Hq2/messages
```

```txt title="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.
```

## The AI answers, unless your rules reserve the answer

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:

```txt
→ POST /matters/job_7Hq2/messages {"text":"Should I sign this lease?","metadata":{"sandbox":{"talk":"advice"}}}
```

```txt title="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:

```txt
→ POST /reviews/tsk_7Hq3/attest {}   # as mbr_7Hq2: adopts it as written
```

```txt title="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](https://services.dev/docs/people/rules.md) reserves the classes, so a Service meets licensing by design: advice reaches the client only from someone credentialed to give it.

## Facts only, when the client does it themselves

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](https://services.dev/docs/people/self.md) has the posture.

```txt
→ POST /matters/job_7Hq2/messages {"text":"Should I file?","metadata":{"sandbox":{"talk":"advice"}}}
```

```txt title="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
```

```txt
→ POST /matters/job_7Hq3/messages {"text":"Should I file?","metadata":{"sandbox":{"talk":"advice"}}}
```

```txt title="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's question, note and flag

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:

```txt
→ 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
```

```txt title="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:

```txt
→ POST /asks/ask_7Hq3?t=… {"answer":"Yes, signed on 1 March. Text me at (415) 555-0199."}
```

```txt title="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:

```txt
→ POST /reviews/tsk_7Hq2/messages {"kind":"note","text":"Drafting your memo now. Call me on (415) 555-0100 if anything changes."}   # as mbr_7Hq2
```

```txt title="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…
```

```txt
→ POST /reviews/tsk_7Hq2/messages {"kind":"flag","signal":"missed_fact","text":"The lease names no end date."}   # as mbr_7Hq2
```

```txt title="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:

```txt
→ POST /reviews/tsk_7Hq2/messages {"kind":"note","text":"Hello from another attorney."}   # as mbr_eligible, without the Task
```

```txt title="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.
```

## Seven surfaces

| Op | API | CLI | MCP | SDK | Site | Docs | App |
|---|---|---|---|---|---|---|---|
| `task.message` | `POST /tasks/:id/messages` | `tasks message <task> <kind> <text>` | never: a person acts | never: a person acts |  | 6 error pages | delivery portal |
| `job.message` | `POST /jobs/:id/messages` | `jobs message <job> <text>` | `send_job_message` | `jobs.message()` |  | 4 error pages | customer portal |
| `job.thread` | `GET /jobs/:id/messages` | `jobs messages <job>` | `list_job_messages` | `jobs.messages()` |  | 2 error pages | customer portal |

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

[Operations](https://services.dev/docs/reference/operations.md) has every column of every row.

The client's message, four ways:

**CLI**

```bash title="CLI"
npx services.dev matters message job_7Hq2 "When will my review be ready?"
```

```txt title="magic values: client.email = signs@example.test"
msg_7Hq3 · AI · answer · sent: Simulated: what the record shows about "When will my review be ready?". The job waits at advise.
```

**API**

```bash title="API"
curl -X POST http://localhost:8787/matters/job_7Hq2/messages -H 'content-type: application/json' -d '{"text":"When will my review be ready?"}'
```

```txt title="200 · msg_7Hq3 · AI · answer · sent: Simulated: what the record shows about "When will my review be ready?". The job waits at advise. · magic values: client.email = signs@example.test"
← 200 · msg_7Hq3 · AI · answer · sent: Simulated: what the record shows about "When will my review be ready?". The job waits at advise.
```

**MCP**

```txt title="MCP"
→ tools/call send_matter_message {"id":"job_7Hq2","text":"When will my review be ready?"}
```

```txt title="magic values: client.email = signs@example.test"
← msg_7Hq3 · AI · answer · sent: Simulated: what the record shows about "When will my review be ready?". The job waits at advise.
```

**SDK**

```ts title="SDK"
await legal.matters.message("job_7Hq2", {"text":"When will my review be ready?"})
```

```txt title="magic values: client.email = signs@example.test"
← 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](https://services.dev/docs/app/buyers.md) and [The delivery portal](https://services.dev/docs/app/members.md).

## Refusals

| Code | Category | HTTP | Why |
|---|---|---|---|
| [`talk_off`](https://services.dev/docs/reference/errors.md#talk_off) | blocked | 403 | This Service's jobs have no thread: its talk is off. |
| [`not_cleared`](https://services.dev/docs/reference/errors.md#not_cleared) | blocked | 403 | The packet opens only to the member who claimed the Task and cleared it. |
| [`question_open`](https://services.dev/docs/reference/errors.md#question_open) | conflict | 409 | A question to the client is out already, and their reply comes through the thread. |
| [`needs_the_party`](https://services.dev/docs/reference/errors.md#needs_the_party) | blocked | 403 | Only the party can do this, through their own link. |
| [`job_ended`](https://services.dev/docs/reference/errors.md#job_ended) | conflict | 409 | The job has ended, for good. |

| `check` gate | Why |
|---|---|
| [`advice_without_adopt`](https://services.dev/docs/reference/check/advice_without_adopt.md) | The 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. |

## In the example brands

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

```bash
grep talk services/contract-review.ts
```

```txt title="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:

```bash
npx services.dev check
```

```txt title="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
```

---

- Up: [Service()](https://services.dev/docs/service.md)
- Previous: [Schedules and triggers](https://services.dev/docs/service/subscriptions.md)
- Next: [The record](https://services.dev/docs/service/record.md)
