第 13 章:四种运行模式与配置体系

定位:本章展示 octos 的四种运行模式(CLI/Gateway/Serve/MCP Serve)以及配置体系的层次结构和热加载机制。前置依赖:第 10 章、第 5 章。适用场景:需要部署和配置 octos 的运维人员和开发者(读者 D),以及想理解运行时架构选择的开发者(读者 B)。

同一套代码,四种运行姿态——这是 octos 作为"Agent 操作系统"的核心设计理念。


13.1 四种运行模式

13.1.1 CLI 模式(octos chat

交互式终端对话(crates/octos-cli/src/commands/chat.rs)。启动 multi-threaded Tokio 运行时(8MB 栈大小,crates/octos-cli/src/commands/chat.rs:69-74),提供 readline 风格的输入界面。

#![allow(unused)]
fn main() {
// chat.rs:69-78
let runtime = tokio::runtime::Builder::new_multi_thread()
    .enable_all()
    .thread_stack_size(8 * 1024 * 1024)  // 8MB 栈——深递归场景需要
    .build()?;
}

8MB 栈大小(而非 Tokio 默认的 2MB)是因为 Agent 的调用链可能很深——特别是嵌套子 Agent 和递归工具调用场景。

退出命令支持多种格式:exitquit/exit/quit:qcrates/octos-cli/src/commands/chat.rs:66-67)。

CLI 参数(crates/octos-cli/src/commands/chat.rs:22-64)支持覆盖配置文件中的关键设置:--cwd--provider--model--max-iterations--verbose。命令行参数优先于配置文件。

13.1.2 Gateway 模式(octos gateway

后台守护进程(crates/octos-cli/src/commands/gateway/)。启动 ChannelManager 监听多个消息频道,将收到的消息路由到 Agent 处理。

GatewayRuntimecrates/octos-cli/src/commands/gateway/gateway_runtime.rs:54-95)持有 Gateway 的核心运行时状态:消息层(agent_handlechannel_mgr)、会话分发(actor_registrysession_dispatcheractive_sessions)、热加载状态(system_promptmax_historyconfig_rx)以及 persona/heartbeat/cron 等后台服务。

Gateway 支持 Profile / 子账号模式。UserProfile.parent_id 用来标记子账号;当前主分支会让子账号继承父 Profile 的结构化 config.llm contract,并在缺省时继承 searchdeep_crawlappsemail,同时把父级 env_vars 作为 base、子账号同名变量覆盖父级(crates/octos-cli/src/profiles.rs:1236-1273; crates/octos-cli/src/commands/gateway/gateway_runtime.rs:221-245)。如果 Gateway 由 ProcessManager 启动,子账号进程还会收到 --parent-profile 参数和父级 env vars 注入(crates/octos-cli/src/process_manager.rs:275-291)。

GatewayDispatchercrates/octos-cli/src/gateway_dispatcher.rs:35-44)从主循环中提取出可测试的命令分发逻辑,支持 /new(新建会话)、/switch(切换 Profile)等内部命令。

13.1.3 Serve 模式(octos serve

Web 服务器(crates/octos-cli/src/commands/serve.rs)。默认端口 50080,默认绑定 127.0.0.1crates/octos-cli/src/commands/serve.rs:214-222)——安全默认值,外部访问需要显式指定 --host 0.0.0.0

提供 Web Dashboard、REST 端点、非 chat 的事件/兼容接口,以及 AppUI 使用的 UI Protocol WebSocket(/api/ui-protocol/ws)。通过 axum 框架构建,AppState 持有全局状态(Provider、工具注册表、会话管理器等)。当前主分支已经删除 chat SSE transport:POST /api/chat?stream=trueGET /api/chat/streamGET /api/sessions/:id/events/stream 不再是 chat 事件通道;标准 chat transport 是 /api/ui-protocol/wscrates/octos-cli/src/api/router.rs:91-137)。

更准确地说,Serve 模式现在是控制面汇聚点,而不是“把 chat 包成 HTTP”。启动时它会组合 Config / ProfileStore、LLM Provider / RetryProvider、ToolRegistry / ToolPolicy、SessionManager、REST/UI Protocol、兼容 WebSocket/事件 harness,以及 swarm dispatch state。swarm state 还会把 config.tool_policy 和注入型环境变量 denylist 投射成 DispatchPolicy::from_agent_gates(tool_policy, true),避免 swarm backend 绕过 native tool policy(crates/octos-cli/src/commands/serve.rs:1128-1156)。

最新主分支还让 Serve 成为 coding/autonomy capability 的声明点。UI Protocol 的 SessionOpened.capabilities 可以包含 coding.tool_contract.v1coding.autonomy.v1coding.agent_control.v1coding.goal_runtime.v1coding.loop_runtime.v1 等 feature;其中 coding.tool_contract.v1 的 payload 来自后端对当前 ToolRegistry、deferred tool set、policy view 和 known model-visible tools 的解析,而不是前端猜测。AppUI 因此可以知道 apply_patchexec_commandspawn_agentwait_agent 这类工具是 available、deferred、disabled_by_policy、missing 还是 unimplemented。

