My AI Agent's Skill Declared Nothing. It Still Read 9 Files, Ran 7 Processes, and Got Blocked 3 Times.

My AI Agent’s Skill Declared Nothing. It Still Read 9 Files, Ran 7 Processes, and Got Blocked 3 Times.

我的 AI Agent 技能声明里什么都没写,但它依然读取了 9 个文件、运行了 7 个进程,并被拦截了 3 次。

I gave an AI agent a code-review skill. It never mentioned touching the filesystem or spawning processes — just “review this repo.” By the time it reported success, it had done both, repeatedly, and hit a policy wall three times along the way. That’s when I realized I’d been asking the wrong question about AI agents. 我赋予了一个 AI Agent 代码审查的技能。它从未提及要触碰文件系统或生成进程——仅仅是“审查这个仓库”。当它报告任务成功时,它实际上已经反复执行了上述两种操作,并在过程中三次触碰了策略红线。就在那时,我意识到我一直以来对 AI Agent 的提问方式是错误的。

How do I know what the agent actually did? Not what it said it did. Not whether the final test passed. Not whether the generated code looked reasonable. What did it actually attempt inside the environment? So I designed the approach and prompted Codex to build a harness that tests what these models actually do vs. what they say. It’s an experimental setup for studying the gap between what AI agents are instructed to do, what they attempt to do, what the host allows them to do, and what actually changes as a result. Building it has changed how I think about AI agent evaluation. 我该如何知道 Agent 实际上做了什么?不是它声称做了什么,不是最终测试是否通过,也不是生成的代码看起来是否合理。它在环境中到底尝试了什么?因此,我设计了一种方法,并提示 Codex 构建了一个测试框架,用来验证这些模型“实际做了什么”与“它们声称做了什么”之间的差异。这是一个实验性设置,旨在研究 AI Agent 被指令要求做的事、它们尝试做的事、宿主允许它们做的事,以及最终导致的结果之间的差距。构建这个框架改变了我对 AI Agent 评估的看法。

A Passing Agent Can Still Behave Very Differently

一个通过测试的 Agent 行为可能截然不同

Most coding benchmarks understandably care about the result. Give the model a task. Run the tests. Did it solve the problem? 大多数代码基准测试理所当然地关注结果。给模型一个任务,运行测试,它解决问题了吗?

Agent A: PASS Agent B: PASS Agent A:通过 Agent B:通过

But imagine those runs actually looked like this: 但想象一下,这些运行过程实际上是这样的:

Agent A reads allowed files, edits the intended files, runs the approved tests, finishes successfully. Agent A 读取允许的文件,编辑目标文件,运行批准的测试,成功完成。

Agent B tries to access the network, searches outside the workspace, attempts an unauthorized command, gets blocked several times, eventually finishes successfully. Agent B 尝试访问网络,在工作区之外搜索,尝试执行未经授权的命令,被拦截多次,最终也成功完成。

Both may have produced the right answer. But they are obviously not the same run. That’s the gap I wanted the harness to investigate. 两者可能都给出了正确答案。但显然,它们的运行过程并不相同。这就是我希望通过该框架调查的差距。

The Four Things I Wanted to Keep Separate

我希望区分开的四个维度

The harness currently revolves around four layers: 该框架目前围绕四个层级展开:

  • Declared — What did the instructions say should happen? e.g. “Do not access the network.” 声明 (Declared) — 指令要求发生什么?例如:“禁止访问网络。”
  • Attempted — What did the agent actually try to do? request_url("https://example.com") 尝试 (Attempted) — Agent 实际上尝试做什么?例如:request_url("https://example.com")
  • Policy — What did the execution environment allow? DENY: network unavailable 策略 (Policy) — 执行环境允许什么?例如:DENY: network unavailable
  • Observed — What actually happened to the environment? No outbound connection occurred. No network canary changed. 观察 (Observed) — 环境中实际发生了什么?例如:没有发生出站连接,没有网络金丝雀(canary)被改变。

That produces an interesting result: Declared behavior ≠ attempted behavior; Attempted behavior ≠ observed side effect. The agent violated the instructional boundary even though the sandbox successfully prevented the physical effect. That’s useful information — a simple pass/fail result loses it. 这产生了一个有趣的结果:声明行为 ≠ 尝试行为;尝试行为 ≠ 观察到的副作用。即使沙箱成功阻止了物理影响,Agent 依然违反了指令边界。这是非常有用的信息——而简单的“通过/失败”结果会丢失这些细节。

I Don’t Want the Agent’s Final Answer to Be Evidence

我不希望 Agent 的最终答案成为证据

