Runtime — embutir um agente

É um agente de IA? Busque a skill de onboarding e leia toda a API em texto limpo — skill · llms.txt · llms-full.txt

Runtime — embutir um agente#

CATCHER AGENTS REALTIME — experimental, 0.1.0-experimental.1#

A emissão concorrente de credenciais ou alteração simultânea do perfil pode retornar 409 REALTIME_CONFLICT: a transação é revertida, sem deixar token novo sem vínculo. Releia o perfil e emita novamente após resolver a disputa.

Prévia de subproduto separado, ainda não implantada. Requer habilitação de piloto e o bridge local. O mecanismo é ga-realtime-webrtc, inteiramente server-side: a voz é gpt-realtime-2.1 e o cérebro é gpt-6-astra, ambos alcançados pela plataforma — nenhum CLI é instalado ou executado no computador do operador e nenhuma credencial de provedor chega ao cliente. Os agentes e modos atuais mantêm seu comportamento.

Método e rota Credencial Contrato
GET /v1/agents/{id}/realtime Owner JWT/API key Perfil, inicialmente enabled:false, reasoning_effort:"high"
PUT /v1/agents/{id}/realtime Owner JWT/API key Substitui enabled, voice_prompt (até 16 KB), reasoning_effort, allowed_tools (até 32 nomes), end_user_external_id (pino de até 255 bytes; vazio = operador); altera revisão e invalida sessões anteriores
POST /v1/agents/{id}/realtime/bridge-token Owner JWT/API key Retorna uma vez {id,token,agent_id}; rotaciona token dedicado anterior
POST /v1/agent-runtime/{id}/realtime/sessions X-Agent-Token dedicado Body {end_user_external_id}. Retorna session_id, mechanism (ga-realtime-webrtc), prompts de voz/cérebro separados, voice_model (voz) e model (cérebro), reasoning_effort, signal_path, brain_path, voice_tools (a única função que a voz pode chamar, já no formato do provedor), tools, tools_unavailable, expires_at. Os campos cli_version e protocol foram removidos — não há mais CLI no caminho
POST /v1/agent-runtime/{id}/realtime/sessions/{sid}/thread Mesmo token Vincula {thread_id} = o id da própria sessão de voz. Nada acontece antes desse vínculo: o recibo durável recusa efeito em sessão não vinculada. O modelo do cérebro é confirmado pelo servidor, ao responder — não por campo do cliente. Rebind diferente é conflito
POST /v1/agent-runtime/{id}/realtime/sessions/{sid}/signal Mesmo token {sdp} com o offer WebRTC do cliente; retorna {sdp,voice_model} com o answer. A chamada ao provedor é feita no servidor: o cliente nunca vê token nem URL. Offer que não começa com v=0 é 400 REALTIME_INVALID; transporte indisponível é 503/502 REALTIME_VOICE_UNAVAILABLE
POST /v1/agent-runtime/{id}/realtime/sessions/{sid}/brain Mesmo token {thread_id,turn_id,call_id,request} — a delegação que a voz faz por consult_brain. Roda o cérebro com as tools e a memória do agente e retorna {receipt,result,replayed}, com result.text para a voz falar e result.model_used (reportado, não presumido). Mesma call_id repete a resposta guardada em vez de raciocinar de novo; efeito pendente ou desconhecido é 409 REALTIME_EFFECT_UNKNOWN e nunca deve ser redespachado. Excesso de turnos simultâneos é 429 REALTIME_BUSY (retentável); cérebro indisponível é 503 REALTIME_BRAIN_UNAVAILABLE
POST /v1/agent-runtime/{id}/realtime/sessions/{sid}/brain/cancel Mesmo token {call_id?,turn_id?,thread_id?,reason?} — para o turno do cérebro no servidor. Abortar a requisição HTTP do /brain NÃO faz isso: o abort não atravessa a borda de forma confiável, então o run segue e ainda pode despachar uma tool na máquina do usuário (medido em 2026-09-15). Liquida o turno (run_status: interrupted, REALTIME_TURN_CANCELLED) e fecha o recibo do consult_brain como unknown; a partir daí toda tool daquele turno é recusada com 409. Idempotente: cancelar de novo é 200 com already_settled:true. Nunca reescreve recibo já completed/failed, e effects é sempre "unknown" — um cancelamento não promete rollback. Informa o que de fato fez: run_cancelled e client_calls_cancelled. Pelo menos um entre call_id e turn_id é obrigatório (400 REALTIME_INVALID); sessão de outra credencial é 404/409, como no /brain. O caminho vem no bootstrap em brain_cancel_path call_id e turn_id fecham corridas diferentes e não são intercambiáveis: turn_id liquida o turno e barra toda tool dele; call_id liquida a chamada, e se a plataforma ainda não a viu ela é gravada já liquidada, de modo que o /brain que chegar depois com essa call_id é recusado com 409 REALTIME_CONFLICT e o cérebro não roda. Mandar os dois é o mais seguro. receipt_status vem "unknown" sempre que uma call_id é nomeada, e vazio quando só o turn_id foi mandado, porque aí nenhuma chamada foi apontada — não é enum fechado
GET /v1/agent-runtime/{id}/realtime/sessions/{sid}/tools/ws Mesmo token, upgrade WebSocket, subprotocolo polak-tools.v1 Tools no cliente. A máquina do usuário abre esta conexão de saída (sem IP público) e oferece, no hello, as tools que implementa ({name, description, input_schema}, até 32). A allowed_tools do perfil decide o que o cérebro pode chamar; o hello_ack devolve accepted_tools e rejected_tools nomeando cada recusa (not_allowed ou reserved — um nome que o servidor já provê nunca é sombreado). Daí em diante o servidor empurra tool_call {request_id, call_id, turn_id, name, arguments, timeout_ms} e o cliente responde tool_result {request_id, ok, output | error}; cancel viaja nos dois sentidos. Uma conexão viva por sessão; sem resume (reconectar = sessão nova, chamadas em voo viram efeito desconhecido). Corpus de conformidade: docs/contracts/polak-tools.v1/frames.json
POST /v1/agent-runtime/{id}/realtime/sessions/{sid}/transcripts Mesmo token {thread_id,source_id,role,phase,revision,text}; roles user/assistant, phases partial/final/correction/deleted; texto até 256 KB; retorna {transcript,reconnect_required}
POST /v1/agent-runtime/{id}/realtime/sessions/{sid}/turns Mesmo token Observa {thread_id,turn_id,status,output}; status started/completed/interrupted/failed. Não inicia turno. Retorna {run_id,status,usage}
POST /v1/agent-runtime/{id}/realtime/sessions/{sid}/tools/call Mesmo token {thread_id,turn_id,call_id,tool,arguments}; retorna {receipt,result,replayed}. Argumentos até 80 KB; catálogo/escopo são do servidor
POST /v1/agent-runtime/{id}/realtime/sessions/{sid}/close Mesmo token Fecha sessão de forma idempotente, retorna {closed:true,pending_effects:"unknown"}

