Tool-call Approval Policy
Applies to: ChengOS v0.2.0+ | Last verified: 2026-09-25 | Source:
crates/cheng-core/src/services/tool_call_approval.rs,crates/cheng-core/src/services/trusted_automation.rs,crates/cheng-file-ops/src/undo.rs,crates/cheng-file-ops/src/protected_paths.rs,crates/cheng-api/src/services/preset_switch_approval.rs
When an agent calls a tool that can change files or run processes, ChengOS decides — before
anything happens — whether the call runs automatically or waits for a person. This page is the
exact policy. The approval mechanism itself (cards, ~~y, /approve, timeouts, the reaper) is
covered in Approvals & Human Review.
One decision, three steps
Every agent tool path — the async and sync tool paths and the tool runtime — calls the same policy function after the node's static configuration and the model's arguments have been merged. Nothing else computes file-operation approval.
- Preflight. The side where the files live observes the planned call without writing: in-process on the server, or over the workspace tool bridge in a client-local CLI or desktop client. It reports canonical paths, whether targets exist, sizes, and content hashes.
- Decision. A pure function takes the merged arguments, the preflight facts, and the trusted environment (never the model's arguments about itself) and returns run automatically or require approval, with a reason. Missing facts always mean review.
- Checked execution. The decision pins the facts it relied on as preconditions. The executor
re-checks them immediately before any side effect and refuses on drift with
stale_precondition, so neither an automatic call nor an approved one can land on a file that changed after it was checked.
Tiers for file operations
These apply to tools/file_ops_hub (and the legacy tools/file_ops alias).
| Tier | Operations | Decision |
|---|---|---|
| Read-only | view_file, list_directory, file_tree, search_files, file_info, diff_file, validate_file | Never reviewed |
| Rehearsal | Any operation with dry_run: true where dry-run is side-effect-free | Never reviewed |
| Adaptive | write_file, edit_file, patch_file, copy_file, create_directory, backup_file | Automatic inside the low-impact envelope, otherwise reviewed |
| Destructive | delete_file, move_file, rollback_file, and every mutating batch_file_ops | Always reviewed |
An operation whose switch (allow_write, allow_delete, …) is off is refused by the hub itself,
so no card is shown for it.
The low-impact envelope. An adaptive call runs automatically only when all of these hold:
- a verified workspace root is bound to the run, and the call has one target;
- the target is not protected or flagged by the write-policy guard;
- the call turns off no safety net (
allow_sensitive,allow_ignored,force_overwrite); - it creates rather than overwrites, or it is an edit/patch that keeps its pre-image backup;
- backups go to the default location;
- the content or diff is at most 64 KiB.
Every automatic call carries an undo credential (below).
Why a call was reviewed
Each decision has a stable reason code, shown on the approval card and written to the audit log:
| Reason | Card says |
|---|---|
destructive_operation | deletes, moves, and rollbacks always need your approval |
mutating_batch | multi-file changes are approved as one change set |
protected_target | the target is a protected path |
policy_guarded_target | the target is a sensitive or ignored path |
safety_override | the call turns off a safety check |
overwrite | it replaces existing content |
change_too_large | the change is larger than the automatic limit |
backup_disabled | the edit turned off its backup |
multiple_targets | it touches many files |
custom_backup_root | backups are redirected to a custom location |
workspace_unbound | no verified workspace root is bound to this run |
preflight_unavailable | the workspace could not describe this change in advance |
planning_failed | the change could not be planned in advance |
process_execution | real process execution (workspace shell or tests) |
Automatic decisions record low_impact_scoped_change, author_opt_out, or trusted_automation.
preflight_unavailable usually means an older CLI or desktop client that cannot answer the
preflight request; upgrading the client restores automatic small edits.
Approval identity and "always allow"
Reviews for destructive operations, batches, protected or guarded targets, safety overrides, and process execution are target-bound: the approval identity names the operation and the exact paths, ending in a digest of the operation, workspace root, and paths. A remembered "always allow" therefore covers exactly that operation on exactly those paths, and "always allow" rules recorded before this policy never cover them. A batch approval is one-shot: it covers the listed operations and is never reusable.
An approved call is still checked: if the previewed file changed between the card and the execution, the call fails as stale instead of landing.
Protected paths
These always need a person, even for a tiny, undoable change. Patterns use gitignore syntax,
relative to the workspace root, and match the canonical path (a symlink or ./ spelling cannot
dodge them):
.git .env .env.* *.pem *.key id_rsa* id_ed25519* .ssh
/.github/workflows /.gitlab-ci.yml
CHENG_FILE_OPS_PROTECTED_PATTERNS (comma-separated, same syntax) adds patterns for a
deployment. Nothing a workflow or a model supplies can remove one.
Undo credentials
Skipping a prompt is only acceptable when the change can be taken back. Every automatic mutation
attaches an undo_credential to its tool output describing exactly what it did:
| Operation | Credential | Revert rule |
|---|---|---|
| Edit or patch (any backed-up rewrite) | Pre-image backup + post-write hash | Restore only if the file still has the post-write hash |
| New file (write or copy) | "Absent before" + post-write hash | Delete only if the file still has the post-write hash |
create_directory | The directories actually created | Remove innermost first, each only while empty |
backup_file | The snapshot created | Remove that snapshot |
Every revert refuses when the path has changed since, so an undo can never destroy later work.
In this release the credential is recorded in the tool output, and so in the execution trace; there is no one-click undo
button yet, and a revert is applied by the file-ops core (cheng_file_ops::undo::revert).
Opting out, safely
require_approval = false on a file-ops node is an author-level switch. It skips review for
adaptive operations and non-destructive batches (reason author_opt_out) — but not for
destructive ones.
Trusted automation is the only way a delete, move, rollback, or destructive batch runs with
nobody watching. It is deployment configuration, never workflow configuration: the operator
writes a JSON file and points CHENG_TRUSTED_AUTOMATION_POLICY_FILE at it.
{
"policies": [
{
"id": "nightly-artifact-cleanup",
"workflow_ids": ["3f1c…"],
"operations": ["delete_file", "move_file"],
"roots": ["/srv/workspaces/artifacts", "client:<root key>"]
}
]
}
Each entry names specific workflows, operations, and roots. There is deliberately no
"all workflows" or "all operations" form. A server root covers itself and everything below it; a
client-local workspace is named by client: plus its root key (a digest, never a path). The node
must also have require_approval = false. Every call an entry authorizes is audited with the
entry's id.
Process execution
tools/workspace_shell and running tests with tools/workspace_tests are always reviewed
(reason process_execution); the card states where the process will run, so a server run is
never mistaken for your laptop. Read-only workspace tools — git, markdown, preview, test
detection — are never gated.
No workflow field can switch this off. An operator can disable it for a whole deployment with
CHENG_WORKSPACE_PROCESS_APPROVAL_DISABLED=true, meant only for trusted automation servers.
Preset switches
An agent may request a different preset for a tool node wired alongside it, but never apply one itself:
tools/list_available_presetsreturns only presets marked LLM-visible and valid for a sibling tool — one that feeds the same agent.tools/request_preset_switchbuilds an approval card with a parameter diff. The preset is applied, and the workflow saved, only after the user approves (web card,~~y, or CLI/approve).
A target that is not a sibling tool of the requesting node is refused. An agent can never re-configure unrelated nodes.
Audit
Every governed decision writes a structured record to the cheng::audit::tool_policy log
target: tool, decision (auto_execute or require_user_approval), reason, whether the review is
mandatory, the trusted-policy ID, whether preconditions were pinned, and the execution and
workflow IDs. Arguments and file content are never logged.
Next steps
- Approvals & Human Review — the gate, decisions, and the reaper.
- File Operation Nodes — every operation and switch of the file-ops hub.
- Remote & Cross-Device Connections — client-local execution and preflight.