Approvals and Sensitive Operations¶
Approvals mediate individual tool calls after arguments are produced and before the tool executes. They are not model-output validation and do not sandbox ordinary embedded Python.
Risk Classes¶
Kedi uses three ordered risk classes:
read_only: observes non-sensitive state;mutating: changes state or performs an effect;sensitive: accesses secrets or otherwise requires stronger review.
Custom Kedi and Python tools default to mutating. A Python @kedi.tool can
declare a different risk:
An argument-aware risk resolver may elevate a call, but cannot downgrade a tool's static risk.
Default Policy¶
Without an explicit policy, read_only calls are allowed and mutating or
sensitive calls are denied. This is the Python/runtime fail-closed default.
The interactive kedi CLI installs a prompt policy for risky calls. It offers
Allow once, Deny, and Allow always for this run. “Always” is scoped to the
current process, tool, and risk level; it is never persisted.
In non-interactive execution, an unanswered prompt is denied.
Static Policies¶
> approval: allow
allow permits registered mutating and sensitive tools in that scope. Use it
only when the complete tool surface and arguments are already trusted.
> approval: deny
deny refuses risky calls. Read-only calls remain automatically allowed.
edit is not a static mode.
Dynamic Policy¶
Define a sync or async handler in Python and select it:
```
from pathlib import Path
from kedi import ApprovalDecision
def review_tool(request):
if request.tool_name != "write_report":
return ApprovalDecision.deny(reason="tool is outside this workflow")
safe_path = Path("reports") / Path(request.arguments["path"]).name
return ApprovalDecision.edit(
{**request.arguments, "path": str(safe_path)},
reason="redirected to the reports directory",
)
```
> approval: `review_tool`
The handler must return ApprovalDecision.allow(),
ApprovalDecision.deny(), or ApprovalDecision.edit(arguments). Returning a
string or arbitrary mapping is an error.
LLM-backed approval helper¶
The built-in helpers module provides an LLM-backed dynamic handler that uses
the active model:
> import: helpers
> approval: `llm_approval`
Experimental and limited-context
Approval requests do not currently include a call reason. llm_approval
therefore judges with limited information: the tool name, description,
declared risk, and call arguments. This helper is not a stable feature and
must not be treated as a complete authorization boundary.
Approval Request¶
The immutable request contains:
| Field | Meaning |
|---|---|
tool_name |
Registered tool name |
arguments |
Deep-copied, read-only argument mapping |
risk |
Effective risk after argument-aware elevation |
adapter_shortname |
Active adapter when known |
description |
Tool description when available |
metadata |
Deep-copied tool metadata when available |
The handler cannot mutate the request in place. It must return edit with a new
complete argument mapping.
Edited Arguments¶
Edited arguments are reclassified and revalidated against the tool signature,
custom Kedi models, and schema before invocation. An edit that redirects a
normal path to .env can therefore become sensitive and face subsequent
policy checks. Invalid edited types fail before the tool runs.
Only an edit decision may carry replacement arguments. Allow and deny cannot
smuggle an edit payload.
Sensitive Files¶
The bundled filesystem tools classify .env and .env.* as secrets. Normal
read_text_file(path) refuses them. The caller must set
secret_files=True, which elevates the call to sensitive, and approval must
then permit it.
Writes and patches targeting secret filenames are likewise elevated before mutation. The opt-in flag requests review; it does not itself authorize access.
Scope and Descendants¶
Approval is lexical and restored when a scope exits. A procedure directive applies to its following tool calls; a profile carries its policy when used.
Subagents have their own policy, but an ancestor's approval ceiling remains in force. A child cannot replace a parent deny with allow. Parent edits are reclassified and checked by child policy. This makes delegation monotonically restrictive rather than a route around approval.
Limits of Approval¶
Approval wraps registered tool calls. It does not intercept arbitrary I/O in a Python block, a package prelude, or third-party code. Treat embedded Python as trusted executable code and use host isolation when that boundary matters.