修补匠Code Patcher
给 Agent 一个工作区,也给它边界Give the agent a workspace and a boundary
任务简报Brief
CI 流水线里有一个失败的 slugify 测试。编码 Agent 需要读文件、改代码、运行测试,但不能碰工作区外的任何东西,也不能静默写入 .env。
A CI pipeline has a failing slugify test. A coding agent must read the file, patch it and run tests, without touching anything outside the workspace or silently writing .env.
编码 Agent 的能力来自文件与 Shell 工具,风险也来自它们。这一关把工作区、内置工具、权限模式和事件观测放在一起:先跑通“读 → 写 → 测试”的闭环,再用权限引擎证明不同模式下的行为差异。你会看到 Python 与 Java 在内置工具命名和规则匹配上的真实差异,而不是抽象概念。
A coding agent gets its power from file and shell tools, and its risk too. This mission combines a workspace, built-in tools, permission modes and event observation: first close the read → write → test loop, then prove how permission modes differ. You will see real Python/Java differences in tool naming and rule matching, not abstractions.
学习目标Learning goals
把 Read/Write/Bash(或 Java 的对应内置工具)限制在工作区内。
Contain Read/Write/Bash (or Java equivalents) to the workspace.
用流式事件确认三个工具都执行成功。
Use streaming events to confirm all three tools succeeded.
验证 DEFAULT 会询问、EXPLORE 会拒绝写入、deny rule 能压过 BYPASS。
Verify DEFAULT asks, EXPLORE denies writes, and a deny rule beats BYPASS.
理解“沙箱”是路径 + 工具 + 权限三层共同作用。
Understand that a sandbox is paths + tools + permissions working together.
核心概念Core concepts
Read / Write / Bash
内置文件与命令工具;Python 名称是 Read/Write/Bash,Java 是 view_text_file/write_text_file/execute_shell_command。
Built-in file and command tools; Python names are Read/Write/Bash, Java uses view_text_file/write_text_file/execute_shell_command.
PermissionMode
DEFAULT 询问、EXPLORE 只读、BYPASS 跳过询问、DONT_ASK 自动处理;模式不是安全边界本身。
DEFAULT asks, EXPLORE is read-only, BYPASS skips prompts, DONT_ASK auto-handles; the mode is not the boundary itself.
PermissionRule
在模式之外再加一条明确规则;规则匹配到工具/内容时优先执行。
An explicit rule on top of the mode; when it matches a tool/content, it takes precedence.
ToolResultEndEvent
工具执行结束事件,带 state;ERROR 不应被当成成功文本。
The tool completion event carries state; ERROR must not be treated as success text.
双轨实现Build it twice
先完整跑通一条轨道,再切换重来。两条轨道写出同一种产物,方便直接对照行为。Run one track end to end, then switch and repeat. Both tracks write the same artifact so you can compare behavior directly.
Bash(cwd=root) 显式绑定工作目录;Read / Write 也只在给定路径下工作。不要依赖进程当前目录。
Bash(cwd=root) binds the working directory; Read / Write operate on given paths. Never rely on the process cwd.
绑定工作区工具Bind workspace tools
Agent 的 state 里放 PermissionContext(mode=BYPASS),仅限实验沙箱;生产环境应从 DEFAULT 开始并加 deny rules。
The agent state carries PermissionContext(mode=BYPASS) for the lab sandbox only; production should start from DEFAULT and add deny rules.
from agentscope.tool import Bash, Read, Toolkit, Write
toolkit = Toolkit(tools=[Read(), Write(), Bash(cwd=root)])
agent = Agent(
name="patcher",
system_prompt="Work only inside the provided repository.",
model=model,
toolkit=toolkit,
state=AgentState(
permission_context=PermissionContext(mode=PermissionMode.BYPASS),
),
react_config=ReActConfig(max_iters=8),
)
观察工具事件Observe tool events
不要只看最终回复;ToolResultEndEvent.state == "error" 时必须失败。生产里这应该触发告警或重试。
Do not trust the final reply alone; fail when ToolResultEndEvent.state == "error". Production should alert or retry.
from agentscope.event import ToolCallStartEvent, ToolResultEndEvent
async for event in agent.reply_stream(UserMsg("user", "Fix the failing test.")):
if isinstance(event, ToolCallStartEvent):
print("tool:", event.tool_call_name)
if isinstance(event, ToolResultEndEvent):
state = getattr(event.state, "value", event.state)
if state == "error":
raise RuntimeError(f"tool failed: {event.tool_call_id}")
权限矩阵The permission matrix
Python 的 deny rule 支持 glob,**/.env 能精确挡住敏感文件;这就是“规则匹配内容”的价值。
Python deny rules support globs; **/.env blocks the sensitive file precisely. That is the value of content matching.
guarded = PermissionEngine(PermissionContext(
mode=PermissionMode.BYPASS,
deny_rules={"Write": [PermissionRule(
tool_name="Write", rule_content="**/.env",
behavior=PermissionBehavior.DENY, source="lab")]},
))
blocked = await guarded.check_permission(
Write(), {"file_path": str(root / ".env"), "content": "TOKEN=x"})
assert blocked.behavior == PermissionBehavior.DENY
# 离线判题
python lab.py check m2
python lab.py check m2
检查会在 lab/work/m2/repo 重建一个坏文件,再验证补丁、测试输出、测试文件未被覆盖以及权限矩阵。
The check recreates a broken file in lab/work/m2/repo, then validates the patch, test output, untouched test file and permission matrix.
lab/missions/m2_engineer/mission.py判题证明了什么What the check proves
- 内置工具执行成功:Read, Write, BashBuilt-in tools executed: Read, Write, Bash
- 补丁后工作区测试通过Workspace tests pass after the patch
- 权限矩阵:DEFAULT 询问、EXPLORE 拒绝写入、deny rule 压过 BYPASSPermission matrix: DEFAULT asks, EXPLORE denies writes, deny rule beats BYPASS
- 产物:lab/work/m2/repo/slugify.pyArtifact: lab/work/m2/repo/slugify.py
Java 的内置工具是 ReadFileTool、WriteFileTool、ShellCommandTool,需要传入根目录字符串;命令工具还要一个 CommandValidator。Windows 上使用 WindowsCommandValidator。
Java built-ins are ReadFileTool, WriteFileTool and ShellCommandTool, constructed with a root path; the shell tool also needs a CommandValidator. On Windows, use WindowsCommandValidator.
注册内置工具Register built-in tools
ShellCommandTool 接收允许的可执行文件集合、审批回调和平台校验器。不要用 cmd /c 绕过校验。
ShellCommandTool takes allowed executables, an approval callback and a platform validator. Do not bypass validation with cmd /c.
Toolkit toolkit = new Toolkit();
toolkit.registerTool(new ReadFileTool(root.toString()));
toolkit.registerTool(new WriteFileTool(root.toString()));
toolkit.registerTool(new ShellCommandTool(
root.toString(), Set.of("python", "python3"),
command -> true, new WindowsCommandValidator()));
用事件确认执行Confirm execution with events
streamEvents 收集 ToolCallStartEvent 名称;ToolResultEndEvent 的状态为 ERROR 时立即失败。
Collect ToolCallStartEvent names from streamEvents; fail immediately when ToolResultEndEvent state is ERROR.
List<AgentEvent> events = agent.streamEvents(
LabSupport.user("Fix the failing slugify test."), context)
.collectList().block();
for (AgentEvent event : events) {
if (event instanceof ToolCallStartEvent start) {
calls.add(start.getToolCallName());
}
if (event instanceof ToolResultEndEvent end
&& end.getState() == ToolResultState.ERROR) {
throw new AssertionError("tool failed: " + end.getToolCallName());
}
}
权限矩阵与 2.0.4 限制Permission matrix and a 2.0.4 limitation
Java 2.0.4 的注解工具在 matchRule 中只匹配 ruleContent == null,glob 不会命中。因此本关用“整工具拒绝”来证明 deny rule 能压过 BYPASS;生产里应使用工具包装或路径校验补上内容级规则。
In Java 2.0.4, annotation tools only match ruleContent == null in matchRule, so globs do not hit. This lab denies the whole tool to prove deny-beats-BYPASS; production needs tool wrappers or path validation for content-level rules.
PermissionEngine guarded = new PermissionEngine(
PermissionContextState.builder()
.mode(PermissionMode.BYPASS)
.addDenyRule("write_text_file", new PermissionRule(
"write_text_file", null,
PermissionBehavior.DENY, "lab"))
.build());
PermissionBehavior blocked = guarded.checkPermission(write,
Map.of("file_path", root.resolve(".env").toString(), "content", "TOKEN=x"))
.block().getBehavior();
// blocked == DENY
// 编译并判题
// cd lab/java && mvn -q -o compile exec:java -Dexec.args="check m2"
cd lab/java && mvn -q -o compile exec:java -Dexec.args="check m2"
检查会重建仓库、执行三个内置工具、跑 Python 测试,并分别验证 DEFAULT / EXPLORE / BYPASS+deny 三种权限决策。
The check recreates the repo, runs all three built-ins, executes the Python tests, and validates DEFAULT / EXPLORE / BYPASS+deny decisions.
lab/java/src/main/java/lab/m2/M2Engineer.java判题证明了什么What the check proves
- 内置工具执行成功:view_text_file, write_text_file, execute_shell_commandBuilt-in tools executed: view_text_file, write_text_file, execute_shell_command
- 工作区测试通过Workspace tests pass after the patch
- 权限矩阵:DEFAULT 询问、EXPLORE 拒绝写入、deny rule 压过 BYPASSPermission matrix: DEFAULT asks, EXPLORE denies writes, deny rule beats BYPASS
Python ↔ Java 差异Python ↔ Java differences
内置工具命名不同:Python
Read/Write/Bash,Javaview_text_file/write_text_file/execute_shell_command。脚本化工具调用时必须使用对应名字。Built-in names differ: Python
Read/Write/Bash, Javaview_text_file/write_text_file/execute_shell_command. Scripted calls must use the right names.Python 的 deny rule 支持 glob(如
**/.env);Java 2.0.4 对注解工具只匹配空ruleContent,内容级规则要自己包装工具。Python deny rules support globs (e.g.
**/.env); Java 2.0.4 only matches nullruleContentfor annotation tools, so content rules need tool wrappers.Python 用
sys.executable跑测试;Java 通过LabSupport.pythonExecutable()查找 venv。Windows 上路径与编码是高频坑。Python runs tests with
sys.executable; Java resolves the venv throughLabSupport.pythonExecutable(). Paths and encoding are frequent Windows pitfalls.
常见坑Pitfalls
把 BYPASS 带进生产:它跳过人类确认,只适合隔离沙箱。生产中至少从 DEFAULT 开始,并给危险工具加 deny rule。
Shipping BYPASS to production: it skips human confirmation and belongs in an isolated sandbox. Start from DEFAULT and deny dangerous tools in production.
只限制工作目录、不限制命令:
python ../../evil.py仍可能逃逸。Shell 工具必须同时校验可执行文件、参数和工作目录。Restricting cwd but not commands:
python ../../evil.pycan still escape. Validate executable, arguments and cwd together.忽略 ToolResult 状态:工具报错但模型继续“成功”回答,最终修改根本没生效。事件里的 state 才是事实来源。
Ignoring ToolResult state: the tool errors while the model keeps answering; the patch never landed. The event state is the source of truth.
领取通行码Claim your pass code
先运行当前轨道的 check。CLI 会打印 PASS CODE、XP 与徽章。粘贴到这里;同一关在每条轨道可各领取一次。Run the check for the current track. The CLI prints PASS CODE plus the exact XP and badge. Paste it here; the same mission can be claimed once on each track.
费曼复述Feynman recall
合上教程,用自己的话讲清机制。关键点覆盖 65%、篇幅 20%、结构 15%;60 分通过并一次性获得 20 XP。Explain the mechanism without looking. Coverage 65%, length 20%, structure 15%; 60 passes and earns +20 XP once.
权限引擎在工具执行前如何决策?DEFAULT、EXPLORE、BYPASS 与 deny rule 的关系是什么?
How does the permission engine decide before a tool runs? How do DEFAULT, EXPLORE, BYPASS and deny rules relate?
查看参考答案Show the model answer
每次工具调用前,权限引擎先看显式规则,再看模式默认行为:DEFAULT 对敏感写入询问,EXPLORE 只读并拒绝写入,BYPASS 跳过询问。deny rule 是更具体的约束,命中时优先于 BYPASS。沙箱还依赖工作区路径和工具本身的参数校验;权限模式只是其中一层。
Before every tool call, the permission engine checks explicit rules, then the mode default: DEFAULT asks for sensitive writes, EXPLORE is read-only and denies writes, BYPASS skips prompts. A deny rule is more specific and wins over BYPASS when it matches. The sandbox also depends on workspace paths and the tool's own argument validation; the mode is only one layer.
间隔复习Spaced review
首次领取后会在明天安排复习。按真实感受评分,下一次间隔会自适应。After the first claim a review is scheduled for tomorrow. Rate how it felt; the next interval adapts.