# Pesquisa: Agent Platform OCI (Christiano Hoshikawa) como referência Python/LangGraph/Langfuse > Fonte: https://github.com/hoshikawa2/agent_platform_oci (README + SPEC-001, 002, 003, 004, 007, 012, 018). Analisado em 2026-09-11 para fundamentar as decisões registradas em [`ADR-022`](../adr/ADR-022-referencia-hoshikawa-agent-platform-oci.md)/[`ADR-023`](../adr/ADR-023-autorizacao-nunca-vem-do-payload.md) (ver também [`../../RI/docs/Assumptions.md`](../../RI/docs/Assumptions.md)). Referenciado por ADR-022/ADR-023. > > **Escopo desta pesquisa**: extrair o padrão *operacional* de LangGraph/Langfuse/MCP daquele projeto para a camada de implementação Python da nossa RI. O núcleo de segurança (PEP/PDP/Client-Bound Entitlement) da nossa arquitetura permanece 100% definido pelo `README.md` na raiz deste repositório — nunca pelo projeto pesquisado aqui. --- ## 1. Padrão LangGraph **StateGraph — nós e shape do state.** O grafo por backend é fixo e corporativo: ```text START → input_guardrails → routing_decision → [agente(s) de domínio] → output_supervisor → output_guardrails → judge → supervisor_review → persist → END routing_decision → handoff → routing_decision (aresta condicional de retorno) ``` `AgentState` é um `TypedDict(total=False)` com: `user_text, sanitized_input, response_text, tenant_id, agent_id, channel, session_id, conversation_key, message_id, route, intent, context, business_context, tool_arguments, mcp_tools, mcp_results, rag_context, rag_metadata, guardrails, judges, metadata, errors`, mais `active_transaction`/`last_transaction` na extensão transacional (contrato durável de estado, sobrevive a checkpoint/resume). Registrar um novo agente é mecânico: import da classe → instanciar no `__init__` do workflow (recebendo dependências compartilhadas: telemetry, tool_router, rag_service, cache, observer, memory) → wrapper assíncrono com span de telemetria → `builder.add_node(...)` → entrada em `add_conditional_edges("routing_decision", ...)` → ligação ao `output_supervisor`. **Duas camadas de roteamento, explicitamente separadas:** - **Supervisor/router local do backend** — decide o fluxo interno de um agente (`routing.yaml`, `ROUTING_MODE=router|supervisor`). - **Global Supervisor / Agent Gateway** — serviço deployável separado que decide **qual backend inteiro** trata a conversa (`GLOBAL_ROUTING_MODE=router|supervisor|hybrid`, `backends.yaml`). Roteamento HTTP entre backends, não é um segundo LangGraph. **Checkpointing**: providers `memory` (testes), `sqlite` (dev local), `mongodb` (distribuído), `autonomous` (produção OCI). Contrato: `{conversation_key, checkpoint_id, state, pending_writes, created_at}`. **Handoff**: intra-backend via aresta condicional `routing_decision → handoff → routing_decision`; inter-backend via `metadata.handover_backend` na resposta, detectado pelo Agent Gateway, que mantém `global_session_id` separado de `backend_session_id`. --- ## 2. Padrão Langfuse **Taxonomia IC / NOC / GRL** (não é "Informational Context" — é evento de **negócio/jornada**, **operacional** e **guardrail**, respectivamente): | Família | Significado | Exemplos | |---|---|---| | IC | evento de negócio/jornada do agente | `IC.GATEWAY_RECEIVED`, `IC.AGENT_STARTED/COMPLETED`, `IC.MCP_CACHE_HIT/MISS/SET/BYPASS/NOT_STORED`, `IC.MCP_TOOL_DEDUPED`, `IC.GLOBAL_BACKEND_SELECTED` | | NOC | erro/indisponibilidade/timeout/degradação | `NOC.RUNTIME_FAILED`, `NOC.MCP_TIMEOUT`, `NOC.LLM_FAILED` | | GRL | governança/guardrail/bloqueio/sanitização | `GRL.INPUT_BLOCKED`, `GRL.OUTPUT_BLOCKED`, `GRL.MASK_APPLIED` | Regra prática: não criar evento por linha de código — só para decisões relevantes. **Instrumentação híbrida**: spans técnicos automáticos por nó do grafo; chamadas LLM capturadas automaticamente via `ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true` (providers compatíveis com OpenAI); eventos de negócio (IC/NOC/GRL) sempre manuais via `_emit_ic/_emit_noc/_emit_grl`, **fail-open** (observer ausente/com erro nunca quebra a jornada). Modo de trace `LANGFUSE_TRACE_MODE=verbose|compact`. **Prompt management**: **não usa** o CMS de prompts do Langfuse — versiona prompt localmente em YAML (`prompt_policy.yaml`, campos `id`/`version`). Langfuse entra como fonte de dados para avaliação em lote (`EvaluationRun.source=langfuse`, apps `evals/offline`/`evals/certification`), não como o motor de julgamento síncrono (esse é um nó do próprio grafo, usando profile LLM `judge`). **Setup local (docker-compose)**: `langfuse-web`, `langfuse-worker`, PostgreSQL, ClickHouse, Redis, MinIO, mais MongoDB (memória do framework, não do Langfuse). Portas: Web/API 3005, MinIO S3 9090/Console 9091, MongoDB 27017, Postgres 5433, Redis 6379, ClickHouse 8124/9002. Fluxo: `docker compose up -d` → criar org/projeto → gerar par de chaves `pk-lf-.../sk-lf-...` em Settings > API Keys → configurar `.env` do backend. --- ## 3. Padrão de integração MCP **MCP Gateway**: app deployável único — `/v1/tools`, `/v1/tools/{name}`, `/v1/tools/{name}/invoke`, `/v1/servers`, `/health`, `/ready`. Contratos `ToolInvocation`/`ToolResult` carregando `tenant_id`, `agent_id`, `business_context`, `metadata.request_id/trace_id`. **MCP Parameter Mapping** (`mcp_parameter_mapping.yaml`): traduz chaves canônicas de negócio (`customer_key`, `contract_key`, `session_key`) para o nome de parâmetro esperado por cada tool. **Cache MCP com eventos Langfuse**: chave `tenant_id:agent_id:tool_name:hash(arguments)`, só tools idempotentes, TTL por tool. Fluxo de eventos: miss → `IC.MCP_CACHE_MISS → IC.MCP_TOOL_EXECUTING → IC.MCP_TOOL_EXECUTED → IC.MCP_CACHE_SET`; hit → só `IC.MCP_CACHE_HIT`. **FastMCP** como padrão de servidor MCP oficial: ```python from mcp.server.fastmcp import FastMCP mcp = FastMCP("telecom_mcp_server") @mcp.tool() def consultar_fatura(msisdn: str | None = None) -> dict: ... mcp.run(transport="streamable-http") ``` ### ⚠️ Achado crítico: autorização rasa e potencialmente insegura O bloco `authorization` do MCP Gateway deles é um **allowlist estático por `agent_id`**: ```yaml authorization: default_policy: deny agents: telecom_contas: allowed_tools: [consultar_fatura, consultar_pagamentos] ``` O campo `agent_id` que governa essa allowlist **chega como dado do payload de entrada da conversa** (`GatewayRequest`/`ToolInvocation`), não como claim de uma identidade de MCP Client autenticada e verificada independentemente. A própria SPEC-004 deles admite: *"a política conversacional não substitui autenticação, autorização, idempotência nem atomicidade no MCP Server."* Não há PDP/OPA/Cedar, não há `policy_decision_id`/`entitlement_version`, não há separação discovery/execution como dois enforcement points deliberadamente independentes. **Isto é estruturalmente o anti-padrão que o ADR-001 da nossa RI proíbe** ("autorização por role autodeclarado do agente"). Ver ADR-023 para a decisão de como reaproveitar a engenharia de proxy sem herdar esse problema. --- ## 4. Convenções de configuração YAML | Arquivo | Propósito | |---|---| | `agents.yaml` | Registro de agentes (agent_id, paths de config isolada, metadados de domínio) | | `routing.yaml` | Intents, keywords/examples/priority, `mcp_tools` por intent, `state_policies` | | `tools.yaml` | Catálogo de tools: server destino, schema de args, cache, `allowed_agents` | | `guardrails.yaml` | Guardrails de entrada/saída habilitáveis por código | | `judges.yaml` | Critérios de avaliação pós-resposta (deterministic/llm), threshold, sample_rate | | `llm_profiles.yaml` | Perfis de modelo/temperatura por componente (router, judge, supervisor, RAG) | | `identity.yaml` | Aliases de nomes de canal → chaves canônicas de negócio (Identity Resolver) | | `mcp_parameter_mapping.yaml` | Tradução de chave canônica → nome de parâmetro esperado por tool | | `mcp_servers.yaml` (+ `.docker.yaml`) | Endereços/transporte dos MCP servers (local vs. Docker) | | `backends.yaml` | Cadastro de backends de agente + regras de roteamento global do Agent Gateway | | `prompt_policy.yaml` | Prompt base/persistente do agente, versionado (`id`, `version`) | | `tool_policies.yaml` | Classifica tool como `read_only`/`transactional`, exige confirmação | | `authorization` (bloco em config do MCP Gateway) | Allowlist de tools por `agent_id` — **não reaproveitar como fonte de autorização** (ver seção 3) | --- ## 5. Modelo de segurança/identidade (SPEC-018) — compatibilidade - **Autenticação de workload** (5 modos: config_file/instance_principal/workload_identity/resource_principal/api_key) — trata de como o *framework* se autentica perante recursos OCI (Vault, GenAI, ADB). **Ortogonal** ao nosso problema de entitlement de tools. - **Autorização** — listada como 5 perguntas a responder, sem PDP formal; na prática resolvida pelo allowlist raso descrito na seção 3. - **Secrets, proteção de dados, segurança de canal** — compatíveis/complementares, não tocam em entitlement. - **Auditoria** — bom modelo de trilha (usuário/canal, agent_id, tenant_id, tool, modelo, guardrail, judge score, trace_id), mas sem equivalente a `decisionId`/`policyDecisionId`/`entitlementVersion` (nossos ADR-017), porque não existe PDP separado gerando essas decisões. **Onde é claramente compatível/ortogonal**: `identity.yaml`/`BusinessContext` — normalização de identidade de **negócio** (cliente final, contrato), nunca decide autorização de tool. Adotável sem tensão com o núcleo de segurança. **Veredito**: SPEC-018 não deve ser usada como spec de segurança normativa para a nossa RI — mistura autenticação de workload com autorização de tool sem separar os dois problemas nem propor um PDP. --- ## 6. Deployment Não é amarrado a OCI na camada de aplicação — roda 100% local via Docker Compose com serviços genéricos self-hostáveis (Redis, MongoDB, PostgreSQL, ClickHouse, MinIO, Langfuse). Integrações OCI (GenAI, ADB, Vault, workload identity) são opções plugáveis entre várias, não requisito de desenvolvimento. --- ## Recomendações de reuso (aplicadas na RI via ADR-022/ADR-023) **Adotar como estão** (não tocam no núcleo de segurança): `identity.yaml`/BusinessContext · taxonomia IC/NOC/GRL + EventEnvelope · setup local do Langfuse via docker-compose · `ConversationSummaryMemory` · heurística RAG-vs-MCP (§30.3-30.4 do Hoshikawa: sistemas → MCP, documentos → RAG) · `mcp_parameter_mapping.yaml` · FastMCP como padrão de servidor MCP. **Adotar com adaptação**: a *forma* de proxy do MCP Gateway (catálogo, `ToolInvocation`/`ToolResult`, cache com eventos, retry/timeout/circuit breaker) — mas a decisão ALLOW/DENY é sempre delegada ao nosso PEP/PDP, nunca ao bloco `authorization` deles. A camada Agent Gateway/Global Supervisor (dois níveis de sessão, handoff) informa o desenho de EP-11/EP-12, mantendo-a estritamente como roteamento de UX/negócio, nunca como autoridade de autorização. `tool_policies.yaml` (read_only/transactional + confirmação) é conveniência conversacional, não controle de segurança. **Não copiar como fonte de verdade de segurança**: o bloco `authorization` por `agent_id` autodeclarado no payload; SPEC-018 como normativa de autorização.