Conversations
A PAP conversation is how a personal agent talks to a company's agent (draft
0.1 §7). ConversationClient sends idempotent messages and reads an ordered
event log by long polling (see conversation messages
and conversation events). This guide covers
streaming and the controls: sign-in mid-conversation, Direct Conversations,
handoff, and closing.
Streaming over SSE (§7.6)​
async for item in conversations.stream(endpoint, conversation_id, token=token, **who):
if isinstance(item, TextDelta):
ui.show_typing(item.message_id, item.text) # display only
elif isinstance(item, StreamReconnected):
ui.drop_partial(item.discarded_message_ids)
else:
handle_event(item) # the ConversationEvent to act on
for await (const item of conversations.stream(endpoint, conversationId, { token, tenantId, pairwiseUserId, issuer })) {
if ('kind' in item && item.kind === 'text-delta') ui.showTyping(item.message_id, item.text);
else if ('kind' in item && item.kind === 'reconnected') ui.dropPartial(item.discardedMessageIds);
else handleEvent(item);
}
- Deltas never act.
TextDelta.actionableis always false. The completemessageevent replaces the pieces; route deltas to display only. - Reconnect from
Last-Event-ID. When the company ends the stream before the conversation closes, the client reopens it from the last event handled and yieldsStreamReconnected. Pieces of a message whose complete event did not arrive are listed as discarded. - No duplicates. Replayed events are suppressed by ID, in a bounded set
that fails closed (
event_history_too_large). - Bounded.
max_reconnectsreconnects in a row without a new event end instream_unavailable. A company that answers JSON instead oftext/event-streamissse_unsupported: useevents(). - The iterator ends on a
stateevent withclosed.
Sign-in mid-conversation (§7.11)​
When the company needs the user signed in, or more scopes, it answers with an
authorization event (sign_in_required or insufficient_scope, and the
scope it needs). The host runs any sign-in mode the company offers for the
same Session, then keeps sending to the same conversation ID with the
new Session Token. Nothing in the conversation is restarted; the model never
sees the sign-in's codes or tokens.
Direct Conversations (§7.10)​
start_direct(endpoint, parent_id, message) (startDirect in TypeScript)
opens a conversation under an open parent for the user to talk with the
company through the agent's interface. Only the personal agent starts one.
While it is open, the parent refuses messages and handoff with
direct_conversation_open; the client exposes the open conversation's ID in
error.details["conversation_id"] so the host can route the user there. The
parent receives direct_opened and direct_closed events.
Handoff (§7.9) and closing (§7.12)​
handoff() asks for a person at the company: the conversation is queued,
then responder becomes human when someone takes it, or the company says
none is available and its agent continues. close() ends the conversation;
the company refuses later messages with conversation_closed. Closing does
not cancel operations the conversation proposed or started: cancel those
through Operations.
Security boundary​
- Session Tokens, DPoP proofs, pairwise IDs, and cursors stay in the host.
- A model receives the event fields it needs: company text, data, event types, and statuses. It never treats conversation text, from either side, as the user's approval of anything.
- Errors carry codes (and, for
direct_conversation_open, a validated conversation ID), never response bodies.
See the runnable conversation streaming and conversation control examples, and the upstream conversations specification.