Reasonix 工程规范全解读:以 Provider/Tool 双注册表与缓存优先上下文为核心的编码 Agent 架构

  • 人工智能
  • AI 应用
  • AI Agent
  • 代码智能体
  • CLI

【免费下载链接】DeepSeek-Reasonix

A reliable coding agent for complex software engineering tasks.

项目地址: https://gitcode.com/GitHub_Trending/de/DeepSeek-Reasonix
点击查看 免费下载

本文是 Reasonix 官方工程规范(docs/SPEC.md)的深度导读。Reasonix 是一个"薄壳"驱动的编码 Agent——核心只认识接口,全部能力由配置与插件提供。文章以规范原文为骨架,逐一拆解设计原则、Provider/Tool 双注册表、MCP 插件层、缓存优先的上下文压缩、按调用门控的权限层与子代理体系,并对照仓库真实源码给出实现级佐证。读完你将对 Reasonix 的架构契约、配置语义与边界行为形成可直接用于二次开发与排障的完整认知。

1. 定位:一份"代码追随它"的契约文档

SPEC.md 开篇即声明了自己的性质:"This document is the contract — code follows it. Change the contract first, then the code."(本文档即契约,代码追随它;先改契约,再改代码)。

Reasonix 是"a thin harness driving multiple models"(驱动多个模型的薄壳):模型、工具、权限策略、斜杠命令全部由配置和插件注入,核心代码不硬编码任何具体模型或工具。这一点在入口文件中有直接体现——cmd/reasonix/main.go 通过四个**空白导入(blank import)**把编译期内建能力注册进各自的注册表:

// Blank imports wire compile-time built-ins into their registries.
_ "reasonix/internal/model/anthropic"
_ "reasonix/internal/model/openai"
_ "reasonix/internal/model/responses"
_ "reasonix/internal/tools/builtin"

空白导入的副作用是各子包在 init() 中自注册,主程序与注册表之间只有接口依赖,没有具体类型依赖——这正是"config- and plugin-driven core"原则的代码级落点。

2. 六大设计原则

  1. 配置与插件驱动核心:核心只认识接口,具体模型和工具按名称从注册表解析、在配置中声明或由插件注入,杜绝硬编码的 switch model。
  2. 单一静态二进制:CGO_ENABLED=0 交叉编译,CLI 开箱即用。
  3. 精简依赖:默认只用标准库;第三方依赖必须是纯 Go、轻量、且不破坏单二进制/跨平台/分发故事——TOML 解析是唯一被接受的第三方依赖(go.mod 中 require BurntSushi/toml)。
  4. 双层扩展机制:编译期内建(通过 init() 自注册)与运行时外部插件(stdio JSON-RPC 子进程,MCP 兼容)。
  5. 接口优先与注册表机制:Provider 与 Tool 都是接口。
  6. 演进而非过度设计。

语言约定:所有代码的英文是唯一主语言——注释、面向用户的字符串、工具描述、系统提示词均如此;README 为双语(README.md 英文 + README.zh-CN.md 中文)。

3. 仓库布局与依赖方向

