Skip to content

Agent chat

Talk to one agent, or let Subsidia pick the best-fit agent: multi-turn sessions, knowledge citations, tool calls, structured JSON and a server-sent event stream.

Agent chat is the endpoint family for conversations with a persona: the agent answers in character, remembers past sessions, reads the documents linked to it, calls your HTTP tools when it needs live data, and can ask a colleague for a second opinion. It is the right choice when your application is built around a specific agent. If you only want to point an existing OpenAI or Anthropic client at an agent, use the Gateway with the model name agent:<slug> instead.

There are two styles, each with a plain and a streamed form:

StylePlainStreamedUse when
A specific agentPOST /v1/agents/:id/chatPOST /v1/agents/:id/chat/streamYour application knows which agent should answer.
Auto-pickPOST /v1/agents/chatPOST /v1/agents/chat/streamYou have several agents and want the best fit for each message, for example behind a single input box.
  1. Message
  2. Session + history
  3. Memory recall
  4. Knowledge retrieval
  5. Tools + consult
  6. Answer
  7. Memory, facts, proof
One turn. The steps with a dashed role (knowledge, tools, consult) are skipped when the corresponding block is disabled or the agent has nothing linked.

Authentication, scopes and billing

Use a developer API key (Authentication). On a restricted key, these routes need the agents scope alongside your capability scope; a readonly key is refused because chatting writes (history, memory). Each chat route is also limited to 30 calls per minute per client, and a key can carry its own per-minute limit and monthly ceiling (Rate limits and quotas).

Each turn is metered against the workspace allowance of questions on the model usage of the whole turn (retrieval, tool rounds and consultations included), and an exhausted allowance is a 402 before any model runs (Rate limits and quotas).

A first call

  1. 1
    Pick an agent

    List your agents with GET /v1/agents and keep the id, or create one.

  2. 2
    Send a message

    Without a sessionId, a new session is created for you and its id is returned.

    bash
    curl https://api.subsidia.protypa.fr/v1/agents/$AGENT_ID/chat \
    -H "Authorization: Bearer $SUBSIDIA_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
    "message": "What is the Q3 invoice total, and is the file complete?",
    "context": [
    { "category": "client", "label": "Client record", "data": { "name": "Atelier Moreau", "plan": "monthly" }, "priority": "high" }
    ],
    "knowledge": { "topK": 5 }
    }'
  3. 3
    Continue the conversation

    Send the returned sessionId back. The agent sees the last 20 messages of the session, so it can answer "and the previous quarter?" without being told what "it" refers to.

    bash
    curl https://api.subsidia.protypa.fr/v1/agents/$AGENT_ID/chat \
    -H "Authorization: Bearer $SUBSIDIA_API_KEY" -H "Content-Type: application/json" \
    -d '{"message": "And the previous quarter?", "sessionId": "cm2kc4t9p000bqz0fr6s2a8dk"}'

Endpoints

POST/v1/agents/:id/chat

Send a message to one agent and get the full answer.

API key (Bearer or x-api-key)agents

Runs one turn with the agent in :id: recalls its memories, loads the session history, retrieves passages from its linked knowledge sources, lets it call its tools and consult colleagues, produces the answer, then saves the exchange, any new memories and the extracted facts. The call returns when the answer is complete; for long answers use the streamed form below.

Knowledge, tools and consultation are on by default, exactly like in the Subsidia app. An agent that has no linked sources or tools simply does not use them. Pass enabled: false in the matching block for an isolated call.

Path parameters

  • idstringrequired
    The agent id.

Request body

  • messagestringrequired
    What you say to the agent. Must be a non-empty string.
  • sessionIdstring
    Continue an existing session (Sessions). Omit it to start a new one: its id comes back in the response. A session that does not exist, or that was started by another user, is a 404 Session not found.
  • contextobject[]
    Structured data injected into the agent prompt for this call only (it is not stored). See Context injection.
  • extractionobject
    Extract structured facts from your message, alongside the answer. See Fact extraction.
  • optionsobject
    Generation settings.
  • responseFormatobject
    Force a structured JSON answer. See Structured JSON answers.
  • knowledgeobject
    Retrieval from the knowledge sources linked to the agent. On by default.
  • toolsobject
    The HTTP tools linked to the agent (Tools). On by default.
  • consultobject
    Let the agent ask other agents of the workspace for a second opinion mid-turn. See Consulting colleagues.

