Conversations

The universal substrate for dialogue — human↔human, human↔agent, and agent↔agent — with identity, permissions, audit, paginated history, agent-readable notes, and real-time streaming built in.

Key Concepts

Conversations models every kind of dialogue with the same primitives, so an agent chat, a threaded email, and a multi-party group all share one API, one permission model, and one audit trail. Conversation documents use folder-scoped Buckets search, so retrieval cannot walk into sibling conversations or unrelated folders.

Projects & Conversations

A conversation is a stateful thread of messages between participants. Conversations can be grouped into projects for topic organization. Every conversation has a channel (e.g. direct, widget, email, or whatsapp) and a type that reflects its participant mix.

The inbox can filter by transport. WhatsApp conversations preserve structured contacts, locations, and media, show the live customer-care session countdown, and project delivery or failure status onto each outbound message. Retry is available only for retryable failures.

Participants

Each participant is a first-class principal — user, agent, operator, or contact. The set of human participants (everything but agents) is what privacy rules are evaluated against. Participants can leave a conversation; a left_at timestamp preserves the audit trail without granting ongoing access.

Messages

A message carries a role, optional text content, rich content_blocks, attachments, and — for agent turns — tool-call metadata. A kind distinguishes triggering messages (message) from non-triggering ones (progress, thinking).

Notes

A background worker periodically summarizes new stretches of a conversation into structured notes — topic, what happened, decisions, open questions, and entities, each stamped with the message window it covers. Notes give agents a compact, searchable memory of long threads without replaying every raw message.

Conversation Lifecycle

Create a conversation, add participants and messages, then read it back. Fetching the conversation returns its metadata and participants; message history is loaded separately through the paginated messages endpoint (see below).

# Create a conversation
curl -X POST https://platform.ergondata.ai/api/v1/conversations/conversations \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "direct",
    "title": "Q3 planning",
    "participants": [
      { "principal_type": "user", "principal_id": "{user_id}" },
      { "principal_type": "agent", "principal_id": "{agent_id}" }
    ]
  }'

# Fetch metadata + participants (no message payload)
curl "https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}?include_messages=false" \
  -H "Authorization: Bearer {token}"

GET /conversations/{id} returns an empty messages array by default. Pass include_messages=true only for small threads; for anything real, page through /conversations/{id}/messages.

Channel Mode

To open an outbound email thread, create the conversation with channel="email", a from_channel_address_id, to[], and an optional first_message. Non-workflow create and reply operations resolve the address through Channels and require channels:addresses:send.

Inbound email is bound one-to-one to a conversation by its Channels thread. A new conversation is created only when the inbound route snapshot has at least one usable notify route or auto-reply route. Notify teams expand to human participants; at most one auto-reply agent is selected.

Messages & Pagination

Message history is served through a keyset-paginated endpoint. Results are always oldest-first within a page; you walk backwards through history with the before cursor and catch up on new messages with after. Each response includes has_more plus oldest_cursor / newest_cursor message ids to use as cursors.

# Latest page (newest 50, returned oldest-first)
curl "https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/messages?limit=50" \
  -H "Authorization: Bearer {token}"

# Older page: pass the previous page's oldest_cursor as `before`
curl "https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/messages?limit=50&before={oldest_cursor}" \
  -H "Authorization: Bearer {token}"

# Time-ranged + skip heavy tool payloads
curl "https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/messages?since_ts=2026-06-01T00:00:00Z&include_tool_payloads=false" \
  -H "Authorization: Bearer {token}"
  • limit — page size (default 50, max 200; clamped server-side).
  • before / after — a message id cursor, resolved within the conversation. An unknown cursor returns 404 rather than silently falling back to the latest page.
  • since_ts / until_ts — inclusive time bounds (naive timestamps are treated as UTC).
  • include_tool_payloads — set to false to null out heavy tool_result / tool_args / content_blocks fields for a lighter scan.

Notes, Digests & Starters

