适用于Atlas助手引擎的与框架无关的租户运行时 SDK。
这是一个内部核心包;代理不会直接安装它。安装依赖于它的框架SDK(
agent-engine-sdk-langgraph或agent-engine-sdk-adk)。
架构概述
Runner SDK 提供平台架构的 AER、Tool Pod 和内存服务器集成组件。编排引擎是一项独立的Atlas助手引擎服务。
组件 | 端口 | 信任级别 | 网络访问 | 密钥访问 | 用途 |
|---|---|---|---|---|---|
AER(代理执行运行时) | 8001 | 中型 | 仅限 LLM API | 仅限法学硕士密钥 | 执行平面 — 运行代理逻辑 |
工具 | 8002 | 不定 | 按需 | 按需 | 数据平面 — 隔离工具执行 |
内存服务器 | 8081 | 中型 | MongoDB | 数据库信用 | 用于语义/情景/分类记忆的内存服务 |
共享亚军会在初创企业时自动检测监听器绑定:当内核启用了带有 IPV6_V6ONLY=0 的 IPv6 时,为 ::(IPv6 双栈 — uvicorn 将其呈现为URL形式的 http://[::]:port),否则0.0.0.0。无需配置 — 在 IPv6 优先网络(单元租户命名空间)上部署的生产环境会自动获取 ::,而本地Docker开发者(通常禁用网桥 IPv6)则回退到 0.0.0.0。探测解析的托管会在初创企业(listener bind resolved: host=...) 时进行记录,因此部署后验证不必从 uvicorn 自己的初创企业行进行推断。
在环境中设置 APP_HOST 以覆盖探测器 — 在内核报告 IPv6 可用但周围网络仅路由 IPv4(例如使用 host_ip: 127.0.0.1 的Docker端口转发)的容器中是必需的。因此,集成测试 Compose 模板设立为 APP_HOST=0.0.0.0。
监听器使用活动 RUNNER_MODE (8001/8002/8081) 的固定默认端口。 RUNNER_MODE 是必需的,由平台运行时注入;本地开发人员会忽略用户 .env 对其进行的覆盖,工作区密钥会完全保留该名称。
启动日志记录
映像 CMD 为 python -m agent_engine_runner_shared.launcher(TypeScript:node …/launcher.js)。启动器在导入 AGENT_ENTRYPOINT 之前安装日志记录,因此导入时崩溃或入口点函数的异常是 ERROR记录(STRUCTURED_LOGGING=true 时为结构化JSON ),而不是工作区日志显示为 INFO。 TenantRuntime 稍后仍会调用 setup_logging / setupLogging;第二次安装是幂等的。在API器进程启动之前发生的故障(解释器/映像错误)仍然依赖于 `docs/api-gateway/README.md <../../../../../docs/api- gateway/README.md# 代理--operator-logs-s3-read-path>`__.
为什么需要三个组件?
这种分离可以实现: - 审核日志记录 - OE 可以看到每个工具/LLM 调用而不执行它们 - 策略执行 - OE 可以在执行前区块调用 - 最小权限- 每个组件仅具有其需要的访问权限- 重放 - OE 可以返回缓存的结果以实现确定性恢复
执行生命周期 &可以获取您的密钥的地方
完整的规范合同:`docs/runner/README.md <../../../../../docs/runner/README.md#execution-lifecycle--secret-availability>`__。代理作者写入正确代码所需的规则:
在 AER 和 Tool Pod 中,模块顶层代码(函数体之外的任何代码)会在进程初创企业时运行一次。它可以依赖 MCP OAuth 配置和租户可交付的初创企业密钥,包括
MONGODB_URI和 LLM API密钥。初创企业期间,调用会话和委派请求凭证不可用。 MCP OAuth缓存写入(~/.agentic/mcp-oauth/<server>.json或已部署的运行时中的AGENTIC_MCP_OAUTH_DIR)在咨询<server>.json.lock文件上进行序列化,因此同一服务器的并发令牌刷新不会相互干扰。工具应用程序构建会在初创企业期间尝试使用
@app.entrypoint函数来发现每个app.llm(llm_id=...)注册。模型请求会重复使用准备好的注册表。构建使用静态初创企业配置,不得执行业务轮换、模型调用或工具主体。构建失败会发出警告并恢复之前的注册,以便后续的模型请求能够重试。即使没有命名 LLM,也会缓存成功的构造。 AER图表缓存和预热仍由框架所有。``app.llm(llm, llm_id=...)`` must be called while your entrypoint is on the call stack — directly in its body, or in a helper function it calls. Calling it from anywhere outside that (module top level, a tool body, a helper invoked from somewhere other than the entrypoint) raises
RuntimeErrorimmediately, naming the offending call site.本地工具体 (
is_local=True) 仅在 AER 自身的进程中运行。 AER 和工具工作负载都会在初创企业时接收租户可交付的密钥。远程工具体(
is_local=False或需要委派凭证的工具)仅在 OE 将调用路由到它们时按需在 Tool Pod 中运行,而不会在构造期间运行。required_secrets声明文档密钥要求;它们不会缩小初创企业密钥环境的范围。即使 SDK 提供了本地默认,凭证工具仍处于远程状态,因为委托授权和工具范围的密钥注入仅发生在 Tool Pod 路由上。
执行流程
正常执行(开始 → 完成)
┌────────┐ ┌─────┐ ┌─────┐ │ 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}│ │ │ ◄──────────────────────────│ │ │ │ │
人机交互(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} │ │ │ ◄──────────────────────────│ │ │ │ │
注意
通过API网关恢复:为简洁起见,上图使用了 OE 的内部 /resume/{id} 路径。开发者使用平面JSON正文调用位于 POST /api/v1/projects/{project_id}/executions/{execution_id}/resume 的API网关:
{"decision": "approved", "reviewer_notes": "optional context"}
封装的 {"human_review": {"decision": "..."}} 形状是 OE 的内部端点协定;网关使用HTTP 400 拒绝它。
执行耗尽(取消)
当操作环境持久取消执行时,它会向拥有该执行的工具/AER 运行时发布 /drain。接收者合约(由 client-libraries/test-fixtures/drain/contract.json 中的共享装置固定):
POST /drain {request_id, execution_id, reason, deadline_at_ms, workspace_id}— 在工作区范围的运行时(APP_ID设立,即每个托管运行时)上,workspace_id是强制性的,并且必须完全匹配:持有令牌对调用者进行身份验证,但不证明命名执行属于此运行时。202 {"outcome": "accepted"}同时排干;随后将200替换为最终结果 (completed/timed_out/delivery_failed)。accepted是仅HTTP级接受 —completed是唯一的静止信号。在
request_id上幂等,每次执行一次排空:重试会重新读取记录的结果,而不是重新运行副作用。deadline_at_ms是绝对的且从不扩展;运行时会拒绝超过RUNNER_DRAIN_MAX_DEADLINE_MS(默认60)的截止日期。最终确定的记录可在RUNNER_DRAIN_RECORD_TTL_S(默认900)后继续存在,因此调用者重试可在其协调窗口中保持幂等— 将其保持在调用者的重试窗口或之上(OE 在 15 分钟内重试排空):较短的TTL会逐出记录当调用者仍在重试时,延迟重新发送得到execution_not_found而不是记录的结果。此运行时的执行从未使用
reason_code=execution_not_found提供delivery_failed的答案 — 一次执行可以独立跨越 AER 和工具运行时,因此精确的 OE 目标不会使错误的completed安全。
每种语言的限制是合同的一部分,而不是实施细节。 Python取消跟踪的异步任务,因此协作工作结算为 completed。 Promise 无法取消,因此 TypeScript 运行时(agent-engine-runner-shared,镜像此合同,必须随之更改)仅中止注册的 AbortController — AER 轮次和 LLM 流 — 而普通工具函数不会收到信号:会被跟踪并阻止准入,超过截止日期则为诚实的 timed_out。状态设计为进程本地化:重新启动的运行时已丢失其活动工作并回答 delivery_failed;重启后的协调属于调用者的持久性记录,而不是此注册表。
每次调用中断
POST /interrupt/call 是排出的外科同级:OE 的每次调用中断以 {execution_id, step_number} 命名一次调用,运行时准确中止该调用的跟踪处理— Python取消异步任务,TS 运行时中止调用的 AbortController — 而保持打开状态,后续调用将继续进行(永远没有准入锁存器)。始终 200,其结果为:interrupted(在连接时发出信号或记录为触发)、not_found(无此类进行中调用)、already_settled(先结束调用)、not_cancellable(跟踪工作)没有信号渠道— 线程上的同步工具体、普通 TS 工具函数 — 如实报告,从未声称已被放弃)。工作区范围与排出路由的范围匹配,共享向量位于 client-libraries/test-fixtures/interrupt-call/contract.json 中。
关键组件
截获的调用如何通过 OE 路由
截获的工具调用和 invoke_llm 现在使用相同的 OE 拥有的执行流。当在 AER 中运行的代理需要调用工具或 LLM 时,包装器会将拦截的请求发送到 OE,并等待 OE 返回最终结果。 OE 通过 Tool Pod /execute 路由工具调用,通过 Tool Pod /invoke_llm 路由 LLM 调用,然后将最终的成功、暂停或错误结果返回 AER。
序列图:
┌─────┐ ┌─────┐ ┌──────────┐ │ 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} │ │ │ │ │
要点:
每个截获的工具或 LLM 调用都会首先跨越 OE 以实现策略、日志记录和重放所有权。
OE 拥有路由 — 远程工具使用
Tool Pod /execute;已批准的本地工具返回到原始 AER 调用堆栈,因此框架原生上下文保持活动状态;截获的 LLM 调用使用Tool Pod /invoke_llm。重放使用缓存的结果 — 在暂停后恢复时,OE 会返回存储的最终结果以防止重复的副作用。
挂起仍然通过包装器展开 — OE 返回序列化的挂起有效负载,然后
SecureToolWrapper将其转换回框架中断。“invoke_llm” 现在与工具合同匹配 — OE 返回最终的
status/result/error有效负载,而不是要求 AER 在批准后执行。LLM流段保留最终消息元数据—
id、name、additional_kwargs和response_metadata遍历类型化流事件,并收集回用于重放的相同最终响应形状。
agent.yaml 环境变量插值
运行时配置加载现在支持对租户拥有的 agent.yaml 路径(目前为 mcp.servers.*.url)的狭窄允许列表进行 ${VAR} 插值。
作用方式:
load_runtime_agent_config(env_vars=...)仅在已列入允许路径的路径替换${VAR}引用。运行时传递
tenant_env_vars()而不是原始os.environ,因此平台拥有的环境变量和密钥不被替换。如果列入白名单的路径引用了未设置的变量,或包含格式错误的
${...}标记,则加载程序会引发明显的验证错误。mcp.servers.*.auth.token_env不得点平台拥有的环境变量。
示例:
mcp: servers: tableau: url: https://${TABLEAU_HOST}/mcp
在运行时,${TABLEAU_HOST} 是从租户拥有的环境子集解析得到的。像 ${OPENAI_API_KEY} 这样的引用会被拒绝,因为该变量是平台拥有的,并在插值之前被过滤掉。
SecureToolWrapper (secure_wrapper.py)
拦截工具调用的中央安全组件:
# 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
键不变量:任何工具或 LLM 调用的执行都是 OE 不知道的。
功能
策略执行
OE 可以根据策略规则区块工具/LLM 调用:
策略执行由Go操作环境处理。请参阅 docs/orchestration-engine/README.md。
重放缓存
当代理在 SUSPEND 后恢复时,LangGraph 执行会从头开始重放。如果没有缓存,这将重新执行工具和 LLM 调用,从而导致重复的副作用(例如,发送通知两次)。
作用方式:
OE 跟踪所有步骤 - 每个工具/LLM 调用都记录其步骤编号、名称和结果
恢复时,代理会重放 - LangGraph 从头开始重新运行,按顺序进行相同的调用
OE 返回缓存的结果 — OE 返回存储的结果,而不是重新执行
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)
重放缓存由Go OE 处理。 AER/SecureToolWrapper 透明地处理缓存的响应:
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
这确保了确定性的恢复 —代理看到的结果与挂起之前看到的结果完全相同,从而防止重复的副作用。
检查点
支持内存检查点和MongoDB检查点:
# In runtime.py def get_checkpointer(self): if mongodb_uri: return MongoDBSaver(client, db_name) return MemorySaver() # Development only
文件结构
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
设计决策
为什么要通过 OE 路由所有内容?
尽管增加了延迟,但通过 OE 进行的路由可提供:1。完整的Atlas 审核跟踪 — 每次调用都会被记录 2。策略执行点-区块调用 3 的单个位置。重放功能— OE 可以返回恢复 4 的缓存结果。与框架无关 — OE 不了解 LangGraph
为何将 AER 和工具分开?
AER 需要 LLM API访问权限,但不应具有数据库凭证
工具可能需要访问权限数据库,但不应具有 LLM 密钥
不同的工具可以在具有不同权限的不同工具中运行
与代理运行时的功能奇偶校验
Runner SDK 现在提供与单片 agent-runtime 相同的功能:
功能 | 代理运行时 | Runner SDK |
|---|---|---|
@ 应用.tool 装饰器 | ✅ | ✅ |
显式网络/超时元元数据 | ✅ | ✅ |
字段编辑 | ✅ | ✅ |
MongoDB检查点 | ✅ | ✅ |
执行日志记录 (JSONL + MongoDB) | ✅ | ✅ |
OpenTelemetry 跟踪 | ✅ | ✅ |
内存集成 | ✅ | ✅ |
Voyage AI嵌入 | ✅ | ✅ |
多租户 (org_id/user_id) | ✅ | ✅ |
SSE 事件流 | ✅ | ✅ |
gRPC Server | ✅ | ✅ |
流媒体支持 | ✅ | ✅ |
人机交互 | ❌ | ✅ |
三组件架构 | ❌ | ✅ |
重放缓存 | ❌ | ✅ |
可选依赖项
根据需要安装功能。如果您的项目使用 uv 进行托管,则使用 uv add,否则使用 pip install:
# 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]"