Skip to content

Conversations and concerns

A conversation is one participant's private exchange with Voice. Every message, reply and proposal in it is sealed under the participant's key and shown to no one else. Each message is a turn that runs off the request path, and the turn's result is read afterwards.

Start a conversation

curl -s -X POST localhost:9000/me/conversations -H "Authorization: Bearer $ME"
{ "id": "cnv-01M5…", "messages": [], "proposals": [], "confirmed": [] }

GET /me/conversations lists the participant's conversations, and GET /me/conversations/{conversation} answers one with its messages, proposals and confirmed concern ids. A conversation that belongs to another participant answers 403 and conversation.not-owned.

Send a message

curl -s localhost:9000/me/conversations/$CNV/messages -H "Authorization: Bearer $ME" \
  -H 'Content-Type: application/json' \
  -d '{"text":"The Route 14 school bus stopped in March and my children now walk the ring road."}'
{ "turn": "cnv-01M5…:turn-1", "message": { "seq": 1, "author": "citizen", "at": "…" } }

The reply is 202: the message is recorded and the turn has started. A message with no text, or more than 4,000 characters, is refused with 422 and conversation.message.empty. Text that contains instructions aimed at the system is content: the model is told so, nothing is obeyed, and no state changes beyond the message being recorded.

Read the turn

Poll the turn until it is no longer thinking:

curl -s localhost:9000/me/conversations/$CNV/turns/cnv-01M5…:turn-1 -H "Authorization: Bearer $ME"
{ "state": "Replied", "reply": "That sounds like a real change to the school run. Is it the morning walk, the afternoon, or both?", "proposal": null }

A turn ends in one of three states: Thinking, Replied with the reply and, when Voice could tell what the problem is, a proposal, or CouldNotInterpret with a message for the participant. On each turn Voice does exactly one of two things: asks one short clarifying question, which proposes no position, cause or culprit and may be skipped, or proposes a structured concern.

Or hold the stream, which sends every message, proposal and turn as it is recorded:

curl -s -N 'localhost:9000/me/conversations/'$CNV'/stream?after=0' -H "Authorization: Bearer $ME"
data: {"type":"message","data":{"seq":2,"author":"voice","at":"…"}}
data: {"type":"turn","data":{"turn":"cnv-01M5…:turn-1","state":"Replied"}}

The gateway closes a stream after fifteen seconds. Reconnect with after set to the last sequence seen; nothing is missed, because every event is also on the conversation.

The proposal

A proposal is Voice's structured reading of the concern, offered to the participant:

{
  "id": "prp-01M5…",
  "summary": "The Route 14 school bus was withdrawn and children now walk along the ring road to school.",
  "elements": [
    { "kind": "Problem", "text": "the school bus was withdrawn", "sourceSpans": [ { "messageSeq": 1, "from": 4, "to": 41 } ] },
    { "kind": "Where", "text": "Route 14; the ring road", "sourceSpans": [ … ] },
    { "kind": "Impact", "text": "children walk the ring road", "sourceSpans": [ … ] }
  ],
  "confidence": "Medium",
  "suggestedIssues": [ { "issue": "iss-01M57P…", "title": "Rural school bus withdrawal" } ],
  "editedByCitizen": false
}

Each element names the passage of the participant's own message it came from, so the participant can see what Voice read and where. The summary adds nothing the participant did not say. The suggested issues are the existing issues the concern may join; a confirmation suggests, it never joins one by itself.

Edit, confirm or discard

curl -s localhost:9000/me/conversations/$CNV/proposals/$PRP/edit -H "Authorization: Bearer $ME" \
  -H 'Content-Type: application/json' -d '{"summary":"…","elements":[…]}'
curl -s -X POST localhost:9000/me/conversations/$CNV/proposals/$PRP/confirm -H "Authorization: Bearer $ME"
{ "id": "cnc-01M5…", "conversation": "cnv-01M5…", "proposal": "prp-01M5…", "summary": "…", "elements": [ … ], "contributions": [] }

A confirmed proposal is a concern, in the participant's edited meaning: a statement by one person, never a fact about the world. It is bound to the message sequence numbers it was extracted from, so a later message does not change it, and a confirm that names a proposal made from messages the participant has since edited is refused with 409 and proposal.stale. Confirming twice answers 409 and proposal.already-confirmed; a discard removes the proposal from the conversation and records nothing else.

GET /me/concerns lists the participant's concerns with the states of their contributions. A concern is still private: nothing is shared until Sharing and review.

Without a model

With no ANTHROPIC_API_KEY, the message is recorded and the turn ends in CouldNotInterpret. The test suites run every conversation against a recorded provider, which answers from fixtures matched on the message's text; Testing says how to record new ones.