这也改变了 Serve 与 autonomous coding agent 的关系:Serve 不只是给人类 UI 提供 chat transport,它还提供模型工具合约、agent lifecycle 查询、goal/loop runtime primitive 的控制面。但当前实现仍是 backend-supervised orchestration:goal/loop runtime primitives 已经定义,master continuation scheduler 也能排队 child/goal/loop wakeup;完整“无人值守长期自我驱动”的调度闭环仍在增量接线中,不能把它写成已经完成的 self-evolving runtime。

flowchart TD
    Serve[octos serve] --> Config[Config + ProfileStore]
    Serve --> Provider[LlmProvider / RetryProvider]
    Serve --> Tools[ToolRegistry + ToolPolicy]
    Serve --> Sessions[SessionManager]
    Serve --> UI[REST + UI Protocol + Event Harness]
    Serve --> Swarm[SwarmState]
    UI --> Coding[ coding.tool_contract.v1 ]
    UI --> Agents[ agent/* lifecycle + artifacts ]
    UI --> Goals[ session/goal/* + loop/* ]
    Tools --> Policy[DispatchPolicy::from_agent_gates]
    Tools --> Coding
    Policy --> Swarm

13.1.4 MCP Serve 模式(octos mcp-serve

octos mcp-serve 不是给人直接聊天的入口,而是把 octos 暴露成 MCP server,供外层 orchestrator 调用(crates/octos-cli/src/commands/mcp_serve.rs:1-5)。默认 transport 是 stdio,也支持 HTTP transport;HTTP 模式要求通过 OCTOS_MCP_SERVER_TOKEN 配置 bearer token(crates/octos-cli/src/commands/mcp_serve.rs:7-11)。

这个入口的关键差异是:每次 run_octos_session 调用都会加载 profile 配置,构造 LLM,标记任务 Running,创建 Agent,运行 prompt,验证 artifact,最后把任务转成 Ready 或 Failed(crates/octos-cli/src/commands/mcp_serve.rs:13-30)。换句话说,MCP Serve 的职责不是维护一个长期交互 UI,而是把 octos 的 Agent 能力包装成可由外部系统调度的任务执行接口。

这里还要避免一个误解:MCP Serve 不会把 octos 内部工具目录直接暴露给外层系统。mcp_server.rs 明确只暴露一个 session-level tool:run_octos_session;外层 caller 得到的是 aggregate outcome,看不到内部 tool calls、iteration events 或 progress stream(crates/octos-agent/src/mcp_server.rs:1-34)。

flowchart LR
    Orchestrator[MCP client / outer agent] --> Tool[run_octos_session]
    Tool --> Dispatch[RealSessionDispatch]
    Dispatch --> Agent[Agent::run_task]
    Agent --> Contract[Workspace contract + validators]
    Contract --> Outcome[McpSessionOutcome]
    Outcome --> Orchestrator
维度CLIGatewayServeMCP Serve
入口octos chatoctos gatewayoctos serveoctos mcp-serve
用户交互终端 readline消息频道Web UI + REST API + UI Protocol + coding/autonomy capabilitiesMCP client / orchestrator
并发模型单会话多频道多会话多用户多会话外部调度驱动的任务调用
默认端口50080stdio;HTTP 默认 127.0.0.1:4033
栈大小8MB默认默认默认
适用场景开发调试消息 botAPI 集成、Web 部署被上层 agent / IDE / 自动化系统编排

13.1.5 四种模式的架构关系

flowchart LR
    subgraph "共享基础"
        Agent["Agent<br/>LLM + Tools + Memory"]
        Config["Config<br/>Provider + Policy + Hooks"]
    end

    subgraph "CLI 模式"
        CLI["octos chat<br/>readline 循环"]
    end

    subgraph "Gateway 模式"
        GW["octos gateway<br/>ChannelManager"]
        TG["Telegram"]
        DC["Discord"]
        SL["Slack"]
    end

    subgraph "Serve 模式"
        SV["octos serve<br/>axum Web 服务器"]
        REST["REST API"]
        Events["Event Harness / legacy WS"]
        UIP["UI Protocol WS"]
        UI["Web Dashboard"]
    end

    subgraph "MCP Serve 模式"
        MCP["octos mcp-serve<br/>MCP server"]
        ORCH["外层 orchestrator"]
    end

    Config --> Agent
    Agent --> CLI
    Agent --> GW
    Agent --> SV
    Agent --> MCP
    GW --> TG & DC & SL
    SV --> REST & Events & UIP & UI
    MCP --> ORCH

图 13-1:四种运行模式共享 Agent 核心。 Config 和 Agent 是共同基础,四种模式只在接入层和调度方式上不同。

13.1.6 共同的启动模式

四种模式共享相似的启动流程(Command Pattern):

  1. 解析 CLI 参数(clap derive)
  2. 加载配置文件(优先级链)
  3. 初始化 tracing 日志(7 天轮转,JSON 格式可选)
  4. 创建 Provider 和 Agent,或在任务调用时按 profile 构造 Provider 和 Agent
  5. 进入各自的运行循环

13.2 配置体系

13.2.1 优先级层次

<cwd>/.octos/config.json > <data_dir>/config.json(通常 ~/.octos/config.json) > legacy platform config dir(如 ~/.config/octos/config.json) > 内置默认值

本地配置优先于数据目录配置,允许不同项目使用不同的 Provider、模型和工具策略。<data_dir> 由调用方按 --data-dir > OCTOS_HOME > ~/.octos 解析后传入;~/.config/octos/config.json 这类平台配置目录只是兼容旧路径,加载时会提示迁移到 ~/.octos/config.jsoncrates/octos-cli/src/config.rs:845-873)。

13.2.2 Provider 自动检测

当用户只指定模型名而未指定 Provider 时,octos 通过模型名前缀自动匹配(详见第 3 章 Provider 注册表):

  • claude-* → Anthropic
  • gpt-* → OpenAI
  • gemini-* → Google
  • deepseek-* → DeepSeek

13.2.3 热加载

Config Watcher(crates/octos-cli/src/config_watcher.rs:1-5)每 5 秒轮询配置文件(crates/octos-cli/src/config_watcher.rs:51-68),通过 SHA-256 hash 检测变更。

ConfigChange 枚举(crates/octos-cli/src/config_watcher.rs:15-25)区分两类变更:

类型可热加载项实现方式
HotReloadsystem_prompt(crates/octos-cli/src/config_watcher.rs:144-148RwLock<String> 直接替换
HotReloadmax_history(crates/octos-cli/src/config_watcher.rs:151-156AtomicUsize 原子更新
不触发 RestartRequiredprovider, modelWatcher 不再把 provider/model 变更归类为重启项;运行中切换仍走 model_check/SwappableProvider
RestartRequiredbase_url, api_key_env需要重建 HTTP 客户端(crates/octos-cli/src/config_watcher.rs:117-121
RestartRequiredsandbox, mcp_servers, hooks需要重建隔离环境或外部连接(crates/octos-cli/src/config_watcher.rs:123-130
RestartRequiredgateway.queue_mode, gateway.channels影响消息分发主循环(crates/octos-cli/src/config_watcher.rs:133-163

13.2.4 SwappableProvider:运行时模型切换,而不是文件热加载

当前实现需要区分两条路径:

  1. 配置文件热加载:Config Watcher 只会把 system_promptmax_history 作为 HotReload 发给主循环;Gateway 收到后分别写入 RwLock<String>AtomicUsizecrates/octos-cli/src/config_watcher.rs:175-180; crates/octos-cli/src/commands/gateway/gateway_runtime.rs:1335-1355)。
  2. 运行时模型切换:Gateway 启动时把当前 LLM 包装为 SwappableProvidercrates/octos-cli/src/commands/gateway/gateway_runtime.rs:256-257);当用户调用 model_check 工具执行切换时,SwitchModelTool 才会显式调用 swappable.swap(new_chain)crates/octos-cli/src/tools/switch_model.rs:290-295)。

换句话说,编辑磁盘上的 config.json 并不会让正在运行的 Gateway 自动切换 provider/model。当前版本里,Watcher 已经不会把 provider/model 变更当作 RestartRequired 报警,但也不会把它们包含在 HotReload payload 里自动应用。安全的心智模型是:system_prompt/max_history 可以文件热加载;provider/model 可以在会话内显式切换;如果你希望磁盘配置里的 provider/model 成为新的启动态,仍应重启进程,让新配置在启动路径中重新构造 Provider 链。

SwappableProvider 本身的关键实现位于 crates/octos-llm/src/swappable.rs:16-23,50-56

#![allow(unused)]
fn main() {
pub fn swap(&self, new_provider: Arc<dyn LlmProvider>) {
    let model_id = leak_str(new_provider.model_id().to_string());
    let provider_name = leak_str(new_provider.provider_name().to_string());
    *self.inner.write().unwrap() = new_provider;
    *self.cached_model_id.write().unwrap() = model_id;
    *self.cached_provider_name.write().unwrap() = provider_name;
}
}

Box::leak()String 转换为 &'static str——代价是一小段永不释放的内存(每次模型切换泄漏几十个字节),换来的是 model_id()provider_name() 可以在不持有 inner 读锁的情况下返回字符串引用。对于一个长期运行的服务,这点内存泄漏完全可接受。

Config Watcher 的安全性:Watcher 在一次轮询中读取所有配置文件并计算 hash,避免了先检查-再读取的 TOCTOU 竞态。如果配置文件解析失败,保留上一次的有效配置并打印警告,不会崩溃。

为什么用轮询而非 inotify? 跨平台兼容性。inotify 是 Linux 特有的,macOS 用 kqueue,Windows 用 ReadDirectoryChangesW。5 秒轮询 + SHA-256 hash 在所有平台上一致工作,且开销极小(一次 SHA-256 计算 < 1 微秒)。


13.3 Feature Flags

octos 通过 Cargo feature flags 控制条件编译:

Feature启用内容
apiWeb API 服务器、监控、OTP、用户管理
admin-bot管理 Bot 能力,在 api 之上附加 Telegram 管理接口
telegramTelegram 频道集成
discordDiscord 频道集成
slackSlack 频道集成
whatsappWhatsApp bridge 频道
email邮件收发集成
feishu飞书/Lark 频道
twilioTwilio webhook/API 频道
wecom企业微信 webhook/API 频道
matrixMatrix AppService 频道
wecom-bot企业微信 Bot WebSocket 频道
qq-botQQ Bot WebSocket 频道
wechatWeChat WebSocket bridge 频道
gitGit 操作工具
astAST 代码结构分析

这让用户可以编译最小化的 octos 版本——只需 CLI 功能时,不引入 Web 服务器和频道集成的依赖。需要注意的是,BrowserTool 是默认内置工具,不对应单独的 Cargo feature;按需编译主要控制的是 API 能力、各频道集成,以及 octos-agent/gitoctos-agent/ast 这类较重工具依赖。完整 feature 依赖关系见附录 D。


工程决策侧栏:热加载 vs 全重启的边界划分

热加载的核心问题是"什么可以安全替换,什么不可以"。

系统提示可以热加载,因为它是无状态的文本——下一次 LLM 调用使用新提示即可,不影响进行中的会话。

Provider/模型需要区分两种情形:对话内显式切换可以通过 SwappableProvider 完成,但这条路径由 model_check 工具触发;直接编辑配置文件里的 provider/model,当前 ConfigWatcher 不会自动应用到运行中的 Gateway,也不会再把这类变更报成必须重启。与之相对,base_url 和 api_key_env 明确属于重启项,因为它们影响底层 HTTP 客户端的构造(连接池、TLS 配置),运行时替换可能导致进行中的请求失败。

Hooks不能热加载,因为 Hook 的 circuit breaker 状态(连续失败计数)需要重新初始化。如果热加载只替换命令但不重置计数器,一个之前被熔断的 Hook 永远不会恢复。

简单规则:文件热加载只覆盖已经接入 ConfigWatcher 的字段(目前是文本和历史窗口),需要重建连接的仍然重启;SwappableProvider 解决的是受控的运行时切换,不是通用配置热更新。


13.4 本章回顾

  1. 四种模式:CLI(终端交互)、Gateway(消息 bot)、Serve(Web/API/AppUI)、MCP Serve(外部 orchestrator 调用),同一代码库四种入口。
  2. 配置层次:本地 > 全局 > 默认,Provider 自动检测简化配置。
  3. 热加载:SHA-256 轮询检测。文件热加载当前只覆盖 system_promptmax_history;provider/model 的运行时切换走 SwappableProvider + model_check 工具;base_urlhooksMCP 等仍需重启。
  4. Serve 控制面:Serve 汇聚 REST、UI Protocol、event harness、profile、tool policy、swarm state 和 coding/autonomy capability,不只是 chat 的 HTTP 外壳;chat 事件通道以 /api/ui-protocol/ws 为准。
  5. MCP Serve 边界:MCP Serve 只暴露 session-level run_octos_session,外层 orchestrator 收到 aggregate outcome,而不是内部工具事件流。
  6. Feature Flags:按需编译,最小化部署体积。

延伸阅读

  • 12-Factor App:https://12factor.net/ — 特别是 Config 和 Processes 章节
  • axum 框架:https://docs.rs/axum/latest/axum/ — octos Serve 模式的 Web 框架

思考题

  1. 模式融合:如果需要在同一进程中同时运行 Gateway(消息 bot)和 Serve(Web API),架构需要做什么改变?
  2. 配置验证:当前配置文件在运行时解析和验证。如果提供一个 octos config validate 命令做离线验证,你会检查哪些内容?

版本演化说明 本章分析基于当前 ../octos main 分支。当前运行模式已经是 CLI / Gateway / Serve / MCP Serve 四入口;Serve 默认端口为 50080,配置优先级以 <cwd>/.octos/config.json<data_dir>/config.json 为主,legacy platform config dir 仅作兼容。