Request examples

curl https://api.subsidia.protypa.fr/v1/agents/$AGENT_ID/chat \
-H "Authorization: Bearer $SUBSIDIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "What is the Q3 invoice total, and is the file complete?",
"context": [
{ "category": "client", "label": "Client record", "data": { "name": "Atelier Moreau", "plan": "monthly" }, "priority": "high" }
],
"knowledge": { "topK": 5 }
}'

Responses

The turn. content is the answer; contentParsed is present only with responseFormat.type: "json" and valid JSON; thoughts only with options.reflection; knowledgeUsed, toolCalls and memoriesSaved are empty or absent when nothing was used or saved. tokenUsage is the model usage of the whole turn, internal steps included.

Example response
{
"agentId": "cm2k8x1ab0001qz0f7h3d9t4e",
"agentName": "Claire",
"agentRole": "Contrôle de gestion",
"content": "The Q3 invoice is 12 480 EUR excluding VAT [1]. It matches the purchase order, but the delivery note is missing from the file.",
"sessionId": "cm2kc4t9p000bqz0fr6s2a8dk",
"memoriesSaved": [
{ "key": "q3_invoice_total", "value": "Q3 invoice: 12 480 EUR excl. VAT.", "type": "fact", "provenance": "knowledge", "salience": 0.65 }
],
"extracted": [],
"tokenUsage": { "promptTokens": 2140, "completionTokens": 62, "totalTokens": 2202 },
"knowledgeUsed": [
{
"ref": 1,
"sourceId": "cm2k9aa7e0003qz0fh8w1p2lb",
"sourceName": "facture-q3-2026.pdf",
"section": "Totals",
"page": 2,
"chunkPreview": "Total HT : 12 480,00 EUR ...",
"similarity": 0.83,
"chunkId": "ck_8f1d2c40",
"collectionId": null,
"dossierName": null
}
],
"toolCalls": []
}

Errors

  • 401Missing, invalid, revoked or expired key.
  • 402API_KEY_BUDGET_EXCEEDEDThe key reached its own monthly question ceiling.
  • 403scope_deniedA restricted key without the agents scope.
  • 403read_only_keyThe key carries the readonly scope.
  • 429rate_limitedMore than 30 calls per minute on this route, or the key's own per-minute limit. Honour retry-after.

Notes

The plain response carries the fields listed above. The streamed form ends with a done event holding the complete result of the turn, which also includes proofId, consultations, mood, masked and contextUsed; use it when you need those.

POST/v1/agents/:id/chat/stream

Send a message to one agent and stream the answer.

API key (Bearer or x-api-key)agents streams (SSE)

Same request body and same turn as POST /v1/agents/:id/chat, answered as Server-Sent Events (text/event-stream). You receive tokens as they are produced, plus progress events for retrieval, tool calls and consultations, and a final done event with the complete result. See the event reference and the client examples.

The HTTP status is 200 as soon as the stream opens. Failures that happen after that point, including an unknown agent or session and an exhausted allowance, arrive as an error event, not as a 4xx. Only an empty message or a malformed body is rejected before the stream starts.

Path parameters

  • idstringrequired
    The agent id.

Request body

  • messagestringrequired
    What you say to the agent. Must be a non-empty string.
  • sessionIdstring
    Continue an existing session (Sessions). Omit it to start a new one: its id comes back in the response. A session that does not exist, or that was started by another user, is a 404 Session not found.
  • contextobject[]
    Structured data injected into the agent prompt for this call only (it is not stored). See Context injection.
  • extractionobject
    Extract structured facts from your message, alongside the answer. See Fact extraction.
  • optionsobject
    Generation settings.
  • responseFormatobject
    Force a structured JSON answer. See Structured JSON answers.
  • knowledgeobject
    Retrieval from the knowledge sources linked to the agent. On by default.
  • toolsobject
    The HTTP tools linked to the agent (Tools). On by default.
  • consultobject
    Let the agent ask other agents of the workspace for a second opinion mid-turn. See Consulting colleagues.

Request examples

