关于 AdversaryGate
AdversaryGate 是用于验证 AI 编码 Agent 补丁的失败关闭式关卡。它比较基线与补丁的测试结果,对变更行进行变异测试,并返回 MERGE、BLOCK 或 INCONCLUSIVE;可量化验证的范围是 Python 与 pytest 项目。
主要功能
- 01在基线和补丁上运行测试
- 02对变更行进行变异测试
- 03评估覆盖率证据
- 04返回三种明确判定
- 05提供 CLI、GitHub Action 和 MCP 服务
适用场景
- 01在 CI 中审核 AI 生成的拉取请求
- 02检查测试是否覆盖变更代码
- 03在合并前阻止回归
- 04标记证据不足的补丁
MCP 相关
AdversaryGate 提供 MCP 服务,Agent 可通过它调用补丁验证关卡。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
uvx adversary-gate-mcpADVERSARY_GATE_BASE_REFBaseline git ref (e.g. origin/main). Unset, the agent may pick it, and whoever picks the baseline picks the oracleADVERSARY_GATE_REQUIRE_PINNED_BASESet to 1 to refuse any run without ADVERSARY_GATE_BASE_REF, so the agent can never choose the baseline (the oracle)ADVERSARY_GATE_PYTHONInterpreter that runs the tests; must be one the agent cannot write toADVERSARY_GATE_POLICYExtra gate flags set by the operator; evidence flags are refused本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
2收录的工具列表 · 更新于 1 天前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| repo | string | 是 | |
| claims | any | 否 | |
| base_ref | string | 否 | |
| pytest_args | any | 否 | |
| timeout_seconds | integer | 否 |
详细介绍
AdversaryGate
AI 使用率高 ≠ 信心高。 Agent 编码流水线的成本 不在于模型的智能,而在于流水线的自我欺骗。
这是一个针对 AI 编码 Agent 编写的补丁的失败关闭式验证门。它会在基线和补丁上运行测试,仅对补丁修改的行进行变异测试,并给出 MERGE、BLOCK 或 INCONCLUSIVE 的结果——其中不确定性是一等结果,而不是默默放行。提供 CLI、GitHub Action 和 MCP 服务。
目前仅支持 Python。 它用于评估 Python/pytest 仓库;对于其他技术栈,它会通过 --test-command 运行测试套件,但结果为 INCONCLUSIVE,绝不会是 MERGE——请参见语言支持。
pip install adversary-gate
🎬 看它如何做出判断(60 秒)