规范给出的逻辑布局(实际路径以源码为准):

  • go.mod / go.sum:module reasonix
  • Makefile:build / cross / vet / fmt / test
  • cmd/reasonix/main.go:入口,空白导入内建 provider/tool
  • cmd/reasonix-plugin-example/:参考 MCP stdio 插件(可运行示例)
  • internal/contract/provider/:Provider 接口 + kind→factory 注册表
  • internal/contract/tool/ 与 internal/tools/builtin/:Tool 接口 + Registry,内建工具集
  • internal/safety/permission/:按调用 Policy(allow/ask/deny → Decision)
  • internal/ext/command/:自定义斜杠命令(.reasonix/commands/*.md)
  • internal/ext/plugin/:stdio JSON-RPC(MCP)客户端,适配远端工具
  • internal/platform/remote/:Remote-SSH 模块(含 forward 端口转发、sftpfs SFTP 文件层、bootstrap 免挂载引导)
  • internal/runtime/agent/:Session + harness 主循环

依赖方向是无环的:cli → {agent, plugin, config} → {tool, provider}。内建子包(如 model/openai、tools/builtin)导入其父包以自注册,父包从不导入子包。Remote-SSH 模块按 cli → remote/bootstrap → remote → {remote/forward, remote/sftpfs, config, netclient} 分层;remote 及其子包从不导入 cli、agent、serve,所有交互(host-key / 密钥提示)通过回调流动,因此桌面模块复用同一表面。

4. 核心抽象一:Provider 与注册表

规范 §3.1 给出 Provider 接口契约:

type Provider interface {
    Name() string
    Stream(ctx context.Context, req Request) (<-chan Chunk, error)
}

// Factory builds a Provider from a resolved config instance.
type Factory func(cfg Config) (Provider, error)

// Register adds a factory under a kind (e.g. "openai"). Called from init().
func Register(kind string, f Factory)

// New instantiates the provider of the given kind.
func New(kind string, cfg Config) (Provider, error)

type Config struct {
    Name    string         // instance name, e.g. "deepseek"
    BaseURL string
    Model   string
    APIKey  string
    Extra   map[string]any // kind-specific options
}

源码实现位于 internal/contract/provider/provider.go:registry 是一个 map[string]Factory{};Register 在重复 kind 时 panic(这是编译期接线错误);New 按 kind 查表并调用工厂。Config 类型在 internal/contract/provider/config.go 中定义,并额外支持 APIKeyFunc——密钥是外部可变状态,换一个失效的 key 无需重建会话。

几个规范强调的要点:

  • OpenAI 兼容厂商 = 配置实例:kind = "openai" 的实例只差 base_url / model / api_key_env。接入一个新的 OpenAI 兼容模型是一次配置修改,不是代码修改。
  • Provider 是"厂商端点":一个 base_url + api_key_env 可挂一个或多个模型。request_url 设置时是 OpenAI 兼容、Anthropic 兼容与 Responses Provider 的精确请求目标;旧 chat_url 保留历史 OpenAI-only 行为。条目可声明单个 model 或 models 列表(可带 default),让一个厂商端点暴露多个模型而无需重复声明端点/密钥。
  • 模型引用解析:default_model、--model 旗标、桌面切换器都通过 Config.ResolveModel 解析,接受 provider 名(→ 其默认模型)、裸模型名或显式 provider/model。
  • 上下文窗口:context_window 是 provider 级回退;model_overrides.<model>.context_window 可单模型覆盖;per-model prices 以模型 ID 为键。
  • 流式工具调用增量按索引在 provider 内累积,只对外发射完整的 ToolCall。

5. 核心抽象二:Tool 与注册表

规范 §3.2 的 Tool 契约:

type Tool interface {
    Name() string
    Description() string
    Schema() json.RawMessage // JSON Schema for parameters
    Execute(ctx context.Context, args json.RawMessage) (string, error)
}

源码 internal/contract/tool/tool.go 中的实现增加了 ReadOnly() bool——它报告工具是否无可见副作用:批量调度只并行化全部只读的调用,bash 与插件工具返回 false(其副作用无法仅凭参数得知)。文件类内建工具(read_file / write_file / edit_file / move_file / bash / ls / glob / grep 家族)通过 init() 的 tool.RegisterBuiltin(t) 自注册进进程级内建集;每次运行再组装一个运行时 *Registry:配置过滤后的内建工具 + 插件提供的工具,Agent 只能看到这个 *Registry。

工具 Schema 在注册表插入时会被规范化(canonicalized),内建契约记录在 docs/TOOL_CONTRACT.md,并有测试把文档表面与同一套 canonical schema 路径对比。Execute 自行解析原始 JSON 参数;错误是返回而非致命——Agent 会把错误回喂给模型,让它自我修正。

6. MCP 插件层:一个客户端,三种传输

规范 §3.3 定义外部插件为配置中声明的 MCP 服务器。线上协议一律是 JSON-RPC 2.0,只有传输层不同——transport 接口(call / notify / close)把握手、tools/list、tools/call 等 MCP 级逻辑写成一份。三种传输(配置 type):

  • stdio(默认):本地子进程,子进程 stdin/stdout 每行一个 JSON 消息(MCP stdio 惯例)。用 command / args / env 声明,ctx 取消/关停时终止。
  • http(即 streamable-http):远端 url 上的服务器。每次请求一个 HTTP POST,服务端回 application/json(单响应)或 text/event-stream(SSE 流携带响应与服务端通知);一旦见到 Mcp-Session-Id 响应头,后续请求回显;静态 headers(如 bearer token)随每次请求发送。当未配置静态 Authorization 头时,用户发起的 OAuth 使用 Protected Resource Metadata / Authorization Server Metadata 发现、动态客户端注册、PKCE S256、loopback 回调、resource indicators 与 refresh-token 轮换;客户端凭据与 token 以 0600 权限存于服务器私有 Reasonix MCP 状态目录(工作区之外),token 绑定配置的资源 URL,URL 变更后绝不复用;OAuth 的发现/注册/token 请求遵循 Reasonix 已解析的网络代理设置。移除声明会清除该状态(除非生效回退使用同一 OAuth 资源)。
  • sse(遗留 2024-11-05 HTTP+SSE):常驻 GET 流接收服务端公布的相对 POST 端点、JSON-RPC 响应与服务端消息;跨源公布的端点被拒绝,防止静态头泄漏。

变量展开:${VAR} / ${VAR:-default} 在 command、args、env、url、headers 中展开,密钥来自环境而非配置文件;${REASONIX_WORKSPACE_ROOT} 与 ${CLAUDE_PROJECT_DIR} 在这几个字段中展开为服务器绝对工作区路径,且优先于同名环境变量或 .env 变量。

生命周期:initialize → notifications/initialized → tools/list;调用走 tools/call {name, arguments}。有工作区根时,initialize 会公布 roots,传输层应答 roots/list 的文件 URI;tools/call 携带每次调用的 _meta.progressToken,匹配的 notifications/progress 消息流入既有工具进度事件通道。stdio 服务器用一条常驻传输处理 initialize、读与写,跨工具调用保持状态(如浏览器会话)。

每个远端工具被适配成 Tool 注入运行注册表,命名空间为 mcp__<server>__<tool>(空格归一化为 _),与 Claude Code 对齐并避免冲突。工具声明的 annotations.readOnlyHint 映射到 Tool.ReadOnly(),默认 false(远端工具不透明);声明 readOnlyHint: true 才可进入并行批量调度与权限层的 reader 默认。prompts/list + prompts/get 以 /mcp__<server>__<prompt> 斜杠命令呈现;resources/list + resources/read 在对话中以 @<server>:<uri> 引用;/mcp 显示已连接服务器及其计数。

参考实现:cmd/reasonix-plugin-example 是一个可运行的最小 MCP stdio 服务器(echo、wordcount 工具),源码注释完整列出了协议逐条行为(cmd/reasonix-plugin-example/main.go),并有端到端测试构建真实二进制驱动它。其工具以 mcp__example__echo / mcp__example__wordcount 呈现,提示词成为 /mcp__example__review,资源成为 @example:doc://style-guide。日志走 stderr(Reasonix 会把插件 stderr 转发到终端),stdout 专供 JSON-RPC,绝不可携带杂文。

配置来源(provenance)是运行时元数据并决定持久化范围:桌面与 CLI 安装写入用户全局 config.toml;项目 reasonix.toml 与 .mcp.json 条目留在所属项目文件;每个配置源都被信任、无独立启动确认步骤;项目条目覆盖同名全局条目,项目 reasonix.toml 覆盖 .mcp.json;编辑写入生效条目的来源,移除则显露出下一个低优先级声明。

7. Agent 循环与双模型协作

7.1 Session 与 Run 循环

规范 §3.4:Session 持有 []Message;Run(ctx, input) 循环为:构建 Request(含工具 Schema)→ provider.Stream → 实时打印文本增量、收集完整工具调用 → 无调用则结束,否则逐个执行工具(内建或插件)并追加结果 → 重复,受 maxSteps 约束。ctx 贯穿全程(Ctrl-C 中止进行中的请求)。

源码 internal/runtime/agent/agent.go 印证了这一结构:maxSteps <= 0 时循环无界,边界由宿主决定——用小时或 token 计的任务预算(TaskBudget)而非轮数。

Runner 是任何具有 Run(ctx, input) error 的东西;Agent 与 Coordinator 都满足它,因此 CLI 对单模型/双模型模式无感知。

7.2 Coordinator:两个模型、两个会话

当 agent.planner_model 指向与执行器不同的 provider 时,Coordinator 让两个模型运行在独立会话中,保持各自提示词前缀缓存稳定:

  • 规划器(低频)在自己的会话中运行:同样的常驻记忆上下文 + 过滤后的只读研究工具集,产出一份精炼计划。路由决策由确定性宿主策略从"干净的用户文本 + 可信回合元数据"选出(executor-only / light planning / full planning / plan-for-approval / explicit plan-only),不调用分类模型、不从 controller 撰写的提示块推断宿主状态。显式 Plan Mode、合成回合、短上下文回复、原子编辑和有界只读行动不会触发第二规划器;跨表面、结构化、歧义与高风险工作使用完整契约。
  • 轻量计划用小的每回合研究预算,返回紧凑目标、1–4 个有序步骤、可能触点与主要验证方式;完整计划用更大的有界预算,区分已验证与候选触点,含风险、验收标准、命令级验证与(相关时)回滚。深度契约固定在一个稳定系统提示词中,只有宿主撰写的 <planner-turn> 块随用户回合变化。
  • 计划以结构化文本移交给执行器——自己会话中的完整工具使用型 Agent,它验证候选假设并执行。
  • 两个会话绝不混用,任何一方的回合都不会扰乱另一方的前缀;两者都只做追加增长、保持缓存友好。这调和了"缓存优先"与"双模型协作":在同一个共享对话中切换模型会破坏前缀、击穿缓存命中,所以不这么做。

8. 上下文管理:缓存优先、只追加的摘要压缩

长任务会填满模型窗口。Reasonix 维护缓存优先、只追加的规范转录,并且只在唯一自动阈值被跨过时安装一段简短的 provider 可见检查点。

8.1 唯一自动触发:compact_ratio

  • 每个 provider 声明 context_window(token)。唯一自动触发是 agent.compact_ratio(默认 0.85;预设 0.70 / 0.80 / 0.85;取值严格在 0 与 1 之间)。triggerTokens = floor(context_window × compact_ratio)。
  • 边界由 CompactRatioMin / CompactRatioMax 定义,源码在 internal/contract/config/edit_compaction.go:必须满足 ratio > CompactRatioMin && ratio < CompactRatioMax,越界返回 ErrCompactRatioRange,并有边界测试覆盖 NaN、±Inf 等(compact_ratio_test.go)。
  • 低于触发线时历史永不被重写:不摘要、不剪枝/投影、不写 sidecar、不 bump 投影版本、无维护事件——任何重写都会使提示缓存从此失效。
  • 到达触发线时运行一次摘要事务:稳定前缀 + 一份结构化摘要 + 近期逐字尾段。验收(正常路径):候选 ≤ 窗口 50%、严格小于源、低于 triggerTokens;候选不向 50% 填充,典型落点约为窗口的 10%–30%。内部构造预算(非用户设置):recentTailBudget = clamp(window×10%, 32K, 96K),摘要输出上限 16K。
  • 用户用 reasonix config compact-ratio [--local] [VALUE] 查看或修改阈值。项目配置覆盖桌面与新 CLI 会话使用的用户全局值;UI 始终显示生效比率。

8.2 max_output_tokens:独立的每回合完成上限

max_output_tokens 是独立的每回合完成上限,推荐 0(自动,非无限;DeepSeek 默认高位 → 约 64K)。用户预设:32768(普通编码/控成本)、65536(重推理/长工具循环)、131072(仅在反复出现 finish_reason=length 后)。负值在协议允许时省略可选线上限制。仅在发送时对剩余窗口裁剪,绝不改变 triggerTokens 或维护时机;计费按实际完成 token,不按上限。

8.3 巨大工具结果与维护事务

  • 巨大工具结果在首次进入模型时被有界化一次:Content 是稳定 ≤32KB 可见形式;RawContent 仅在两者不同时保留完整原文。维护从不重写旧工具体。ModelMessages 剥离 RawContent,使 provider 序列化与缓存哈希永不包含它。
  • 自动维护在 ContextManager.Prepare 中由当前投影 + 只追加规范尾部计划一次。规范转录永不被重写。后续阈值把先前摘要 + 新历史合并成单份摘要(无多段合并)。失败记录代际 blocked/failed 回执;同代不再为另一次自动摘要付费;手动 compress 可重试。
  • 在输出上限被截断的摘要会以更大输出上限重试一次;再次被截断则保留类型化错误码并记录上限。
  • 旧多阈值键(soft_compact_ratio、tool_result_snip_ratio、compact_force_ratio、cold_resume_prune、context_editing)在普通启动时被移除、运行时忽略;不使用原生 provider 工具清理,所有 provider 都走本地摘要检查点路径。

8.4 什么能在折叠中存活

逐字保留(每次压缩):系统提示词、足够小可作简报的第一条用户回合、折叠区内适配保留预算的每条用户回合、近期尾段。keep 策略保护的消息也存活(记录过失败的执行只保留其含失败信息的行)。其余都是尽力而为。

用户回合受不同于其管辖工作的标准约束:第 4 回合提出的约束("不要改公开 API")只存在于转录中,而受其约束的代码仍可从工作区重推导——损失的对称性而非 token 数决定取舍。保留有界而非无条件:单回合最多 1500 token,合计 min(8192, window×5%),最旧的优先。用 [[keep]] 前缀(keep 策略 user_marked,默认开启)可无视大小逐字保留;压缩遥测携带 user_kept / user_dropped 计数,提交的检查点若不得不折叠某条用户回合会发出命名 [[keep]] 的警告。

两项性质限制损失:每次折叠从规范转录而非先前摘要重新推导摘要(摘要不链式、反复压缩不叠加漂移);压缩只写投影——规范转录保留所有原文,折叠细节仍可经 history 工具与归档(reasonix/archive/<timestamp>.jsonl)恢复。

这是提示词前缀唯一会变的位置——一个刻意的、罕见的"缓存重置点"。context_window = 0 可对某实例禁用压缩。

8.5 只读的 history 与 memory 工具

  • history 工具提供对会话 JSONL 的按需 BM25 检索:scope="project" 搜索当前控制器的会话目录;scope="global" 还搜索用户全局会话目录与压缩历史归档;operation="around" 可读取命中附近有界转录窗口。搜索保留最佳命中并修剪尾部纯常见词噪声;0 结果响应会指导 Agent 用更稀有词或扩大范围重试。
  • memory 工具提供对自动记忆文件的按需搜索/列表/读取,与写工具互补:memory 查已存在内容、remember 保存或更新事实、forget 从活动索引移除陈旧事实并把文件归档留痕。归档文件在本地管理表面(/memory、TUI、桌面面板)可见,但被排除在活动记忆检索之外。
  • 每个真实用户回合前,有界 BM25 召回从原始用户消息选择相关活动事实,作为低权威用户回合后缀追加;通用回合被抑制、项目事实覆盖等效全局回退、陈旧事实降权、召回受结果/字符预算约束,且绝不改动稳定系统提示词或工具 Schema。
  • remember 的自动允许只限有界、非敏感、仅创建的项目/参考写入;全局事实、偏好、反馈、更新、重复、敏感/超大内容以及每次 forget 都需要全新的人工批准(即使 Auto/YOLO 姿态下)。事实带不可变 ID、单调修订、时间戳、类型与作用域;更新为前一修订做快照;恢复与归档恢复创建更高修订并拒绝路径逃逸、符号链接、碰撞与覆盖。详细实现契约见 docs/SESSION_MEMORY_RETRIEVAL.md。

9. 权限层:按工具调用门控

编码 Agent 自主运行 shell 命令、编辑文件。权限层每次工具调用决定允许、拒绝或先询问用户。它独立于模型与 CLI——Agent 在执行时咨询 Gate 接口;Gate 由静态 Policy 加可选交互式 Approver 构建。源码在 internal/safety/permission/permission.go 定义了 Decision(Allow / Ask / Deny)与 Policy(Mode + Allow/Ask/Deny 规则列表),并明确:读工具总是回退 Allow,写工具的 fallback 是 Mode(默认 Ask)。

9.1 规则语法

规则是 Tool(匹配该工具族的任意调用)或 Tool(specifier)(调用的主体匹配 specifier)。Bash 与文件变更批准使用 Claude Code 风格族,如 Bash(npm run build)、Bash(npm run test:*)、Edit(docs/**)。内建文件变更含写入、编辑、notebook 编辑、符号/范围删除与 move_file 重命名/移动;遗留小写工具 ID 仍可加载。Bash=<literal> 是精确命令形式:字面量中的元字符是普通字符,只有完全相同的完整命令匹配。:* 后缀表示 Bash 命令前缀批准;生成的规则同时拒绝后续引入 shell 运算符的命令,所以 Bash(go test:*) 不覆盖 go test ./... && rm -rf tmp。主体从调用的 JSON 参数通过已知键集合(command、path/file_path、pattern)通用提取。规则解析实现见 internal/contract/permrule/permrule.go。

9.2 动态 Bash 与优先级

参数/算术展开、赋值、heredoc、未证实的重定向与 shell glob 不能复用裸 Bash、前缀或 glob 批准;记忆的批准是精确 Bash=<literal> 规则。嵌套/间接执行更严格:命令/进程替换、动态命令名、解析失败、eval、source、shell -c、PowerShell/cmd 命令字符串与运行时内联代码旗标都要求交互 Ask/Auto 中的人类;Guardian、允许 hooks 与已批准计划窗口不能替用户作答。进阶 [permissions] allow_dynamic_bash = true 让 Allow 回退(含 Auto)覆盖此类;显式 ask/deny 规则保持优先。

优先级:deny > ask > allow > fallback。fallback 对只读工具是 Allow、对写工具是 Mode(默认 Ask)。deny 总是赢——宽 allow = ["Bash"] 仍可被 deny = ["Bash(rm -rf*)"] 雕刻;反之 ask 覆盖宽 allow 强制对危险子集弹提示。

9.3 解析 Ask 与无头运行

交互前端(聊天 TUI)通过 Approver 提供"允许一次 / 本次会话允许此批准范围 / 始终允许此批准范围 / 拒绝"。Bash 默认范围是具体命令主体,用户可选保守的命令前缀范围(如 Bash(go test:*));文件变更工具的一次性会话授权覆盖本会话剩余编辑,持久授权在可及路径时按路径存储为 Edit(<path>),所有内建文件变更工具共享。

无头运行(reasonix run、子代理、任何无 TTY/无 approver 的场景)不能弹窗:Ask/manual 失败关闭(fail closed)、Auto 只允许普通写 fallback、YOLO 可绕过普通 Ask 决策。嵌套/间接 Bash 在无头 Ask/Auto/DontAsk 下被拒绝(除非存在相同字面量授权);YOLO 或 allow_dynamic_bash = true 可选择退出。Deny 是每个模式下的硬阻断:工具绝不执行,模型收到可自适应的 "blocked" 结果(与 plan-mode 拒绝同形)。

9.4 MCP 授权与计划模式的关系

安装 MCP 服务器即授权其全部工具,没有第二层服务器/裸工具/写/破坏性批准策略;项目配置同样被信任、无需独立启动确认;显式全局 deny 规则仍赢。readOnlyHint / destructiveHint 是调度、Plan/只读限制与缓存转实时安全重分类的内部事实。严格只读子代理注册表只暴露带 readOnlyHint: true 且无 destructiveHint 的已授权工具。双模型规划器经固定 use_capability 代理(绝不直接 mcp__* Schema)调用已授权、非破坏性 MCP;破坏性工具留给执行器。

计划模式是 plan-first 协作工作流而非全工具只读模式。宿主先强制显式相位退出(complete_step 虽只读但属批准后执行相位,自报 plan-unsafe 并被拒);之后普通内建与 Bash 调用走与标准模式相同的 Ask/Auto/YOLO、显式 ask/deny、Sandbox 路径。单模型 Plan 在 Plan 激活期间继续阻断 MCP 写/破坏性目标。plan_mode_read_only_commands 仅为配置/会话往返保留,在主 Plan 工作流中既不授予也不撤销调用。

9.5 工具批准姿态与协作模式

用户决策与工具批准分离。工具批准有三个姿态(见下表,摘自规范 §3.7):

工具批准姿态工具批准计划批准ask 问题
Need approval / ask遵循权限策略(Ask 交互式弹窗)等待用户等待用户
Auto approve / auto写 fallback 自动允许;显式 ask/deny 规则仍适用等待用户等待用户
YOLO approval / yolo普通提示自动允许;deny 规则与强制新评审保留等待用户等待用户
已批准计划执行窗口已批准计划的写 fallback 自动允许;显式 ask/deny 规则保留未来计划仍等待等待用户

开箱即用(mode = "ask"、无规则):交互式 reasonix 在每次 writer/bash 调用前提示;reasonix run 因无 approver 对这些调用失败关闭。无人值守自动化用 reasonix run --auto ... / -y(等价于 --permission-mode auto)允许普通写 fallback;显式 ask 规则在 Auto 下仍失败关闭,deny 规则硬化每个姿态。

协作模式与工具批准分离:桌面作曲器呈现 normal(正常模式)/ plan(计划模式)/ goal(目标模式)。/goal <objective> 启动自主、会话作用域的活动目标:控制器把目标上下文前置到用户回合(在缓存稳定的系统提示词之外)并持续发出续回合,直到模型报告完成、同一受阻状态重复三次、用户停止或安全续行上限到达。受阻状态匹配对大小写/空白/标点归一化;重启目标开始全新受阻审计。目标视为任务契约:若目标含 Context / Request / Output format / Constraints / Pause policy 段,它们定义自主工作边界;缺失时模型从对话与工作区推断轻量契约。/goal clear 移除活动目标;切入 plan/normal 模式会清除活动目标(底层工具批准姿态被保留)。

10. 斜杠命令与 @ 引用

10.1 三类命令共享一个分发

  • 内建动作(/compact、/new、/clear、/effort、/mcp、/help)本地操作会话状态,绝不触达模型。/new 开新会话并保存上一转录供 resume/history;/clear 需确认后丢弃当前上下文(不保存、不删项目记忆)。
  • 自定义命令是 .reasonix/commands/(项目)与用户配置目录(macOS/Linux 为 ~/.reasonix/commands/)下的 Markdown 文件,项目目录在重名时覆盖用户目录。review.md 成为 /review;子目录命名空间化(git/commit.md → /git:commit)。调用即渲染其正文并作为下一个用户回合发送。frontmatter 是可选的 --- 围栏块(简单 key: value 行,无 YAML 依赖,保持精简),识别 description 与 argument-hint;正文替换 $ARGUMENTS(全部参数、空格连接)、$1…$N(位置参数,缺失为空)、$$(字面 $)。加载是纯函数(command.Load(dirs...))且有测试;畸形文件被跳过而非致命。
---
description: Review the staged diff
argument-hint: [focus-area]
---
Review the staged diff. Focus on $ARGUMENTS, list bugs with file:line.
  • MCP prompts(§6)以 /mcp__<server>__<prompt> 呈现。

10.2 CLI 模态/作曲器所有权

Bubble Tea 聊天 TUI 只有一个底部作曲器。斜杠命令覆盖层必须声明是否拥有键盘输入:模态覆盖层(/mcp、/resume、/rewind、批准提示、非键入 ask 选择卡)拥有导航/确认/取消键,打开时必须隐藏作曲器;输入型覆盖层(斜杠/@ 自动补全、ask 自由文本模式)挂在 textarea 上,必须保持作曲器可见。新 CLI 覆盖层必须更新 chat_tui.hideComposer() 并扩展布局测试,使 bottomRows() 覆盖 panel + status 或 panel + composer + status 两种形态,防止非活动聊天输入框渲染在模态面板之下。

10.3 @ 引用解析

聊天消息可内嵌 @ 引用;回合发送前每个引用被解析并前置为模型可读的带标签块。@<server>:<uri>(已连接 MCP 服务器)→ MCP 资源(resources/read),包装为 <resource ref="…">…</resource>;@<path>(其他情况)→ 本地文件或目录,但仅当路径真实存在于磁盘——这个存在性门是消歧器:普通 @提及或邮箱地址解析不到文件、保持字面文本。文件包装为 <file path="…">…</file>(大小上限,二进制文件注明而不倾倒);目录变为递归列表(深度优先,跳过 .git、node_modules 等常见噪声)。解析异步进行(脱离 TUI 事件循环),取失败以通知呈现但不阻塞回合;读取是用户发起的只读操作,不通过权限门。输入 / 或 @ 打开输入框上方的自动补全菜单;@ 菜单每次只导航一级目录(os.ReadDir,绝不做递归遍历——对巨型目录有界):目录项下钻、文件项完成、MCP 资源与顶级条目并排。

11. 子代理体系:档案、声明与依赖图

11.1 档案(profile)与显式 CLI 执行

子代理档案是带 runAs: subagent(桌面/CLI 编辑器管理的档案另加 invocation: manual)的 Skill。档案复用既有项目/全局 Skill 文件,不引入新状态格式或数据库;manual 调用把档案排除出固定 Skill 索引(模型无法隐式发现),显式 /<name> <task> 仍可用。无头契约明确:

  • reasonix subagent try <name> ... <task>:使用只读 Skill 运行器;
  • reasonix subagent run <name> ... <task>:使用正常权限与沙箱路径;
  • 普通 Controller.Run / reasonix run 不变,不把斜杠前缀输入重解释为子代理命令。

生效模型与 effort 优先级:per-profile agent.subagent_models / agent.subagent_efforts → 本次调用的 model/effort → 档案 frontmatter → agent.subagent_model / agent.subagent_effort → 执行器/默认模型配置。task 接受可选 profile 与 write_paths;fleet 在会话调度器下分发 2–64 个档案感知任务(agent.max_subagent_concurrency 默认 6;agent.max_parallel_writers 默认 3)。档案名在运行时从 Skill 存储解析,绝不进入工具 Schema 或父系统提示词。用户侧命令与文件格式契约见 docs/SUBAGENT_PROFILES.md。

档案描述的是工人,不是运行。委托是五个独立概念:档案规定工人如何思考、TaskSpec 规定本次调用要什么、CapabilityGrant 规定可触碰什么、ContextCapsule 规定从什么出发、SchedulerPolicy 规定何时运行。档案可携带能力上限(allowed-tools、read-only)但绝不携带每次调用值(max_turns、write_paths、重试或验证策略)。agent.ProfileFromSkill 是唯一收窄点;internal/runtime/delegation/profile_boundary_test.go 在任何加宽时失败。

11.2 关闭声明由宿主裁决

写子代理结束时调用 complete_subtask,提交 status、summary、所受 acceptance_criteria(每条附上运行过的命令或改过的路径)与遗留的 unresolved。提交的状态是声明而非裁决:父代理看到之前,宿主对照自己的回执核查每条引用——verification 标准必须命名宿主记录为已运行的命令,diff/files 必须命名宿主观察到写入或读取的路径,manual 注记绝不自我背书。回执无法背书的任何标准被降为 unsatisfied,持有一条未满足标准的报告不能保持 complete,降级会打印原因;宿主从不抬升状态。父代理依次收到:裁决后的状态与标准、子代理自己的散文、宿主对变更与运行的回执。

11.3 写声明是强制的,不是建议性的

声明 write_paths 是一个真值源,同时用于调度与执行。子代理声明显式路径后,宿主在运行前把其注册表绑定到声明:路径感知内建写工具(write_file、edit_file、multi_edit、move_file、notebook_edit、delete_range、delete_symbol)拒绝声明外的任何参数路径(move_file 两端都查);路径在符号链接解析后与最深现存祖先比较,.. 遍历或声明内符号链接都不能把写入洗出声明;bash 仅在 OS 沙箱能把写根重绑到声明时保留,否则整体从子代理注册表移除;MCP 走 use_capability,在解析时(任何 MCP 进程运行前)拒绝每个未证明只读的目标;宿主无法路径作用域的写工具被丢弃;运行后宿主对比记录变更与声明,把声明外路径作为子代理宿主回执报告给父代理。

省略 write_paths 不是无作用域写工具:该运行声明整个工作区,因而与每个其他写声明串行化。声明路径才买来并行;代价是在 OS 沙箱无法强制写根的宿主机上失去 bash。

11.4 子代理上下文继承是显式的

子代理不隐式继承任何东西。它精确收到:系统提示词(DefaultTaskSystemPrompt / DefaultReadOnlyTaskSystemPrompt 或档案正文,别无其他组合)、工作区根(首回合 <workspace-context>)、任务文本(用户回合本身)、完成契约(追加到写者任务回合)、委托指导(嵌套子代理新会话的 <subagent-context>)、计划模式标记与推理/响应语言(设置了时)、先前转录(仅经 continue_from / fork_from)、依赖项最终答案(仅声明 depends_on 的 fleet 项首回合的 <upstream-results>)。

构造上不继承:REASONIX.md、AGENTS.md、CLAUDE.md、项目与全局记忆(记忆队列被禁用,子代理也无法记录记忆)、父对话、当前 Goal、规划器输出、未声明依赖的兄弟结果。必须传给子代理的约束今天只能放在档案正文、任务文本或 depends_on 边上——没有环境通道。

每次运行都在转录 sidecar 记录 ContextCapsule:工作区、系统提示词来源与哈希、已解析工具作用域与 Schema 哈希、模型与 effort、父会话与工具调用 id、任何恢复的转录、以及 inherited 块(其字段除 upstream 外全为 false)。capsuleHash 是其稳定身份——"为什么这个评审没看到那条约束"从记录得到答案;行为不同的两次运行可 diff 而非猜测。胶囊只持引用与摘要,绝不复制父上下文——这使委托廉价、子代理前缀可缓存。

11.5 Fleet:一个小依赖图

fleet 项可声明 id 与 depends_on。这是全部图词汇:无条件、无表达式、无动态扇出。示例:

research ──▶ implement backend ──┐
        └──▶ implement frontend ─┴──▶ integration test ──▶ review

id 默认 1 基位置;重复 id、未声明的 id、自环或环在 preflight 失败,无法完成的 fleet 绝不启动。依赖完成即运行;相互无排序的项在会话调度器下并行。依赖是图的属性而非任务的:它们活在 fleet 计划中、绝不进入 TaskSpec——这阻止 depends_on 变成工作流语言的第一个关键字。

边携带依赖的答案而非仅顺序:依赖项以每个已完成依赖的最终答案在 <upstream-results> 块中开场(按 depends_on 顺序、共享一个字节预算,宽扇入不淹没开场回合、单依赖不被饿死)。边作为 ContextRequest.Upstream 传输,绝不进任务文本(任务文本必须保持交付分类所评判的原始目标)。图在唯一该放宽处放宽写声明 preflight:只有可同时运行的项需要不相交 write_paths;implement → review 对被边串行化、可共享路径——扁平 fleet 表达不了这一点。

失败处理只有一个旋钮:失败或跳过的任务总是跳过整条下游分支;独立分支继续,除非 fail_fast(停止启动新任务,运行中的任务留给它完成——写者绝不半途被弃)。失败带身份而非仅消息:失败项在其状态旁报告 reason=——provider.transient(后续尝试可清除)、provider.rejected(重试无用)、context.exhausted(输入超出窗口);它杀死的分支继承该原因。分类读取错误已携带的身份(*provider.APIError 状态、ErrCompactionRequired、被中断的流),绝不读消息。到达终点的运行无论多少项失败都不答错误:聚合报告它们,以多少任务被回答为头。context.Canceled 保留给真正被中断的调用。

节点可让位给引用:adopt_ref 命名一个已完成子代理,其答案取代该节点位置——什么都不执行、不声明 write_paths、不与活跃写者冲突,其依赖以该答案经普通依赖边开场。preflight 用与 read_subagent_result 相同的门证明(已完成、在本对话血统与工作区内)。宿主不裁决采纳答案是否仍有效——结构里没有任何东西说明工作区在其下是否已变动,内核不读结构的判断就不做。配合 list_subagents,这是被重启中断的 fleet 的恢复路径:枚举子代理、以已完结节点采纳重新签发同一图、只为剩余部分付费。

无动态扇出是被度量过的排除而非口味:一个把某条目提示映射到另一条目提交主体的 for_each 曾被构建并发布在表面之后,然后与它取代的调用对比度量——同一图、一个任务处理所有主体。跨三个 fixture、二十八个对真实 provider 的分级运行,两臂每次求解,而映射为此多花 70%–98% 的 token(每个精确置换 p ≤ 0.03)。第三个 fixture 被构建为映射应赢的案例(十个近同代码模块、答案即十个独立推导之和),它在那里也没赢。原因:映射出的子代理为每个主体付一次完整子代理固定成本(前缀、推理、关闭契约),而带工具带的单子代理根本不必持有主体——逐个读取,事实可提取时就 grep。代码与其测试套件已移除,历史保留它们。

11.6 一个子代理构造原语

生成子代理的 API 很多(task、read_only_task、fleet、parallel_tasks、run_skill、/<profile>、reasonix subagent run|try、桌面预览),其后的执行原语必须保持唯一。每个入口点把请求编译成 ProfileExecSpec 交给 TaskTool.RunProfileSpec——唯一解析深度、工具作用域、权限、沙箱、写声明、调度器槽位、MCP 前端、转录与胶囊、证据账本与完成契约的地方。这是安全边界,不是风格偏好:安全边界若分散在多个构造路径,只需被遗忘一次——过去的回归(预览路径构建未受约束的文件工具、档案编辑器保存时丢弃 read-only)都是少了一个入口点的一层。不持久化转录的入口点用 ContextRequest.Ephemeral 声明,而非自建会话。internal/runtime/agent/spawn_boundary_test.go 枚举仍直接调用低层运行器的文件,任何新增都失败;剩余入口(internal/assembly/boot、internal/frontend/cli/review.go、desktop/subagents_app.go)是已知债务而非先例。

11.7 MCP 并发:只读不是无状态

子代理共享一个会话 Host 及其连接,各自保留自己的 use_capability 前端与账本。对 stdio 服务器意味着共享一个进程、因此共享其会话状态。只读不意味无状态:浏览器服务器开页、选标签、滚动——每个工具都可诚实声明只读(什么都没碰文件系统),两个子代理并发调用会在双方都看不见的状态上交错;写声明无济于事(无物可声明)。因此配置的服务器携带并发策略:

[[mcp.servers]]
name = "browser"
concurrency = "serial"   # parallel (default) | serial

serial 表示运行时绝不在整个会话同时对该服务器跑两个调用(无论哪个子代理发起)。门在共享运行时上——被交错的进程恰在该作用域共享;等待中的调用仍尊重自己的取消。名字像已知有状态服务器的(browser、playwright、puppeteer、chrome、chromium、selenium)默认 serial;显式配置总是赢;其余保持 parallel。这是刻意保守的第一版:每服务器一个策略而非每能力;per-tool parallel_safe / exclusive 提示与显式 concurrency_key 分组是后续细化。

11.8 度量委托是否划算

reasonix run --json 在既有 token、缓存、成本、时长总计旁发射每运行委托计数器:

计数器回答的问题
subagent_runs、subagent_nested_runs实际运行了哪种形态,而非配置了哪种
tool_calls − subagent_tool_calls父代理与子代理的工作拆分
subagent_mutations、duplicate_work_paths两个子代理是否重做了同一文件
completion_reports、completions_prose_only运行有多少以可核查声明结束
false_completions、criterion_downgrades宿主拒绝背书的声明
write_scope_violations逃出声明范围的写入

--ablate subagent 移除 task、read_only_task、fleet、parallel_tasks,但运行仍可通过 runAs=subagent 档案 Skill 委托——把该臂读作"无 task-tool 委托"而非"单 Agent",并用 subagent_runs 看实际发生什么。嵌套深度是 agent.max_subagent_depth。false_completions 是最重要的计数器——它来自 §11.2 的裁决,衡量宿主拒绝的声明而非评审者观点,是区分"fleet 更快完成"与"fleet 自称完成"的唯一个数。要对照实测噪声底线读这些数字:同一任务同一臂跑两次,每任务 token 使用中位移动 19%、最高 54%,而该实验中整臂差是 2.5%——单次运行证明不了任何关于委托的事,效应必须越过方差才算效应。

规范的度量记录(一个模型、四个任务形态,各对比中性提示与强制委托孪生):三处独立模块的一行修复花费 3.8 倍 token;24 文件搜索 1.5 倍 token、2.2 倍墙钟;36 文件三包迁移 2.6 倍 token、4.1 倍墙钟;三个真正异构分支(理论上最优形态)三次重复 2.4 倍 token、3.7 倍墙钟;成功率处处 100%,强制臂散布约为中性臂两倍——委托也买来方差。27 个实测子代理运行平均 134k token 是 9.3 次模型调用累计的提示 token(同一约 14k 上下文每次重发),非 134k 新素材;约 90% 缓存命中下每个子代理真实价格平均 ¥0.017。

为何委托罕见也从同一批运行可答:33 次可用委托的运行中 15% 委托,bash 以 10:1 压过每类委托调用。基础系统提示词从不提委托,每次提及都在技能索引里且每条都是刹车("重度路径……仅在任务真正需要上下文密集工作时,不在弱相关上");task 工具描述只说工具做什么、永不说何时伸手。模型已有更便宜的并行——一个往返里多个工具调用、不复制上下文。度量表明刹车是正确的默认;缺口是无人识别委托能付钱的罕见案例。强制不补齐缺口:强制 fleet 运行中父代理在派发前已在自己推理中完成全部三个修复,子代理重读代码去应用父代理已推导的编辑——委托移动了打字,没移动思考。一个假设仍未证伪而非被反驳:父代理真的被其读到的东西伤害时,委托的隔离应当付钱——把工作区 compact_ratio 钉到 0.5% 仍产生零次压缩,因为 Agent 靠写脚本而非阅读保持会话小。迁移案例最有教益:放任时 Agent 读单个文件、写脚本、28 秒改 108 个调用点;拆三包后没有分支能看到解决全部三者的变换——看似并行形态的任务不是拆分更便宜的证据。

12. 数据模型与配置

12.1 核心数据类型

规范 §4 定义(internal/contract/provider/provider.go 有同源实现):

type Role string
const (RoleSystem Role = "system"; RoleUser Role = "user"
       RoleAssistant Role = "assistant"; RoleTool Role = "tool")

type Message struct {
    Role       Role       `json:"role"`
    Content    string     `json:"content,omitempty"`
    ToolCalls  []ToolCall `json:"tool_calls,omitempty"`
    ToolCallID string     `json:"tool_call_id,omitempty"`
    Name       string     `json:"name,omitempty"`
}

type ToolCall   struct { ID, Name, Arguments string }              // Arguments: raw JSON
type ToolSchema struct { Name, Description string; Parameters json.RawMessage }
type Request    struct { Messages []Message; Tools []ToolSchema; Temperature float64; MaxTokens int }

type ChunkType int
const (ChunkText ChunkType = iota; ChunkToolCall; ChunkDone; ChunkError)

type Chunk struct {
    Type     ChunkType
    Text     string    // ChunkText
    ToolCall *ToolCall // ChunkToolCall
    Err      error     // ChunkError
}

值得注意:源码中的 Message 已演进出更多字段(RawContent、ReasoningContent、ResponsesItems、ToolExecution、HostAuthored 等),ModelMessages 会在发送前剥离宿主本地元数据,保证 provider 序列化与缓存哈希稳定——这正是 §8 缓存优先架构的数据面。

12.2 TOML 配置全解

解析顺序:flag > 项目 ./reasonix.toml > 用户配置文件 > 内建默认。自 Reasonix v1.8.1 起,用户配置在 macOS/Linux 位于 ~/.reasonix/config.toml、Windows 位于 %AppData%\reasonix\config.toml(迁移与数据路径见 docs/CONFIG_PATHS.md)。标记为 user/global only 的字段不被项目 reasonix.toml 覆盖。Provider 条目用 api_key_env 命名密钥;保存的密钥值存在于 Reasonix 全局 <Reasonix home>/.env(CLI 与桌面共享);项目 .env、home .env、继承的 shell 环境变量、遗留凭据与 OS keyring 不是 provider 密钥的运行时回退。项目 .env 仍为 MCP/插件设置的、工作区作用域的非 provider ${VAR} 展开供料,不引入 provider 密钥或 Reasonix 控制变量。

完整示例(摘编自规范 §5,可直接对照 reasonix.example.toml):

default_model = "deepseek"   # provider name (→ its default model) or "provider/model"
# language    = "zh"                # ui language tag; empty = auto-detect from $LANG / $REASONIX_LANG

[ui]
# shortcut_layout = "desktop"       # classic|desktop; compatibility setting
# cursor_shape = "bar"              # CLI/TUI textarea cursor: underline|block|bar
show_turn_usage = false              # hide per-request token/cost receipts in the TUI; default true

[agent]
system_prompt = "You are Reasonix, a coding agent..."  # or system_prompt_file = "..."
temperature       = 0.0
reasoning_language = "auto"       # visible reasoning text: auto|zh|en
# plan_mode_read_only_commands = ["gh issue view"]   # legacy compatibility only; Plan bash uses Permissions
# planner_model = "deepseek-pro"   # optional: two-model collaboration (low-frequency planner)
# subagent_model = "deepseek-pro"   # optional default for runAs=subagent skills
# subagent_effort = "high"           # optional default reasoning effort for subagents
# subagent_models = { review = "deepseek-pro", security_review = "deepseek-pro" }
# subagent_efforts = { review = "max", security_review = "high" }

# A vendor endpoint exposing several models under one base_url/key.
[[providers]]
name           = "deepseek"
kind           = "anthropic"
base_url       = "https://api.deepseek.com/anthropic"
# request_url  = "https://proxy.example.com/anthropic/v1/messages" # optional exact provider request URL
# models_url   = "https://proxy.example.com/v1/models"             # optional model discovery URL
models         = ["deepseek-flash", "deepseek-v4-pro"]
default        = "deepseek-flash"   # optional; defaults to models[0]
api_key_env    = "DEEPSEEK_API_KEY"
web_search     = true
context_window = 1000000   # tokens; harness compacts older history near this limit (0 disables)
# max_output_tokens = 0              # recommended: automatic (DeepSeek default high → ~64K)
# max_output_tokens = 32768          # ordinary coding / cost control
# max_output_tokens = 65536          # heavy reasoning / long tool loops
# max_output_tokens = 131072         # only after repeated finish_reason=length
# max_output_tokens never changes compact_ratio
# idle_timeout_seconds = 300         # per-provider stream idle watchdog; default 300s (a silent pre-header wait is retried once, so up to ~2×)
# model_overrides = { "deepseek-flash" = { context_window = 1000000, max_output_tokens = 32768 } }

[environment]
enabled = true   # inject a stable startup summary of OS, shell, and common tool versions
offline = false  # set true when outbound network access is unavailable; prevents futile retries

[tools]
enabled = []   # omit/empty = all built-ins
bash_timeout_seconds = 120   # foreground safety cap; set 0 for no tool-local cap
protect_changed_files = true   # refuse a whole-file write over a change made since the agent last read or wrote the file
browser_tools = true   # built-in browser tools; the browser starts on first use
mcp_startup_timeout_seconds = 30   # background initialize + tools/list safety cap
mcp_call_timeout_seconds = 300   # default MCP call safety cap; plugin/tool overrides may raise it

[tools.shell]
prefer = "auto"   # auto (default) | bash | powershell | pwsh — force the shell tool's interpreter
# path = "C:\\Program Files\\PowerShell\\7\\pwsh.exe"   # explicit executable for the chosen shell
# [tools.shell.env]   # extra variables every bash command inherits (user-global; a project reasonix.toml cannot set this)
# BASH_ENV = "~/.reasonix/bashsandbox.rc"

[skills]
# paths = ["~/my-skills", "../shared/skills"]   # extra custom skill roots
# excluded_paths = ["~/.agents/skills"]         # hide convention roots without deleting folders
# disabled_skills = ["review"]                  # hidden from prompt, slash invocation, and skill tools
# suppress_warnings = true                      # quiet non-fatal skill loading warnings; doctor still reports issues

[permissions]
mode  = "ask"                              # writer fallback when no rule matches: ask|allow|deny
deny  = ["Bash(rm -rf*)", "Bash(git push*)"]   # hard-blocked in every mode
allow = ["Bash(go test:*)", "Bash(git status:*)"]  # never prompted
ask   = []                                 # force a prompt even if otherwise allowed

[sandbox]
# workspace_root = ""          # file-writers confined here; empty = cwd
# allow_write    = ["/tmp"]    # extra dirs write_file/edit_file/multi_edit/move_file may modify
# forbid_read    = ["${HOME}/.ssh"]   # paths read/list/search tools and sandboxed bash may not inspect

[serve]
# auth_mode = "token"          # token (default)|password|none; none still requires the launch token for changes
# token = ""                   # optional fixed token; empty token mode generates one at startup
# password_hash = ""           # bcrypt hash generated with reasonix serve --hash-password --password '...'
# behind_proxy = false         # trust X-Forwarded-* only behind a trusted reverse proxy

[[plugins]]
name    = "example"            # type defaults to "stdio"
command = "reasonix-plugin-example"
args    = []
# env   = { FOO = "bar" }
# startup_timeout_seconds = 60         # initialize + tools/list cap; 0 = global/default cap
# call_timeout_seconds = 600            # per-server MCP call timeout; 0 = global/default cap
# tool_timeout_seconds = { "generate_video" = 1800 }   # raw MCP tool names
# [[plugins]]                   # a remote MCP server over Streamable HTTP
# name    = "stripe"
# type    = "http"             # "stdio" (default) | "http" | "sse"
# url     = "https://mcp.stripe.com"
# headers = { Authorization = "Bearer ${STRIPE_KEY}" }   # ${VAR} / ${VAR:-default} expanded

要点注解:

  • reasonix setup 会写出这份默认配置,让 CLI 开箱可用。
  • [ui].cursor_shape 归一化为 underline/block/bar,空或未知值回退 bar;只作用于 Bubble Tea CLI/TUI textarea,桌面与浏览器输入保持平台原生光标行为。
  • [serve] 控制 reasonix serve 的 HTTP 浏览器前端:未设 auth_mode 时每次启动生成一个 token;none 是显式退出(Serve 前端由其他认证托管时用),读保持开放,而每个状态改变请求(含批准)仍需启动 token,否则以 auth.launch_token_required 拒绝。token 写入 <Reasonix home>/remote/ 下 0600 文件而非终端;[serve] 只从用户配置读取。密码模式要求启动 --password 或存储的 bcrypt password_hash(reasonix serve --hash-password --password '...' 生成);behind_proxy 必须保持 false,除非服务器确实位于拥有 X-Forwarded-For / X-Forwarded-Proto 头的可信反向代理之后。
  • MCP 服务器也可在项目根 .mcp.json 用 Claude Code 的精确 mcpServers 模式声明(command/args/env、type/url/headers、${VAR} 展开),在 TOML 文件之后读取并合并进 [[plugins]];重名时 reasonix.toml 赢(更显式的 Reasonix 专属源):
{ "mcpServers": {
  "stripe": { "type": "http", "url": "https://mcp.stripe.com",
              "headers": { "Authorization": "Bearer ${STRIPE_KEY}" } }
} }
  • MCP 启动与单次工具调用生命周期分离:调用者短暂等待冷启动,共享的 launch/授权/initialize/tools/list 序列可在后台继续至 mcp_startup_timeout_seconds(默认 30);每服务器 startup_timeout_seconds 覆盖该上限;MCP 调用超时在连接就绪后才开始计时。

12.3 Sandbox:权限之下的执行层

[sandbox] 是权限(策略)之下的执行层。Phase 0 把文件写入内建(write_file、edit_file、multi_edit、move_file)限制在 workspace_root(默认 cwd)、Reasonix 用户配置目录与 allow_write 内:写入目标解析为绝对、无符号链接路径(符号链接目录或 .. 无法隧道逃逸),落在所有根之外即被拒,错误回喂模型。限制默认开启(root = cwd),编辑留在项目内而 Agent 仍可更新自己的全局配置。forbid_read 列出 Agent 不应读取/列出/搜索的文件或目录,条目支持 ${VAR} / ${VAR:-default} 展开,应为绝对路径或用 ${HOME}(如 ${HOME}/.ssh)。bash 在 OS 沙箱可用时默认也被囚禁([sandbox] bash = "enforce":macOS 用 Seatbelt、Linux 用 bubblewrap):每条命令只允许写相同根加平台特定命令临时/缓存根、拒绝 forbid_read 下的读取、仅当 network = true 时可达网络。

Windows 状态:Reasonix 不在 Windows 上提供 OS 级 Bash 沙箱,生效模式固定为 off;含 bash = "enforce" 的旧配置可读但解析为 off,reasonix doctor 报告被忽略的值,桌面控件只读——Bash 在 Windows 上不受限运行,进程内文件工具继续强制 workspace_root / allow_write / forbid_read。无 OS 沙箱时 bash = "enforce" 拒绝 bash 执行而非无限制运行;安装平台沙箱后端(Linux 的 bubblewrap/bwrap、macOS 的 sandbox-exec),或显式设 [sandbox] bash = "off" 恢复 pre-1.16 的无限制 shell 行为。逃逸提示与更广的 OS 支持是 §15 路线图的 Phase 1 余项。

12.4 进度租约与遗留键迁移

执行器在 todo 激活期间跟踪自适应进度租约:新完成、唯一成功读取、命令或变更续约;精确重复不续约。8 个无进展工具调用轮后宿主追加一次性重新评估提示(Goal 模式下强制重计划并继续,Goal 外可能结束当前尝试)。串行契约分等级但保持单一 in_progress 规则。退役的 [agent].max_steps 与 planner_max_steps 键仍可解析(升级兼容),但被一次性迁移忽略并移除(internal/contract/config/retired_keys.go);CLI --max-steps 旗标仍是单次执行的显式控制。

13. 错误处理、代码风格与分发

错误处理(规范 §6):库代码用 fmt.Errorf("...: %w", err) 包装并返回,绝不打印或调用 os.Exit;只有 cli / main 决定退出码与面向用户的消息;工具执行错误回喂模型而非致命;网络层应对 429 / 5xx 做有界指数退避(接口已预留,实现可跟进)。

代码风格(规范 §7):gofmt + go vet 必须干净;包名小写;导出标识符有文档;注释解释 为什么 而非 是什么;不做过早泛化,宁清晰直接。

分发(规范 §8,与 Makefile 对应):

CGO_ENABLED=0 go build -ldflags "-s -w -X main.version=$(VERSION)" -o reasonix ./cmd/reasonix

交叉矩阵:darwin|linux|windows × amd64|arm64;版本经 ldflags 注入(git describe --tags --always);安装途径为预编译二进制 / go install / 未来的 brew tap。

14. 路线图(当前范围之外)

  • 沙箱 Phase 1:为 bash 提供 OS 级囚禁,使命令(不只 Phase 0 的文件写入内建)被限制在工作区内。macOS Seatbelt 与 Linux bubblewrap 已随附、可用时默认开启。余项:逃逸提示——检测沙箱不可用或沙箱拒绝失败,提供显式的、权限门控的无限制重跑(reasonix run 中命令直接失败、模型自适应),完成"盒内允许、边缘提示"模型。
  • MCP 长尾(刻意推迟):headersHelper 认证、剩余 .mcp.json 作用域(local / user——project 已发货)、工具搜索推迟、list_changed 实时更新、channels / elicitation / roots、提供 provider 而非仅工具的插件。
  • Anthropic 原生 provider kind(原生提示缓存控制),证明注册表可泛化到一种线上格式之外。
  • "总是允许"持久化:把学到的规则写回项目配置;reasonix run 的每会话权限覆盖旗标。

15. 结语:契约先行,能力由配置与插件供给

回看整份规范,Reasonix 的架构内核可以浓缩为三句话:核心只认接口(Provider 与 Tool 双注册表,internal/contract/provider/provider.go 与 internal/contract/tool/tool.go 是它们的源码家);能力来自配置与插件(TOML 声明 provider、插件声明 MCP 服务器、命令目录声明斜杠命令,reasonix setup 开箱即用);安全与上下文是有边界的工程决策(权限层按调用门控、沙箱按平台分级、上下文压缩只有一个自动阈值且缓存优先、子代理上下文显式继承、写声明强制、fleet 依赖图极简)。规范原文明确要求"先改契约,再改代码"——这份文档因此既是架构师的实现合同,也是使用者的行为手册:从 compact_ratio 到 concurrency = "serial"、从 Bash(go test:*) 到 adopt_ref,每一个旋钮都有确定的语义、优先级与边界,而对应的 Go 源码与测试让这些语义可以被直接验证。

  • 人工智能
  • AI 应用
  • AI Agent
  • 代码智能体
  • CLI

【免费下载链接】DeepSeek-Reasonix

A reliable coding agent for complex software engineering tasks.

项目地址: https://gitcode.com/GitHub_Trending/de/DeepSeek-Reasonix
点击查看 免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值