Whenever an agent participates, a background worker automatically summarizes new windows of the conversation into structured notes. Each note records a topic, what_happened, decisions, open_questions, and entities, plus the window_start / window_end message range it covers.

Notes are searchable with hybrid lexical and semantic ranking plus time-window filters. Agents use this same endpoint (as the conversations.notes.search tool) to recall earlier context, and the agent runtime automatically swaps old note-covered raw messages for a compact summary when a thread grows past its context budget.

Completed notes are folded into one living digest that captures the thread's current state without discarding the underlying note history. Agents can offer conversation starters from their runtime configuration (off, custom presets, or inferred from authorized participant-scoped digest context with an optional custom fallback list).

# Search notes by keyword within a time window
curl "https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/notes?q=billing&since_ts=2026-06-01T00:00:00Z&limit=20" \
  -H "Authorization: Bearer {token}"

Notes point back at the exact message window they summarize. To drill in from a note, call the messages endpoint with the note's window_start / window_end as since_ts / until_ts.

Privacy & Note-Taking

Every conversation has a privacy_level of private (default) or public. Privacy governs whether an agent may surface this conversation's content into a different conversation it is acting in. Private content may only be surfaced when every human participant of the source is also present in the destination (destination-subset):

surface(source → acting) allowed iff humans(source) ⊆ humans(acting)

In-thread reads are never restricted, and public conversations fall back to normal permission checks. This is enforced by the agent runtime and, as defense-in-depth, by the Conversations service itself. The same destination-subset rule also gates cross-conversation writes: an agent may only post into (or add participants to) another private thread when that thread's humans are a subset of the acting conversation's — otherwise the write is refused before it happens.

Always-public conversations & the privacy lock

All-agent conversations (every participant is an agent) are forced public — there are no humans to scope to — and attempts to set them private are rejected. An individual agent can also be privacy-locked (an always-public toggle governed by agents:agents:privacy:manage); every conversation a locked agent joins is forced public. This keeps autonomous agent activity observable rather than hidden.

Granular read permissions

Beyond full thread access, two finer grants exist. conversations:notes:view lets a principal read a conversation's notes without seeing every message — useful for observers who only need the summarized memory. agents:agents:conversations:view (on an agent) grants derived read-only access to that agent's public conversations even without a direct grant on the thread, so a supervisor watching an agent can follow its public dialogues.

Agent-to-agent dialogue & the pause signal

Autonomous agent-to-agent (A2A) dialogue is off by default and enabled per company via the A2A settings. When enabled, an agent-sent message is relayed only to the other agent participants (never back to the sender), bounded by two loop-safety budgets: a cap on consecutive agent turns and a rate cap on agent messages per rolling window. Both are clamped to platform hard limits. When a budget is hit, the thread receives a single non-triggering status message ("Agent-to-agent exchange paused — waiting for a human.") and the exchange stops; a human reply resets the budget and the agents can resume.

Automatic note-taking

Notes run automatically whenever an agent participates in a conversation. There is no enablement setting at the company or conversation level. Cadence controls how often a note is cut — first-to-fire across every_messages, every_minutes, and every_tokens, with a min_messages floor.

# Make a conversation public and override its note cadence
curl -X PATCH https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id} \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "privacy_level": "public",
    "note_taking_cadence": { "every_messages": 15 }
  }'

The company-wide default cadence is managed under IAM settings via the company note-taking settings endpoints. A conversation can override that cadence; clearing the override restores the company default.

Real-Time Stream

Subscribe to a conversation's WebSocket stream to receive new messages, streaming agent tokens, turn-completion events, and message.updated attachment progress as they happen. Inbound attachment refs expose status and process_skip_reason. Combine the stream with paginated messages for backfill.

const ws = new WebSocket(
  `wss://platform.ergondata.ai/api/v1/conversations/ws/conversations/${conversationId}/stream?token=${token}`
);

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  // e.g. "message.created", "message.updated", "token", "done"
  console.log(msg.type, msg);
};

Ready to build? See the Conversations API Reference for every endpoint, or the Agent Hub guide for how agents read and write conversations.