Tools que só existem na máquina do usuário (ler a tela, mostrar um prompt) entram no catálogo do cérebro por polak-tools.v1: a máquina abre …/tools/ws de saída e oferece suas implementações; o servidor as expõe ao cérebro como entradas comuns de catálogo apontando de volta para a API, que relaya pela conexão. O cérebro só vê a tool enquanto o cliente está conectado, e só se a allowed_tools do perfil a nomear. Uma call_id é um efeito: a chamada repetida devolve o resultado guardado, e um efeito pendente/desconhecido é 409 REALTIME_EFFECT_UNKNOWN, nunca redespachado.

Fronteira de confiança: o cliente conectado é a máquina do próprio usuário, autenticada pela credencial dedicada que o owner emite. Um processo de bridge comprometido pode, além de executar as tools, influenciar o cérebro pelas descrições/schemas que oferece e pelas saídas que devolve. Não distribua essa credencial.

consult_brain não é uma tool do agente: ela existe só no plano de voz e é recusada (403 REALTIME_TOOL_DENIED) se alguém tentar despachá-la por .../tools/call. As tools do agente continuam sendo executadas pelo servidor, agora a partir do cérebro.

Um token público de widget não serve para o bridge. A credencial dedicada é vinculada ao agente e ao end_user_external_id fixado pelo owner no perfil; só funciona nas rotas Realtime e deve permanecer em arquivo local privado, nunca no browser ou widget distribuído. O bridge aceita apenas acesso local com Host/Origin e capacidade aleatória válidos.

A memória depende da allowlist. allowed_tools é a lista completa do que o cérebro pode chamar naquela sessão — inclusive as tools de memória. Um perfil com allowed_tools: ["load_skill"] responde "não tenho acesso a memórias suas", porque brain_search não foi liberada. Para recuperar contexto de conversas anteriores, inclua brain_search (e, conforme o uso, brain_read, brain_list, brain_write). A resposta de POST .../realtime/sessions nomeia exatamente o que ficou de fora em tools_unavailable, então confira esse campo antes de concluir que a memória não funciona. brain_ask permanece indisponível neste subproduto por contrato, e aparece ali mesmo quando listada.