curl
curl -N https://api.subsidia.protypa.fr/v1/agents/$AGENT_ID/chat/stream \
-H "Authorization: Bearer $SUBSIDIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "What is the Q3 invoice total?"}'

Responses

A text/event-stream: one data: line per event, each holding a JSON object { "type": ..., "data": ... }, blank-line separated. Comment lines : ping are sent every 15 seconds to keep proxies from closing an idle connection.

Example response
data: {"type":"stage","data":{"name":"prepare","phase":"start"}}
data: {"type":"stage","data":{"name":"retrieval","phase":"start"}}
data: {"type":"stage","data":{"name":"retrieval","phase":"end","ms":412}}
data: {"type":"knowledge","data":[{"ref":1,"sourceId":"cm2k9aa7e0003qz0fh8w1p2lb","sourceName":"facture-q3-2026.pdf","section":"Totals","page":2,"chunkPreview":"Total HT : 12 480,00 EUR ...","similarity":0.83,"chunkId":"ck_8f1d2c40"}]}
data: {"type":"tool_call","data":{"toolName":"crm_lookup","arguments":{"client":"Atelier Moreau"}}}
data: {"type":"tool_result","data":{"toolName":"crm_lookup","arguments":{"client":"Atelier Moreau"},"result":{"status":"active"},"success":true,"durationMs":182}}
data: {"type":"token","data":{"delta":"The Q3 invoice is "}}
data: {"type":"token","data":{"delta":"12 480 EUR excluding VAT [1]."}}
: ping
data: {"type":"mood","data":{"agentId":"cm2k8x1ab0001qz0f7h3d9t4e","name":"Claire","emotion":"focus","valence":0.2,"arousal":0.4,"intensity":0.5}}
data: {"type":"done","data":{"agentId":"cm2k8x1ab0001qz0f7h3d9t4e","agentName":"Claire","agentRole":"Contrôle de gestion","content":"The Q3 invoice is 12 480 EUR excluding VAT [1].","sessionId":"cm2kc4t9p000bqz0fr6s2a8dk","memoriesSaved":[],"extracted":[],"tokenUsage":{"promptTokens":2140,"completionTokens":14,"totalTokens":2154},"knowledgeUsed":[{"ref":1,"sourceName":"facture-q3-2026.pdf"}],"toolCalls":[{"toolName":"crm_lookup","success":true,"durationMs":182}],"proofId":"pf_a059713a5f1c4e0b8d7a3c21e9b64f10"}}

Errors

  • 401Missing, invalid, revoked or expired key.
  • 403scope_deniedA restricted key without the agents scope.
  • 429rate_limitedMore than 30 calls per minute on this route, or the key's own limit.

POST/v1/agents/chat

Let Subsidia pick the best-fit agent, then answer.

API key (Bearer or x-api-key)agents

Like POST /v1/agents/:id/chat without the agent. A lightweight planning step reads the message together with the name, role and mission of every agent of the workspace and routes to the single best fit; if it cannot decide, the oldest agent of the workspace answers. The response tells you who answered through agentId, agentName and agentRole. Keep using the returned sessionId and you stay in the same conversation, but note that the router re-evaluates every message: a follow-up may be routed to a different agent. For a stable conversation partner, take the agentId from the first answer and switch to the :id form.

The quality of the routing is the quality of your role and mission fields (Agents).

Request body

  • messagestringrequired
    What you say to the agent. Must be a non-empty string.
  • sessionIdstring
    Continue an existing session (Sessions). Omit it to start a new one: its id comes back in the response. A session that does not exist, or that was started by another user, is a 404 Session not found.
  • contextobject[]
    Structured data injected into the agent prompt for this call only (it is not stored). See Context injection.
  • extractionobject
    Extract structured facts from your message, alongside the answer. See Fact extraction.
  • optionsobject
    Generation settings.
  • responseFormatobject
    Force a structured JSON answer. See Structured JSON answers.
  • knowledgeobject
    Retrieval from the knowledge sources linked to the agent. On by default.
  • toolsobject
    The HTTP tools linked to the agent (Tools). On by default.
  • consultobject
    Let the agent ask other agents of the workspace for a second opinion mid-turn. See Consulting colleagues.

Request examples

