Conhecimento#
O RAG por agente: suba documentos e a busca híbrida entra automaticamente no
turno. Endpoints sob /v1/agents/{id} (JWT/API key).
POSTAdicionar texto#
POST /v1/agents/{id}/knowledge — corpo { title?, content }. O campo é
content (não text). Resposta 201: { document_id, source }. Erro:
KNOWLEDGE_CONTENT_REQUIRED.
Enviar de novo o mesmo title é upsert: substitui o documento no lugar,
não cria um segundo. É o que permite re-semear um corpus inteiro sem duplicar
trechos — veja Manter um corpus curado abaixo.
GETListar o corpus curado#
GET /v1/agents/{id}/knowledge — o que já está semeado no cérebro do agente:
{
"documents": [
{ "document_id": "knowledge/spec-da-linguagem", "title": "Spec da linguagem",
"origin": "text", "updated_at": "2026-07-23T14:02:11Z" },
{ "document_id": "knowledge/manual.pdf", "title": "manual.pdf",
"origin": "file", "source_id": "src-9f2c" }
],
"total": 2
}
origin diz de onde o documento veio e, com isso, como removê-lo:
origin |
Veio de | Como remover |
|---|---|---|
text |
POST .../knowledge |
DELETE .../knowledge/{docID} |
file |
POST .../sources |
DELETE .../sources/{source_id} — traz source_id |
GET .../sources não lista os documentos de texto: ele lista só os arquivos
ingeridos. Para o corpus curado, a listagem é esta rota.
DELETERemover um documento#
DELETE /v1/agents/{id}/knowledge/{docID} → 204. {docID} é o slug — o
último segmento do document_id. Mandar o document_id inteiro
URL-encodado (knowledge%2Fspec) responde 404: a rota casa um único segmento
de path. Em shell: ${document_id##*/}. Um documento com origin=file responde 409 KNOWLEDGE_DOCUMENT_FILE_BACKED: ele pertence a uma fonte, e apagar só a página
deixaria a fonte e seus trechos órfãos — apague a fonte. Um id que resolva para
fora do namespace knowledge/ responde 400 KNOWLEDGE_DOCUMENT_INVALID.
DELETEEsvaziar o corpus#
DELETE /v1/agents/{id}/knowledge → 200 { deleted, kept_file_backed }.
Apaga os documentos de texto e mantém os que vieram de arquivos (removidos
pela fonte), reportando os dois números. Idempotente: num corpus já vazio
responde { "deleted": 0 }. Requer papel owner ou admin — esvaziar um
corpus inteiro é manutenção, não operação de agente.
Manter um corpus curado#
Um corpus grande (uma spec + dezenas de prompts + exemplos) precisa continuar vivo: quando o material muda, você re-semeia. O ciclo:
BASE=https://agents-api.catcher.one/v1/agents/$AGENT_ID
AUTH="X-API-Key: $CATCHER_API_KEY"
# 1. o que já está lá (conte antes e depois)
curl -s -H "$AUTH" "$BASE/knowledge" | jq '.total'
# 2. semear/atualizar — mesmo title = substitui, nunca duplica
curl -s -X POST -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"title":"Spec da linguagem","content":"..."}' "$BASE/knowledge"
# 3a. re-semeadura incremental: apague só o que saiu do corpus
curl -s -X DELETE -H "$AUTH" "$BASE/knowledge/spec-antiga"
# 3b. ou re-semeadura do zero: esvazie e repita o passo 2
curl -s -X DELETE -H "$AUTH" "$BASE/knowledge" | jq
Como o passo 2 é upsert, a re-semeadura normal não precisa do wipe — só use
3b quando os títulos mudaram de forma e você quer garantir que nada antigo
sobrou. O que essas rotas escrevem é exatamente a superfície que o agente
consulta no turno (a busca do próprio cérebro), então o que você lista aqui é o
que ele recupera lá.
POSTEnviar arquivo#
POST /v1/agents/{id}/sources — multipart/form-data, campo file (máx 25
MiB). Converte, fatia e embeda de forma síncrona (pode passar de 30s; roda no
timeout estendido). Resposta 202 Accepted:
{ id, status, filename, mime?, chunks_count, enabled, error? }.
GETListar fontes#
GET /v1/agents/{id}/sources — { sources: [sourceResponse], total }.
sourceResponse: { id, status, filename, path?, mime?, chunks_count, enabled, error? }.
GETDetalhe da fonte#
GET /v1/agents/{id}/sources/{sid} — a fonte (filename, path, chunks_count,
status) com os chunks indexados — os trechos exatos que a busca retornaria.
Resposta 404 SOURCE_NOT_FOUND para um sid desconhecido.
PATCHPausar/retomar fonte#
PATCH /v1/agents/{id}/sources/{sid} — corpo { "enabled": false }. Pausa a
fonte na busca sem deletar: os chunks ficam, mas saem de toda consulta;
enabled: true traz de volta. O botão reversível ao lado do destrutivo
"remover". Resposta 200: sourceResponse.
POSTReindexar fonte#
POST /v1/agents/{id}/sources/{sid}/reindex — re-fatia e re-embeda a partir do
arquivo original guardado, mantendo o mesmo source_id (sem delete +
re-upload quando a política de chunking muda). Resposta 202 com a fonte em
processing; é add-before-delete — a fonte nunca fica vazia no meio do caminho.
DELETERemover fonte#
DELETE /v1/agents/{id}/sources/{sid} → 204. Remove o arquivo e os chunks
dele da base de conhecimento.
O
{sid}é UM segmento. Nas quatro rotas por-fonte (GET,PATCH,DELETEe.../reindex), um id que não permaneceria um único segmento — vazio,.., ou com o separador escapado (a%2Fb,%2e%2e, inclusive duplo%252f) — recebe400 SOURCE_ID_INVALIDantes de qualquer chamada sair. Isso é distinto de404 SOURCE_NOT_FOUND: um significa "esse id não é endereçável", o outro "essa fonte não existe". Mande oidque oGET .../sourcesdevolveu, sem escapar nada.
GETInspetor de chunks#
GET /v1/agents/{id}/knowledge/chunks — todos os trechos indexados na base
(sem consulta). Read-only, sem LLM: é assim que um script de seed responde "o seed
funcionou?".
{
"total": 2,
"chunks": [
{ "content": "Escreva curto…", "source": "House style", "surface": "brain",
"document_id": "knowledge/house-style" },
{ "content": "EC50 do AVX-006 = 22,4 µM…", "source": "protocolo-thv.pdf",
"surface": "brain", "document_id": "knowledge/protocolo-thv.pdf", "source_id": "src_9f2…" }
]
}
document_id é o mesmo id que POST /v1/agents/{id}/knowledge devolveu, então dá
para casar trecho ↔ documento e conferir o corpus contra o que você semeou.
source_id aparece só em trecho vindo de arquivo — e aí são os chunks reais da
fonte; um documento de texto volta como um trecho único (a página é a unidade
que a busca devolve).
origin separa o que você semeou do que o sistema derivou: text (prosa
curada), file (arquivo ingerido — e aí são os chunks reais da fonte) ou distilled
(um card da Refinery, destilado a partir do seu corpus). Um audit de "o que eu
semeei?" é a listagem menos os distilled.
surface diz onde o trecho está indexado: brain (a base de conhecimento atual
do agente) ou knowledge (a base vetorial legada). A listagem é a união das duas,
porque o agente recupera das duas — se você vê trechos em knowledge, eles estão
ativos na recuperação e podem ser migrados com
POST /v1/agents/{id}/knowledge/backfill. Se uma das superfícies estiver
indisponível no momento, a resposta traz surfaces_unavailable junto com o que deu
para ler — o inspetor nunca deixa de fora uma superfície em silêncio.
O inspetor cobre o corpus de conhecimento e os cards destilados dele — o mesmo
conjunto que a busca recupera. Memória do end-user, persona e páginas internas do
agente nunca aparecem. Corpus vazio → total: 0 com chunks: [].
GETBuscar no conhecimento#
GET /v1/agents/{id}/knowledge/search — parâmetros:
| Param | Default | Uso |
|---|---|---|
q |
— | obrigatório — a consulta |
k |
5 | trechos por superfície (cap 20) — veja abaixo: não é o total |
source_id |
— | escopa a busca a um arquivo |
mode |
— | qa_first | qa_only | chunks (prioriza os cards de Q&A) |
Resposta 200:
{
"query": "qual o EC50 do AVX-006",
"k": 5, "source_id": "", "mode": "qa_first",
"chunks": [
{ "content": "EC50 do AVX-006 = 22,4 µM…", "source": "protocolo-thv.pdf", "score": 0.94 },
{ "content": "- prefere unidades em µM", "source": "O que você lembra deste usuário",
"score": 1, "pinned": true }
],
"counts": { "brain": 4, "knowledge": 0, "memory": 1, "conversation": 0, "total": 5 }
}
k limita cada superfície, não o total. O agente recupera de várias superfícies
em paralelo e o resultado é a união delas, então total normalmente excede k
(k=1 pode devolver 5 trechos; k=10, 8). Dimensione sua janela de contexto por
counts.total, nunca por k.
counts |
de onde vem | limite |
|---|---|---|
brain |
as páginas de conhecimento do agente | k páginas + os cards de Q&A destilados, que entram por cima quando mode=qa_first |
knowledge |
a base vetorial legada (agentes em modo legado) | k |
memory |
o bloco de memória do end-user | 0 ou 1 — fixo |
conversation |
recall de conversas anteriores (opt-in) | 3 |
total |
o que realmente veio na resposta | soma das acima |
O bloco de memória é fixado (pinned), não recuperado. Ele acompanha toda
busca — inclusive uma consulta sem nenhuma relação com o conteúdo — porque a memória
curada do end-user é injetada inteira, sem busca semântica. Ele vem com score: 1 e
"pinned": true: esse 1 é marcador de fixação, não um grau de similaridade.
Trechos ranqueados de verdade não têm o campo pinned. Se você ordena, filtra ou
mostra score na sua UI, cheque pinned primeiro — sem isso a memória parece um
match perfeito que nunca foi calculado.
Duas coisas sobre counts.memory que surpreendem se você não souber:
- Este endpoint é a visão do operador, não de um end-user. Ele não aceita identificador de end-user, então a memória que aparece é sempre a da partição do operador — nunca a de um cliente seu. Não existe forma de inspecionar a memória de um end-user específico por aqui; ela entra no turno de chat daquele end-user.
counts.memorypode virar0de um dia para o outro sem nada ter mudado. O bloco é montado com o perfil de estilo + os fatos de longo prazo + as observações do dia ainda não consolidadas. Um agente cuja memória seja só observações do dia fica commemory: 0depois da virada do dia (UTC), até a consolidação promover aquilo a fato de longo prazo. Se você comparar duas medições em torno da meia-noite UTC, é isso — não é regressão.
GETCards de Q&A#
GET /v1/agents/{id}/qa — a FAQ destilada pela Refinery:
{ cards: [ { id, question, answer, provenance: [...], confidence, status, enabled } ], total }.
DELETEDescartar um card de Q&A#
DELETE /v1/agents/{id}/qa/{cardID} → 204. Remove UM card destilado (owner/admin).
cardID é o slug, ou o id que o GET .../qa devolveu — os dois funcionam.
Por que você vai precisar disso. A auto-evolução transforma perguntas mal
respondidas em cards novos. Então uma query de teste contra
GET .../knowledge/search pode virar um card dentro de um agente de produção — e
passar a ranquear alto para aquela pergunta. É feature, não bug, mas a consequência
merece um aviso: sonde com um agente descartável, não com o que atende cliente.
Se um card sintético apareceu, este endpoint é a saída.
O card volta na próxima destilação se o conhecimento de origem ainda sustentar a pergunta — descartar não é uma regra permanente, é uma remoção.
| Resposta | Significa |
|---|---|
400 QA_CARD_INVALID |
o cardID está vazio ou não é um card — tente o id que o GET .../qa devolveu. Também cobre o separador escapado (a%2Fb, %2e%2e): o id é um segmento e não pode apontar para fora de faq/ |
404 page not found (sem envelope) |
você mandou um / literal dentro do cardID. O roteador não casa a rota, então essa resposta não tem error_code/trace_id — trate um 404 sem envelope como "URL malformada", não como "card inexistente" |
400 QA_CARD_NOT_ADDRESSABLE |
o agente está em modo legado: tem cards, mas sem endereço individual. Nenhum id vai funcionar — não vale procurar outro |
404 QA_CARD_NOT_FOUND |
esse card não existe (mais) neste agente |
POSTDestilar (Refinery) e evoluir#
POST /v1/agents/{id}/knowledge/refine — inicia um run de destilação (BYO
key): a Refinery relê o conhecimento e produz/atualiza os cards de Q&A. É
assíncrono — resposta com o id do run; acompanhe em
GET /v1/agents/{id}/knowledge/refine/{rid} (status/resultado).
POST /v1/agents/{id}/knowledge/evolve — força um ciclo de auto-evolução da
base (reorganiza/depura o conhecimento destilado).