Sessões e mensagens#
O chat pelo Console (autenticado por JWT/API key) usa os mesmos handlers do runtime, com endpoints extras de gestão de sessão.
POSTCriar sessão#
POST /v1/agents/{id}/sessions — corpo { title?, end_user_external_id?, session_key? }, resposta 201: { id, agent_id, title, created_at }.
<a id="sessoes-nomeadas"></a>
Sessões nomeadas (session_key) — memória durável por nome. Enviar
session_key troca "cria uma sessão nova" por "garanta a sessão chamada X":
201na primeira vez (criou),200em toda chamada seguinte (reusou), sempre com o MESMOid. Chamar no boot de cada processo é seguro.- Resposta:
{ id, session_id, agent_id, session_key, title, created, created_at }—createddiz se ESTA chamada criou a conversa (útil pra semear a primeira vez);idesession_keysão campos distintos de propósito (veja o próximo item). - O
iddevolvido NÃO é o seu nome. Ele é derivado de (conta, agente, nome), então o mesmo nome em contas diferentes são conversas diferentes e ninguém "reserva" um nome globalmente. Guarde oidse quiser, mas reenviar osession_keysempre chega no mesmo lugar. session_keyem branco →400. Nunca vira uma sessão anônima em silêncio.- O
titlesó é aplicado na criação; um ensure posterior não renomeia uma conversa viva.
GETListar sessões#
GET /v1/agents/{id}/sessions — { sessions: [sessionResponse], total }.
POSTEnviar mensagem#
POST /v1/agents/{id}/sessions/{sid}/messages — corpo { content }, mesmo
envelope de resposta do runtime (run_id, user_message, assistant_message,
usage, provider, model, tool_calls).
POSTEnviar mensagem (streaming)#
POST /v1/agents/{id}/sessions/{sid}/messages/stream — o mesmo turno por SSE,
com o catálogo de eventos da seção Streaming (SSE). É a variante JWT/API key do
.../agent-runtime/.../messages/stream (que usa prt_).
DELETEDescartar uma mensagem#
DELETE /v1/agents/{id}/sessions/{sid}/messages/{messageId} → 204.
Tira a mensagem do contexto dos próximos turnos mantendo a linha no
transcript, marcada com discarded_at. Existe para um caso concreto: depois que
uma resposta inventada entra na conversa, o modelo lê a própria invenção como
fato estabelecido e a repete — nem uma ordem explícita ("execute a ferramenta X
agora") a desloca. Antes, a única saída era abandonar a sessão inteira, jogando
fora todos os turnos reais junto.
O messageId é o mesmo id que a telemetria do run chama de
assistant_transcript_id (GET /v1/agents/{id}/runs/{runId}/usage) — se você
detectou a fabricação pelo run, já tem o id na mão.
É uma marca, não um delete: GET .../messages continua listando a mensagem,
agora com discarded_at preenchido. A resposta inventada é justamente a evidência
que você quer guardar. Descartar de novo o mesmo id é 204 (idempotente); um id
que não existe nessa conversa é 404 MESSAGE_NOT_FOUND.
GETDetalhe do run#
GET /v1/agents/{id}/runs/{runId} — o registro priced do turno, com custo:
{
"id": "9f2c…", "agent_id": "…", "session_id": "sess_a1b2c3",
"model": "gpt-5.6-luna", "status": "completed",
"input": "…", "output": "…",
"usage": { "input_tokens": 812, "output_tokens": 143, "total_tokens": 955 },
"cost_usd": 0.0144, "rounds": 2, "duration_ms": 26600,
"error": "", "started_at": "2026-07-02T01:00:00Z",
"context_tokens": 91200, "compactions": 1
}
Quando o reserva atendeu (ou quando o run falhou depois de a cadeia inteira
recusar), o registro traz também answered_by_model/answered_by_provider e
model_failures[] — ver "Cérebro reserva". Num run que morreu no teto de
requisição (error_code: "RUN_TIMEOUT"), rounds e usage são os do trabalho
feito até ali, nunca 0.
context_tokens é o input REAL da última chamada ao provider (o tamanho da conversa
como o modelo a viu); compactions quantas vezes o engine compactou o contexto no
turno — ver "Compaction automática de contexto".
Um run failed diz POR QUÊ, mesmo depois do stream sumir. Tanto
GET …/runs/{runId} quanto GET …/runs/{runId}/usage trazem error_code (o código
tipado gravado na hora da falha) e, no /usage, também stop_reason:
{ "status": "failed", "error": "engineclient: /v1/run returned 502: {\"error\":\"max tool rounds exceeded\"}",
"error_code": "MAX_TOOL_ROUNDS", "stop_reason": "max_rounds", "rounds": 20 }
Um run completed traz stop_reason: "completed" e nenhum error_code (a chave é
omitida, nunca ""). Use isso para separar falha durável (MAX_TOOL_ROUNDS,
context_window_exceeded, TOOLS_UNAVAILABLE — repetir o mesmo turno reproduz o
problema) de falha transitória (provider_unavailable, RATE_LIMITED, 5xx —
repetir pode resolver).
GETTraço do turno (inspector)#
Três leituras detalham como o turno aconteceu — a base de um painel de depuração:
| Rota | Conteúdo |
|---|---|
GET /v1/agents/{id}/runs/{runId}/usage |
O uso/custo do run (o painel de traço puxa daqui) |
GET /v1/agents/{id}/runs/{runId}/tool-calls |
As chamadas de ferramenta do run, em ordem (args, resultado, erro) |
GET /v1/agents/{id}/transcripts/{transcriptId}/llm-prompts |
Os prompts enviados ao LLM naquele transcript (system + rounds) |
GETExportar conversa#
GET /v1/agents/{id}/sessions/{sid}/export — retorna a transcrição como um
arquivo Markdown para download (Content-Disposition: attachment), não JSON.
GETBuscar mensagens#
GET /v1/agents/{id}/sessions/search?q=… — full-text search nas mensagens do
agente (todas as sessões). Resposta 200 com os matches e a sessão de cada um.
GETUsuários finais#
GET /v1/agents/{id}/end-users — os end-users que o seu app atende através do
agente (quem aparece como end_user_external_id nas sessões):
{
"end_users": [
{ "id": "…", "external_id": "cliente-42", "display_name": "…",
"sessions": 7, "last_seen": "2026-07-20T…Z" }
],
"total": 1
}