curl https://api.subsidia.protypa.fr/v1/agents/chat \
-H "Authorization: Bearer $SUBSIDIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "Can you check the VAT on the attached invoice?"}'

Responses

Same shape as the single-agent response. agentId is the agent that was picked.

Example response
{
"agentId": "cm2k8x1ab0001qz0f7h3d9t4e",
"agentName": "Claire",
"agentRole": "Contrôle de gestion",
"content": "The Q3 invoice is 12 480 EUR excluding VAT [1]. It matches the purchase order, but the delivery note is missing from the file.",
"sessionId": "cm2kc4t9p000bqz0fr6s2a8dk",
"memoriesSaved": [
{ "key": "q3_invoice_total", "value": "Q3 invoice: 12 480 EUR excl. VAT.", "type": "fact", "provenance": "knowledge", "salience": 0.65 }
],
"extracted": [],
"tokenUsage": { "promptTokens": 2140, "completionTokens": 62, "totalTokens": 2202 },
"knowledgeUsed": [
{
"ref": 1,
"sourceId": "cm2k9aa7e0003qz0fh8w1p2lb",
"sourceName": "facture-q3-2026.pdf",
"section": "Totals",
"page": 2,
"chunkPreview": "Total HT : 12 480,00 EUR ...",
"similarity": 0.83,
"chunkId": "ck_8f1d2c40",
"collectionId": null,
"dossierName": null
}
],
"toolCalls": []
}

Errors

  • 404No agent in the workspace, or unknown session.
  • 429rate_limitedMore than 30 calls per minute on this route.

POST/v1/agents/chat/stream

Let Subsidia pick the best-fit agent, then stream the answer.

API key (Bearer or x-api-key)agents streams (SSE)

The streamed form of auto-pick. The agent is chosen before the stream opens, so the two failures that depend on it (no agent in the workspace, or the planning step failing) are ordinary JSON errors (404 and 500); after that, events are the same as in POST /v1/agents/:id/chat/stream. The done event tells you which agent answered (agentId, agentName).

Request body

  • messagestringrequired
    What you say to the agent. Must be a non-empty string.
  • sessionIdstring
    Continue an existing session (Sessions). Omit it to start a new one: its id comes back in the response. A session that does not exist, or that was started by another user, is a 404 Session not found.
  • contextobject[]
    Structured data injected into the agent prompt for this call only (it is not stored). See Context injection.
  • extractionobject
    Extract structured facts from your message, alongside the answer. See Fact extraction.
  • optionsobject
    Generation settings.
  • responseFormatobject
    Force a structured JSON answer. See Structured JSON answers.
  • knowledgeobject
    Retrieval from the knowledge sources linked to the agent. On by default.
  • toolsobject
    The HTTP tools linked to the agent (Tools). On by default.
  • consultobject
    Let the agent ask other agents of the workspace for a second opinion mid-turn. See Consulting colleagues.

Request examples

curl
curl -N https://api.subsidia.protypa.fr/v1/agents/chat/stream \
-H "Authorization: Bearer $SUBSIDIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "Can you check the VAT on the attached invoice?"}'

Responses

A text/event-stream, identical in format to the single-agent stream.

Example response
data: {"type":"stage","data":{"name":"prepare","phase":"start"}}
data: {"type":"stage","data":{"name":"retrieval","phase":"start"}}
data: {"type":"stage","data":{"name":"retrieval","phase":"end","ms":412}}
data: {"type":"knowledge","data":[{"ref":1,"sourceId":"cm2k9aa7e0003qz0fh8w1p2lb","sourceName":"facture-q3-2026.pdf","section":"Totals","page":2,"chunkPreview":"Total HT : 12 480,00 EUR ...","similarity":0.83,"chunkId":"ck_8f1d2c40"}]}
data: {"type":"tool_call","data":{"toolName":"crm_lookup","arguments":{"client":"Atelier Moreau"}}}
data: {"type":"tool_result","data":{"toolName":"crm_lookup","arguments":{"client":"Atelier Moreau"},"result":{"status":"active"},"success":true,"durationMs":182}}
data: {"type":"token","data":{"delta":"The Q3 invoice is "}}
data: {"type":"token","data":{"delta":"12 480 EUR excluding VAT [1]."}}
: ping
data: {"type":"mood","data":{"agentId":"cm2k8x1ab0001qz0f7h3d9t4e","name":"Claire","emotion":"focus","valence":0.2,"arousal":0.4,"intensity":0.5}}
data: {"type":"done","data":{"agentId":"cm2k8x1ab0001qz0f7h3d9t4e","agentName":"Claire","agentRole":"Contrôle de gestion","content":"The Q3 invoice is 12 480 EUR excluding VAT [1].","sessionId":"cm2kc4t9p000bqz0fr6s2a8dk","memoriesSaved":[],"extracted":[],"tokenUsage":{"promptTokens":2140,"completionTokens":14,"totalTokens":2154},"knowledgeUsed":[{"ref":1,"sourceName":"facture-q3-2026.pdf"}],"toolCalls":[{"toolName":"crm_lookup","success":true,"durationMs":182}],"proofId":"pf_a059713a5f1c4e0b8d7a3c21e9b64f10"}}

