Ir para o conteúdo
mcpd
sem daemon
Código

Cada etapa que o seu agente dá, mantida até o fim e registrada.

O mcpd é um daemon, não um framework. Ele segura uma execução entre as etapas: as repetições, os temporizadores, a semana de espera por uma aprovação, enquanto o seu próprio código faz o trabalho dentro de cada etapa, em qualquer linguagem. Cada etapa cai em um trace com o que custou.

Latência do modelo
A strip chart recording model latency over the last hour: a baseline near 420 milliseconds with five occasional excursions towards 800 milliseconds.
A strip chart recording model latency over the last hour: a baseline near 420 milliseconds with five occasional excursions towards 800 milliseconds.

Compile e execute

git clone https://github.com/mcpd/mcpd && cd mcpd && make build && ./target/release/mcpd demo

Depois abra localhost:4317. Sem conta, sem banco de dados para subir, nada para assinar. Ainda não há release: a imagem e os pacotes chegam junto com a primeira. Leia o início rápido de cinco minutos.

Uma execução que dá para ler.

Um agente que falha não falha na linha de log que menciona o erro. Ele falha três etapas antes, quando o modelo escolheu a ferramenta errada e depois escolheu de novo.

O mcpd reconstrói a execução inteira como uma árvore: cada chamada de modelo, cada chamada de ferramenta, cada recuperação, com o prompt e a resposta exatos anexados a cada uma, a latência de cada uma e quanto cada uma custou.

Abra a interface

Trace b3d9e71a Fault
0ms 2.1s 4.2s 6.3s 8.4s
  1. agent support_agent fail
  2. model chat
  3. tool returns_policy
  4. model chat
  5. tool returns_policy
  6. model chat
  7. tool returns_policy

The model called one tool three times and never answered. That is visible in four seconds here, and invisible in a log file.

Tambor da execução

A mesma execução, enrolada em um tambor registrador. O tempo dá a volta nele, uma trilha por span. Gire-o, e a pena lê o span embaixo dela.

Arraste para girar o tambor, ou use as setas 2.64 s · tool returns_policy · 90 ms

Uma execução que sobrevive ao processo que a iniciou.

O daemon é dono da execução entre as etapas. O seu processo é dono de tudo que acontece dentro de uma etapa, e não entrega nada: nem o modelo, nem o prompt, nem uma credencial.

Quadro de leases

O daemon mantém uma etiqueta por etapa no quadro. Um worker só segura uma etiqueta enquanto seus heartbeats chegam. Pare o worker e, depois do lease e de uma janela de recuperação, a etiqueta volta ao quadro para o outro worker.

Clique, ou pressione Espaço, para parar o worker draft · worker-a · started · lease 25 s

O grafo é dado, os corpos são seus

Um workflow é escrito uma vez, em código. O grafo vai para o daemon como uma definição que ele valida e versiona. Os corpos das etapas ficam no seu processo, em qualquer uma das seis linguagens, para um worker servir.

Repetições, temporizadores e esperas são linhas

Uma falha transitória é repetida dentro do orçamento da própria etapa. Um sleep ou uma espera por aprovação segura a execução sem nenhum processo vivo, atravessando uma implantação, uma queda ou uma semana. Um evento responde à espera por nome e por chave, e um evento que chega duas vezes é uma duplicata, não uma segunda execução.

Toda execução é um trace

Cada etapa é um span com a sua entrada, a sua saída e o uso que ela reportou, precificado com a mesma tabela que precifica todos os outros spans. A chamada de modelo dentro de uma etapa fica abaixo dela, então a busca, o custo e os avaliadores funcionam sobre uma execução sem nenhuma mudança.

Leia sobre workflows

Instrumente na linguagem que você já escreve.