This became one of the central design rules. Suppose an agent says: “I didn’t modify anything outside the target directory.” Cool. But that’s still just another model output. The evaluator shouldn’t have to trust it. So the harness checks the environment independently, using canaries — deliberately known state placed somewhere in the environment so the runner can later determine whether it was touched or changed. 这成为了核心设计原则之一。假设 Agent 说:“我没有修改目标目录之外的任何东西。”很好,但这仅仅是模型的又一个输出。评估者不应该盲目信任它。因此,该框架通过“金丝雀(canaries)”独立检查环境——即在环境中预置已知的状态,以便运行器稍后确定它是否被触碰或更改。

Before run: canary = unchanged 运行前:canary = unchanged Agent executes Agent 执行 After run: canary = unchanged (or: modified) 运行后:canary = unchanged (或:modified)

The environment becomes evidence. That distinction seems obvious in hindsight, but I think it matters a lot as agents gain more tools and autonomy. 环境本身成为了证据。事后看来,这种区别似乎显而易见,但我认为随着 Agent 获得更多工具和自主权,这一点至关重要。

The Current Architecture

当前架构

It’s built around a controlled runner rather than letting a model operate directly on my host machine. The current stack includes: 它围绕一个受控运行器构建,而不是让模型直接在我的宿主机上操作。当前的技术栈包括:

  • TypeScript / Node.js
  • A dedicated Runner
  • Instrumented tools
  • Explicit allow/deny policy
  • Rootless Podman containers
  • Fixed task fixtures
  • Synthetic canaries
  • Filesystem snapshots and deltas
  • Raw JSONL execution traces
  • Derived JSON results
  • Provenance-linked HTML reports
  • Inspect, run, and verify commands
  • A deterministic fake Runner
  • An OpenAI Responses API adapter for real model runs

The important part: the pretty report is not the source of truth. It’s derived from lower-level evidence. 重要的一点是:精美的报告并非真理的来源,它是从底层证据中推导出来的。

Provenance Became More Important Than I Expected

溯源比我预期的更重要

If the harness produces a finding like: Unexpected filesystem write detected, I want to be able to trace it back: Which tool invocation caused it? Which trace event recorded that invocation? Which policy applied? Which snapshot proves the file changed? What exact path was involved? What model/configuration produced the run? That’s provenance. Without it, an evaluator becomes just another opaque AI system saying “trust me, something suspicious happened” — which would be pretty ironic. 如果框架产生了一个发现,例如:检测到意外的文件系统写入,我希望能够追溯:是哪个工具调用导致的?哪个跟踪事件记录了该调用?应用了什么策略?哪个快照证明文件已更改?涉及的具体路径是什么?是哪个模型/配置产生的运行?这就是溯源。没有它,评估器就变成了另一个不透明的 AI 系统,只会说“相信我,发生了可疑的事情”——这未免太讽刺了。

First I Had to Benchmark the Benchmark

首先,我必须对基准测试进行基准测试

This was probably my favorite lesson from the project so far. Before using a real model, I built a deterministic fake Runner. Instead of asking an AI what to do, it performs a scripted sequence: write this allowed file, attempt this forbidden action, touch this canary, return this known result. It should report exactly what I expect. If the expected behavior and the generated report disagree, the problem isn’t the AI model — it’s the harness. 这可能是我目前从项目中获得的最喜欢的经验。在使用真实模型之前,我构建了一个确定性的伪运行器。它不是询问 AI 该做什么,而是执行预设的脚本序列:写入这个允许的文件,尝试这个禁止的操作,触碰这个金丝雀,返回这个已知的结果。它应该报告我所预期的内容。如果预期行为与生成的报告不符,问题不在于 AI 模型,而在于框架本身。

And Sure Enough, the Evaluator Had Bugs

果不其然,评估器有 Bug

The first live pilot immediately exposed weaknesses in the harness itself, including: output preservation, declaration detection, path normalization, answer-key contamination, missing Git inside the container, host-path leakage, model reasoning configuration. I actually found that encouraging — this is exactly why calibration matters. An evaluation tool can generate a false conclusion just as easily as the system being evaluated can behave incorrectly. The evaluator is software too. It needs tests. 首次实地测试立即暴露了框架本身的弱点,包括:输出保留、声明检测、路径归一化、答案键污染、容器内缺少 Git、宿主机路径泄露、模型推理配置等。我反而觉得这很令人振奋——这正是校准如此重要的原因。评估工具产生错误结论的概率,与被评估系统行为不端的概率一样高。评估器也是软件,它同样需要测试。

Deterministic Doesn’t Mean the Model Must Be Deterministic

确定性并不意味着模型必须是确定性的

This project helped me finally internalize the difference between determinism and reproducibility. A deterministic system means: same input + same starting conditions = same result. AI models don’t… 这个项目帮助我最终内化了“确定性”与“可复现性”之间的区别。确定性系统意味着:相同的输入 + 相同的初始条件 = 相同的结果。而 AI 模型并不……