关于 Estigia
Estigia 在现有 AI 编码 Agent 下方执行工作流检查。它记录问题认领、核对后续写入,并将评审结论绑定到确切提交,在不可逆的 Git 操作前重新验证。
主要功能
- 01记录问题认领
- 02按认领核对写入
- 03评审绑定到确切字节
- 04推送与合并门禁
- 05Agent 配置与状态工具
适用场景
- 01防止处理问题时修改无关文件
- 02提交变化后使评审结论失效
- 03推送前检查问题跟踪器状态
- 04为编码 Agent 安装生命周期门禁
MCP 相关
Estigia 包含 MCP 服务,可通过 `npx -y @asanabrial/estigia mcp` 启动。其容器镜像通过 stdio 运行该服务,但不含 `git` 或 `gh`。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
estigIA — AI 编码 Agent 的工作流权威
你的 Agent 说它已经认领了问题。它说评审已通过。它说分支是最新的。
Estigia 会把这些从你信以为真的话,变成经过核验的事实:认领记录在本地工作流存储中(~/.estigia/state.db),此后 Agent 所做的每次写入都会根据这条记录进行衡量,而评审结论则绑定到精确的字节——因此,之后的推送会使其失效,也不需要任何人记得去留意。
一个二进制文件。它运行在你已有的任意 Agent 之下,不会运行你的模型。唯一例外是评审:launch_review_input 会针对某个审查视角启动评审 CLI,充当反驳者或验证者:它会使用在 PATH 中找到的第一个 claude 或 codex,且没有设置项可供选择。手动调用 record_lens_report、record_refutation 和 record_validator_result 会被拒绝,并返回 recorded-by-capture-only;只有捕获流程可以写入这些记录。
誓言之河。Agent 作出的承诺,会绑定到精确的字节。
Leteo 是遗忘之河。Estigia 是誓言之河——众神以河水起誓,违背誓言便要在奥林匹斯山外受罚九年。同一个冥界中的两条河,职责恰好相反:一条让你遗忘,另一条让你信守承诺。
使用感受
大多数时候,什么也不会发生。你不会调用 Estigia,你的 Agent 也不会,除了认领它即将处理的问题之外。之后它就一直在后台运行,只有在它说“不”时,你才会注意到它。
未认领任何问题的运行完全不受它管辖,行为与以前完全相同——每个会话都设门禁,那是锁,不是工作流。Agent 认领问题 12 的那一刻,它写入的每个文件都会根据这条认领进行衡量;每个无法撤销的步骤——推送、合并、打标签、发布——都会在执行前重新核验存储中的认领记录。
当它拒绝操作时,会说明读取了什么,以及该怎么处理:
$ git push origin HEAD
estigia: git push: gh issue view failed (1): none of the git remotes ... (read-failed)
[world-action] the tracker could not be read; write nothing and retry the read
error: failed to push some refs
无法读取跟踪器和你不得推送是不同的答复。Estigia 不会把前者变成后者。这种区分正是大部分设计的核心。
它不是什么
它不是记忆(Leteo 负责这个),也不是规划。 它不会运行你的工作模型(它启动的唯一进程是审查器),不会定义你的工具,也不会管理你的上下文——它位于你现有 Agent 的下层,只负责一件事:工作流权限。任何不符合这句话的功能都不会加入。
安装
# Linux and macOS
curl -fsSL https://raw.githubusercontent.com/asanabrial/estigia/main/scripts/install.sh | sh
# Windows
irm https://raw.githubusercontent.com/asanabrial/estigia/main/scripts/install.ps1 | iex
就是这些。无需预先安装任何东西——无论是 Rust、Python,还是运行时。压缩包是
预构建二进制文件,每个脚本都会将下载内容与旁边发布的校验和进行核对,并拒绝安装任何校验不符的文件。二进制文件会放在 ~/.local/bin,或在 Windows 上放到 PATH 中;ESTIGIA_INSTALL_DIR 可更改安装位置,ESTIGIA_VERSION 可指定非最新版本的标签。
如果机器上已经有 Node,npm 也能使用同一安装程序:
npx -y @asanabrial/estigia setup --all
该软件包不包含二进制文件。它会获取同一个压缩包,核对同一个 .sha256,以相同方式记录候选版本,将其放入同一目录并运行;关于它为何不会从 npm 缓存运行 Estigia,请参阅
docs/lifecycle.md。MCP 注册表中的条目启动的也是同一个软件包,命令为
npx -y @asanabrial/estigia mcp。其中的软件包名称是 mcp-name: io.github.asanabrial/estigia。这里将其直接写出而非放在注释中,是因为注册表会读取 crate 的 README 中这一行来接受
cargo 软件包,而 crates.io 会删除 HTML 注释。
如果要自行构建(这是唯一需要 Rust 的方式):
cargo install estigia # the published crate
cargo install --git https://github.com/asanabrial/estigia # main as it stands
之后,源码构建会一直被视为源码构建:它没有安装程序记录,因此,在向你的 Agent 写入内容的命令执行前,必须先指定 --allow-source-build。这是有意为之——请参阅
docs/lifecycle.md,了解该记录能证明什么,以及其证明范围为何比听起来更窄。
如果你正在开发 Estigia 本身,scripts/build-install.sh(macOS 和 Linux)或
scripts/build-install.ps1(Windows)可以在代码检出目录中完成整个流程:要求 rustup 和
cargo 已在 PATH 中;如有需要,通过现有的 rustup 安装固定版本的工具链;编译锁定的发布版本;通过 Cargo 重新安装;为每个 Agent 运行 setup;最后运行 status 和 doctor。请先关闭正在使用 Estigia 的 Agent——Windows 无法替换正在运行的二进制文件,任何平台也都可能拒绝替换被 Agent 锁定的文件。
目前没有发布容器镜像。根目录中的 Dockerfile 使用固定版本的工具链构建镜像,其中只包含服务:通过 stdio 运行 estigia mcp,不包含 git 和 gh,因此,在容器中,任何访问跟踪器的工具都会拒绝执行。该镜像供需要通过运行容器来检查服务的目录使用。
在你的 Agent 中完成设置
安装二进制文件本身不会带来任何变化。这一步会让 Estigia 介入 Agent 的工作流程:
estigia tui # the setup screen: a list of agents, each opening onto its own settings; `s` shows what it will write; `u` uninstalls an installed agent, or the push guard from the menu, after listing what would go
estigia setup claude-code # or just one, without the screen
estigia setup --all # or all of them
无论何时,都可以先检查再行动:
estigia setup claude-code --dry-run # exactly what would change, writes nothing
estigia status # what is installed, where, and what runs still hold
estigia sessions # the sessions seen on this machine, the issue each holds, and which went silent
estigia sessions send <run-id> "..." # leave a message in another session's mailbox; its next hook point delivers it
estigia doctor # skill, skill-assets, gh, remote, guard, prune, pi-subagents, lockfiles, permission — read-only
estigia prune # branches and checkouts no delivery owns; --confirm removes
estigia changes # uncommitted work in every checkout, held or orphan — read-only
estigia flush --status # the GitHub writes a closed git operation window queued; plain `flush` sends them
宿主的自动权限模式会批准宿主自身的提示,但不会批准 Estigia 的决定:无论 Claude Code 使用
bypassPermissions 和 auto,OpenCode 使用 --auto,还是 Codex 使用 never 审批策略,拒绝仍然是拒绝,问题仍然是问题,因为门控会在询问宿主权限层之前作出判断,而且门控中的任何内容都不会读取该模式。测试会验证启用或未启用该模式时,钩子和插件的回答保持一致;estigia doctor 会显示 Claude Code、Codex、OpenCode 和 Pi 各自配置的模式。哪些内容经过实际运行测试,哪些没有,请参阅 docs/honesty.md。
setup 会安装三项核心内容:Agent 读取的技能、写入该 Agent 始终加载的指令文件中的简短指令,以及让权限成为硬性机制而非建议的生命周期钩子。--skill-only 会在指令之前停止。Estigia 支持八种 Agent;钩子会安装到所有具有可拒绝事件的 Agent 中——共五种,钩子门禁可读取五种方言;另有三种仅用于已退役的宿主。每次 Claude Code 设置还会安装两个稳定且不活动的审阅者定义:review-blind 和 review-blind-sealed。它们由同一个模板渲染两次,是 Judgment Day 的两位裁判,仅此而已。两份裁判定义都带有 model: inherit,并附有一段要求判决说明依据的文字。Evidence standard 决定未封存的授权——reading 下为只读,measuring 下则额外仅授予 shell;封存授权固定不变,只包含一个工具,即网页搜索,不允许读取仓库。除非启动时指定不可变目标——一个提交、差异或提交范围——以及评判标准,否则两者都不会生效。每次启动 Judgment Day 时,编排器都会传入实际生效的 judge 模型。普通审阅在任何级别都不会授权裁判;Judgment Day 是 gentle-ai v4 的可选功能,也是裁判唯一存在的场景——参见
judgment day。设置过程绝不会创建带编号的裁判文件。
普通审阅的审阅者不会被安装。review_tier 授权的封存审阅视角——review-lens、review-risk、review-readability、review-reliability 和 review-readback——以及反驳者 review-refuter 和验证器 review-validator,都由 launch_review_input 从嵌入二进制文件的模板启动,不带任何工具,也绝不会读取 ~/.claude/agents。设置过程会撤销早期版本在这七个名称之一之下安装的副本:由 Estigia 所有且未被编辑的副本会被删除;已编辑的副本会保留,并解除对其路径的所有权;Estigia 从未记录过的副本则保持原样。这些名称仍为保留名称,因此项目定义不能取而代之;手动 Task 启动其中任何一个都会被拒绝,并返回 reviewer-launched-by-the-binary——不存在手动启动途径。另外两份定义 developer.md 和 analyst.md,仅会在 Model routing 指定其角色时安装;当对应条目被移除时也会撤销,因为带有工具授权的定义就是权限,而升级不能授予任何人未曾要求的权限。它们会安装到 Claude Code 和 OpenCode 中,并分别采用各自宿主的方言:Claude Code 使用逗号分隔的 tools: 行和 effort:;OpenCode 使用 permission: 映射和文件名,不包含 effort: 字段。默认情况下路由为空;指定角色便会安装对应定义,而其旁边的模型字段只表示该定义使用的模型。该行的第三个值 by task 不指定任何角色,也不会安装任何内容:工作的性质决定模型和投入程度,启动 Agent 会记录自己的选择——正如 docs/honesty.md 所述,这是声明而非检查。在这些路径下,若定义并非由 Estigia 编写,就会被拒绝而非替换:首先在预检阶段、任何内容写入之前拒绝;然后在实际写入前再次检查,以拦截其间新出现的定义——此时技能和指令已经写入其后。直接通过 config set 和
config edit 写入不会修改外部定义。
每个保留角色都由操作者掌控。在 Claude Code 当前的 Agent 或旧版 Task 启动任何保留角色之前,Estigia 会从启动工作目录一路检查到第一个 .git 仓库根目录,并递归拒绝任何项目 .claude/agents 定义中使用了相同文件名或在有效 YAML 中声明了相同名称的情况。对于 judge,随后会证明规范的用户级定义是唯一具有该身份的用户级定义,并且与 Estigia 能生成的某种渲染结果相符——而不是嵌入的字节本身,因为嵌入内容会是未经渲染的模板;对于七个通过二进制文件启动的名称,项目目录检查就是全部验证,随后无论用户目录中有什么都会拒绝启动。安装前会对两个 judge 执行相同的递归用户目录唯一性预检,在 reviewer 边界处重新验证,并在不替换另一参与方抢先创建的路径的前提下创建新的 reviewer。证据缺失、不可读、已更改、重复或含糊不清时都会拒绝。普通 Agent 仍按项目优先的规则解析。被拒绝或无法验证的 reviewer 不计入评审;回退方案只能是单独的会话或持久化评审交接,绝不能缩减评审规模或将评审串行化。
这里唯一需要有人参与的流程是安装界面,它最后会打印出无需人工参与即可复现相同结果的命令:
the same result without the questions:
estigia setup claude-code
estigia config set "Model routing" "developer=inherit" --agent claude-code
普通评审会按层级,为同一不可变目标和标准分配视角:low 为零个,medium 为一个,high 为四个。每个视角的严重问题都由所有层级通用的台账裁定——没有拨盘、没有多数决,也没有一致性策略——完整说明见 skill/policies/blind-judges.md。传输层会记录一个计算得出的精确收据判定;它不能证明这些视角确实运行过。Judgment Day 仅在明确请求时运行,会取代该目标的视角评审,且不会签发收据。
已停用的 judge 小组耗费了多少成本,相关历史数据见
docs/review-cost.md;各层级视角评审的成本目前还没有测量。
无论是谁再次执行此流程——在另一台机器上、通过脚本,或在容器镜像中——都需要一种无需提问的明确写法,而刚刚成功运行的流程是了解这种写法的唯一可靠来源。这些命令会生成与通过提问得到的结果逐字节相同的结果,并且有测试会同时运行这两种方式进行验证。
status 会分别报告两件事,因为Agent 是否被告知 Estigia 的存在与Estigia 是否能够阻止它是两个不同的问题;当有人查看一次写入操作却没有声明的运行记录时,需要知道的是后者。工具部分可能永久不可用:无法确定服务器注册位置的适配器会打印 tools off 及原因;仅仅尚未注册的适配器则会打印修复命令:
claude-code configured
harness: gate on, tools on
codex configured
harness: gate off, tools on
卸载
estigia uninstall --all --dry-run # what would go, changes nothing
estigia uninstall --all # carry it out
第一条命令会告诉你第二条命令将执行什么操作。若要退出一个 Agent,同时保留其他 Agent:
estigia uninstall claude-code
还有推送防护,它位于仓库中,而不是某个 Agent 中:
estigia guard --uninstall
卸载只会移除 Estigia 创建的内容,不会移除其他内容。 不会移除其他工具的区块、你自己的备注、Estigia 覆写之前就已存在的文件,也不会移除后来被你编辑过的 Estigia 自有文件;这类文件会被保留,并单独列出,而不会悄悄删除。之所以能分辨这些内容,是因为安装时会记录创建了哪些文件,以及写入内容的摘要;之后,磁盘上的内容本身已无法区分它们。
有两项内容会特意保留。~/.estigia/ 属于你,而不属于任何 Agent——其中包含界面语言和你的检出列表;移除一个 Agent 时,不应连它也一起移除。检出目录在 .git/estigia/ 中的自有条目也会保留:卸载命令针对的是 Agent,而不是仓库;擅自访问你当前所在的检出目录,正是这种设计要避免的问题。只有 estigia config forget 会移除这些条目,并会说明移除了哪个文件。
关于全部内容,包括 REPLACE 和 OVERWRITE 的含义,以及为何使用这两个词,请参阅 docs/what-it-writes.md。
为问题立誓
estigia claim 12 --run-id claude-0198fe1c --horizon 2026-08-01T18:00Z
estigia gate Edit --run-id claude-0198fe1c --input '{"file_path":"src/x.rs"}'
estigia release --run-id claude-0198fe1c
Agent 会改用 MCP 工具执行此操作——你很少需要亲自输入这些命令。运行 ID 根据 Agent 的会话生成,并由 SessionStart 报告,因此 Agent 已经知道该 ID。系统会要求提供该 ID,而不是猜测,因为在错误的运行 ID 下记录的声明永远无法与门禁匹配;主动询问总比悄无声息地出错好。release 还会移除本次运行创建的隔离检出目录及其中检出的本地分支,前提是跟踪器已确认交付完成,且没有未发布的内容;它会在回复中说明保留了什么及其原因。它也会删除本次运行记录的所有远程分支头,因为 start_branch 关联的分支不一定就是已发布的分支,而 GitHub 的自动删除功能只会删除后者。estigia prune 会清理没有交付成果归属的内容:它会列出已合并或已关闭的分支和检出目录,保留任何由有效声明持有或包含远程没有的提交的内容,并且只有在指定 --confirm 时才会移除其余内容。它还会查找干净的孤立检出目录:其远程分支已不存在,但其中的提交仍存在于另一个远程引用中;修剪规则介绍了这条额外的清理路径及其保守的归属检查。
是 harness,而非文档
Estigia 是一个 harness:它不会要求 Agent 遵循工作流,而是提供工具。
skill/ 下的约定是 Agent 可以阅读、也可以忽略的文本——这意味着“机械检测伪证”只是一个美好愿望。没有任何东西能阻止一次运行在没有声明的情况下写入内容,或根据一个已不存在的 SHA 所绑定的裁决交付结果。harness 解决了第一个问题,并在交付时绑定第二个问题,但不会在交付那一刻重新检查——具体界限见 docs/honesty.md:
agent (Claude Code)
│
├─ tool: Edit / Write / Bash
│ └─ PreToolUse → estigia hook pre-tool-use
│ └─ verify_claim against the tracker → deny
│
├─ turn about to end
│ └─ Stop → estigia hook stop
│ └─ while the run still holds its issue → block, so the turn continues
│
└─ tool: mcp__estigia__claim / verify_claim / transition / …
└─ estigia mcp → answered in this process, against `gh`
git
└─ pre-push → estigia hook pre-push
└─ refuses a push no live claim authorises, whoever typed it
两部分缺一不可。工具让 Agent 能够真正执行工作流——提供三十六项带有模式定义的操作,而不是手动拼接 gh 命令并听天由命。若约定要求某个名称,但没有工具执行它,就会说明未提供该名称的原因,而不是将其列出;这样,工具表面就不会提供无人响应的调用。交付路径所需的唯一一次强制推送也包含在其中,因此它会像其他写入一样经过裁定,而不是在 shell 中直接输入。门控则负责处理 Agent 试图绕过这些工具时的情况。
Estigia 不会运行你的工作模型——它只会通过 launch_review_input 启动审查者——而且它本身不持有任何权限。本地工作流存储 ~/.estigia/state.db 才是权威来源:声明、状态、回执、发现、裁决、支出、任务和 blocked-until 条款都以行的形式存储其中;同一台机器上的两次运行会在同一事务中相互裁定。跟踪系统保存问题及其文本、拉取请求、分支、标签,以及本地提交后写入的受派人信息,但这些都只是投影,永远不参与裁决——代价是该存储只是在一台机器上的一份副本,正如 docs/honesty.md 所述。运行中断后,不会留下任何权威来源:存储中关于某个检出所受约束的记录,就是支配它的全部内容,无论写入该记录的运行是否仍在,能否被询问。会话结束后留下的本地运行指针则是另一回事——它是一条机器可读的记录,绝非权威来源;estigia doctor 的 stale-run-pointer 行以及预推送钩子 estigia guard 安装的、与存储记录进行核对的机制都会运行,estigia gate 也会触及同一机制,正是为了处理已中断运行留下的虚假指针。estigia doctor 的 prune 行及其对应的 estigia prune 命令,则用于处理运行中断后留下的另一部分:没有任何存活声明拥有的分支和检出;它们只会在有人确认后才会列出并移除。
没有 Agent 能绕过的关卡
Agent 钩子只会为安装它的 Agent 触发。pre-push 钩子位于 git 之下:
estigia guard # into this repository
estigia guard --uninstall # removes only a hook Estigia wrote
$ git push origin HEAD
estigia: git push: gh issue view failed (1): none of the git remotes ... (read-failed)
[world-action] the tracker could not be read; write nothing and retry the read
error: failed to push some refs
它会在最后一刻而非最初时刻拦截操作,而且对每个 Agent 和每个人都有效。PreToolUse 关卡仍然最重要——它会在推送前数小时拦截认领竞争失败的情况——但只有一个 Agent 配备了它。Claude Code 还会注册一个 Stop 钩子:只要运行仍持有其 issue,这一轮就会继续,而不会结束。成功读取后,该钩子在每个续期窗口内最多读取一次认领状态(读取失败或状态未知时会再次读取),因此,连续运行的多个短轮次不会耗尽 GitHub API 配额。OpenCode 也能做到这一点,具体取决于 TUI 编辑的 Loop plugin 设置:启用后,estigia setup opencode 会写入一个可选插件;只要 Estigia 表示运行仍在进行、还有其他 issue 可认领,或 blocked 项记录的退出条件已满足,插件就会继续会话——并保留目标及停止原因,让已停止的循环一目了然,且 /flow-status 会报告其状态;Agent 可以通过 estigia goal stop-after 要求它在当前任务结束后停止,此设置会被仍有效的认领忽略,而认领释放后则会生效。如果某一轮结束时运行仍持有认领,就会继续执行,并用一句话说明 issue 和运行,而不会再次重复整个目标。如果某个会话结束这一轮只是为了等待正在运行的子会话,就会等待而非重新提示,并在子会话结束时继续(详情)。它的命令是 /flow-loop 和 /flow-status,这些名称不会与通用的 /goal 和 /loop 冲突。Pi 将它作为自有扩展 estigia-loop.ts 接入;当 Pi 报告会话已结束时,它会继续会话并注册相同的两个命令;其目标保存在内存中,因此重启 Pi 后会丢失。Codex 通过同一行下的 Stop 和 SessionStart 钩子条目获得相同的循环,每个条目都需要在 config.toml 中设为可信,且不提供 --wait、--max 或 /flow-status。Claude Code 也能使用它:在其 commands/ 目录下放置两个斜杠命令,并添加一个 Stop 条目,同时提供上限设置和 /flow-status。默认的 off 不会写入任何内容。另有一个独立的 Goal loop plugin 行,默认也为 off;它会为 OpenCode 写入通用的 goal-loop.js,或为 Pi 写入 estigia-goal-loop.ts,提供 /goal <condition> 和 /loop [interval] <prompt>,但不关联任何 Estigia 命令:模型会在每第三轮和达到上限的那一轮评估对话记录,而 docs/honesty.md 说明了为什么这绝不能作为证据。estigia status 会在每个 Agent 的运行环境行旁报告其续行词。
誓言一经立下,便不可撤回
尚未认领任何事项的运行不受 Estigia 管辖,但下文的仓库 Unclaimed write policy 另有规定时除外:默认情况下,对于此类运行发起且门禁能够限制在检出目录内的仓库写入,会先交由用户处理;无法限制在检出目录内的写入则会被允许,并在下次会话开始时报告检出目录已被修改;若该设置为 off,则此类运行进行的所有写入都会不受干预地通过。每个会话都设置门禁,那是锁,不是工作流权限。
唯一的例外,由操作员主动启用。 在安装了 estigia guard 的 pre-push hook 的检出目录中,不持有认领事项的运行会在所有分支上被拒绝执行任何提交(git commit、cherry-pick、revert、merge、rebase、am)以及任何 git push,并返回拒绝原因 unclaimed-commit;git 运行该 hook 时,pre-push hook 也会拒绝同一推送。解决方法是认领一个 issue,然后通过 start_branch 开始工作;认领事项会覆盖其隔离检出目录。读取不受门禁限制,编辑和测试仅受下文的 Unclaimed write policy 限制;未安装 guard 的检出目录不会拒绝提交,没有跟踪器传输或未安装契约的检出目录也是如此。
另一个例外由仓库设置决定,并且默认启用。 Unclaimed write policy(ask、off 或 required,仅通过 <repo>/.git/estigia/estigia.local.md 设置)管辖在配置了跟踪器的检出目录中、不持有认领事项的运行。required 会拒绝所有无法放在检出目录外的仓库写入,并返回 issue-required;默认值 ask 会在 Claude Code 上对能够限制在检出目录内的写入询问用户,在无法询问的主机上拒绝该写入;无法限制在检出目录内的写入则会被允许,并在下次会话开始时列出检出目录中已修改的路径;off 则采用上文所述的行为。关于其适用范围,请参阅
docs/configuration.md;关于其不适用的范围,请参阅
docs/honesty.md。
运行一旦立下誓言——estigia claim 12——它进行的每次仓库写入都会根据该认领事项进行检查,而每个不可逆边界都会重新向存储验证该认领事项。价值就在这里。技能自身账本中的事件 I07,并非“Agent 未认领就进行了写入”;而是一次运行在认领竞争中落后了五秒,33 秒后才收到通知,随后又继续工作了 48 分钟,因为它的循环中没有任何步骤再次读取认领状态。那次运行已经立下誓言。门禁会阻止这种情况。
$ estigia gate Edit --run-id claude-0198fe1c --input '{"file_path":"src/x.rs"}'
estigia: src/x.rs: gh issue view failed (1): no git remotes found (read-failed)
[world-action] the tracker could not be read; write nothing and retry the read
常规写入以及 Claude Code 的 Stop hook,可以使用两分钟续期窗口内的应答——这正是 SKILL.md 已经要求的检查频率。不可逆边界则绝不例外:git push、git merge、git pull、创建或删除标签时的 git tag(列出标签属于读取)、gh pr merge、
gh pr create、gh release create 和 gh release edit 都会始终重新验证。
门禁只会读取认领状态,因此才能置于每次编辑的关键路径上:出错的代价只会是拒绝写入,绝不会损害 issue。测试会在结构层面对此进行强制验证。
工具
共三十六项操作,由表驱动。其中三十四项各自映射到契约要求绑定必须提供的一项操作;
send_message 和 list_sessions 从本地存储返回结果,并非绑定操作。tools/list 包含架构和
readOnlyHint,因此 Agent 无需阅读本文档,就能判断某项操作是读取还是写入。十项交付工具(claim、start_branch、publish_review、
launch_review_input、collect_review_input、record_computed_verdict、release_ci、release、
transition、review_status)带有 _meta["anthropic/alwaysLoad"],因此会延迟加载 MCP 工具的宿主会在启动时加载它们;每项工具都接受 compact,启用后会从回答中省略
build(docs/review-cost.md):
read verify_claim (issue, run_id, expect_state)
read list_state (state, run_id, identities)
read check_closing_keywords (issue)
read review_status (issue) # carries `lane` and `corrections`
read review_read (issue, view, epoch, id, with_text, candidate, request, path, side, pr, head, base, digest, phase, worktree)
read review_tier (base, head, parent_head, clearing_finding)
read target (base, recorded_base, worktree, native_start, manifest)
read tasks (issue) # the task checklist, from the local store
read list_sessions ([status, runtime, run, session_id, repo_dir, limit, cursor]) # bounded page; no options answers the entries `estigia sessions --json` prints
WRITE claim (issue, run_id, horizon[, takeover, target_operation, force, reason_file])
WRITE handoff (issue, run_id, to) # records it locally; projects `dev:<to>` with read-back
WRITE heartbeat (issue, run_id, expect_state, body_file)
WRITE transition (issue, to, run_id)
WRITE comment (issue, body_file)
WRITE blocked_until (issue, run_id, until_issue, until_state) # local store only: no tracker write
WRITE task (issue, run_id, id, text, route) # local store only: no tracker write
WRITE send_message (run_id, to, repo_dir, text) # local store only; the person is asked first, over MCP elicitation
WRITE release (issue, run_id)
WRITE ensure_states ()
WRITE flush ([read_only]) # sends what a closed git operation window queued, oldest first; refuses while it is closed
WRITE start_branch (issue, run_id, branch, base)
WRITE judge_workspaces (issue, run_id, epoch, pr, head, base, digest, count) # bare per-judge directories, Judgment Day's
WRITE assist (issue, run_id, branch, base) # assisted repositories only
WRITE draft_pr (run_id, title, body_file) # assisted repositories only
WRITE create (identity, title, body_file, priority, domain, runtime, run_id) # identity and clauses go to the local store, not the body; optional `repo` (owner/name) files in another repository, unclaimed
WRITE publish_review (issue, run_id, branch, base, pr_title, pr_body_file)
WRITE republish_review (issue, run_id, branch, base, pr_title, pr_body_file) # leased force-push
WRITE handoff_review (issue, run_id, target_operation, epoch, pr, head, base, digest, blocker, discharger)
WRITE record_review_finding (issue, run_id, reviewer, epoch, pr, head, base, digest, id, class, evidence, impact)
WRITE record_disposition (issue, run_id, epoch, pr, head, base, digest, dispositions)
WRITE launch_review_input (issue, run_id, name) # starts the lens, refuter or validator a review_status collect offers, detached
WRITE collect_review_input (issue, run_id, name[, wait_seconds]) # records what the launched reviewer answered
WRITE record_computed_verdict (issue, run_id, epoch, pr, head, base, digest)
WRITE escalate_review (issue, run_id, epoch, pr, head, base, digest, escalated_to, reason)
WRITE record_spend (issue, run_id, phase, epoch, pr, head, base, digest, conditions, input, tokens, seconds, contexts)
WRITE release_ci (issue, run_id, epoch, pr, head, base, digest)
三十六项工具。契约列出每个绑定都必须映射的十七项操作。工具整合的操作仍由保留下来的名称提供其能力:claim 通过 takeover 执行 reclaim,list_state 通过 identities 返回身份信息,review_status 包含发布通道和修正次数,review_read 读取过去由六项工具读取的内容,review_tier 限定修复范围,target 则对基准变动进行分类。契约要求但没有任何工具执行的名称,会在 NOT_EXPOSED 中注明,并附上原因以及 bindings/ 中负责该操作的章节;这些名称不会对外公布,因此接口不会提供无人响应的调用。接缝测试会确保两份列表保持一致:既未公开也未声明的操作会导致构建失败。
MCP 服务是手写的,而非基于 rmcp 构建。同一个二进制文件还会响应每次编辑的 PreToolUse 钩子,因此,每个进程都启动一次 tokio 运行时,只为通过管道传输几行 JSON,却要付出数千次的开销。保持 crate 同步执行,才能让这个门禁足够轻量。代价列在 UNIMPLEMENTED 中:没有资源、没有提示词、没有采样,除 send_message 的一个问题外没有引导;除 initialized 外没有通知,并且一次只处理一个请求。
配置
二十九项类型化设置详见 docs/configuration.md,按主题分组——权限、仓库与跟踪器、交付、门禁限制、审查与变更、Agent 运行时、本机——且 estigia config list、设置界面和参考文档都按同一顺序展示。每一行都注明其作用范围:设置值由谁决定,是此 Agent、此仓库、每个工作区检出,还是此机器。读取该表即可生成有效配置;如果无法生成,则会明确指出可以写入什么内容作为替代。
棘轮机制
只有运行某条命令能够解除阻断时,消息才可以提及该命令。 指出死路不如什么都不说。
每项拒绝都会给出类型化代码、世界状态发生了什么变化、能否安全重放,以及解决方案。解决方案要么是可运行的调用——由 every_command_a_rejection_names_parses 使用真实解析器进行校验;要么是封闭词汇表中的原因,用来说明为何无法诚实地提供命令:operator-knowledge、world-action、human-authority。
$ estigia setup
estigia: setup needs to know which agent to configure (agent-not-named)
run: estigia setup --all # or one of: agents, claude-code, codex, opencode, pi, cursor, zcode, deepseek-harness
以 2026-07-31 的 issue-flow 为基准:87 处拒绝,没有一处指向可执行的后续操作。棘轮机制只有从零开始才能启动。
本工具不衡量的内容
详见专门的文档:docs/honesty.md。文档中的每一项都说明了此 crate 不检查什么,并注明发现该问题的度量方式。本仓库其他位置新增的任何声明都要先对照该文件进行评估,而 docs/README.md 则列出了该文档所属集合的目录。
布局
src/harness/ the gate, the run pointer, the transport, the lifecycle protocol, doctor
src/harness/mcp/ the workflow tools and the stdio JSON-RPC they arrive on
src/setup/plugin.rs the OpenCode gate, the Pi gate extension, the two review relays and the optional loop plugin and Pi loop extension, because those hosts read a module rather than a settings line
src/setup/pi_prompts/ the two Pi prompt templates, `claim.md` and `review-status.md`, embedded and written unchanged
src/setup/pi_look.js the Pi look extension, embedded and written unchanged
src/setup/pi_theme.json the Pi theme `estigia`, embedded and written unchanged
src/setup/goal_loop.js the generic `/goal` and `/loop` plugin, embedded and written unchanged
build.rs derives the transport's refusal vocabulary from the transport itself, and embeds the build's commit and dirtiness
src/fence.rs a marked block in somebody else's file — invariant three, written once
src/config/ typed model, the settings table as data, which markers the table lives in
src/setup/ agent adapters, companions, dry-run, exact inverse
src/skill.rs the embedded markdown and its install/status/uninstall
src/outcome.rs the failure taxonomy: what landed, what may be replayed, what to run
src/cli/ dispatch, and the only place a refusal becomes an exit code
skill/ SKILL.md, bindings/, policies/, protocols/, references/, assets/, agents/ — not compiled
docs/ the reference the sections above point at, one question each, review cost included — map: docs/README.md
scripts/ the two installers a release is fetched with, and the Windows source reinstall
assets/ branding, referenced from this file
有一份文档放在代码旁边,而不在代码里面,它回答的问题与本文不同。AGENTS.md——CLAUDE.md 链接到的文件——说明了 Agent 在这里如何工作,以及它对文档负有什么责任。这个仓库的规划材料放在议题中,而这个工作区里没有任何规划材料。
src/transport/ 是传输层,也是唯一的传输层。它最初是 skill/scripts/github.py,并随负载一同发布,因为 bindings/github.md 将每项可逆操作都路由到它——没有它的负载,其绑定的第一条命令就不存在。现在,绑定列出了 Estigia 自己的工具,操作由当前进程处理,脚本也已删除:代码树中没有 .py 文件,没有任何路径需要 Python,CI 中也没有安装 Python 的步骤。如今,与它交锋所体现的价值,已在上文独立一节的诚实性约定中说明。
完全不需要任何解释器曾是本句的说法;当一个测试开始执行这个 crate 生成的 OpenCode 插件,而不再只是读取它的源码时,这句话就不再成立了。该插件是 JavaScript;用第二种语言编写却从不运行的门禁,只不过是没有任何交锋检验的一条规则副本。因此,node 已列入 AGENTS.md 的验证清单,两个工作流也都会安装它,而不再相信运行器镜像中自带了它;docs/honesty.md 则记录了这些测试衡量的内容。此后,原来的测试又增加了两个同伴,因此现在共有三个测试需要该运行时:原测试会通过两代宿主驱动 shell 命令的运行位置;the_plugin_hands_the_gate_the_directory_every_write_lands_in 会驱动每个受门禁控制的写入工具在 OpenCode 1 的 file_path 和 OpenCode 2 的 path 上的写入位置;the_loop_plugin_continues_while_estigia_says_there_is_work 会驱动可选循环插件的退出行为。
一个 crate。首日就拆成五个,会在任何边界值得拆分之前先把边界拆开;只有当一个边界值得拥有自己的位置时,才会将它拆分出来。
退出码
四个,而且没有一个表示“出了问题”:
| 代码 | 含义 |
|---|---|
0 | 命令完成了它所述的操作 |
1 | 命令拒绝执行;拒绝信息会说明是否已经写入任何内容 |
2 | 世界可能已经发生变化,但此进程无法判断 |
3 | 无法读取调用内容:没有尝试执行任何操作,也没有做出任何决定 |
第四种退出码独立于 1,因为两者的区别正是钩子要读取状态的全部原因。拒绝是一项决定——世界说了不——而这个 crate 编写的每个脚本都会传递该状态,这正是阻止推送的机制。用法错误不是决定,传递它只会让输入命令的人无法采取行动,却仍阻止推送。这一点经过了实际验证:一个构建遗留的 pre-push 钩子,其 hook 命令多接受一个标志时,会以 1 退出,而 git push 返回 error: unexpected argument --from-a-newer-build,并拒绝推送。
只要第四种退出码存在,本节就一直写着三种;这正是 CLAUDE.md 第一条规则禁止的偏差——而它所描述的读者是脚本。every_exit_code_the_readme_lists_is_one_this_binary_has 现在会将表格与枚举逐项核对。
测试框架会把传输层自身的五种代码映射到这一分类,而不是将它们合并。1 表示控制面已被读取并答复停止——不要重放。2 表示操作员的配置有误——其中包含一条可以修复问题的命令。3 表示控制面没有给出任何答复——重试读取,绝不可将其视为停手指令或放行许可。4 和 5 表示写入可能已完成,但没有人观察到——去查明情况。
这些区别体现在拒绝信息中,便于查阅。issue-flow 的 5 同时表示“没有写入任何内容”和“写入了某些内容,但我不知道是什么”;自信地报告最接近的已命名状态,就是在撒谎。
许可证
MIT。源自其他 MIT 许可项目的文件,其声明见 THIRD_PARTY_NOTICES.md。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。