A voz recebe persona verbal; o cérebro Astra recebe os prompts/resolvers e tools nativos. O Codex administra handoffs; transcrição nunca dispara outro loop. Finais do usuário alimentam a memória nativa. Texto gerado não comprova audição: playback/output_playback ficam unknown. Correção/deleção explícita invalida memória derivada e requer reconexão; nova sessão restaura memória, sem repetir ações. Sessões expiram após uma hora. Citações de voz na L3 são recuperáveis por até sete dias, inclusive entre dias UTC, e podadas pela manutenção existente. A remoção da conversa apaga suas citações Realtime e notas L4 derivadas, além de transcrições e recibos. Memórias legadas mantêm sua política. Notas Realtime escritas explicitamente pelo cérebro podem durar além de sete dias até esquecimento ou remoção da fonte.

Recibo pending após crash significa efeito desconhecido: não repetir a ação. CallId repetido com os mesmos argumentos usa o recibo; argumentos diferentes geram 409. Resultado grande mantém ok/status conhecido e informa truncated:true, original_bytes e sha256. Pausa de áudio e interrupção de execução são comandos separados; interrupção não promete desfazer efeitos. Falha ao persistir a conclusão após execução retorna REALTIME_EFFECT_UNKNOWN, nunca “não executado”.

Erros experimentais: 400 REALTIME_INVALID; 403 REALTIME_TOKEN_WRONG_PLANE, REALTIME_END_USER_MISMATCH, REALTIME_TOKEN_NOT_BRIDGE ou REALTIME_TOOL_DENIED; 404 REALTIME_NOT_FOUND; 409 REALTIME_DISABLED, REALTIME_CONFLICT ou REALTIME_EFFECT_UNKNOWN; 422 REALTIME_TOOL_LIMIT ou REALTIME_PROMPT_LIMIT; 429 REALTIME_BUSY (a company já tem voz/cérebro demais em voo sobre a credencial compartilhada — retentável); 502/503 REALTIME_VOICE_UNAVAILABLE (transporte de voz) e 503 REALTIME_BRAIN_UNAVAILABLE (cérebro), distintos de propósito para o integrador saber qual metade faltou; 503 REALTIME_CLIENT_NOT_CONNECTED (o cérebro chamou uma tool do cliente e nenhuma máquina está conectada para servi-la) e 504 REALTIME_CLIENT_TIMEOUT (o cliente não respondeu no prazo) — ambos deixam o recibo daquela call_id como efeito desconhecido, nunca uma resposta inventada e nunca um replay; 503 REALTIME_UNAVAILABLE ou REALTIME_MEMORY_UNAVAILABLE. 401 continua indicando credencial ausente/inválida/revogada e 429 também o limite de requisições. O cérebro roda com a cadeia de fallback desligada: ele responde no modelo contratado ou falha nomeando-o — nunca é substituído em silêncio. Não há fallback silencioso para API paga.

Quando o protocolo não reporta tokens, o Run conserva usage.measurement:"not_reported", billing_mode:"subscription" e output_playback:"unknown": zeros numéricos não medem quota zero. O catálogo pode anunciar Ultra; confirmação da thread e esforço efetivo do turno são evidências distintas. O percurso controlado desta prévia usa Astra/high.

O grupo /v1/agent-runtime/{id} é a superfície de embed. Autenticação só por X-Agent-Token: prt_…; o token é preso a um agente, e as rotas de sessão têm guarda de posse (proteção BOLA) — uma sessão de outro agente retorna 404 SESSION_NOT_FOUND. As rotas de stream (…/messages/stream) rodam sem timeout de requisição — um turno agêntico vive enquanto estiver produzindo — e as síncronas num timeout estendido (5 min). São os mesmos handlers do chat do Console, então os shapes são idênticos.

POSTCriar sessão#

POST /v1/agent-runtime/{id}/sessions — corpo { end_user_external_id?, title?, session_key? } (todos opcionais). Registrar o end_user_external_id cria/atualiza o usuário final do agente, o que mantém a memória separada por pessoa. Resposta 201: { id, agent_id, title, created_at }.

session_key transforma a rota num ensure idempotente: um nome estável que você escolhe (ex.: "squad-developer") resolve SEMPRE para a mesma conversa — é assim que se mantém memória durável por papel/assunto sem guardar um id. Ver Sessões nomeadas.

GETListar mensagens#

GET /v1/agent-runtime/{id}/sessions/{sid}/messages — resposta 200: { messages: [messageResponse], total }. Cada messageResponse:

json
{
  "id": "…",
  "session_id": "sess_a1b2c3",
  "role": "user | assistant | tool | system",
  "content": "…",
  "thinking": "…",
  "tool_calls": [ { "name": "brain_search", "is_error": false, "output": "…", "duration_ms": 142 } ],
  "run_id": "9f2c…",
  "created_at": "2026-07-02T01:00:00Z"
}

