SDK de tempo de execução do Tenant independente de estrutura para o Atlas Agent Engine.
Este é um pacote principal interno ; os agentes não o instalam diretamente. Instale um SDK de framework (
agent-engine-sdk-langgraphouagent-engine-sdk-adk), que depende dele.
Visão geral da arquitetura
O SDK do Runner fornece os componentes de integração AER, Pod de Ferramentas e Servidor de Memória da arquitetura da plataforma. O mecanismo de orquestração é um serviço separado do mecanismo do Atlas Agent.
Componente | Porta | Nível de confiança | Acesso à rede | Acesso a segredos | Propósito |
|---|---|---|---|---|---|
AER (Tempo de Execução do Agente) | 8001 | Médio | Somente APIs de LLM | Somente chaves LLM | Plano de execução - executa a lógica do agente |
Ferramenta | 8002 | Varia | Conforme necessário | Conforme necessário | Plano de dados - execução de ferramenta isolada |
Servidor de memória | 8081 | Médio | MongoDB | Credes de banco de dados | Serviço de memória para memória semântica/episódica/taxonômica |
O Corredor Compartilhado detecta automaticamente a ligação do ouvinte na inicialização: :: (IPv6 pilha dupla — o uvicorn renderiza isso como http://[::]:port no formato de URL ) quando o kernel tiver IPv6 habilitado com IPV6_V6ONLY=0, caso contrário 0.0.0.0. Nenhuma configuração necessária — a produção implanta em redes IPv6first (namespaces de locatários de células) obtém :: automaticamente e o desenvolvimento local do Docker (onde a ponte IPv6 geralmente está desabilitada) volta para 0.0.0.0. O host resolvido pela sondagem é registrado na inicialização (listener bind resolved: host=...) portanto, a verificação pós-implementação não precisa inferir da própria linha de inicialização do uvicorn.
Defina APP_HOST no ambiente para substituir a sondagem — necessário em contêineres onde o kernel relata IPv6 como disponível, mas a rede circundante roteia apenas IPv4 (por exemplo, encaminhamento de porta Docker com host_ip: 127.0.0.1). O conjunto de modelos de testes de integração APP_HOST=0.0.0.0 por esse motivo.
O ouvinte usa a porta padrão fixa para o RUNNER_MODE ativo (8001/8002/8081). RUNNER_MODE é necessário e injetado pelo tempo de execução da plataforma; dev local ignora substituições do usuário .env para ele, e segredos do espaço de trabalho reservam o nome inteiramente.
Registro de inicialização
A imagem CMD é python -m agent_engine_runner_shared.launcher (TypeScript: node …/launcher.js). O iniciador instala o registro antes de importar AGENT_ENTRYPOINT, portanto, uma falha no tempo de importação ou uma exceção da função de ponto de entrada é um registro ERROR (JSON estruturado quando STRUCTURED_LOGGING=true) em vez de um rastreamento bruto que os registros de espaço de trabalho mostrariam como INFO. TenantRuntime ainda liga para setup_logging / setupLogging mais tarde; essa segunda instalação é idempotente. As falhas que ocorrem antes do início do processo do iniciador (erros de intérprete/imagem) ainda dependem da heurística de leitura do API Gateway documentada em `docs/api-gateway/README.md <../../../.././docs/api- gateway/README.md# agente--operator-logs-s3-read-path>'__.
Por que três componentes?
Essa separação permite: - Registro de auditoria - O OE vê todas as chamadas de ferramentas/LLM sem executá-las - Aplicação de políticas - OE pode bloquear chamadas antes da execução - Menor privilégio - Cada componente só tem o acesso necessário - Repetição - OE pode retornar resultados em cache para retomada determinística
Ciclo de vida de execução & onde seus segredos estão disponíveis
Contrato normativo completo: `docs/runner/README.md <../../../../../docs/current/README.md#execution-Lifecycle--secret-availability>`__. As regras que um autor agente precisa para escrever o código correto:
O código de nível superior do módulo (qualquer coisa fora de um corpo de função) é executado uma vez na inicialização do processo, tanto no AER quanto no Pod da Ferramenta. Ele pode confiar na configuração do MCP OAuth e nos segredos de inicialização entregáveis pelo locatário, incluindo
MONGODB_URIe chaves de API LLM. Sessões de invocação e credenciais de solicitação delegadas não estão disponíveis durante a inicialização. As gravações em cache do MCP OAuth (~/.agentic/mcp-oauth/<server>.jsonouAGENTIC_MCP_OAUTH_DIRem tempos de execução implantados) são serializadas em um arquivo de aviso<server>.json.lockpara que as atualizações simultâneas de token do mesmo servidor não possam se sobrepor umas às outras.A construção de aplicação de ferramentas tenta a função
@app.entrypointdurante a inicialização para descobrir cada registroapp.llm(llm_id=...). As solicitações de modelo reutilizam o registro preparado. A construção usa configuração de inicialização estática e não deve executar uma curva comercial, chamada de modelo ou corpo de ferramenta. A construção com falha avisa e restaura os registros anteriores, deixando uma solicitação de modelo posterior capaz de tentar novamente. A construção bem-sucedida é armazenada em cache mesmo sem LLMs nomeados. O cache e o aquecimento de gráficos AER permanecem de propriedade da estrutura.``app.llm(llm, llm_id=...)`` deve ser chamado enquanto seu ponto de entrada estiver na pilha de chamadas — diretamente em seu corpo ou em uma função assistente que ele chamar. Chamando-o de qualquer lugar fora disso (nível superior do módulo, um corpo da ferramenta, um assistente invocado de algum lugar diferente do ponto de entrada) levanta
RuntimeErrorimediatamente, nomeando o site de chamada ofensivo.Corpos de ferramenta locais (
is_local=True) são executados somente no processo da própria AER. As cargas de trabalho de AER e de ferramenta recebem segredos entregáveis pelo locatário na inicialização.Corpos de ferramentas remotas (
is_local=False, ou ferramentas que exigem credenciais delegadas) são executados somente no Pod de Ferramentas, sob demanda, quando a OE direciona uma chamada para eles — nunca durante a construção. as declaraçõesrequired_secretsdocumento requisitos secretos; eles não restringem o ambiente secreto da inicialização.As ferramentas credenciadas permanecem remotas mesmo que o SDK forneça seu padrão local, porque a autorização delegada e a injeção de segredo com escopo de ferramenta ocorrem somente na rota do Pod de Ferramentas.
Fluxo de execução
Execução normal (início → conclusão)
┌────────┐ ┌─────┐ ┌─────┐ │ Client │ │ OE │ │ AER │ └───┬────┘ └──┬──┘ └──┬──┘ │ │ │ │ POST /invoke │ │ │ ──────────────────────────►│ │ │ │ │ │ │ Create Execution (PENDING) │ │ │ │ │ POST /execute │ │ │─────────────────────────►│ │ │ │ │ │ │ Build LangGraph │ │ │ Start ainvoke() │ │ │ │ ┌───────────────────────┼──────────────────────────┼───────────────┐ │ │ LOOP: For each tool/LLM call │ │ │ │ │ │ │ │ │ │ POST /tool/execute │ │ │ │ │◄─────────────────────────│ │ │ │ │ {tool, args, step} │ │ │ │ │ │ │ │ │ │ Log + Policy Check │ │ │ │ │ │ │ │ │ │ {proceed, route_to} │ │ │ │ │─────────────────────────►│ │ │ │ │ │ │ │ │ │ │ Execute tool │ │ │ │ │ │ │ │ │ POST /tool/result │ │ │ │ │◄─────────────────────────│ │ │ │ │ {result, status} │ │ │ └───────────────────────┼──────────────────────────┼───────────────┘ │ │ │ │ │ │ Graph completes │ │ │ │ │ POST /executor/callback │ │ │◄─────────────────────────│ │ │ {COMPLETED, result} │ │ │ │ │ GET /execution/{id} │ │ │ ──────────────────────────►│ │ │ │ │ │ {status: completed, result}│ │ │ ◄──────────────────────────│ │ │ │ │
Human-in-the-Loop (SUSPEND/RESUME)
┌────────┐ ┌─────┐ ┌─────┐ │ Client │ │ OE │ │ AER │ └───┬────┘ └──┬──┘ └──┬──┘ │ │ │ │ POST /invoke │ │ │ ──────────────────────────►│ │ │ │ │ │ │ POST /execute │ │ │─────────────────────────►│ │ │ │ │ │ ... tool calls ... │ │ │ │ │ │ │ app.suspend(reason) │ │ │ → tool pod reports │ │ │ status: "suspend" │ │ │ (out-of-band, not │ │ │ result content) │ │ │ │ │ │ interrupt() on the │ │ │ OE-confirmed status │ │ │ Save checkpoint │ │ │ │ │ POST /executor/callback │ │ │◄─────────────────────────│ │ │ {SUSPENDED, metadata} │ │ │ │ │ GET /execution/{id} │ │ │ ──────────────────────────►│ │ │ {status: suspended} │ │ │ ◄──────────────────────────│ │ │ │ │ ╠════════════════════════════╬══════════════════════════╣ ║ ⏸️ WAITING FOR HUMAN - Reviews and Approves ║ ╠════════════════════════════╬══════════════════════════╣ │ │ │ │ POST /resume/{id} │ │ │ {decision: "approved"} │ │ │ ──────────────────────────►│ │ │ │ │ │ │ Store human decision │ │ │ │ │ │ POST /execute │ │ │ {resume: true} │ │ │─────────────────────────►│ │ │ │ │ │ │ Resume from checkpoint │ │ │ │ ┌───────────────────────┼──────────────────────────┼───────────────┐ │ │ Replayed steps return CACHED results │ │ │ │ │ │ │ │ │ │ POST /tool/execute │ │ │ │ │◄─────────────────────────│ │ │ │ │ │ │ │ │ │ {cached_result} │ │ │ │ │─────────────────────────►│ │ │ └───────────────────────┼──────────────────────────┼───────────────┘ │ │ │ │ │ │ Continue execution │ │ │ (new steps) │ │ │ │ │ POST /executor/callback │ │ │◄─────────────────────────│ │ │ {COMPLETED, result} │ │ │ │ │ GET /execution/{id} │ │ │ ──────────────────────────►│ │ │ {status: completed} │ │ │ ◄──────────────────────────│ │ │ │ │
Observação
Retomar por meio do gateway de API: o diagrama acima usa o caminho /resume/{id} interno da OE por brevidade. Os desenvolvedores chamam o gateway de API em POST /api/v1/projects/{project_id}/executions/{execution_id}/resume com um corpo JSON plano:
{"decision": "approved", "reviewer_notes": "optional context"}
A forma {"human_review": {"decision": "..."}} envolta é o contrato de endpoint interno da OE; o gateway o rejeita com HTTP 400.
Deficiente de execução (cancelamento)
Quando o OE cancela uma execução de forma duradoura, ele POSTA /drain para o tempo de execução da Ferramenta/AER que o possui. O contrato do receptor (fixado pelo dispositivo compartilhado em client-libraries/test-fixtures/drain/contract.json):
POST /drain {request_id, execution_id, reason, deadline_at_ms, workspace_id}— em um tempo de execução com escopo de trabalho (conjuntoAPP_ID, ou seja, todo tempo de execução gerenciado),workspace_idé obrigatório e deve corresponder exatamente: o token do portador autentica o chamador, mas não comprova que a execução nomeada pertence a esse tempo de execução.202 {"outcome": "accepted"}durante a drenagem;200com o resultado final (completed/timed_out/delivery_failed) depois.acceptedé somente aceitação em nível de HTTP —completedé o único sinal de inatividade.Idempotente em
request_id, uma drenagem por execução: uma nova tentativa releia o resultado registrado em vez de executar novamente os efeitos colaterais.deadline_at_msé absoluto e nunca estendido; o tempo de execução rejeita prazos além deRUNNER_DRAIN_MAX_DEADLINE_MS(padrão 60s). Os registros finalizados sobrevivemRUNNER_DRAIN_RECORD_TTL_S(padrão 900s) para que as novas tentativas de chamador permaneçam idempotentes durante sua janela de reconciliação — mantenha-a igual ou superior à janela de novas tentativas do chamador (OE tenta novamente 15 minutos): um TTL mais curto despeja o registro enquanto o chamador ainda está tentando novamente e os reenvios atrasados obtêmexecution_not_foundem vez do resultado registrado.Uma execução que esse tempo de execução nunca atendeu responde
delivery_failedcomreason_code=execution_not_found- uma execução pode abranger os tempos de execução de AER e ferramentas de forma independente, portanto, o direcionamento exato de OE não torna umcompletedfalso seguro.
Os limites por idioma fazem parte do contrato, não são um detalhe de implementação. O Python cancela as tarefas assíncronas monitoradas, de modo que o trabalho conjunto se estabelece como completed. Uma promessa não tem cancelamento, então o tempo de execução do TypeScript (agent-engine-runner-shared, que espelha este contrato e deve mudar junto com ele) aborta apenas AbortController s registrados — a vez AER e fluxos LLM — enquanto as funções de ferramenta simples não recebem sinal: elas são rastreados e têm entrada bloqueada, e ultrapassar o prazo é um timed_out verdadeiro. O estado é o processo local por design: um tempo de execução reiniciado perdeu seu trabalho ativo e responde delivery_failed; a reconciliação entre reinicializações pertence ao registro durável do chamador, não a esse registro.
Interrupção por chamada
POST /interrupt/call é o irmao cirurgico da drenagem: a interrupção por chamada do OE nomeia uma chamada por {execution_id, step_number} e o tempo de execução cancela exatamente o identificador rastreado dessa chamada — o Python cancela a tarefa de assíncrono, o tempo de execução TS cancela a AbortController da chamada — enquanto a execução permanece aberto e as chamadas posteriores prosseguem (nunca trava de entrada). Sempre 200 com um resultado: interrupted (sinalizada ou gravada para disparar no anexar), not_found (nenhuma chamada a bordo), already_settled (a chamada foi encerrada primeiro), not_cancellable (trabalho rastreado sem canal de sinal — um corpo de ferramenta de sincronização em um thread, uma função simples de ferramenta TS — relatados abertamente, nunca declararam ter sido abandonados). O escopo do espaço de trabalho corresponde à rota de drenagem e os vetores compartilhados residem em client-libraries/test-fixtures/interrupt-call/contract.json.
Componentes principais
Como as chamadas interceptadas são roteadas através do OE
Chamadas de ferramenta interceptadas e invoke_llm agora usam o mesmo fluxo de execução de propriedade da OE. Quando o agente em execução no AER precisa chamar uma ferramenta ou um LLM, o wrapper envia a solicitação interceptada para o OE e aguarda que o OE retorne o resultado final. OE roteia as chamadas de ferramentas por meio de Tool Pod /execute e as chamadas LLM por meio de Tool Pod /invoke_llm e, em seguida, retorna o resultado final de sucesso, suspensão ou erro de volta para AER.
Diagrama de Sequência:
┌─────┐ ┌─────┐ ┌──────────┐ │ AER │ │ OE │ │ Tool Pod │ └──┬──┘ └──┬──┘ └────┬─────┘ │ │ │ │ 1. POST /tool/execute │ │ │ ────────────────────────►│ │ │ {name or invoke_llm, │ │ │ args/messages, step} │ │ │ │ │ │ │ Log + Policy Check │ │ │ │ │ │ 2a. Replay cache hit │ │ 2a. Final cached result │ │ │ ◄────────────────────────│ │ │ {status, result, │ │ │ from_cache} │ │ │ │ │ ├──────────────────────────┼────────────────────────────┤ │ if OE must execute the tool │ │ │ │ │ │ 2b. POST /execute or │ │ │ /invoke_llm │ │ │ ───────────────────────────────────────► │ │ via Tool Pod │ │ │ ◄─────────────────────────────────────── │ │ {status, result} │ │ │ │ ├──────────────────────────┼────────────────────────────┤ │ │ │ │ 3. Final OE-owned result │ │ │ ◄────────────────────────│ │ │ {status, result, error, │ │ │ duration_ms, pod_name} │ │ │ │ │
Pontos principais:
Cada ferramenta interceptada ou chamada LLM atravessa primeiro o OE para propriedade de política, registro e repetição.
OE possui roteamento — ferramentas remotas usam
Tool Pod /execute; as ferramentas locais aprovadas retornam à pilha de chamadas original do AER para que o contexto nativo da estrutura permaneça ativo; chamadas LLM interceptadas usamTool Pod /invoke_llm.O replay usa resultados em cache — Na retomada após SUSPEND, OE retorna o resultado final armazenado para evitar efeitos colaterais duplicados.
A suspensão ainda se desenrola no wrapper — OE retorna a carga útil serializada da suspensão e, em seguida,
SecureToolWrappera converte de volta à interrupção do framework.``invoke_llm`` agora corresponde ao contrato da ferramenta — OE retorna a carga útil final de
status/result/errorem vez de solicitar à AER que execute após a aprovação.Os blocos de fluxo LLM preservam metadados da mensagem final —
id,name,additional_kwargseresponse_metadataviagens através do evento de fluxo digitado e são coletados de volta na mesma forma de resposta final usada para repetição.
agent.yaml interpolação de variável de ambiente
O carregamento de configuração de tempo de execução agora suporta interpolação ${VAR} para uma lista limitada de caminhos agent.yaml de propriedade do locatário, atualmente mcp.servers.*.url.
Como funciona:
load_runtime_agent_config(env_vars=...)substitui referências${VAR}somente em caminhos permitidos.O tempo de execução passa
tenant_env_vars()em vez deos.environbruto, portanto, os envelopes e segredos de propriedade da plataforma são excluídos da substituição.Se um caminho da lista de permissões fizer referência a uma variável não definida ou contiver um marcador
${...}malformado, o carregador gerará um erro de validação nítido.mcp.servers.*.auth.token_envnão deve ponto para um env var de propriedade da plataforma.
Exemplo:
mcp: servers: tableau: url: https://${TABLEAU_HOST}/mcp
No tempo de execução, o ${TABLEAU_HOST} é resolvido do subconjunto de ambientes de propriedade do locatário. Uma referência como ${OPENAI_API_KEY} é rejeitada porque essa variável pertence à plataforma e é filtrada antes da interpolação.
SecureToolWrapper (secure_wrapper.py)
O componente de segurança central que intercepta chamadas de ferramentas:
# Every tool call goes through this flow: def execute_tool(tool_name, arguments): # 1. Send the intercepted tool call to OE response = request_oe_approval(oe_url, execution_id, tool_name, arguments) # 2. Check for policy denial if not response.proceed: raise PolicyDeniedException(response.reason) # 3. OE either returns a final outcome or routes the call back in process if response.route_to == "callback": result = local_executor() report_oe_result(result) return result if response.status == "error": raise ToolExecutionError(response.error) if response.status == "suspend": interrupt(response.result) return response.result
Invariante chave: nenhuma ferramenta ou chamada LLM é executada sem que o OE saiba disso.
Características
Aplicação de políticas
A OE pode bloquear chamadas de ferramenta/LLM com base nas regras da política:
A aplicação da política é tratada pelo Go OE. Consulte docs/orchestration-engine/README.md.
Cache de repetição
Quando um agente retoma após SUSPEND, a execução do LangGraph é repetida desde o início. Sem o cache, isso reexecutaria ferramentas e chamadas LLM, causando efeitos colaterais duplicados (por exemplo, enviando notificações duas vezes).
Como funciona:
OE rastreia todas as etapas - Cada chamada de ferramenta/LLM é registrada com seu número de etapa, nome e resultado
Ao retomar, o agente repete - o LangGraph é executado novamente desde o início, fazendo as mesmas chamadas em ordem
OE retorna resultados em cache - Em vez de executar novamente, o OE retorna o resultado armazenado
Original Execution: Step 1: lookup_policy("POL-123") → executed, result stored Step 2: query_mongogpt(...) → executed, result stored Step 3: human_review(...) → SUSPEND (waiting for human) Resume Execution: Step 1: lookup_policy("POL-123") → CACHED (returns stored result) Step 2: query_mongogpt(...) → CACHED (returns stored result) Step 3: human_review(...) → CACHED (returns human's decision) Step 4: send_notification(...) → executed (new step)
O cache de replays é tratado pelo Go OE. O AER/SecureToolWrapper lida com respostas em cache de forma transparente:
Manuseio AER/SecureToolWrapper:
# In secure_wrapper.py - OE already returns the final replay outcome response = await request_oe_approval(...) if response.from_cache: log_cached_result(tool_name, step) return response.result
Isso garante a retomada determinística - o agente vê exatamente os mesmos resultados que via antes de suspender, evitando efeitos colaterais duplicados.
Checkpoint
Suporta checkpointers na memória e MongoDB :
# In runtime.py def get_checkpointer(self): if mongodb_uri: return MongoDBSaver(client, db_name) return MemorySaver() # Development only
Estrutura do arquivo
src/agent_engine_runner_shared/ ├── runtime.py # TenantRuntime - main SDK entry point ├── secure_wrapper.py # SecureToolWrapper, SecureWrappedLLM ├── models.py # Pydantic models for all API contracts ├── context.py # Per-execution context (contextvars) ├── metrics.py # Metrics collection and @with_metrics decorator ├── utils.py # Logging utilities, env helpers ├── logging.py # Durable execution logging (JSONL + MongoDB) ├── memory.py # MemoryEngine integration, MemoryWriter ├── voyage.py # VoyageService for embeddings ├── events.py # SSE event streaming, observability ├── types.py # Protocol definitions (MemoryEngineProtocol) ├── tracing/ # OpenTelemetry tracing │ ├── setup.py # setup_tracing(), get_current_trace_context() │ └── exporters.py # JSONLSpanExporter, MongoDBSpanExporter └── server/ ├── base.py # BaseServer abstract class ├── aer.py # AER implementation ├── tool.py # Tool Pod implementation └── memory.py # Memory Server implementation
Decisões de design
Por que rotear tudo através da OE?
Embora adicione latência, o roteamento por OE fornece: 1. Trilha de auditar completa - Todas as chamadas são registradas 2. ponto de aplicação da política - Local único para bloquear chamadas 3. Capacidade de repetição - OE pode retornar resultados em cache para retomar 4. Agnóstico em termos de framework - OE não conhece o LangGraph
Por que separar AER e ferramenta?
OAER precisa de acesso à API LLM, mas não deve ter credenciais de banco de dados
Aferramenta pode precisar de acesso ao banco de dados , mas não deve ter chaves LLM
Diferentes ferramentas podem ser executadas em diferentes ferramentas com diferentes permissões
Paridade de recursos com o tempo de execução do agente
O SDK do Runner agora oferece paridade de recursos com o monolítica agent-runtime:
funcionalidade | Agent-runtime | SDK do executor |
|---|---|---|
@app.tool decorator | ✅ | ✅ |
Metadados explícitos de rede/tempo limite | ✅ | ✅ |
supressão de campo | ✅ | ✅ |
Checkpoint do MongoDB | ✅ | ✅ |
Registro de execução (JSONL + MongoDB) | ✅ | ✅ |
Rastreamento OpenTelemetry | ✅ | ✅ |
Integração de memória | ✅ | ✅ |
Incorporações de IA do Voyage | ✅ | ✅ |
Multilocatário (org_id/user_id) | ✅ | ✅ |
Streaming de eventos SSE | ✅ | ✅ |
Servidor gRPC | ✅ | ✅ |
Suporte de transmissão | ✅ | ✅ |
Human-in-the-loop | ❌ | ✅ |
Arquitetura de três componentes | ❌ | ✅ |
Cache de repetição | ❌ | ✅ |
Dependências opcionais
Instale recursos conforme necessário. Use uv add se seu projeto for gerenciado com uv ou pip install de outra forma:
# uv projects uv add agent-engine-runner-shared uv add "agent-engine-runner-shared[mongodb,tracing]" uv add "agent-engine-runner-shared[tracing]" uv add "agent-engine-runner-shared[mongodb]" # pip pip install agent-engine-runner-shared pip install "agent-engine-runner-shared[mongodb,tracing]" pip install "agent-engine-runner-shared[tracing]" pip install "agent-engine-runner-shared[mongodb]"