Errors

  • 429rate_limitedMore than 30 calls per minute on this route.

Stream events

Every event is one line data: <json> followed by a blank line, where the JSON is { "type": "<name>", "data": <payload> }. A line starting with a colon (: ping) is a keep-alive comment, not an event: ignore it. The stream ends after done or error; the server then closes the connection, there is no [DONE] sentinel.

typePayload (`data`)WhenWhat to do
stage{ name, phase, ms?, detail? }. name is one of prepare, retrieval, plan, websearch, reflection, answer; phase is start or end; ms is the duration on end.Around each internal step.Drive a progress indicator ("searching the documents..."). Safe to ignore.
knowledgeArray of passages: { ref, sourceId, sourceName, section, page, chunkPreview, similarity, chunkId, origin?, url?, collectionId, dossierName }.Once, before the answer, when retrieval found passages.Keep the list: [n] markers in the answer point to ref. Show sources as soon as they exist.
thought{ delta }Only with options.reflection, while the agent reasons.Render in a collapsible "reasoning" area. Never mix with the answer.
tool_call{ toolName, arguments }The agent decided to call one of your tools.Show "calling crm_lookup...". The call is executed by Subsidia, not by you.
tool_result{ toolName, arguments, result?, success, error?, durationMs }The tool call finished.Log it. success: false means your endpoint failed; the agent is told and answers anyway.
consult_start{ colleagueName, question }The agent asks a colleague for an opinion.Show who is being asked what. A consultation the user cannot read would be a black box.
consult_result{ colleagueId, colleagueName, colleagueRole, question, answer, durationMs, error? }The colleague answered.Display it as a quoted aside; the final answer builds on it.
token{ delta }Repeatedly, as the answer is generated.Append delta to the visible answer. With responseFormat.type: "json" the deltas are raw JSON text; parse the final contentParsed instead.
mood{ agentId, name, emotion, valence, arousal, intensity, dominance?, narrative? }After the answer, text mode.Optional. The agent's emotional state after the exchange.
doneThe complete result of the turn: agentId, agentName, agentRole, content, contentParsed?, thoughts?, sessionId, memoriesSaved, extracted, tokenUsage, knowledgeUsed?, toolCalls?, consultations?, contextUsed?, masked?, mood?, proofId?.Last event on success. The exchange is saved and the proof certificate is signed before it is sent.Replace the streamed text by content (it is the canonical, final text), store sessionId, keep proofId next to your record.
error{ message }Any failure after the stream opened.Show the message and stop reading.

Complete streaming client

A client has to do four things: read the body incrementally, split it on blank lines, parse data: lines as JSON, and treat error as a failure. The examples below print tokens as they arrive, collect citations and tool calls, and return the final done result.