Seis SDKs, um comportamento. Registrar um span nunca bloqueia, nunca lança uma exceção e nunca cresce sem limite: se o mcpd estiver inacessível, o seu agente não percebe, que é a única maneira aceitável de a observabilidade falhar. Cada um também traz um worker, então as etapas de uma execução durável são servidas pelo mesmo processo que as registra.

Nenhum deles é obrigatório. Aponte OTEL_EXPORTER_OTLP_ENDPOINT para o mcpd e um SDK do OpenTelemetry que você já executa chega até ele, sem nenhum código nosso dentro da sua aplicação. Os seis são uma conveniência sobre uma superfície HTTP, não uma condição para ser registrado.

A observabilidade que vem presa a um framework só consegue registrar agentes construídos com aquele framework. O mcpd aceita o que chegar ao seu endpoint OTLP: um framework de agentes, um laço que você mesmo escreveu, ou os serviços que qualquer um dos dois chama.

instrumente uma execução

/**
 * One ticket, one trace.
 *
 * `withSpan` opens the span, keeps it active for everything the callback
 * starts, and ends it when the callback settles. Nothing inside has to be
 * passed a handle to be nested under it.
 */
async function handleTicket(ticket: Ticket): Promise<{ answer: string; traceId: string }> {
  let traceId = ''
  const answer = await mcpd.withSpan('support_agent', async (run) => {
    // Keep the id: a score or a piece of feedback that arrives later attaches
    // to this run by it.
    traceId = run.traceId
    // Tags label the whole run, so the trace list can filter on them.
    run.setTags(['support', 'tier-1'])
    run.setCorrelation({ sessionId: ticket.id, userId: ticket.customerId })
    run.setInput({ question: ticket.question })

    const order = await lookUpOrder(ticket)
    const answer = await draftAnswer(ticket, order)

    run.setOutput({ answer })
    return answer
  })
  return { answer, traceId }
}

Seis sinais, correlacionados pela execução que os produziu.

A maioria das ferramentas dá os traces em um lugar e os logs em outro, e deixa você casar os horários no olho. O mcpd guarda os seis sinais sob os mesmos ids de trace e de span, então um pico em um gráfico abre a execução que o causou, e uma execução mostra as linhas de log que emitiu.

Bancada de triagem

Seis tipos de sinal chegam misturados em uma só esteira. O leitor lê o trace id de cada um, e cada peça de uma execução cai na gaveta dessa execução.

Aponte para uma gaveta, ou use as setas trace 4bf9 · span 3 · log 1 · metric 1 · score 1 · feedback 1

13 linguagens que concordam entre si.

Cross-language conformance

tests/e2e/run.mjs

Channel root tree status error model tokens score feedback ids
TypeScript
Python
Go
Elixir
Kotlin
Rust
cURL
TypeScript
Python
Go
Elixir
Kotlin
Rust

13 languages, one canonical run, one daemon. Every trace comes back the same shape.

481 checks

A conformance panel with one row per language and one column per family of check. Every cell passes. The families are: the root span is the agent run; every child hangs off the root; a failed step makes the whole run an error, and only that step; the error carries a message, on the step that failed; the model call records its model; token usage and cost survive the wire; the scores land, with their scale; the feedback lands; ids are OpenTelemetry-shaped. The suite asserts 37 checks in each of the 13 languages.

Cada SDK é testado por conta própria, o que prova que cada um faz o que os seus autores quiseram e não prova nada sobre eles combinarem entre si. Por isso cada um escreve a mesma execução contra um único daemon, e os traces são lidos de volta e comparados uns com os outros.

Um comportamento que vale em uma linguagem e não vale em outra aparece ali e em nenhum outro lugar. Roda no CI a cada mudança. Como isso se compara.

Quem leu o quê, inclusive quem foi barrado.

A pergunta que decide uma revisão de segurança não é se os dados estão seguros. É se você consegue mostrar, depois, quem chegou até eles.

Audit log

GET /v1/api/audit

