Segurança
Uma página para responder a uma auditoria. O que fica guardado, o que é enviado para fora, quem pode ler e os controles que o mcpd não tem.
Tudo o que vem abaixo foi tirado do código do próprio daemon e de
spec/mcpd.spec.json, que é de onde a API é gerada. Onde um
controle não existe, esta página diz isso, em vez de deixar a lacuna para
você descobrir em um questionário.
O que o mcpd armazena
O mcpd armazena o texto das conversas do seu agente. É para isso que ele
serve, e uma auditoria deveria começar por aí. Um span do tipo
model_generation carrega input e
output: o prompt exato enviado ao modelo e o texto exato que
voltou. Para a maioria das pessoas, esse é o dado mais sensível que elas
têm.
Nos seis sinais, os campos de texto livre que ficam armazenados são:
| Sinal | Conteúdo que ele guarda |
|---|---|
| Spans | input, output, attributes, metadata e a mensagem de erro |
| Logs | a mensagem e os fields estruturados |
| Métricas | labels |
| Notas | reason e metadata |
| Feedback | o comment de texto livre e metadata |
| Notas de aspecto | a frase, em text |
Junto do payload, cada sinal carrega campos de correlação que ligam uma
execução ao resto do seu sistema: projeto, ambiente, nome e versão do
serviço, sessão, thread, usuário, recurso, execução, requisição, experimento
e release. O user_id passa por hash na entrada, a menos que
você desligue a ocultação. Os demais são armazenados como chegaram, então
trate qualquer um deles como um campo capaz de identificar uma pessoa, caso
você coloque uma identidade dentro dele.
A contagem de tokens e um valor de custo são calculados na ingestão e guardados com a execução. Nada sobre a chamada ao modelo é consultado em um provedor.
Onde isso é gravado
Um backend, escolhido na inicialização. O padrão é um arquivo SQLite
embutido em ./mcpd.db, definido com --data. O
índice de busca em texto completo fica no mesmo arquivo, então o
armazenamento é um arquivo só para fazer backup e um arquivo só para
proteger. Com --postgres-url ou --clickhouse-url
os dados passam a ficar em um banco que você mesmo opera.
As execuções duráveis, quando estão ligadas, vivem em um segundo arquivo
SQLite ao lado do primeiro, seja qual for o armazenamento de traces. Ele
guarda a entrada, o estado, a saída e a correlação de toda execução, e a
entrada, a saída e o erro de toda etapa, exatamente como foram enviados:
sem ocultação, com o user_id sem hash, porque uma etapa
posterior os lê de volta e um valor reescrito mudaria o que a execução faz.
Os spans que uma execução escreve no armazenamento de traces são ocultados
como qualquer outro. Proteja o arquivo de execuções como você protege o
armazenamento de traces, ou mais. Tanto a retenção quanto o apagamento por
titular alcançam esse arquivo, para as execuções que terminaram; veja
Execuções.
O mcpd não criptografa o que grava. Criptografia em repouso é responsabilidade do volume no armazenamento embutido, e do banco de dados nos outros dois.
O que sai da máquina
Com a configuração padrão, nada. O daemon faz requisições de saída em exatamente quatro lugares, e todos eles ficam desligados até você configurá-los. Não há verificação de atualização, relatório de uso nem telemetria de espécie alguma.
| Destino | O que é enviado | Desligado, a menos que |
|---|---|---|
| Backend de armazenamento | Tudo o que o mcpd armazena, pela sua própria conexão. | Você defina --postgres-url ou --clickhouse-url. O armazenamento padrão é um arquivo local e não precisa de rede nenhuma. |
| Encaminhamento |
Todo span armazenado, reemitido como OTLP JSON para
{endpoint}/v1/traces. Isso inclui o payload:
input e output saem como os atributos
input.value e output.value, cada um limitado
a 32 KiB. Valores já ocultados continuam ocultados, e um
user_id com hash é encaminhado como o hash.
| Você defina --forward-endpoint, ou um bloco forward no arquivo de configuração. |
| Webhooks de monitores e de objetivos | Um corpo JSON fixo: o id e o nome do monitor ou do objetivo, o projeto, a condição, o que ele observa, o limiar, a leitura, a quantidade de amostras, uma linha de detalhe e um horário. Não há texto de prompt nem de resposta nele. | Um monitor ou um objetivo no arquivo de configuração indique um webhook. |
| Modelo juiz |
O critério, mais a pergunta da execução e a resposta dela, cada uma
truncada em 4.000 caracteres, enviadas por POST para
{endpoint}/chat/completions. Esse é o único
caminho que manda texto de prompt e de resposta armazenados para um
provedor de modelos.
| Um bloco judge no arquivo de configuração indique um endpoint, um modelo e ao menos um critério, e quem chama peça esse avaliador pelo nome. |
A interface que o binário serve em / está compilada dentro
dele. As fontes, os estilos e os scripts vêm da mesma origem, e ela não faz
nenhuma requisição a terceiros.
Chaves e escopos
A autenticação é por chaves de API. Não há conta, sessão nem login. A chave
é apresentada como Authorization: Bearer … ou no cabeçalho
X-Mcpd-Key, e os dois lados passam por SHA-256 antes de serem
comparados, de modo que nem o tamanho da chave configurada nem a posição do
primeiro caractere errado podem ser observados pelo tempo de resposta.
São seis escopos, na ordem do alcance que concedem:
| Escopo | O que ele permite | Por que é um escopo próprio |
|---|---|---|
ingest | Gravar sinais. Não ler nada. | Essa chave vive em todo contêiner que emite um span, inclusive nos que rodam código de terceiros. Ela não pode também abrir todo prompt armazenado. |
read | Ler sinais armazenados. | É na leitura que estão os prompts. |
write | Alterar dados armazenados: notas, feedback, datasets, experimentos e reconhecimentos. | Vários desses handlers leem um payload no caminho. Capturar uma execução de produção em um dataset lê o input e o output daquela execução, o que é mais do que emitir um span. |
execute | Iniciar, cancelar e sinalizar execuções, e publicar eventos para elas. | Iniciar uma execução gasta dinheiro e alcança os seus sistemas. Uma chave que pode registrar uma nota não deveria também poder disparar trabalho. |
worker | Reivindicar etapas e informar o que aconteceu com elas. | É a chave dentro do processo que roda os corpos das etapas. Uma reivindicação devolve o input da etapa e o output de cada etapa anterior, então ela lê payloads, e não deveria também poder iniciar execuções nem mudar dados armazenados. |
admin | Destruir dados e ler o log de auditoria. | Apagar não é ler. |
Qual escopo cada endpoint exige está declarado por endpoint na especificação e publicado na referência gerada da API HTTP, de forma que existe uma resposta para "o que este endpoint precisa", e não uma por documento.
O recuo, e o que ele significa para uma chave que você já tem
Uma lista write não definida recua para a lista
ingest, e uma lista admin não definida recua para
a lista read. Listas execute e
worker não definidas recuam para a lista write, e
portanto para a lista ingest quando essa também não está
definida. É isso que impede que uma atualização revogue o acesso em toda
implantação configurada antes de os escopos mais estreitos existirem. Uma
chave de administração também vale para execute e
worker, para que um operador possa conduzir uma execução à
mão. Juntas, essas regras significam que, em um daemon com apenas uma chave
de ingestão e uma chave de leitura:
- a chave de ingestão também anexa notas e feedback, inicia execuções e reivindica etapas, e
- a chave de leitura também apaga traces e, pelo recuo de administração, inicia execuções e reivindica etapas.
Ou seja, uma chave de leitura é uma chave de exclusão até você definir
--admin-key, e as duas chaves alcançam a superfície de
execuções até você definir --execute-key,
--worker-key e --admin-key. É assim que você deve
entregar as chaves. Nada se estreita sozinho. Cada escopo tem uma opção,
--ingest-key, --read-key,
--write-key, --admin-key,
--execute-key e --worker-key, e cada uma recebe
uma lista; todas elas também podem ser definidas por projeto no arquivo de
configuração.
Sem chave nenhuma, todos os escopos ficam abertos
Esse padrão é deliberado: uma primeira execução em um notebook não pode exigir
uma credencial. Ele é também a coisa mais perigosa em uma instância
exposta, então, quando o daemon se liga a um endereço fora do loopback, ele
registra na inicialização um aviso para cada superfície aberta a quem não
deveria alcançá-la. Cada aviso nomeia a superfície e as opções que a
fecham. Rode mcpd config antes de expor uma porta: ele imprime
a configuração para a qual uma invocação resolve e então termina. Ele traz
uma linha por escopo, e nomeia a lista para a qual um escopo recuou, então
admin auth key required (read keys) avisa que as chaves de
leitura ainda podem apagar.
Docker e Kubernetes
mostra a saída.
fechado antes de estar acessível
mcpd --ingest-key "$INGEST" --read-key "$READ" Um projeto é uma fronteira de isolamento
Uma chave configurada no nível superior concede todo projeto. Uma chave
listada, em vez disso, sob um projeto nomeado no bloco
projects do arquivo de configuração concede aquele projeto e
nenhum outro, e cada projeto mantém a sua própria lista por escopo. Uma
mesma chave pode aparecer sob vários projetos, que é como um time de
plataforma mantém uma chave que cobre o que ele opera sem ficar com o
servidor inteiro.
No caminho de gravação, a verificação é por sinal, não por requisição:
- Um lote pode citar vários projetos. Sinais que citam um projeto que a chave não alcança são descartados e informados na resposta como rejeitados, com o motivo, então uma frota apontada para o projeto errado é descoberta a partir da resposta que ela já lê. O resto do lote é armazenado.
No caminho de leitura, todo endpoint que cita um projeto confere esse
projeto contra o que a chave alcança: traces, spans, logs, métricas,
notas, feedback, análises, datasets, experimentos e o log de auditoria.
Os itens dentro de um dataset são endereçados pelo id do dataset, e o daemon
procura de qual projeto aquele dataset é e confere a chave contra ele, de
modo que uma chave que não abre o projeto não lê os casos capturados dele.
Um id que não existe é recusado do mesmo jeito que um id que a chave não
alcança, então o endpoint não serve para testar quais ids existem. Três endpoints não recebem projeto e ficam acessíveis a qualquer chave
com o escopo de leitura, sejam quais forem os projetos a que ela está
limitada: a lista de projetos, o catálogo de avaliadores e o conjunto de
capacidades do armazenamento (GET /v1/api/capabilities). A
lista de projetos é filtrada pelo alcance da chave, então uma chave
limitada a um projeto só vê os projetos que ela alcança. Em uma instância
compartilhada, nomes de projeto costumam ser nomes de times ou de
clientes. O estado dos monitores e o estado dos objetivos estiveram nessa
lista até serem filtrados pelo alcance de projeto da chave, o que sempre
deveria ter sido o caso: um monitor nomeia o projeto que vigia e carrega o
webhook para onde envia.
A API nomeia só o host desse webhook, porque a URL de um webhook muitas vezes é a própria credencial.
Uma recusa diz qual dos dois problemas ela é
Uma chave ausente ou desconhecida é 401, e uma chave reconhecida que este endpoint ou este projeto não admite é 403. A separação é operacional: um 401 manda você procurar um erro de digitação na credencial, e um 403 diz que a credencial está certa e a permissão é que está errada.
$ curl -sw "%{http_code} " "$MCPD/v1/api/traces?project=acme"401 this endpoint needs an API key; send it as `Authorization: Bearer <key>`$ curl … -H "authorization: Bearer $TYPO"401 that key is not one this daemon knows$ curl … -H "authorization: Bearer $INGEST"403 that key is not valid for reading$ curl … -H "authorization: Bearer $BILLING_READ"403 that key does not reach the project "acme" Essa última mensagem é a mesma, exista o projeto ou não. Confirmar quais nomes de projeto são reais é um vazamento pequeno, e em um daemon compartilhado esses nomes são os nomes dos seus clientes.
O log de auditoria
Devolver um payload armazenado para fora do mcpd é registrado, e toda
exclusão também, então a pergunta "quem leu esta execução" tem resposta. Uma
linha carrega quando, qual chave, qual ação, qual projeto, qual trace ou
dataset ela citou, e se a requisição foi atendida ou recusada. O
GET /v1/api/audit lê o log de volta, do mais novo para o mais
antigo, e exige o escopo admin.
Dezoito ações são registradas: get_trace, list_spans,
list_logs, list_feedback,
list_dataset_items, capture_into_dataset,
read_trace_aspect_notes, get_theme, get_theme_flow, list_theme_history, list_theme_examples,
delete_trace, erase_subject, e cinco sobre
execuções duráveis: list_runs, get_run,
list_run_events, get_step_cause e
cancel_run. A fronteira é o payload, não a requisição. Listar
traces devolve uma projeção de nomes, tempos, contagem de tokens e custo,
sem nenhum prompt ou resposta dentro, então isso não é registrado; abrir um
desses traces é, porque é ali que está o texto. Listar execuções duráveis é
registrado, porque toda execução na lista carrega a entrada, a saída e o
estado dela como foram enviados. Uma leitura recusada de uma execução é
registrada sob o projeto dono da execução.
As chamadas de worker não são registradas: reivindicar uma etapa, declarar
que ela começou, o heartbeat, o complete e o fail. Um lease carrega, sim, a
entrada da etapa e as saídas das etapas anteriores, mas o laço do worker
reivindica muitas vezes por minuto, e uma linha por reivindicação
enterraria as leituras que uma pessoa fez. Trate a chave
worker como uma chave que lê todo payload das execuções que
ela serve. Iniciar uma execução também não é registrado; reenviar uma chave
de idempotência ainda guardada responde com a execução que aquela chave
iniciou, então trate a chave execute como uma chave capaz de
ler as execuções que ela inicia.
Cinco propriedades que um auditor deveria conhecer:
- A chave nunca é escrita no log. O principal é um resumo
SHA-256 truncado da chave, o que distingue uma chave de outra sem guardar
um verificador dela. Uma requisição sem chave em um daemon aberto é
registrada como
anonymous. - A pessoa vem do seu ingress, ou não vem. O mcpd não tem
contas de usuário, então, por conta própria, uma linha atribui uma ação a
uma chave e não a um ser humano. O campo
subjectcarrega uma identidade quando--trusted-identity-headere--trusted-proxy-cidrestão os dois definidos, e fica vazio caso contrário, que é o padrão. O mcpd nunca estabelece essa identidade por si mesmo; ele registra o que um endereço indicado por você afirmou. - Uma recusa também é registrada. A linha é gravada antes de o 403 ser devolvido, porque uma tentativa recusada no projeto de outro time é exatamente a linha para a qual um log de auditoria existe. Ler o log não é registrado: ele cresceria sem que payload algum fosse lido, e a linha não diria nada que quem tem a chave admin já não saiba.
- Ele registra qual chave e, opcionalmente, quem. A chave é
guardada como um resumo truncado, nunca a chave em si, então o log não é
um lugar onde credenciais se acumulam. Isso responde "uma chave admin
apagou isto" e não "qual pessoa apagou", porque o mcpd não tem provedor de
identidade. Se você quer a segunda resposta, dê a
ele o cabeçalho que o seu ingress já define e os endereços autorizados a
defini-lo:
--trusted-identity-header x-forwarded-usercom--trusted-proxy-cidr 10.42.0.0/16. As duas metades são obrigatórias. Um cabeçalho é um texto que qualquer um que alcance a porta pode enviar, então o mcpd só o lê a partir de um endereço indicado por você; uma requisição vinda de qualquer outro lugar é atendida normalmente e a linha dela não carrega sujeito, que é a resposta honesta em vez de um palpite. Sem nenhuma das duas opções, nenhum sujeito é registrado, e esse é o padrão. - A retenção não o corta. A passagem de retenção remove spans, logs, métricas, traces, notas, feedback, notas de aspecto e execuções duráveis terminadas. Ela deixa o log de auditoria em paz, porque um log que expirasse junto com os dados que ele descreve não poderia dizer quem leu os dados apagados.
- Ele nunca faz falhar a requisição que descreve. Uma linha
que não pode ser gravada é anotada no log e contada como
mcpd_audit_write_failures_totalem/metrics, e a leitura mesmo assim retorna. Um log de auditoria capaz de derrubar o caminho de leitura é um risco pior do que aquele que ele evita, então acompanhe esse contador em vez de contar com requisições falhando.
O que nenhuma chave cobre
- A interface. A marcação em
/é servida a qualquer um que alcance a porta. Os dados por trás dela não: toda requisição que ela faz passa pela API de consulta e encontra as mesmas chaves. Não existe tela de login dentro do daemon. - As sondas e o
/metrics. Sem autenticação, de propósito, porque um kubelet e um coletor não conseguem carregar uma chave. Eles informam status, versão, tempo no ar, o nome do backend de armazenamento e contadores, nunca payloads. - Origem. A API de consulta e as sondas respondem com
Access-Control-Allow-Origin: *, então uma página de navegador em qualquer origem pode chamá-las. O mcpd não usa cookies nem credenciais implícitas, então essa página ainda precisa ter uma chave: uma política de origem permissiva não entrega uma chave a ninguém.
O que a ocultação faz, e o que ela não consegue fazer
A ocultação vem ligada por padrão e roda antes da gravação, como terceiro
estágio do pipeline de entrada. Um valor que uma regra casa nunca chega ao
banco de dados, o que é uma garantia diferente de um valor apagado depois, e
a diferença é justamente a que um auditor pergunta. O que ela não consegue
fazer é manter os bytes fora da rede: eles já a atravessaram quando a regra
roda. Para isso, os SDKs trazem MCPD_CAPTURE_PAYLOADS=false,
que descarta input e output antes de um span
entrar na fila e deixa todo o resto dele.
-
Decode
OTLP or native, gzip or plain
-
Sample
Decided once, at the root
-
Redact
Before anything is written
-
Price
From the configured price book
-
Store
One transaction per batch
Redaction is stage three, not a cleanup job. A value that a rule matches never reaches storage at all.
Uma regra é um conjunto de trechos em minúsculas mais um modo. Uma chave casa quando o nome dela, em minúsculas, contém um desses trechos. Os valores casados são mascarados, mascarados em parte, recebem um hash com chave, ou são descartados. Quando uma chave casada contém um objeto, tudo o que está dentro dele também é ocultado, sejam quais forem os nomes dos campos filhos, e um segredo enviado como número JSON é tratado igual a um enviado como texto. A tabela completa de regras está em Ocultação e retenção.
Agora o limite, que uma auditoria vai perguntar e que nenhuma configuração
remove. As regras casam nomes de campo, não o formato de um
valor. Uma chave de API que alguém colou no meio de uma frase, e
que chega em um campo chamado content ou
question, não é casada por nada no pipeline. O nome do campo é
inocente, então o valor é armazenado por inteiro.
A ocultação, portanto, impede uma coisa específica:
- Ela impede a captura estruturada de credenciais e identificadores, em que a credencial chega num campo com o nome daquilo que ela é.
- Ela não varre texto livre, e não é um classificador de conteúdo.
- Ela não detecta um segredo pelo formato nem pela entropia.
- Ela não toca no nome de um span nem na mensagem de um registro de log. Esses são identificadores usados para agrupar, e não payloads, e reescrevê-los quebraria o agrupamento. Não monte o nome de um span com o e-mail de um cliente.
- Ela não toca no armazenamento de execuções. Os payloads de uma execução durável são o estado de trabalho dela e ficam guardados como foram enviados; só os spans que uma execução escreve no armazenamento de traces são ocultados.
Um controle atua, sim, sobre o formato, e ele é detecção depois do fato
em vez de prevenção: o avaliador embutido no_leaked_secret penaliza
uma execução cuja resposta carrega um token com cara de credencial,
por prefixo conhecido ou por uma sequência longa de letras e números com
maiúsculas e minúsculas. Ele pontua uma execução que já está armazenada. Ele
não remove nada, não lê o prompt, e só roda quando você pede. Encare-o como
um jeito de descobrir que isso aconteceu, não como uma segunda linha de
ocultação.
Dois limites valem, independentemente de alguma regra ter casado. Qualquer texto único maior que 128 KiB é truncado em um limite de caractere, com um marcador, e a varredura para em uma profundidade de 32. Os dois existem para que um único documento descontrolado não decida o tamanho do seu banco de dados nem prenda uma CPU.
O --no-redact desliga o pipeline inteiro, o que só faz sentido
quando algo antes dele já oculta. Onde a ocultação aplica hash a um
identificador, defina --redact-key: a chave embutida é uma
constante em código publicado, e identificadores vêm de espaços pequenos,
então um hash sem chave pode ser revertido testando candidatos.
Retenção e apagamento
O --retention-days apaga sinais mais antigos que essa
quantidade de dias. Omita a opção e nada é apagado, que é o padrão certo:
uma ferramenta que descartasse em silêncio os traces do mês passado seria
descoberta no pior momento possível.
A passagem roda em um temporizador de hora em hora dentro do daemon, nunca
dentro da requisição de alguém. Ela cobre todo projeto citado no bloco
projects, cada um com o seu próprio retention_days
quando ele define um, e com o valor global quando não define, mais o projeto
padrão. Um projeto sem janela em lugar nenhum não é cortado.
Para um pedido sobre uma pessoa, o
DELETE /v1/api/subjects/{user_id}?project=… remove toda
execução registrada em nome dessa pessoa no projeto, com os spans, logs,
métricas, notas e feedback ligados a elas, e responde com uma contagem
por sinal. O identificador passa pelo mesmo hash, com a mesma chave, antes
de ser comparado, então o pedido funciona contra identificadores com hash
sem que você mesmo aplique hash em nada, e a resposta diz quantos
identificadores armazenados estavam em um formato que o pedido não
conseguiu casar. Para remover uma execução em vez de uma pessoa, o
DELETE /v1/api/traces/{trace_id} remove essa execução e
tudo o que está ligado a ela em uma única transação. Os dois exigem o escopo
admin e os dois vão para o log de auditoria.
Ocultação e retenção
percorre o pedido de apagamento e o que ele consegue e não consegue
prometer.
O que o mcpd não faz
Nada do que vem abaixo existe nesta versão. Está listado porque uma auditoria que descobre esses pontos depois é um desfecho pior do que uma auditoria que os lê aqui.
| Não existe no mcpd | O que fazer no lugar |
|---|---|
| Terminação TLS. O mcpd fala HTTP puro. Prompts e respostas atravessam esse enlace por inteiro. | Termine o TLS no seu ingress ou na sua malha de serviço, e mantenha a porta do daemon fora de qualquer rede não confiável. |
| SSO. Nenhuma integração com provedor de identidade, sem OIDC, sem SAML, sem tela de login. | Coloque a interface atrás do proxy de identidade que você já opera. A identidade de uma pessoa é aquilo que esse proxy estabelece. |
| SCIM. Nenhum provisionamento de usuários ou de grupos, porque o mcpd não tem o conceito de conta de usuário. | Provisione no proxy. Dentro do mcpd a unidade é uma chave, adicionada a uma lista ou removida dela. |
| Papéis além dos seis escopos. A autorização é um escopo e uma lista de projetos. Não há papel por endpoint nem grupo. | Separe por chave e por projeto. Se uma distinção importa, faça dela um projeto. |
| Estabelecer quem é a pessoa. O log de auditoria consegue registrar um sujeito, mas o mcpd nunca descobre quem é quem chama. Ele registra o que o seu ingress afirmou, e só a partir de um endereço indicado por você. | Defina --trusted-identity-header e --trusted-proxy-cidr para que o cabeçalho que o seu proxy já define chegue à linha de auditoria. Sem eles, uma linha atribui uma ação a uma chave, e emitir uma chave por time estreita o que o resumo aponta. |
Ciclo de vida de chaves. Chaves são listas lidas na inicialização. Não há expiração, calendário de rotação nem cota por chave. O teto que existe é por projeto: --quota-per-minute limita quantos sinais um projeto pode armazenar em um minuto, seja qual for a chave que os enviou. | Configure várias chaves, uma por time ou por frota, para que revogar uma seja uma edição mais um reinício, em vez de reimplantar tudo o que emite um span. Faça de uma frota o seu próprio projeto se ela precisa do próprio teto. |
| Criptografia em repouso. O mcpd grava arquivos comuns ou linhas comuns. | Criptografe o volume, ou use um banco de dados que faça isso. |
Como relatar algo
Suspeitas de vulnerabilidade vão para [email protected] com
"Security" no assunto, não para uma issue pública. O
SECURITY.md do repositório diz qual é o escopo e o que um bom
relato contém. Uma instância sem autenticação que alguém escolheu expor é um
padrão documentado, não um achado. Qualquer coisa que passe por uma chave
que estava configurada, por uma regra de ocultação configurada, ou
atravesse a fronteira de um projeto, é um achado.
A seguir
- A tabela de regras, os quatro modos e o hash com chave: Ocultação e retenção.
- O que definir antes de uma porta ficar acessível: Docker e Kubernetes.
- Cada opção e variável de ambiente: Configuração.
- Alcançar uma instância implantada a partir de código ou de um agente: Integração.