B 和 C 是同一个补丁。 代码相同、测试相同、执行过程相同。唯一的区别是,C 提供了证据(--diff + --coverage-json),而 B 没有。
| 补丁 | 覆盖率证据 | 判定 | 退出码 | |
|---|---|---|---|---|
| A | 破坏测试 | 标记为 untrusted | BLOCK | 1 |
| B | 正常 | 无——从未测量 | INCONCLUSIVE | 2 |
| C | 正常 | 已测量,两个制品均有 SHA-256 哈希 | MERGE | 0 |
中间这一行就是整个产品的核心。没有生成的测量结果就不算测量,因此无法达到门槛;而一个无人能重新推导的数字,只是贴着虚假精度标签的观点。
python3 demo/demo.py # runs the three scenarios; exits non-zero if one regresses
python3 demo/render_gif.py # rebuilds demo/demo.gif from the captured frames
demo/demo.py 是一个验收测试,不是截图:它会构建测试夹具,以子进程方式运行真实 CLI,读取真实退出码,并在三种判定中任何一种发生变化时失败。其证据制品已提交至 demo/out/。
INCONCLUSIVE不是错误。 它表示验证门在说 “我没有测量这个”,退出码为 2,这样 CI 会将其视为“暂不合并”,而不是“通过”。从INCONCLUSIVE到MERGE的单步路径,请参见diff_coverage如何获取其值。
🚀 快速开始(CLI 和 GitHub Action)
一分钟内完成首次运行
在一个 Git 仓库的根目录中运行;Agent 的改动应基于 main(新文件需先跟踪:对它们运行 git add -N),并已执行 pip install adversary-gate coverage:
BASE=main # the commit before the agent's change
W=$(mktemp -d)
git worktree add -q "$W/before" "$BASE" # the baseline, exactly as committed
git diff --no-ext-diff "$BASE" > "$W/changes.diff" # baseline -> your working tree
coverage run -m pytest -q && coverage json -q -o "$W/coverage.json"
adversary-gate --baseline "$W/before" --patch . \
--claim tests/test_shop.py::test_discount_never_negative \
--diff "$W/changes.diff" --coverage-json "$W/coverage.json" > "$W/answer.json"
echo "exit $?" # 0 MERGE, 1 BLOCK, 2 INCONCLUSIVE
python -c "import json,sys; a=json.load(open(sys.argv[1])); print(a['decision'], '-', a['reason'])" "$W/answer.json"
git worktree remove --force "$W/before"
将 --claim 替换为能证明修复有效的测试(path::test)。对于正确的单行修复,首次运行预期会得到以下结果:
exit 2
inconclusive - every claim held and the fix is proven, but suite strength's lower bound is 0.38,
below the 0.75 floor (1/1 mutants killed): the change is too small to measure at this floor,
which takes at least 5 killed mutants; see next_steps
这不是失败:修复确实已得到证明,而验证门也明确表示,它无法衡量如此小的改动中测试的强度。请参见小型改动。
命令行用法
adversary-gate \
--baseline /path/to/before \
--patch /path/to/after \
--test-path tests/test_auth.py \
--test-id test_token_expiry \
--diff changes.diff \
--coverage-json coverage.json \
--model "${AGENT_MODEL_NAME}" \
--evidence-log evidence.jsonl \
--report
例如,在你自己的流水线中生成这两个制品:
git diff --no-ext-diff baseline...patch > changes.diff
coverage json -o coverage.json # after: coverage run -m pytest
哪些测试?列出若干个,或让覆盖率来决定
一次运行可以验证任意数量的声明,最终判定涵盖所有声明
(一条 REFUTED 就会阻止合并;一条 UNVERIFIED 就会使结果变为 INCONCLUSIVE):
adversary-gate ... --test-path tests/test_auth.py --test-id test_expiry --test-id test_refresh
adversary-gate ... --claim tests/test_auth.py::test_expiry --claim tests/test_api.py::TestLogin::test_ok
或者一个也不列出,让门禁验证执行过变更源代码行的测试——从 coverage.py 的逐测试上下文中读取,而不是猜测:
printf '[run]\ndynamic_context = test_function\n' > contexts.coveragerc
coverage run --rcfile=contexts.coveragerc -m pytest
coverage json --show-contexts -o coverage.json
adversary-gate --baseline before --patch after \
--diff changes.diff --coverage-json coverage.json --discover-claims
补丁改写过的测试文件仍会被选中,并使用基线中的副本进行判定(即上文所述的基线预言机);它会列在 discovery 下(rewritten_test_files_judged_by_baseline),与任何无法解析为节点 ID 的项目一起列出,并显示上限(--max-claims,默认为 10)以及因此删去了多少项。从 3.0.9 起,被删去的测试不会再被搁置不判定:当完整测试套件关闭(--full-suite-path "")时,如果保留的声明合并后本应通过,补丁会改判为 INCONCLUSIVE,并说明应提高上限或运行测试套件。记录时未包含上下文的报告属于用法错误(退出码 3),而不是“没有测试”。
项目的解释器、资源限制与环境
adversary-gate ... --python .venv/bin/python \
--timeout 120 --cpu-seconds 60 --memory 2G \
--pass-env PATH --env PYTEST_DISABLE_PLUGIN_AUTOLOAD=1
| 标志 | 默认值 | |
|---|---|---|
--python | 门禁自身使用的解释器 | pytest 运行所用的解释器——即安装了项目依赖的解释器 |
--timeout | 30 | 每项声明或变异运行的墙钟时间(秒);none 表示不设限 |
--cpu-seconds | 10 | 每项声明或变异运行的 CPU 时间(秒);none 表示不设限 |
--full-suite-timeout | 900 | 附带完整测试套件运行的墙钟时间(秒) |
--full-suite-cpu-seconds | none | 附带完整测试套件运行的 CPU 时间(秒) |
--full-suite-memory | 4G;Android 上为 none | 附带完整测试套件运行的地址空间 |
--memory | 512M;Android 上为 none | 每次运行的地址空间(K/M/G);none 表示不设限——否则 JVM 和 Node 无法启动 |
--pass-env NAME | — | 将变量复制到运行环境中(运行时从最小环境启动) |
--env NAME=VALUE | — | 设置一个变量 |
--serial-sides | 关闭 | 将检查的基线和补丁两侧依次运行,而非同时运行;并在声明和变异完成后运行完整测试套件,而非与声明并行运行;同时强制设置 --mutation-workers 1(适用于小型机器,以及共用固定 /tmp 路径或端口的测试)。无论采用哪种方式,变异都不会与测试套件同时运行(AG-043) |
--mutation-workers N | 1 | 同时运行的变异数,每个变异使用树的独立副本。仅适用于不会在仓库外共享任何资源的声明:两个运行若访问同一个 /tmp 路径、端口或测试数据库,其中一个就会失败,而失败的运行会被计为已杀死的变异(AG-043)。不能与 --census-min-mutants 一起使用 |
所有运行——声明、变异和附带的完整测试套件——都采用相同的设置,唯独时间和内存限制不同:完整测试套件使用独立限制,因为为单项测试设定的限制会导致两侧所有有实际规模的测试套件无法运行(超时,或 pytest 在 512M 地址空间下因 MemoryError 退出):这不会导致 MERGE(两侧都失败时无法验证),但本应由测试套件捕获的回归会从 BLOCK 变为 INCONCLUSIVE(AG-033)。输出会在 execution 下记录这些设置,其中只包含变量的名称,绝不包含其值。若运行因内存限制而被终止(MemoryError、Fatal process out of memory、Could not reserve enough space……),报告会将其标记为测试框架进程退出,而不是测试失败:这并不能说明补丁有问题。
不是 pytest 测试套件,或者要运行尚未审阅的补丁?
# 1. run your own command instead of pytest (any language, any runner)
adversary-gate --baseline before --patch after \
--test-path run_tests.sh --test-command "./run_tests.sh"
# 2. wrap every run in bubblewrap: no network, private PID//tmp,
# read-only system, only the repo writable
adversary-gate ... --sandbox bwrap
# 3. both together -- the usual shape for a patch from someone else
adversary-gate ... --test-command "make check" --sandbox bwrap \
--require-network-isolation
--test-command 无需 --test-id(没有可指定的节点 ID),而且除非传入 --full-suite-command,它也会用于附带的全套测试运行。
--sandbox bwrap 需要安装 bubblewrap,若未安装则会拒绝启动——请参阅
执行边界,了解它具体会和不会隔离什么。
CI 集成的退出代码:
0—MERGE:所有声明均已顺利执行,并达到覆盖率和套件强度下限。1—BLOCK:检测到回归或附带测试套件失败。2—INCONCLUSIVE:基础设施错误、缺少测试、缺少覆盖率证据或测试套件强度不足(阻止合并)。3—USAGE_ERROR:参数错误、JSON 格式错误,或未将覆盖率声明为不可信。
GitHub Action 集成(action.yml)
只需提供一个 ref。 base-sha 会推导基线、差异和覆盖率报告,因此你安装后首先看到的不会是 INCONCLUSIVE:
name: Verification Gate
on: [pull_request]
jobs:
verify-agent-patch:
runs-on: ubuntu-latest
steps:
# base-sha is a commit in history: this is not optional.
- uses: actions/checkout@v4
with:
fetch-depth: 0
# Your Python, your dependencies -- the tests run here.
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install -r requirements.txt pytest coverage
- name: Run AdversaryGate
uses: Sanflow10/adversary-gate@v3
with:
base-sha: ${{ github.event.pull_request.base.sha }}
evidence-log: 'evidence.jsonl'
无需指定测试名称:Action 会按测试记录覆盖率,并验证执行了拉取请求所更改代码行的测试。你可以使用 claims(每行一个 path::test),或使用 test-path + test-id(每行一个 id)自行指定;除非设置 discover-claims: 'true',否则两种方式都会关闭自动发现。
任何非零退出码都会使此步骤失败,这是安全的默认设置。 BLOCK(1)、INCONCLUSIVE(2)以及用法或测试框架错误(3/4)都会使检查变红。不要只添加 continue-on-error 就此作罢:这样会让 BLOCK 和其他结果一起通过。如果某些拉取请求本质上会得到 INCONCLUSIVE(例如对 C++ 或 SQL 的更改,门禁会运行但无法测量),请避免此步骤失败,改为根据 exit-code 输出进行分支判断(2.13.0 及更高版本):
- name: Run AdversaryGate
id: gate
uses: Sanflow10/adversary-gate@v3
continue-on-error: true
with:
base-sha: ${{ github.event.pull_request.base.sha }}
- name: Act on the decision
if: always()
env:
EXIT_CODE: ${{ steps.gate.outputs.exit-code }}
run: |
case "$EXIT_CODE" in
0) echo "MERGE" ;;
2) echo "::warning::INCONCLUSIVE: the gate could not measure this change; a human decides" ;;
1) echo "::error::BLOCK"; exit 1 ;;
*) echo "::error::the gate did not finish (exit '${EXIT_CODE}')"; exit 1 ;;
esac
请根据 exit-code 而不是 decision 进行分支判断:未打印判定结果的运行(退出码 3/4)仍会报告 decision: inconclusive,而空的 exit-code 表示 Action 在门禁运行前就已停止。只有在你确定门禁无法测量更改时才让 2 通过,例如在单独的工作流中使用 paths:;其他情况下请保留默认设置。
它会使用你的 Python 运行测试。 门禁本身运行在独立的私有 venv 中,使用自己的 Python 3.12;此步骤前后,你的作业所用的 python 不会改变。测试会使用 PATH 中第一个能够导入 pytest 的 python/python3 运行,也就是 setup-python 步骤放入其中的解释器;或者使用你通过 python: 指定的解释器。如果没有符合条件的解释器,测试会使用门禁自己的解释器运行,并在运行结果中说明。上文的 CLI 标志包括 timeout、cpu-seconds、memory、pass-env 和 env。
使用哪个 ref?
v3.0.17包含所有修复;发布它的 Release 工作流也会移动浮动的v3标签,该标签会跟随每个 3.x 版本。3.0.4 之前的版本不受支持。@main会跟随main,并获取之后合入的内容。对于任何重要用途,请固定为完整的提交 SHA(uses: Sanflow10/adversary-gate@<full-sha>),这样它就不会被移动。
各项内容的作用及来源:
| 构件 | 来源 |
|---|---|
baseline | <base-sha> 的树,通过私有索引(read-tree + checkout-index)获取——即提交时的每个文件,不包含工作树残留;不是 git archive,后者会应用仓库的导出属性(AG-044) |
diff | git diff --no-ext-diff <base-sha>...HEAD |
coverage-json | 在检出目录中、门禁自身的边界内运行的 coverage-command:sandbox 输入、最小环境加上 pass-env/env,以及仓库外一个全新且可写的目录(在 3.0.7 之前,它作为一个裸 shell 步骤运行,并使用作业的整个环境) |
patch | 检出目录本身 |
如果该 ref 不在你的本地历史记录中,Action 会以退出码 4 停止,并提示你设置 fetch-depth: 0,而不是使用它自行编造的基线进行测量。coverage-command 默认为 coverage run -m pytest + coverage json(发现 claims 时使用逐测试上下文),其中的 coverage 是你自己的解释器上的 coverage.py——如果缺失,会安装在它旁边,而不是安装到该解释器中。你可以覆盖此命令来指定自己的测试套件,或传入 coverage-json 来提供构件。
所有推导出的内容都相当于你可以手动运行的一条 git diff。 如果你希望自行组装内容——例如用于 monorepo、生成的差异,或来自非 coverage.py 工具的覆盖率数据——请改为显式传入路径:
- name: Collect coverage evidence
run: |
git diff --no-ext-diff ${{ github.event.pull_request.base.sha }}...HEAD > changes.diff
python -m pip install pytest coverage
coverage run -m pytest
coverage json -o coverage.json
- name: Run AdversaryGate
uses: Sanflow10/adversary-gate@main
with:
baseline: './baseline'
patch: './patch'
test-path: 'tests/test_token_expiry.py'
test-id: 'test_token_expiry'
diff: 'changes.diff'
coverage-json: 'coverage.json'
coverage-floor: '0.80'
suite-strength-floor: '0.75'
model: '${{ matrix.model }}'
evidence-log: 'evidence.jsonl'
如果没有 diff + coverage-json(且没有用于推导它们的 base-sha),Action 会报告 INCONCLUSIVE,因为没有覆盖率证据可以证明达到最低要求。如果你确实想直接声明该数值,请同时设置 coverage-ratio 和 coverage-source: 'untrusted'。
📦 安装
pip install adversary-gate
在信任 PyPI 之前,先检查它提供的内容 — pip index versions adversary-gate。3.0.4 之前的版本均不受支持:每个版本都缺少此后添加的 fail-open
修复(不应到达 MERGE 的补丁却到达了它)。推送签名标签时会向 PyPI 发布(Trusted Publishing,AG-008 已关闭),下方的 GitHub Release 也会包含相同的构建。其他所有方式也都可用,而且都不需要
PyPI:
# straight from the repository
pip install git+https://github.com/Sanflow10/adversary-gate.git
# or clone it
git clone https://github.com/Sanflow10/adversary-gate.git
cd adversary-gate && pip install .
# or don't install it at all
PYTHONPATH=src python3 -m adversary_gate --help
| 方式 | 跟随 | 需要 |
|---|---|---|
pip install git+https://…adversary-gate.git | main | 网络 |
git clone + pip install . | main | git |
PYTHONPATH=src python3 -m adversary_gate --help | main | 无 |
uses: Sanflow10/adversary-gate@main | main | GitHub Actions |
| GitHub Release wheel(下方) | 3.0.17 — 包含所有修复 | 只需要 pip |
pip install adversary-gate(PyPI) | PyPI 当前提供的版本 — 请先检查 | 网络 |
安装已发布的 wheel
git+… 始终跟随 main。若要安装固定版本的构建,有两种方式,目前两者包含相同的代码:
# PyPI -- 3.0.17. Versions before 3.0.4 are unsupported: each lacks fixes the
# CHANGELOG lists after it.
pip install adversary-gate==3.0.17
# GitHub Release -- 3.0.17: every fix in this document.
# The tag carries the "v", the filename does not.
pip install https://github.com/Sanflow10/adversary-gate/releases/download/v3.0.17/adversary_gate-3.0.17-py3-none-any.whl
第二种方式是普通的公开 URL — 不需要 PyPI、GitHub 登录或 git — 但必须存在该标签对应的 GitHub Release:如果返回 404,说明 v3.0.17 的 Release 尚未发布。发布分两步,按以下顺序进行,且只有第一步需要手动操作:
- 有人推送签名标签:
git tag -s vX.Y.Z -m "X.Y.Z"和git push origin vX.Y.Z。推送会启动 Publish to PyPI,它会构建该标签自身的代码树 — 核心包和 MCP 包adversary-gate-mcp— 并通过 Trusted Publishing 上传两者,因此每个文件都带有 PyPI 来源证明。若pyproject.toml中的版本与标签不符,它会拒绝该标签,并且不会读取任何 API 令牌。开始构建前,它会使用git verify-tag,依据仓库变量RELEASE_ALLOWED_SIGNERS中的密钥检查标签签名(allowed_signers行保存在代码树之外,避免推送携带自己的密钥):轻量标签、没有签名的附注标签、由其他密钥签署的标签,或未设置该变量时,都不会发布任何内容。 - Actions → Release → Run workflow。 若没有该标签,或签名检查不通过,它会拒绝运行;若 PyPI 缺少任一包,也会拒绝运行。之后它会运行完整测试套件,如果测试失败,则拒绝发布;然后构建 wheel 和 sdist,在干净的解释器中安装 wheel,将它们附加到 Release,并移动该 Release 主版本线的浮动标签(
v3)— 只向前移动,因此重新发布旧版本不会使标签倒退。
带标签运行 Publish to PyPI → Run workflow,即可完成中途停止的运行:已在 PyPI 上的文件会跳过,绝不会被替换。
在 3.0.3 之前,Release 工作流会使用工作流的令牌自行创建标签,并使用 API 令牌仅上传核心包。由该令牌触发的事件不会启动其他工作流,因此对于 3.0.2 和 3.0.3,PyPI 发布工作流从未运行:uvx adversary-gate-mcp 一直提供 3.0.0,核心文件没有来源证明,且这些标签均未签名。和所有 3.0.4 之前的版本一样,它们均不受支持。
PyPI
3.0.4 之前的版本均不受支持。本项目由此发展而来的审计针对 2.0.1 运行,该版本早于本文档中的所有修复:当时还没有 --diff 和 --coverage-json(AG-002)。
AG-008 已于 2026-10-04 关闭。 Trusted Publisher 已在 PyPI 项目设置中注册(Sanflow10 / adversary-gate / pypi-publish.yml,无环境),2.4.0 已通过它发布 — 使用 OIDC,无令牌 — 且 wheel 和 sdist 均附有 PyPI 来源证明。在此之前,每个标签的上传步骤都会失败,并显示 invalid-publisher: valid token, but no corresponding publisher,而 2.2.0 / 2.3.0 从未发布到 PyPI。
🤖 编码 Agent:Hermes(MCP)和 Jev(分类)
两种集成,一条规则:集成说什么都不能让门禁变得更宽松。
无法自我评分的 Agent — MCP 服务(Hermes,任意 MCP 客户端)
pip install "adversary-gate[mcp]" # in an environment the agent cannot write to
# or, without installing anything: uvx adversary-gate-mcp (the same server, as its own PyPI package)
adversary-gate-mcp # stdio MCP server
adversary-gate-mcp --print-hermes-skill # SKILL.md for ~/.hermes/skills/adversary-gate/
adversary-gate-mcp --print-hermes-config # the mcp_servers block for ~/.hermes/config.yaml
工具:verify_repo(repo, claims?, pytest_args?, base_ref?) 和 gate_policy()。verify_repo 根据基线检查仓库的工作树——无论是否已提交,均包括未跟踪的文件——它会将基线提交的树写入临时目录,每个文件都按已提交的状态写入(而不是使用 git archive,后者会应用仓库的导出属性 — AG-044),写入差异,运行带有逐测试上下文的 coverage.py(数据文件位于临时目录中,不会向仓库写入任何内容),在未指定声明时发现这些声明,并运行门禁。结果包含 decision、mergeable、fix_proven、每项声明的 oracle,以及完整的工件。
Agent 决定要评判什么;操作者决定评判标准有多严格,以及依据什么。 工具参数只指定证据。决定结果的一切都来自服务的环境,由运行服务的人在 Agent 的 MCP 配置中设置:
| 变量 | Agent 为什么不能设置它 |
|---|---|
ADVERSARY_GATE_POLICY | 门禁标志(--sandbox bwrap、--triage jev、下限)。如果 Agent 能传入 --coverage-floor 0,就等于给自己评分。这里会拒绝证据标志。 |
ADVERSARY_GATE_BASE_REF | 基线就是 oracle:Agent 可以提交一项被改写的测试,并将该提交指定为基线。如果未固定基线,Agent 的 base_ref(默认为 HEAD)必须已位于远程跟踪分支上——Agent 在本地创建的提交会被拒绝,结果为 inconclusive,且不会运行任何内容(3.0.11;此前会接受任何提交)——结果中会显示 "baseline_chosen_by": "agent"。能推送或写入 .git/refs/remotes 的 Agent 可以绕过这项检查。固定基线可以指定基线,但无法防范能够写入仓库 .git(其中的 refs、objects 或配置)的 Agent——与任何以你的权限运行的代码一样,应在它无法写入这些内容的环境中运行。 |
ADVERSARY_GATE_PYTHON | 解释器的 site-packages 也属于测试框架——在那里安装的插件会在 pytest 内运行。请使用 Agent 无法写入的解释器。 |
ADVERSARY_GATE_REQUIRE_PINNED_BASE | 设为 1 后,上述基线固定要求将成为强制要求:如果没有 ADVERSARY_GATE_BASE_REF,verify_repo 将返回 inconclusive,且不会运行任何内容,而不是依据 Agent 指定的基线进行评判。默认关闭,因此依赖 base_ref 的服务仍可正常运行;gate_policy 会将其报告为 baseline_pin_required。 |
pytest_args 仅接受测试路径;选项(-p evil)会被拒绝。随附的 Hermes 技能会告诉 Agent:在说“完成”之前先调用门禁;遇到 block 时修复代码而非测试;绝不将 inconclusive 报告为成功;只有在 merge 时才推广自行编写的技能。
使用 Jev(TypeSafe AI)进行风险分流——建议只能收紧标准
export TYPESAFE_API_KEY=sk-...
adversary-gate ... --diff change.diff --triage jev # --triage-threshold 0.70 by default
Jev 是一个经过校准的决策模型:输入文本和架构,约 100 ms 后输出带概率的类型化答案。门控只向它询问一个选择题(low / medium / high 风险)和一个是/否题(这是否会改变调用方依赖的契约?)。置信度高的 high 会提高本次运行的门槛——强度置信度提高到 0.95,覆盖率下限提高到 0.90——因此,对于普通变更可通过的 5/5 变异体分数,对高风险变更而言就是 INCONCLUSIVE。low、medium、置信度不足的 high、错误或超时都不会改变任何内容;Jev 的回答不会导致决策变得更宽松,因此将补丁作者编写的文本交给它是安全的。无论结果如何,答案都会记录在 execution.triage 中。
- 差异内容会离开本机(截断至 64 KiB,且仅限
https)。因此它是可选功能。 - Jev 目前处于早期访问阶段(于 2026-09-15 发布)。客户端遵循 TypeSafe 发布的请求/响应格式,并通过与使用该格式的本地服务进行测试——本仓库不会使用实时 API 进行测试。
🎯 原因
Agent 能编写流畅、看似合理的代码,而将基础设施错误折叠成“通过”的简单流水线,会批准测试已损坏、从未运行或被改写到与缺陷一致的补丁。更换模型无法解决这个问题;确定性验证才可以。AdversaryGate 不在验证器中使用 LLM:无论补丁由哪个模型编写,同一个测试框架都会进行判断;如果没有测量某项内容,它会明确说明,而不是显示绿色勾号。
📚 延伸阅读
| 文档 | 内容 |
|---|---|
| 🧮 决策方式 | fail-closed 模型、决策矩阵、diff_coverage 和 suite_strength、为什么单行修复会得到 INCONCLUSIVE、退出代码分类、来源信息 |
| 🌐 语言支持 | 在非 Python/pytest 仓库上会发生什么 |
| 🛡️ 执行边界 | 在不受信任的代码上运行前请阅读:--sandbox bwrap 及其限制 |
| ⏱️ 性能与兼容性 | 耗时、支持的 Python 版本、弃用项 |
| 📄 更新日志 | 各版本的变更内容 |
| 🔐 安全 | 报告 fail-open 问题。错误的 MERGE 就是全部失效模式,因此这里的缺陷就是安全缺陷 |
| 📋 问题登记表 | 对门控本身发现的所有问题:修复前均以真实退出代码复现,以及仍未解决的问题 |
📄 许可证
Copyright © 2026 Sanflow(Sanflow10)。
从 3.0.0 版起发布的版本均依据 GNU Affero General Public License v3.0(AGPL-3.0-only)分发,并附带该许可证第 7 条所允许的附加条款——详见 NOTICE。2.x 系列(截至 2.14.2,包含该版本)依据 MIT License 发布,依据该许可证获取的副本仍适用该许可证。仅分发最新版本:不再发布早期版本的标签、GitHub Releases 和 PyPI 文件。
如需不受 AGPL 义务约束的商业许可证,请联系 [email protected]。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。