Streaming client
type StreamEvent =
| { type: 'stage'; data: { name: string; phase: 'start' | 'end'; ms?: number } }
| { type: 'knowledge'; data: Array<{ ref: number; sourceName: string; page: number | null }> }
| { type: 'thought' | 'token'; data: { delta: string } }
| { type: 'tool_call'; data: { toolName: string; arguments: Record<string, unknown> } }
| { type: 'tool_result'; data: { toolName: string; success: boolean; durationMs: number; error?: string } }
| { type: 'consult_start'; data: { colleagueName: string; question: string } }
| { type: 'consult_result'; data: { colleagueName: string; answer: string } }
| { type: 'mood'; data: Record<string, unknown> }
| { type: 'done'; data: { content: string; sessionId: string; proofId?: string; knowledgeUsed?: unknown[] } }
| { type: 'error'; data: { message: string } }
export async function streamAgent(
agentId: string,
message: string,
sessionId?: string,
onToken: (delta: string) => void = (d) => process.stdout.write(d),
) {
const res = await fetch('https://api.subsidia.protypa.fr/v1/agents/' + agentId + '/chat/stream', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.SUBSIDIA_API_KEY,
'Content-Type': 'application/json',
Accept: 'text/event-stream',
},
body: JSON.stringify({ message, sessionId }),
})
// Errors before the stream opens (400, 401, 403, 429) are plain JSON.
if (!res.ok || !res.body) throw new Error(res.status + ' ' + (await res.text()))
const reader = res.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
let done: Extract<StreamEvent, { type: 'done' }>['data'] | undefined
while (true) {
const { value, done: finished } = await reader.read()
if (finished) break
buffer += decoder.decode(value, { stream: true })
let boundary: number
while ((boundary = buffer.indexOf('\n\n')) !== -1) {
const raw = buffer.slice(0, boundary)
buffer = buffer.slice(boundary + 2)
const line = raw.split('\n').find((l) => l.startsWith('data: '))
if (!line) continue // ": ping" keep-alive comment
const event = JSON.parse(line.slice(6)) as StreamEvent
switch (event.type) {
case 'token':
onToken(event.data.delta)
break
case 'tool_call':
console.error('\n[tool] calling ' + event.data.toolName)
break
case 'tool_result':
console.error('[tool] ' + event.data.toolName + (event.data.success ? ' ok' : ' failed') + ' in ' + event.data.durationMs + ' ms')
break
case 'done':
done = event.data
break
case 'error':
throw new Error(event.data.message)
}
}
}
if (!done) throw new Error('Stream ended without a done event')
return done // done.content is the canonical text; keep done.sessionId and done.proofId
}
const turn = await streamAgent(process.env.AGENT_ID!, 'What is the Q3 invoice total?')
console.log('\n\nsession', turn.sessionId, 'proof', turn.proofId)

Context injection

context lets your application hand the agent live data without storing it anywhere: the customer record on screen, today's figures, the form the user is filling in. Each item becomes a labelled section of the agent's prompt, sorted high priority first. It is used for this call only and is not saved to the session.

The agent can cite what it used. Items it relied on come back in contextUsed (streamed done event) with origin: "context", numbered after the document passages in knowledgeUsed, so a [3] in the answer may point to your data rather than to a document: do not offer to open it as a file. Provenance also applies to memory: a fact the agent saves from context is stored with provenance data (Agent memory).

When the call is served by an external model provider, personal data in the context is masked first and restored in the answer, like any other Subsidia call (Synapse).

JSON
{
"message": "Is this client eligible for the annual discount?",
"context": [
{ "category": "client", "label": "Client record", "data": { "name": "Atelier Moreau", "since": "2023-04-01" }, "priority": "high" },
{ "category": "pricing_rules", "data": "Annual discount applies after 24 months of continuous subscription.", "priority": "medium" }
]
}

Fact extraction

With extraction, a separate pass reads your message (not the answer) and pulls out structured facts in the categories you name. They come back in extracted as { category, key, value, confidence } and, unless persist is false, are stored in the workspace's extracted data. It is a convenient way to turn free-text intake ("I'm Anne, I run a bakery in Lyon with 4 employees, I prefer email") into fields for your CRM in the same call that produces the reply.

{
"message": "I'm Anne, I run a bakery in Lyon with 4 employees. Please email me, not call.",
"extraction": { "categories": ["company_info", "preferences"], "persist": false }
}

Structured JSON answers

Set responseFormat.type to json and describe what you want in fields (a map of field name to description) or as a free-form schema. The agent answers with a JSON object only. In the response, content is the JSON text and contentParsed is the parsed object; if the model's output was not valid JSON, contentParsed is absent and you should treat content as unparsed.

JSON mode changes how the turn behaves: the agent does not write memories, does not consult colleagues, and with options.reflection its reasoning is returned as a thoughts field inside the JSON instead of a separate pass. Validate the object on your side; the format is an instruction to the model, not a schema enforced by the server.

