OpenHarness源码研究-5-基础设施-配置/认证/权限/扩展前言把配置、认证、权限、扩展体系、记忆和Swarm这些基础设施一次讲清楚。它们不直接产生对话但没有它们Agent Loop 一步都走不了。配置-四层覆盖与ProviderProfileSettings 是整个系统的唯一真相来源。它通过 Pydantic BaseModel 定义加载时走四层优先级cli arg → env var → settings.json → default以 model 参数为例oh --model deepseek-chat覆盖OPENHARNESS_MODEL环境变量覆盖~/.openharness/settings.json里的model字段覆盖代码中的默认值claude-sonnet-4-6。具体实现# config/settings.py 第651-663行 def merge_cli_overrides(self, **overrides): updates {k: v for k, v in overrides.items() if v is not None} merged self.model_copy(updateupdates) # 如果覆盖了 profile 相关字段触发 profile 同步 if profile_updates: return merged.sync_active_profile_from_flat_fields().materialize_active_profile() return merged这里有一个容易踩坑的设计扁平字段 vs ProviderProfile 的双轨制。Settings上同时有model、api_format、base_url这些扁平字段也有profiles: dict[str, ProviderProfile]这个结构化字段。两者描述的是同一件事当前用什么模型但来源不同——扁平字段来自 CLI 覆盖Profile 来自持久化配置。materialize_active_profile()的作用是把当前 active profile 的数据投影回扁平字段。sync_active_profile_from_flat_fields()则反过来——把 CLI 覆盖的扁平字段写回 profile。这对方法的注释写得清楚# config/settings.py 第441-458行 def materialize_active_profile(self) - Settings: Project the active profile back onto legacy flat settings fields. # config/settings.py 第461-504行 def sync_active_profile_from_flat_fields(self) - Settings: Fold legacy flat provider fields back into the active profile.设计这种双轨制的原因是兼容——旧版只有扁平字段新版引入了 Profile 概念。如果从零开始设计可能根本不需要这层同步。模型别名系统用户输sonnet不是合法的 API 模型名需要在内部转成claude-sonnet-4-6# config/settings.py 第121-127行 _CLAUDE_ALIAS_TARGETS { sonnet: claude-sonnet-4-6, opus: claude-opus-4-6, haiku: claude-haiku-4-5, sonnet[1m]: claude-sonnet-4-6[1m], opus[1m]: claude-opus-4-6[1m], }还有特殊的opusplan别名——在 plan 模式下用 Opus其他时候用 Sonnet。把模型选择和权限模式绑定是个实用的设计。认证-三种流统一为一个ResolvedAuth认证体系的场景很杂Claude API Key、OpenAI API Key、GitHub OAuth 设备码、Codex JWT Token、Claude 订阅 OAuth Token。每种来源不同、存储位置不同、刷新策略不同。AuthManager 把它们统一为一个返回类型# config/settings.py 第99-108行 dataclass(frozenTrue) class ResolvedAuth: provider: str # 供应商名 auth_kind: str # api_key | oauth_device | external_oauth value: str # 实际的token/key值 source: str # 来源描述如 env:ANTHROPIC_API_KEY state: str # configured | missingresolve_auth()的查找顺序体现了优先级1. 外部订阅绑定codex_subscription / claude_subscription → 从本地文件读 JWT/OAuth token → 可能触发 refresh 2. OAuth 设备码copilot_oauth → 从 keyring 读 GitHub token → 换 Copilot session token 3. Profile 级别的 credential_slot → 从 keyring 读 profile 专用 key 4. 环境变量ANTHROPIC_API_KEY / OPENAI_API_KEY / DASHSCOPE_API_KEY / ... 5. settings.json 中的 api_key 字段 6. keyring 中存储的 API key这套优先级保证了命令行临时覆盖 环境变量 持久化配置 keyring。同时外部订阅这一类认证在最前面因为它最特殊——token 有过期时间需要刷新逻辑。认证的存储有两套机制keyring系统密钥链macOS 是 KeychainLinux 是 Secret Service和明文文件~/.openharness/下的 JSON 文件。keyring 用于 API Key 这种敏感数据明文文件用于 OAuth token 缓存和外部订阅绑定。权限-5层决策链permissions/checker.py的PermissionChecker.evaluate()是一个顺序执行的决策链前一步拦截了就不往后走1. 敏感路径保护SENSITIVE_PATH_PATTERNS → ~/.ssh/*、~/.aws/credentials、~/.kube/config 等 → 硬编码不可配置不可绕过。防御 prompt injection 的最后一道墙 2. 工具黑名单denied_tools → settings 中显式禁止的工具名直接拒绝 3. 工具白名单allowed_tools → settings 中显式允许的工具名直接放行 4. 路径规则path_rules → 基于 fnmatch glob 的路径级控制 → 可以指定允许读 ~/projects/*或禁止写 /etc/* 5. 命令规则denied_commands → 匹配 shell 命令字符串如 rm -rf /* 6. 权限模式PermissionMode → FULL_AUTO全部放行 → PLAN读操作放行写操作阻止 → DEFAULT读操作放行写操作弹窗确认关键实现细节is_read_only不仅仅是个标志位它决定了整个后半段逻辑。读操作在 DEFAULT/PLAN 模式下都是直接放行的只有当工具声明自己是非只读时权限检查才真正介入。敏感路径列表值得单独拿出来看# permissions/checker.py 第18-37行 SENSITIVE_PATH_PATTERNS ( */.ssh/*, */.aws/credentials, */.aws/config, */.config/gcloud/*, */.azure/*, */.gnupg/*, */.docker/config.json, */.kube/config, */.openharness/credentials.json, */.openharness/copilot_auth.json, )这些路径在任何权限模式下都不可访问。即使你开了--dangerously-skip-permissions这个检查也不会跳过——它是在PermissionChecker.evaluate()最开头就执行的不经过任何模式判断。扩展体系-四种途径给AI加能力Hook-生命周期拦截Hook 只有 4 个事件但覆盖了关键的拦截点# hooks/events.py class HookEvent(str, Enum): SESSION_START session_start SESSION_END session_end PRE_TOOL_USE pre_tool_use POST_TOOL_USE post_tool_use每种 Hook 有三种实现方式Command Hook执行一个 shell 命令把 payload 通过环境变量或$ARGUMENTS模板注入HTTP HookPOST 到指定 URLpayload 作为 JSON bodyPrompt Hook / Agent Hook调 LLM 判断返回{ok: true}或{ok: false, reason: ...}PRE_TOOL_USE可以阻止工具执行POST_TOOL_USE可以做事后审计。SESSION_START/END用于初始化和清理。Hook 的 matcher 机制用fnmatch做通配符匹配可以指定只对 bash 工具生效或只对包含特定关键字的 prompt 生效。MCP-外部工具和资源MCPModel Context Protocol是一种标准化的工具扩展协议。任何实现了 MCP 协议的服务端都可以作为工具源接入# mcp/client.py class McpClientManager: async def connect_all(self): ... async def close(self): ... def list_statuses(self): ...MCP 工具会和内置工具一起注册到 ToolRegistry 中。对 Agent Loop 来说MCP 工具和内置工具没有区别——都是BaseTool的子类实例。Plugin-项目级扩展包Plugin 是比 Skill 更重的扩展机制。每个 Plugin 有自己的 manifest可以注册命令、Hook、Skill。Plugin 的发现基于目录扫描加载时做 manifest 校验。Skill-slash 命令路由Skill 是用户最常见的扩展入口。通过/skill-name的方式调用。Skill 的注册是声明式的——在特定目录下放一个 markdown 文件定义 name 和 description运行时自动发现。handle_line()处理用户输入时先查 slash 命令注册表匹配到就路由给对应 handler没匹配到就当作普通对话发给引擎。记忆系统-文件级的持久记忆记忆系统用 Markdown 文件做持久化每个记忆是一个独立的.md文件放在项目下的.claude/memory/目录中.claude/memory/ ├── MEMORY.md ← 索引文件一行一条 ├── coding-prefs.md ← 具体记忆文件 └── project-context.md每个记忆文件有 frontmatter 元数据name、description、type正文是记忆内容。[[wikilink]]语法用于关联相关记忆。召回路径build_runtime_system_prompt()在构建 System Prompt 时先加载 MEMORY.md 索引作为概览注入再用find_relevant_memories()做关键词检索把和当前用户 prompt 最相关的几个记忆全文注入# memory/search.py 第12-40行 def find_relevant_memories(query, cwd, max_results5): tokens _tokenize(query) for header in scan_memory_files(cwd): meta_hits sum(1 for t in tokens if t in header.title header.description) body_hits sum(1 for t in tokens if t in header.body_preview) score meta_hits * 2.0 body_hits # 标题命中权重是正文的2倍Swarm-多Agent协作Swarm 系统允许一个leader Agent 启动多个worker Agent 并行工作。核心组件TeammateSpawnConfig定义 worker 的 peer 配置name、prompt、model、permissions、worktree 隔离路径TeammateMailboxAgent 间通信的消息队列。每个 Agent 有一个 inbox 目录消息是独立的 JSON 文件。写入先写.tmp再os.rename保证原子性读取按时间戳排序Worktree可选的 git worktree 隔离每个 worker 在独立的文件系统沙箱中操作Backendsubprocess子进程、in_process协程、tmux/iterm2终端面板三种执行模式Mailbox 支持的消息类型user_message文本消息、permission_request/response权限协商、shutdown关闭指令、idle_notification空闲通知。AgentTool第 4 篇提过就是通过TeammateExecutor.spawn()启动新 AgentSwarm 相当于 AgentTool 的多对多版本——不只是嵌套调用而是持续性的团队协作。总结Settings 四层覆盖cli → env → file → default扁平字段和 ProviderProfile 双轨同步是历史包袱不是理想设计AuthManager 把 API Key / OAuth / JWT 三种认证流统一为ResolvedAuth查找顺序体现了优先级的精心安排PermissionChecker 是 5 层顺序决策链敏感路径保护是最外层、不可绕过Hook 提供 4 个生命周期拦截点MCP 提供标准化外部工具协议Plugin 和 Skill 负责扩展发现和路由记忆系统用 Markdown 文件做持久化召回时标题命中权重是正文的 2 倍Swarm 把单 Agent 的工具调用升级为多 Agent 的持续性协作Mailbox 用文件系统实现消息队列写到最后