Permissions and Approval: a Three-State Rule Set, Matching by Tool and Path, and the Boundaries of Auto Mode
Give the agent an approval gate: judge every tool call against a set of allow/ask/deny three-state rules, match rules by tool name and path pattern, remember approval decisions, and work out exactly when fully automatic mode is responsible to enable.
Today's Goals
- Design a three-state permission rule set matched by tool and path, and explain the match priority
- Turn approval into a state machine that doesn't block the loop, letting the model try another path on denial instead of hanging
- Name the three preconditions for auto mode, and which one missing means it shouldn't be turned on
Yesterday gave it write access; today gives it a gate. When you finish, scroll back up and tick the three goals off.
Plain-Language Walkthrough
Deciding who signs off: what the new hire does alone and what needs asking
Once the new hire has commit rights, you two will have a conversation roughly like: "running tests, reading logs, editing your own module — no need to ask me; database migrations, CI config, releases — ask first."
That conversation's product is not a permission list but a criterion. That matters, because you cannot enumerate every operation. What you actually convey is: if this goes wrong, is the cost reversible? does it affect only you or other people?
An Agent's approval gate writes that criterion into code. But it has a difficulty a new hire does not: you cannot converse with it, only give it rules. And wrong rules fail in two directions:
- Too strict: nearly every step asks. After thirty approvals the user stops reading and just presses yes — at which point the gate no longer exists, only its ceremony.
- Too loose: it finishes the job on its own, including the part you did not want. And you find out afterwards.
So the gate's design goal is not to block a lot but to ask about the few things worth asking about and leave the rest alone. Today's three tasks all serve that goal: make the verdict three-state rather than two, match rules by specificity rather than order, and make approval a pause inside the loop rather than an exception.
Three states, not two: ask is where "it depends" lives
First, why there must be three states.
A system with only allow and deny forces every "it depends" operation into one side. Put them in allow and they never reach a human; put them in deny and the user finds the Agent cannot do real work, so the first thing they do is loosen the rules — quite possibly all the way.
The third state, ask, gives "context decides" a place to live. Should editing a file under src be allowed? It depends which file, into what, and what task is underway. A rule table cannot answer that; a human answers it at a glance.
Each state's semantics must be spelled out, especially the third:
| Verdict | Semantics | When to use |
|---|---|---|
| allow | do it, do not disturb anyone | reversible, low risk, frequent — running tests, checking status |
| ask | stop and ask | context-dependent operations, which is most writes |
| deny | not permitted, and do not ask | irreversible or clearly wrong things; you should not agree even if asked |
The difference between deny and ask is not only strictness but whether to disturb the human. You do not want the Agent editing test files, and you also do not want it asking every time — that shifts the judgment cost onto you. Deny's correct semantics are "this road is closed, find another."
And one rule finally pays off today: read-only tools pass by default. That is the point of the read-only flag added to tool definitions on day three — the gate need not know any specific tool, only ask "will you change something?"
What to match: tool name, path pattern, command prefix, and who wins
Rules must express three kinds of condition: tool name (edit_file), path pattern (test/**) and command prefix (node --test*). Writing that is easy; the hard part is who wins when several match.
The obvious approach is by order, first match wins. Do not. It turns the rule table's order into implicit semantics: you append a more specific rule at the end and some broad rule above eats it, and the error is "nothing happened" — an especially painful class of problem to diagnose when rules come from several places (the default table, user configuration, this session's temporary approvals).
This course has one rule: the more specific rule wins; at equal specificity, the stricter wins.
/** More conditions and fewer wildcards mean more specific */
function specificity(rule: Rule): number {
let score = 0
if (rule.tool && rule.tool !== '*') score += 2
if (rule.path) score += rule.path.includes('*') ? 2 : 3
if (rule.command) score += rule.command.includes('*') ? 2 : 3
return score
}
const STRICTNESS: Record<Decision, number> = { deny: 3, ask: 2, allow: 1 }
export function decide(tool: ToolDef, subject: Subject, state: PermissionState): Verdict {
if (tool.readOnly) return { decision: 'allow', reason: 'read-only tools pass by default' }
const hits = state.rules.filter((rule) => matches(rule, subject))
// With no rule matched, a write defaults to ask, never to allow
if (hits.length === 0) {
return { decision: 'ask', reason: 'no rule matched; writes need confirmation by default' }
}
const winner = hits.reduce((best, rule) => {
const bySpec = specificity(rule) - specificity(best)
if (bySpec !== 0) return bySpec > 0 ? rule : best
return STRICTNESS[rule.decision] > STRICTNESS[best.decision] ? rule : best
})
return { decision: winner.decision, reason: winner.why ?? 'matched a rule', rule: winner }
}STRICTNESS = {"deny": 3, "ask": 2, "allow": 1}
def specificity(rule: Rule) -> int:
"""More conditions and fewer wildcards mean more specific"""
score = 0
if rule.tool and rule.tool != "*":
score += 2
if rule.path:
score += 2 if "*" in rule.path else 3
if rule.command:
score += 2 if "*" in rule.command else 3
return score
def decide(tool: ToolDef, subject: Subject, state: PermissionState) -> Verdict:
if tool.read_only:
return Verdict("allow", "read-only tools pass by default")
hits = [rule for rule in state.rules if matches(rule, subject)]
# With no rule matched, a write defaults to ask, never to allow
if not hits:
return Verdict("ask", "no rule matched; writes need confirmation by default")
# The sort key is the rule itself: specificity first, then strictness
winner = max(hits, key=lambda r: (specificity(r), STRICTNESS[r.decision]))
return Verdict(winner.decision, winner.why or "matched a rule", winner)Both versions do the same thing, and the Python one states the rule more directly: the sort key is the priority definition. Expressing policy as one comparable key is far more maintainable than a chain of ifs — adding a condition class just adds one component to the key.
One easily missed detail: with no rule matched, a write's default verdict must be ask. A gate that allows by default is no gate, and that mistake only surfaces on the day something goes wrong. The starter's default implementation gets this wrong (defaulting to allow), and three self-test items fail.
Here is what the course's default rule table looks like, a decent example rather than an industry standard:
run_command command=node --test* -> allow running tests is a read-only verification
run_command command=git status* -> allow
edit_file path=test/** -> deny tests are the acceptance criterion; do not edit them green
edit_file path=**/*lock* -> deny lock files should be generated by the package manager
other writes: ask by default | read-only tools: allow by defaultThat "do not edit tests" rule deserves a sentence: models really do tend to edit tests until they pass, because that is the shortest path to the goal "all tests green." That is not bad character; you wrote the goal as "make the tests pass" rather than "make the code correct." The rule table closes that shortcut for you.
Approval is a pause inside the loop, not an exception
Now today's most important design decision. The gate sits before tool execution and has exactly two outcomes:
- Pass: the loop executes the tool as usual.
- Not pass: return a tool result explaining why it did not run.
The second is the key. A refused call looks exactly like a failed tool execution in the message array: an assistant message carrying the requested call, and a tool message carrying the reason for failure. So the model reroutes as it would after any failure — you need invent no new message type for approval, and need not touch yesterday's working loop.
/** null means pass; returning a result means "this call does not execute" */
export async function passGate(
call: ToolCall,
tool: ToolDef,
options: GateOptions
): Promise<ToolResult | null> {
const subject = subjectOf(tool.name, call.args)
const verdict = decide(tool, subject, options.state)
if (verdict.decision === 'allow') return null
if (verdict.decision === 'deny') return denied(tool, verdict.reason)
// With no channel to ask (CI, a pipe, another Agent's shell), treat it as refused.
// A gate that auto-approves in an unattended environment is no gate.
if (!options.approve) return denied(tool, 'confirmation is required, but this environment cannot ask')
// This await is the pause inside the loop: the model waits, the tool has not run, nothing happened
const answer = await options.approve({ call, tool, subject, verdict })
if (!answer.approved) return denied(tool, answer.note ?? 'the user refused this operation')
remember(subject, answer.scope, options)
return null
}async def pass_gate(call: ToolCall, tool: ToolDef, options: GateOptions) -> ToolResult | None:
"""None means pass; returning a result means "this call does not execute" """
subject = subject_of(tool.name, call.args)
verdict = decide(tool, subject, options.state)
if verdict.decision == "allow":
return None
if verdict.decision == "deny":
return denied(tool, verdict.reason)
# With no channel to ask (CI, a pipe, another Agent's shell), treat it as refused
if options.approve is None:
return denied(tool, "confirmation is required, but this environment cannot ask")
# This await is the pause inside the loop: the model waits, the tool has not run
answer = await options.approve(ApprovalRequest(call, tool, subject, verdict))
if not answer.approved:
return denied(tool, answer.note or "the user refused this operation")
remember(subject, answer.scope, options)
return NoneConsider the cost of implementing refusal as a thrown exception: you catch it outside the loop, decide whether the turn continues, and still add a tool result message (or the next request is invalid, since every call needs a matching result). All the complexity piles onto the error path, which is the hardest place to test. The loop's change today is five lines precisely because the gate was designed to return a result.
Two more disciplines in the implementation:
- An unrecognizable answer counts as refusal. A bare newline, an "okay," or input simply ending must not count as consent.
- The approval prompt must say why you are being asked. The user cannot see the rule table; the two lines on their terminal are their entire basis for judgment. The lab's prompt reads:
Confirmation needed: edit_file src/calc.js
Reason: no rule matched; writes need confirmation by default
Allow? y this once / a this whole session / w write it into the config / n noAfter a refusal: feed the reason back as a result and the model reroutes itself
The wording of a refusal decides what the model does next. This is the same principle as day four's "a failure message is the model's next action," but more visible here.
Write "permission denied" and the model retries the same call in place (it assumes a transient failure). Write "tests are the acceptance criterion; do not edit them green, change the code under test" and it takes another route. The lab shows this directly — the offline script deliberately takes the shortcut first:
The quickest way is to change that test case.
> edit_file({"path":"test/calc.test.js","old_string":"test('divide by zero should throw', ()...)
x edit_file fed back 115 chars - this edit_file call did not run: tests are the acceptance criterion and must not be edited green. Change the code under test.
I cannot change the tests, so I will change the code - adding a divide-by-zero guard.
> edit_file({"path":"src/calc.js","old_string":"export function divide(a, b) {\n return a /...)
Confirmation needed: edit_file src/calc.js
Reason: no rule matched; writes need confirmation by defaultOf five tool calls, one was stopped by deny, one paused to ask, and three passed straight through. That is what this gate should look like: the things worth asking got asked, and nothing else disturbed anyone.
A general suggestion on wording: a refusal has three parts — what was refused, why, and which route is available instead. The third is the most often omitted and the most useful.
Remembering the choice: this turn, this session, or persistently
By the fifth time the same operation is queried, the user is pressing yes without thinking. So remembering is a necessary feature of the gate, not a nicety. The three scopes have distinct semantics:
| Scope | Lifetime | Suited to |
|---|---|---|
| This turn | no more asking within this task | "this once" — most common and safest |
| This session | until exit | one task that repeatedly touches the same files |
| Persistent | written down, still valid at next start | stable team conventions, e.g. "this directory is free to edit" |
They differ in where they are recorded: this turn goes into a one-shot set cleared at the start of each turn; this session appends an allow rule to the rule table; persistent appends and also writes to disk. Once persisted, it participates in matching alongside the default table under the same "more specific wins" rule, needing no extra logic.
The clearing moment for "this turn" is worth noting: it follows a user task, not the session. When a user says "this once," they mean the thing they just asked for, not the rest of this terminal's life. So clearing happens at the start of each run.
Which configuration file to write to and how layered configuration merges is day twenty-one's packaging subject; today only demonstrates the semantics of remembering.
The three preconditions for auto mode
Finally, the unavoidable question: can we just make it fully automatic?
Yes, conditionally. Turning full auto on is responsible if and only if all three of the following hold, and with any one missing it should stay off:
- A sandbox. What it can touch is bounded: a temporary directory or a container, a limited file scope, no network access it should not have. The test is "in the worst case, you can accept what it can damage."
- Rollback. You can undo with one action after a mistake. The test is not "we have git" but "uncommitted changes are protected too" — which is why day fourteen's snapshots and rollback are auto mode's real precondition.
- Observability. There is a complete record of what it did, reviewable line by line afterwards. Day seven's event log is this one.
Taken together, they yield a perhaps counterintuitive conclusion: auto mode is not a switch but the result of a whole stack of infrastructure. Turning off the approval gate without the first two is not auto mode, it is unsupervised.
And yesterday's command blacklist now has a clearer place: it is the last line against slips, not an isolation mechanism. Real isolation is the first precondition.
Source Reading
Hands-On Lab
Today changes none of the six tools; everything changes in the step just before tool execution. The starter leaves four exercises, two of which (the default verdict, and what to do with no channel to ask) are the "wrong code raises no error and surfaces only on the bad day" kind. Unmodified it passes three of nine.
- Implement the rule table and matcher supporting tool name, path pattern and command prefix, with priority by "more specific wins, stricter wins at equal specificity."
- Insert the gate into the tool execution path: read-only tools pass, write and command tools stop and ask; confirm that with no channel to ask the result is refusal.
- Implement the three memory scopes and add the "why you are being asked" line to the approval prompt.
- Turn refused calls into fed-back tool results, and watch the model reroute to the code under test after being stopped from editing the tests.
- Run the self-test:
MOCK=1 SELFTEST=1 pnpm startshould print 9/9 passed, with the call sequence and the number of questions both reproducible.
Acceptance is five ticks: the self-test prints 9/9 passed; read-only tools never trigger approval; editing tests is stopped by deny and the model reroutes; answering y applies the change while answering n leaves the file byte-identical; and all three memory scopes visibly work (asked once this turn, never again this session, and the persistent one written to a file).
Interview Questions
Today's three questions test permission-model design judgment, not recited security knowledge:
- Designing an Agent's permission model, which states do you need? What do rules match on?
- When a user refuses a tool call, how should the loop continue? Why not simply throw?
- Under what circumstances may an Agent run in fully automatic mode? Which preconditions would you require?
Full bilingual prompts, analyses and key points are in this course's day-five question bank. Question two discriminates most — it appears to ask about exception handling and actually asks whether you have thought about what a refused call looks like inside the message array.
Checklist and Tomorrow
- I can state each of the three verdicts' semantics, especially that deny versus ask is not only about strictness
- I can explain why rule priority follows specificity rather than order
- I know an unmatched write must default to ask, and what to do with no channel to ask
- I can say what "approval is a pause, not an exception" saves in the implementation
- I know where each memory scope is recorded, and what the clearing of "this turn" follows
- I can name auto mode's three preconditions and which day of the course each corresponds to
Tomorrow is D6, "Error Handling and Self-Correction: Feeding Failures Back, Backoff Retries, Loop Detection and Cancellation." Over five days we have met several failure classes piecemeal: a stream cut mid-way, arguments that are not valid JSON, an original that does not match, a hung command, a refusal by the user. Tomorrow puts them together into one taxonomy with a single criterion: feed back the errors the model can fix, and raise only the rest to the user. It also settles two debts from today — backoff retries with jitter, and the full cancellation chain from keypress all the way to the child process.
Interview questions
Designing an agent's permission model: which states do you need, what do rules match on, and which rule wins when several match?设计一个 Agent 的权限模型,你需要哪几种状态?规则按什么匹配、命中多条时听谁的?
Common in ChinaCommon overseasIntermediate#permissions#agent-designHow to reason about it · think before answering
- This screens for having actually gated real tools. Answering allow and deny misses the third case; the signal is justifying ask and explaining conflict resolution.
- How to break it down: ask whether some operations depend on what you are doing right now. They do, and they are most write operations — whether editing a file is fine depends on which file, what change, and which task. A rule table cannot answer that but a human glance can, hence a third state.
- Spell out the semantics, especially that deny differs from ask in kind, not just degree: deny means the path is closed, stop asking, because asking pushes the decision cost onto the user. Without deny the system fails in both directions: users get prompt fatigue and click through, or the rules get widened until nothing is enforced.
- Then matching: a tool name is not enough, because editing source and editing tests are the same tool, so path patterns and command prefixes are first-class conditions. You also need a will-this-mutate flag on every tool so read-only tools pass by default, and that flag must exist in the first version of the tool protocol, or externally loaded tools end up either all allowed or all prompted.
- Finally conflicts, the half most people get wrong: do not take the first matching rule in table order, because order becomes implicit semantics and a newly added specific rule gets swallowed by a broad one, with no error at all. The right rule is most specific wins, strictest wins on ties, implemented as a comparable sort key. And with no rule matched, a write must default to ask, because a default-allow gate is not a gate.
- Likely follow-up: where do rules come from? At least three places — built-in defaults, user config, and in-session approvals. Merge them into one table and let specificity rather than origin decide, so adding a source needs no change to the decision logic.
分析过程 · 先想清楚再作答
- 这题在筛「有没有把门装在真用的东西上」。答「允许和禁止两种」的人没考虑过第三种情况;区分度在于你能不能说出 ask 存在的理由,以及规则冲突怎么解。
- 怎么拆:先问「有没有一类操作,答案取决于当时在干什么」。有,而且它是绝大多数写操作——改某个文件该不该允许,取决于改的是哪个文件、改成什么、当时在做什么任务。规则表答不了这种问题,人看一眼能答,所以必须有第三态 ask。
- 三态的语义要说清,尤其是 deny 与 ask 的差别不只是严格程度:deny 是「这条路封了,别再问」,问了等于把判断成本转嫁给用户。少了 deny,系统会向两个极端失效——要么把人问烦(他一路按同意,门就没了),要么被放宽到没有约束。
- 然后是匹配维度:工具名不够用,必须还能按路径模式与命令前缀匹配,因为「改代码」和「改测试」是同一个工具。另外要有一个「这个工具会不会改东西」的标记,让只读工具默认放行——这个字段必须在工具协议第一版就留,否则外部接进来的工具(MCP、Skills)要么全放行要么全问一遍。
- 最后是冲突解决,这是最容易答错的一半:不要按规则表顺序取第一条命中的。顺序会变成隐含语义,你在末尾加一条更具体的规则却被上面某条宽泛规则吃掉,而现象是「什么也没发生」。正确口径是「更具体的赢,具体度相同时更严格的赢」,实现上就是把优先级写成一个可比较的排序键。还有一条:写操作没有规则命中时默认必须是 ask,默认放行的门等于没有门。
- 可预期的追问:规则从哪来?至少三处——内置默认、用户配置、本次会话里的临时批准。三处合成一张表,靠具体度而不是来源决定优先级,这样加一处来源不需要改判定逻辑。
Key points
- Three states, not two: ask exists for operations whose answer depends on the current task
- Deny versus ask is about whether to interrupt the user, not merely strictness
- Match on tool name, path pattern, and command prefix, plus a read-only flag so reads pass by default
- Resolve conflicts by specificity rather than table order, most specific then strictest, as a sort key
- Unmatched writes default to ask, and rules from defaults, config, and the session merge into one table
答题要点
- 三态而不是两态:ask 是给「取决于当时在干什么」的操作留的位置
- deny 与 ask 的差别是「要不要打扰人」,不只是严格程度
- 匹配维度:工具名、路径模式、命令前缀,另加一个只读标记让只读工具默认放行
- 冲突解决按具体度而不是表顺序:更具体的赢、同具体度更严格的赢,写成一个排序键
- 写操作无规则命中时默认 ask;规则可以来自默认表、配置与本次会话,靠具体度统一裁决
The user rejects a tool call. How does the loop continue, and why not just throw?用户拒绝了一次工具调用,循环该怎么继续?为什么不能直接抛错?
Common in ChinaCommon overseasDeep dive#approval#agent-loopHow to reason about it · think before answering
- This looks like an exception-handling question but really asks whether you have pictured what a rejected call looks like in the message array. Answering catch it and tell the user misses that the problem is structural.
- How to break it down: start from the constraint. When the model requests tools, an assistant message carrying the call list is appended, and the protocol requires exactly one result message per call, or the next request is invalid and most gateways reject it outright. So a rejection must become a result message one way or another.
- Conclusion: design the gate so that returning a tool result means the call was not executed. Return nothing to allow, or a failed result explaining why. A rejected call then looks exactly like a tool failure, and the model reroutes as it would after any failure, with no new message type and only a few lines of change in the loop.
- Price the alternative: throwing forces you to catch outside the loop, decide whether the turn continues, and still synthesize a result message, piling complexity onto the error path, which is the hardest path to test. With parallel calls it is worse, since one rejection should not void two calls that already succeeded.
- One behavioral detail: write the rejection in three parts — what was blocked, why, and which alternative exists. Permission denied makes the model retry the same call; testing is the acceptance criterion, change the code under test makes it reroute. A tool's failure text is the model's behavior spec.
- Likely follow-up: does waiting for approval block the loop? Yes, and it should — that is the pause. What matters is the no-channel case, in CI, pipes, or another agent's shell, where you must treat ask as deny, since auto-approving turns the most dangerous environment fully autonomous.
分析过程 · 先想清楚再作答
- 这题表面在问异常处理,实际在问「你有没有想过被拒的那次调用在消息数组里长什么样」。答「捕获异常、提示用户」的人没意识到问题出在消息结构上。
- 怎么拆:先看约束。模型请求调工具时,消息数组里会多一条带调用清单的助手消息;而协议要求**每个调用都必须有且只有一条对应的结果消息**,否则下一轮请求不合法,大多数网关会直接报错。所以「拒绝」这件事必须以某种形式变成一条结果消息,逃不掉。
- 结论:把审批门做成「返回一条工具结果就等于不执行」。放行返回空,不放行返回一条 ok 为 false 的结果,内容是为什么不执行。于是被拒的调用和「工具执行失败」在消息数组里长得一模一样,模型会像处理任何一次失败那样自己改道——不需要为审批发明新的消息类型,循环里的改动只有几行。
- 反过来算抛异常的代价:你要在循环外面接住它、判断这一轮还要不要继续、还得补一条工具结果消息,复杂度全堆在错误路径上——而错误路径是最难测的地方。而且并行调用时更糟:一个被拒不该让另外两个已经执行完的调用作废。
- 还有一条决定行为的细节:拒绝文本要写成模型能改道的样子,三段——被拒的是什么、为什么、可以换哪条路。写「permission denied」它会原地重试同一个调用;写「测试是验收标准,请改被测代码」它会换一条路。工具的失败信息就是模型的行为规范。
- 可预期的追问:那审批的等待会不会阻塞循环?会,而且应该会——它就是循环里的一次暂停,模型在等、工具没执行、什么都没发生。要注意的是没有提问渠道的场景(CI、管道、别的 Agent 的 shell):此时必须按拒绝处理,自动同意等于把最危险的环境变成全自动。
Key points
- Protocol constraint: exactly one result message per tool call, or the next request is invalid
- Shape the gate so returning a result means not executed, and returning nothing means allowed
- Rejections then look like tool failures, so the model reroutes on its own and the loop barely changes
- Throwing piles complexity onto the error path and, with parallel calls, voids calls that already succeeded
- Rejection text has three parts: what was blocked, why, and the alternative; with no channel, treat ask as deny
答题要点
- 协议约束:每个工具调用必须有且只有一条结果消息,否则下一轮请求不合法
- 门的形状是「返回一条结果就等于不执行」,放行返回空
- 于是被拒调用与工具失败同构,模型按处理失败的方式自己改道,循环几乎不用改
- 抛异常会把复杂度堆到错误路径上,并行调用时还会牵连已经成功的调用
- 拒绝文本三段:被拒的是什么、为什么、可以换哪条路;没有提问渠道时按拒绝处理
When is it acceptable to run an agent fully autonomously, and what preconditions do you require?什么情况下可以给 Agent 开全自动模式?你会要求哪些前置条件?
Common in ChinaCommon overseasIntermediate#autonomy#safetyHow to reason about it · think before answering
- This tests engineering judgment and honesty. Answering never, too risky scores nothing, and answering sure, I always run it is worse. The signal is offering checkable conditions instead of an attitude.
- How to break it down: translate autonomy into who absorbs the mistakes. Nobody is watching, so the environment must absorb them, and three preconditions map to three ways of absorbing: a sandbox bounds the damage, rollback undoes what happened, and logs make it auditable. Missing any one means do not enable it.
- Make each criterion concrete. Sandbox: the worst thing it can damage is something you accept losing — throwaway directory or container, limited file scope, network cut where it is not needed. Rollback: one action returns you to the previous state, and having version control is not enough, because uncommitted work must survive too, which is why file snapshots are required. Observability: a complete, replayable record of what it did, which is what an append-only event log provides.
- State the counterintuitive conclusion: autonomy is not a switch, it is the output of infrastructure. Turning off the approval gate without the three preconditions is not autonomy, it is unsupervised. Conversely, once all three hold, the marginal value of prompting drops, because prompting existed to let a human stop irreversible actions.
- One production point people miss: do not judge a gate by how often it asks. A gate that asks on every step is often less safe, because it trains users to approve without reading. The two right metrics are whether any irreversible action slipped through and how many times a typical task interrupts the user.
- Likely follow-up: does a dangerous-command denylist count as isolation? No, it is fat-finger protection. There are a hundred ways around a regex, and real isolation only comes from the sandbox. Volunteering that distinction shows you know how thin that layer is.
分析过程 · 先想清楚再作答
- 这题在考工程判断力,也在考诚实。答「不能开,太危险」得不到分,答「可以开,我平时都开」更糟。区分度在于你能不能给出可检查的条件,而不是一个态度。
- 怎么拆:把「自动」翻译成「出错时谁来兜」。人不在场兜,所以必须让环境兜。三条前置条件正好对应三种兜法:沙盒兜住损失范围、回滚兜住已经造成的改动、日志兜住事后追责。少一条就不该开。
- 三条各自的判据要具体。沙盒:最坏情况下它能损坏的东西你能接受——临时目录或容器、有限的文件范围、切断不该有的网络。可回滚:出错后能一键退回,而且判据不是「有版本控制就行」,未提交的改动也要保得住,所以文件快照是必需的。可观测:它做过什么有完整记录、能一条条回放,这就是只追加的事件日志的用处。
- 结论要点出一个反直觉的地方:**自动模式不是一个开关,而是一整套基础设施的结果。** 只把审批门关掉、不做前三条,那不叫自动,叫无人监督。反过来,三条都具备时,审批门的价值会自然下降——因为「问一句」的收益本来就来自「人能拦住不可逆的事」。
- 生产视角补一条常被忽略的:衡量一道门的好坏不要用「问的次数」。每步都问的门实际安全性往往更低,因为它训练用户不看内容直接同意。正确的两个指标是「不可逆操作有没有漏过去」和「一个典型任务里用户被打断几次」。
- 可预期的追问:命令黑名单算不算隔离?不算,它是防手滑的最后一道。正则绕过的办法有一百种,真正的隔离只能靠第一条。这个区分要主动讲,它说明你知道自己那道闸有多厚。
Key points
- Translate autonomy into who absorbs errors: with no human present, the environment must
- Three preconditions: a sandbox bounding damage, rollback that covers uncommitted work, and replayable observability
- Missing any one means do not enable it; disabling the gate without them is unsupervised, not autonomous
- Judge a gate by irreversible actions that slipped through and interruptions per typical task, not prompt count
- A command denylist is fat-finger protection, not isolation; only the sandbox provides that
答题要点
- 把「自动」翻译成「出错时谁来兜」:人不在场,所以环境必须兜
- 三个前置条件:沙盒(损失范围可接受)、可回滚(含未提交改动)、可观测(完整可回放的记录)
- 少一条就不该开;只关掉审批门不做这三条,叫无人监督不叫自动
- 衡量门的好坏用「不可逆操作有没有漏过去」与「典型任务被打断几次」,不用问的次数
- 命令黑名单是防手滑不是隔离,真正的隔离只能靠沙盒