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, or an email thread) and a type that reflects its participant mix.
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",
"starter_mode": "infer",
"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
falseto null out heavytool_result/tool_args/content_blocksfields 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. New conversations support starter_mode:user-talks-first,preset, orinfer. Presets are deterministic; inferred starters are generated from authorized participant-scoped digest context.
# 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: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: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.