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.

  1. 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.
  2. 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.
  3. 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).

TierOperationsDecision
Read-onlyview_file, list_directory, file_tree, search_files, file_info, diff_file, validate_fileNever reviewed
RehearsalAny operation with dry_run: true where dry-run is side-effect-freeNever reviewed
Adaptivewrite_file, edit_file, patch_file, copy_file, create_directory, backup_fileAutomatic inside the low-impact envelope, otherwise reviewed
Destructivedelete_file, move_file, rollback_file, and every mutating batch_file_opsAlways 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:

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:

ReasonCard says
destructive_operationdeletes, moves, and rollbacks always need your approval
mutating_batchmulti-file changes are approved as one change set
protected_targetthe target is a protected path
policy_guarded_targetthe target is a sensitive or ignored path
safety_overridethe call turns off a safety check
overwriteit replaces existing content
change_too_largethe change is larger than the automatic limit
backup_disabledthe edit turned off its backup
multiple_targetsit touches many files
custom_backup_rootbackups are redirected to a custom location
workspace_unboundno verified workspace root is bound to this run
preflight_unavailablethe workspace could not describe this change in advance
planning_failedthe change could not be planned in advance
process_executionreal 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:

OperationCredentialRevert rule
Edit or patch (any backed-up rewrite)Pre-image backup + post-write hashRestore only if the file still has the post-write hash
New file (write or copy)"Absent before" + post-write hashDelete only if the file still has the post-write hash
create_directoryThe directories actually createdRemove innermost first, each only while empty
backup_fileThe snapshot createdRemove 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:

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