Time Principal Action Project Result
14:02:11 a3f9c2e1b7d4 get_trace checkout ok
14:02:11 a3f9c2e1b7d4 list_spans checkout ok
14:04:38 7c1e08b53a9f list_dataset_items billing refused
14:07:02 d52a4f8c0e63 capture_into_dataset checkout ok
14:11:55 d52a4f8c0e63 delete_trace checkout ok
14:19:20 7c1e08b53a9f list_feedback billing ok

A refusal is recorded like a read. The principal is a digest of the key, never the key. Retention never trims this table.

18 actions

An audit log with six entries, each showing the time, a truncated digest of the key that acted, the action, the project and the result. One entry, a read of another project's dataset items, is recorded as refused.

Seis escopos de chave

Emitir telemetria, ler prompts, alterar dados guardados, destruí-los, iniciar execuções e reivindicar etapas são privilégios diferentes. Uma lista write sem definir aceita as chaves de ingestão, e uma lista admin sem definir aceita as chaves de leitura. Listas execute e worker sem definir aceitam as chaves de escrita. Atualizar não tranca você do lado de fora.

Um projeto é uma fronteira

Chaves, retenção e amostragem são todas por projeto. Uma chave listada em um projeto alcança aquele projeto, e o daemon resolve de quem é um dataset antes de devolver um único caso dele.

Nada telefona para casa

Existem quatro pontos de requisição para fora no daemon inteiro, e você configura cada um deles: o encaminhador, dois webhooks e o juiz. Não há verificação de atualização, telemetria nem servidor de licença.

Qual chave, e qual pessoa

O registro nomeia a chave. Dê ao mcpd o cabeçalho que o seu ingress já define, e os endereços autorizados a defini-lo, e ele nomeia também a pessoa. Vindo de qualquer outro lugar, um cabeçalho é uma afirmação que qualquer um poderia fazer, então a linha fica vazia em vez de carregar um palpite.

Um pedido de exclusão é uma chamada

Um DELETE remove todas as execuções registradas para uma pessoa, com os spans, os logs, as métricas, as notas e os feedbacks pendurados nelas, e responde com uma contagem por sinal. Linhas de um projeto que aquela chave não pode abrir não são tocadas nem contadas.

Confira antes de confiar

mcpd doctor avalia uma implantação: se o armazenamento aceita uma escrita e a devolve, se um escopo ficou aberto, se um projeto configurado já recebeu algum span. Tudo que ele quer que você resolva vem com o nome da opção que corrige.

O que o mcpd não faz.

Toda ferramenta tem uma lista dessas. A maioria a mantém para uso interno, e você descobre o conteúdo dela na segunda semana. Aqui está a nossa, com o motivo no lugar de uma data.

Sem OTLP sobre gRPC.
O HTTP leva traces nas duas codificações, e logs e métricas em JSON. gRPC não tem suporte nenhum.
Sem exporters de fornecedor pelo nome.
O mcpd encaminha OTLP, o que alcança a maioria dos destinos com uma única implementação e faz do mcpd um salto no caminho, e não o lugar onde os seus dados têm de ficar. Um destino que só fala o próprio protocolo está fora de alcance.
Sem SSO nem SCIM.
As chaves têm escopo por projeto, o que é uma fronteira de isolamento, não um provedor de identidade. A identidade de uma pessoa é o que o seu ingress estabelecer na frente do mcpd.
Sem ingestão em várias regiões.
No Postgres ou no ClickHouse, ele roda como várias réplicas atrás de um único serviço. Isso não é failover entre regiões.

O relato completo, medido contra uma auditoria direta do código do produto estabelecido, está em PARITY.md. Ele é atualizado quando alguma coisa chega, não quando alguma coisa é planejada.

Cinco minutos até o seu primeiro trace.

Execute o daemon, aponte o seu agente para ele, abra a interface. Se demorar mais do que isso, é um defeito e nós queremos o relato.

Começar

docker run -p 4317:4317 ghcr.io/mcpd/mcpd