Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

定位:本章展示 octos 的三种运行模式(CLI/Gateway/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 用来标记子账号;Gateway 初始化时会把父 Profile 的 providermodelbase_urlapi_key_envfallback_modelsenv_vars 合并进子 Profile 配置(crates/octos-cli/src/profiles.rs:29-33; crates/octos-cli/src/commands/gateway/gateway_runtime.rs:128-144)。

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)。默认端口 8080,默认绑定 127.0.0.1crates/octos-cli/src/commands/serve.rs:23-31)——安全默认值,外部访问需要显式指定 --host 0.0.0.0

提供 Web Dashboard、REST 端点、SSE 流式输出。通过 axum 框架构建,AppState 持有全局状态(Provider、工具注册表、会话管理器等)。

维度CLIGatewayServe
入口octos chatoctos gatewayoctos serve
用户交互终端 readline消息频道Web UI + REST API
并发模型单会话多频道多会话多用户多会话
默认端口8080
栈大小8MB默认默认
适用场景开发调试消息 botAPI 集成、Web 部署

13.1.4 三种模式的架构关系

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"]
        SSE["SSE 流式"]
        UI["Web Dashboard"]
    end

    Config --> Agent
    Agent --> CLI
    Agent --> GW
    Agent --> SV
    GW --> TG & DC & SL
    SV --> REST & SSE & UI

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

13.1.5 共同的启动模式

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

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

13.2 配置体系

13.2.1 优先级层次

本地 .octos/config.json > 全局 ~/.config/octos/config.json > 内置默认值

本地配置优先于全局配置,允许不同项目使用不同的 Provider、模型和工具策略。

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 原子更新
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。当前版本里,安全的心智模型是: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 频道集成
email邮件收发集成
gitGit 操作工具
astAST 代码结构分析

这让用户可以编译最小化的 octos 版本——只需 CLI 功能时,不引入 Web 服务器和频道集成的依赖。需要注意的是,BrowserTool 是默认内置工具,不对应单独的 Cargo feature;按需编译主要控制的是 API 能力和各频道集成。


工程决策侧栏:热加载 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),同一代码库三种入口。
  2. 配置层次:本地 > 全局 > 默认,Provider 自动检测简化配置。
  3. 热加载:SHA-256 轮询检测。文件热加载当前只覆盖 system_promptmax_history;provider/model 的运行时切换走 SwappableProvider + model_check 工具;base_urlhooksMCP 等仍需重启。
  4. 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 v0.1.0。截至本书写作时,三种运行模式的入口和配置体系无重大变化。Feature flags 列表可能随功能扩展而增加。