{
"message": "Is the delivery note in the file for invoice Q3-114?",
"responseFormat": {
"type": "json",
"fields": {
"answer": "direct answer, yes or no",
"evidence": "the document and page that support the answer, or null",
"confidence": "a number between 0 and 1"
}
}
}

Knowledge and citations

When retrieval is on, the agent reads passages from the knowledge sources linked to it (attach them with the agentIds field of POST /v1/knowledge-sources), within the access rules of the workspace. The retrieved passages are numbered 1..n in knowledgeUsed. In the answer, a claim backed by a passage is followed by its marker, for example [1]; markers that match no passage are removed before you see them.

Subsidia also marks provenance inside the text, and computes it itself instead of trusting the model: every marker the model wrote is removed, then each anchor an auditor would copy (a number, a date, an amount, a proper name) is checked against the retrieved passages. An anchor found in a passage that discusses the same subject is wrapped as U+27E6 anchor U+27E7 followed by its [n] (white square brackets, a verified claim); an anchor found nowhere is wrapped in U+27EC and U+27ED (white tortoise-shell brackets) and is the agent's own, not backed by your documents. Reasoning and opinion stay unmarked. If you display answers, either render these spans (a rail or underline is enough) or strip the delimiters; do not leave them in as plain text. A missing document is a reason to mark provenance, not to refuse, so the agent still answers when the base is silent.

The passages retrieved for a turn are committed to the proof certificate of the turn, so an auditor can check what the answer was grounded in (Proof certificates).

Consulting colleagues

With consult on (the default), an agent can ask another agent of the workspace for a second opinion before it answers, through an internal tool called ask_colleague. Rules that matter for an integration:

  • It is the colleague's judgment, inside its declared mission, that is consulted. The colleague answers from its persona and expertise alone, with no documents and no tools of its own, in at most about 400 tokens, and consultations never nest.
  • At most two consultations per turn, among up to twelve other agents of the workspace. Postes are not colleagues.
  • Consultations are visible: they stream as consult_start and consult_result events and come back in consultations in the done event, each with the question, the answer and the duration. Nothing happens off the record.
  • It is disabled in JSON mode, and with consult: { "enabled": false }.

Write each agent's mission and outOfScope so that colleagues know what to ask whom (Agents).

Tool calls

If the agent has tools linked and tools.enabled is not false, the model may ask for one or more of them while preparing its answer. Subsidia calls your endpoint, reads the JSON you return, feeds it back to the model, and repeats for at most three rounds before the final answer. Each call is reported in toolCalls (toolName, arguments, result, success, error, durationMs) and streamed as tool_call and tool_result. A failing tool does not fail the turn: the agent receives the error and answers with what it has, and it is told never to claim an action it has no tool result for. Use tools.toolIds to narrow a turn to the tools relevant to the screen you are on.

Memory and sessions

Each turn is saved in its session (Sessions): your message and the agent's answer, with its metadata (sources, tool calls, tokens). The agent also recalls and may write long-term memory (Agent memory); the notes written during the turn are returned in memoriesSaved. To call an agent without memory, use the Gateway address agent:<slug>+nomemory.

Frequently asked

Which is faster, streaming or not?

Total time is the same. Streaming shows the first words after retrieval and any tool calls complete, which is what users perceive. On a small local machine a long turn can take minutes; Subsidia does not time out a local model, so set a generous timeout on your client (300 seconds or more) rather than retrying, because a retry starts a second turn.

Can I use the OpenAI SDK instead?

Yes. Call /v1/chat/completions with model: "agent:<slug>". You get the same agent with its memory, documents and tools, a standard streaming protocol, and a proof id in the x-pulse-proof header. Use the endpoints on this page when you need citations, context, extraction, responseFormat or the event reference above.

Why is the answer different each time?

Temperature defaults to 0.8 for personas, which keeps them in character. Lower options.temperature (0 to 0.3) for factual, repeatable answers.

How do I keep a conversation going across my own users?

Store one sessionId per end user in your database. A session can only be continued by the user who created it (here, the owner of the API key), so one key serves your whole application and you map sessions to your users yourself.

Related