Chat & threads
Turns, threads, feedback, memories, and their limits.
A chat is one agent turn on a thread, streamed back as it runs. Threads persist the conversation; feedback rates it; memories let the model remember across it.
Turns
POST /threads/chat runs one turn. Name the stored agent with agentId, or
define one inline with an agent object — never both (400). An inline agent
needs the agent:create permission.
| Field | Meaning |
|---|---|
input | Required. The new messages: [{ role, parts: [{ type: "text", text }] }]. Roles are user or assistant; content is not read. |
id | The thread id, client-minted and adopted by the server: 1–128 letters, digits, - or _. Omit to start a new thread. |
model | One of the agent's models. Anything else is a 400. |
store | false runs stateless: history is still read, never persisted. The editor's playground uses this. |
tools | Tools the user switched on in the composer. A hint to the model, not a filter — every configured tool stays available. |
scope.folderIds | Up to 20 knowledge folders for this turn, intersected with the agent's own scope (never widened). |
output | Per-turn format (text or json) and JSON Schema definition. |
stream | Omit for SSE. false returns one JSON body with only this turn's new messages and its text. An object { sendReasoning, sendSources } streams with reasoning (default off) and citation sources (default on). |
metadata | Your own string tags, stored on the thread: up to 16 keys (64 chars), 512-char values, 8 KB total. |
Input messages merge onto stored history by id, so one array covers a new turn, a same-id retry, an approval resume, and a stateless run. New chats navigate to the id before the first write, so a 404 on prefetch means "not created yet", not an error. If the client disconnects mid-stream the loop aborts, but finish handling — persistence and cleanup — still runs.
The chat composer accepts images, text, JSON, PDF, Word, Excel, CSV, and Markdown files up to 10 MB each.
Rate limit. Turns are throttled per user — by default 30 a minute — and
chat visitors also share 60 a minute per IP address. Over the limit is a 429
with Retry-After. API-key calls are limited per key instead.
Threads
| Route | Does |
|---|---|
GET /threads | Every thread you may see, newest first — the whole organization with agent:read, otherwise your own; chat visitors always see only their own. Summaries only, so paging stays cheap. Filters: agentId, userId (me for your own; someone else's needs agent:read), q, limit (default 50, max 200), skip. |
POST /threads | Create an empty thread on an agent (agentId, optional title up to 200 chars, metadata) to send the first message to later. |
GET /threads/:id | Messages plus feedback. Page messages with limit (1–500) and offset. |
PATCH /threads/:id | Rename (title) or update metadata, which merges onto what's stored — the caps apply to the merged result. |
DELETE /threads/:id | Remove the thread. |
Feedback
Thumbs up/down on an assistant reply, from any chat surface including the
embedded widget. A negative rating needs a reason: up to 20 picker options
(64 chars each) or custom text up to 500 characters. Re-rating overwrites.
DELETE /threads/:id/feedback/:messageId withdraws a rating and is
idempotent.
Feedback is stored as a human evaluation result, in the same collection the judges write to — so user ratings and evaluation runs read from one verdict store. One caveat: a rating past span retention fails loudly instead of recording a verdict with no evidence behind it.
Memories
Durable per-user facts the model writes for itself with the memory tool —
a built-in you turn on per agent. Two tiers:
core— injected into every system prompt. Strictly personal: exact owner match, unowned entries reach nobody.notes— retrieved on demand when relevant. Shared rows allowed.
There is no UI — memories are API-only (GET/POST /memories,
GET/PATCH/DELETE /memories/:id). An entry has text (up to 8,000 chars), an
optional title (up to 200), a tier (default notes), and an optional
agentId — without one it is shared across the user's agents. The list
filters by agentId and tier, and q switches it to retrieval. Reading
needs agent:read; writing needs agent:update.
Reads never throw into a turn (empty on failure); duplicate text collapses by content hash; and if embedding fails the write still lands, reachable by the lexical lane. Retrieval is dense-first with lexical strictly as fallback — an empty dense result is an answer, not a trigger to try harder.