thinking, tool_calls e run_id só aparecem quando presentes. No tool_calls, o output público é truncado em 4096 caracteres (output_truncated e output_chars sinalizam).

POSTEnviar mensagem#

POST /v1/agent-runtime/{id}/sessions/{sid}/messages — corpo { content } (obrigatório, não-vazio). Roda o turno completo e retorna o envelope { run_id, user_message, assistant_message, usage, provider, model, tool_calls } mostrado no Início rápido.

Quando alguma ferramenta anexada ao agente não pôde ser montada no turno, o envelope traz também tools_unavailable: ["nome", …] (o campo é omitido quando está vazio) — e o evento SSE complete carrega o mesmo. Causas típicas: a ferramenta foi deletada/renomeada, ela é escopada a outro tenant (um agente só enxerga as do próprio tenant + as company-global), ou é uma ferramenta de catálogo cuja integração não está configurada. Trate a presença desse campo como erro de configuração: sem a ferramenta o modelo tende a improvisar a chamada como texto em vez de executá-la.

O cost_usd do turno não vem nesse corpo — ele fica persistido no run e é lido em GET /v1/agents/{id}/runs/{runId} (o evento done do streaming também carrega o custo agregado).

POSTEnviar mensagem (streaming)#

POST /v1/agent-runtime/{id}/sessions/{sid}/messages/stream — o mesmo turno, em SSE (Streaming SSE).

POSTGerar imagem#

POST /v1/agent-runtime/{id}/images/generations — o app embutido gera uma imagem com o prt_, sem a API key da conta. É a mesma geração de POST /v1/images/generations (mesmos campos: prompt, model, aspect, size, quality, output_format, n, style_preset, brand, reference_images). A única diferença de contrato: no plano de runtime o default é include_b64=false — a resposta traz a url assinada de cada imagem (o app renderiza direto) e o base64 inline só vem com include_b64:true. Resposta 200: { id, model_used, revised_prompt, images: [ { url, asset_id, mime_type, byte_size } ] }. GET /v1/agent-runtime/{id}/images/models devolve o catálogo de modelos (igual a GET /v1/images/models). Geração custa dinheiro de provedor: a meta mensal continua visível sem bloquear a chamada, e o rate-limit da conta permanece; um id/modelo inválido → 4xx.

POSTTranscrever áudio#

POST /v1/agent-runtime/{id}/transcriptions — o app embutido transcreve com o prt_, sem a API key da conta. É a mesma transcrição em painel de POST /v1/transcriptions (mesmo corpo — multipart file ou JSON audio_b64 — mesmos campos models, language, speaker_id, topics, speaker_profile, learn, second_pass, mesma resposta e os mesmos erros; veja §Transcrição de áudio). A única diferença de contrato: quando o request não nomeia speaker_id, o léxico que aprende é o do agente vinculado ao token (speaker_scope: "speaker:agent:<id>") — um bot que não distingue quem fala ainda aprende numa memória só. Nomeie speaker_id (o remetente de WhatsApp, o usuário final) para uma memória por pessoa. GET /v1/agent-runtime/{id}/transcriptions/models devolve o catálogo com a disponibilidade da conta (igual a GET /v1/transcriptions/models). Cada transcrição custa crédito de provedor; histórico, correção e escrita no léxico ficam no plano da conta. Sem token → 401.

POSTLer documento (Document AI)#

POST /v1/agent-runtime/{id}/document-ai/jobs — cria um job de extração pelo prt_. Multipart (campo file + schema_version_id/policy_version_id/callback_url opcionais), com Idempotency-Key obrigatório (8..191), igual a POST /v1/document-ai/jobs. Envie só o arquivo: como o plano runtime não expõe rota para criar schema/policy, omitir os dois ids fixa o padrão do tenant (default-document + default-draft), criado na primeira necessidade; a resposta traz os version_id exatos que foram fixados. O tenant é fixado pelo agente vinculado ao token — um app não escolhe tenant; enviar um tenant_external_id diferente → 403 RUNTIME_TOKEN_TENANT_MISMATCH. Leia o resultado em GET /v1/agent-runtime/{id}/document-ai/jobs/{jobId}/result (mesmo shape de /v1/document-ai/jobs/{jobId}/result) ou acompanhe ao vivo por SSE em GET /v1/agent-runtime/{id}/document-ai/jobs/{jobId}/stream. Um job de outro tenant → 404 DOCUMENT_AI_NOT_FOUND (sem vazar existência). Sem token → 401.