Channel autonomy & the approval policy
Why tool calls from Telegram, Discord, and other channels get "Denied by non-interactive channel approval policy", how the tool-approval and path-sandbox gates differ, and how to let your agent create and run workflows from a channel.
When you drive Revka from a channel (Telegram, Discord, Slack, …) instead of the CLI, tool calls run under a stricter policy than they do at your terminal. If the agent reports something like:
❌ Denied by non-interactive channel approval policy.
…it hit the channel approval gate. This page explains what that gate is, the
two independent policy layers people most often confuse, the one non-obvious
gotcha (tool_search), and the exact configuration to let your agent create and
run workflows from a channel.
Why channels are different: interactive vs non-interactive
Section titled “Why channels are different: interactive vs non-interactive”Revka’s autonomy model assumes that, in supervised mode, a human can approve a risky tool call before it runs.
| Surface | Mode | What happens on a tool that needs approval |
|---|---|---|
| CLI | Interactive | You get a [Y]es / [N]o / [A]lways prompt |
| Channels (Telegram, Discord, …) | Non-interactive | There is no operator at a prompt, so the call is auto-denied |
So on a channel, “needs approval” effectively means “denied.” This is a safety default — it prevents an unattended bot from silently performing side-effecting actions that were meant to be reviewed.
The two gates (don’t conflate them)
Section titled “The two gates (don’t conflate them)”A single file write passes through two independent checks. They fail with different messages and have different fixes:
-
Tool-approval gate — may this tool be called at all? Driven by
[autonomy] level,auto_approve, andalways_ask. Failure message: “Denied by non-interactive channel approval policy.” -
Path sandbox — where may a file tool read or write? Driven by
[autonomy] workspace_only,allowed_roots, andforbidden_paths. Enforced inside the tool, independent of the approval gate. Failure message: a path error such as “Path not allowed by security policy: …” or “Resolved path escapes workspace allowlist: …”. A read-only block on a write tool surfaces as “Action blocked: autonomy is read-only”.
The important consequence: a write to a perfectly allowed path can still be denied because the tool wasn’t approved — and an approved tool can still be denied a path outside the sandbox. Fixing the wrong layer won’t help.
How the approval decision is made
Section titled “How the approval decision is made”For each tool call on a channel, Revka evaluates, in order:
level = "full"→ never requires approval (everything allowed).level = "read_only"→ side-effecting tools are blocked downstream.always_askcontains"*"or the tool name → always requires approval → auto-denied on channels (this overrides everything below).- The
shelltool → governed by its own command allowlist (allowed_commands/block_high_risk_commands), not the outer gate. - MCP-namespaced tools (name contains
__, e.g.revka-operator__run_workflow,kumiho-memory__kumiho_memory_engage) → auto-approved on channels. auto_approvecontains"*"or the tool name → allowed.- Otherwise (the supervised default) → requires approval → auto-denied on channels.
Key takeaway: bare-named built-in tools
Section titled “Key takeaway: bare-named built-in tools”This rule (step 5 vs step 7) is the crux of most channel issues:
- Built-in tools have bare names —
file_read,file_write,shell,tool_search,browser, etc. They are not auto-approved on channels. Only the read-only ones in the defaultauto_approvelist work out of the box; anything else must be added explicitly. - MCP / operator tools are namespaced with
__— they are auto-approved on channels by default.
So to enable a channel workflow you usually only need to allowlist the bare
built-in tools involved (tool_search, file_write). The operator workflow
tools themselves (revka-operator__create_workflow, etc.) are already allowed by
the __ rule.
The non-obvious part: tool_search
Section titled “The non-obvious part: tool_search”Revka uses deferred tool loading by default. To keep the context window small,
most operator and MCP tools are not loaded up front — only lightweight stubs
are listed in the system prompt. Before the agent can call a deferred tool, it must
first call the built-in tool_search tool to pull in that tool’s full schema.
tool_search has a bare name, so on a channel it is auto-denied unless you
allowlist it — and because it’s the gateway to every deferred tool, this blocks
the agent before it can ever reach create_workflow or run_workflow. The
denial you see is for tool_search, even though your request was about workflows.
tool_search only loads tool schemas into context; it performs no side effects,
and every tool it surfaces is still gated separately. It is safe to auto-approve.
Recipe: create & run workflows from a channel
Section titled “Recipe: create & run workflows from a channel”Add the bare built-in tools the flow needs to [autonomy].auto_approve, then
restart the daemon (config is read at startup):
[autonomy]level = "supervised"
auto_approve = [ # read-only defaults (keep these) "file_read", "web_search_tool", "web_fetch", "calculator", "glob_search", "content_search", "image_info", "weather",
# required for channel-driven workflows: "tool_search", # activate deferred operator/MCP tools "file_write", # write the workflow YAML into ~/.revka/workflows
# optional — already auto-approved by the "__" rule, listed here # only to also cover older builds: "create_workflow", "run_workflow", "get_workflow_status",]
# The built-in default is just ~/.revka/workflows. Add the artifacts/workspace# roots explicitly if a workflow needs to write outside the workspace dir.allowed_roots = ["~/.revka/workflows", "~/.revka/artifacts", "~/.revka/workspace"]
always_ask = [] # must NOT contain "*" or your workflow toolsThe end-to-end channel flow then completes:
tool_search → file_write (YAML into ~/.revka/workflows/) → run_workflow
→ get_workflow_status.
Where workflows write, and why the path is already allowed
Section titled “Where workflows write, and why the path is already allowed”~/.revka/workspace is the workspace_dir, so the path sandbox permits writes
there automatically. ~/.revka/workflows is the only entry in the built-in
default allowed_roots — it sits outside workspace_dir, but being in
allowed_roots is what lets file tools write to it. ~/.revka/artifacts is
not a default root: add it to allowed_roots yourself if a workflow needs to
write there directly. If a workflow write fails with the approval message
(not a path message), the path is not the problem; the file_write tool simply
wasn’t allowlisted.
The path sandbox applies to file tools regardless of level — even with
level = "full", writes are still confined to workspace_dir + allowed_roots
and blocked under forbidden_paths. Autonomy controls whether a tool runs; the
sandbox controls where files may go.
What runs where: workflow execution is not channel-gated
Section titled “What runs where: workflow execution is not channel-gated”run_workflow starts the run as a background task in the workflow engine and
returns immediately. The workflow’s internal steps (agent spawns, shell, file
steps, …) are governed by the workflow engine, not by the channel approval
gate that governs the agent’s direct tool calls during a chat turn.
The exception is intentional: a workflow that defines a human_approval step
will pause and ask you to approve it from the channel (reply to the approval
prompt). That’s a feature of the workflow, not the autonomy policy.
Security posture: targeted vs. full
Section titled “Security posture: targeted vs. full”| Approach | Config | Trade-off |
|---|---|---|
| Targeted (recommended) | level = "supervised" + allowlist only the specific tools | Least privilege; you add a tool the first time you genuinely need it. The path sandbox still confines all file writes. |
| Full | level = "full" | No approval gate for any channel tool call. Simplest; broadest blast radius. Reasonable for a single-owner, trusted bot. File writes are still sandboxed by allowed_roots. |
Limitation: no per-channel autonomy
Section titled “Limitation: no per-channel autonomy”Autonomy settings apply uniformly to all non-interactive channels. There is currently no per-channel override — you cannot, by configuration, auto-approve a tool on Telegram while still prompting on Discord. Per-channel granularity would require a code change.
Troubleshooting
Section titled “Troubleshooting”- “Denied by non-interactive channel approval policy” → a bare-named tool
isn’t allowlisted. To see which tool, set
[channels_config] show_tool_calls = trueand look for the❌ <tool>: …line, then add<tool>toauto_approve. - “Path not allowed by security policy …” / “Resolved path escapes workspace
allowlist …” / “Action blocked: autonomy is read-only” → the path sandbox or
read-only gate, not the approval gate. Check
allowed_roots,workspace_only, and thatlevelisn’tread_only. - Edits seem ignored → restart the daemon;
config.tomlis read at startup. always_askset to"*"→ this overridesauto_approveand the__rule, so everything is denied on channels. Remove it.
Reference: [autonomy] keys
Section titled “Reference: [autonomy] keys”| Key | Default | Effect |
|---|---|---|
level | "supervised" | read_only | supervised | full. On channels, full never prompts; supervised auto-denies anything not otherwise allowed. |
auto_approve | read-only tools (file_read, web_search_tool, web_fetch, calculator, glob_search, content_search, image_info, weather) | Tools that skip approval. User entries are merged with the defaults. Accepts "*". |
always_ask | [] | Tools that always require approval; overrides auto_approve and the __ rule. On channels = always denied. Accepts "*". |
workspace_only | true | Confine file paths to workspace_dir + allowed_roots. |
allowed_roots | ["~/.revka/workflows"] | Extra roots file tools may read/write outside the workspace. |
forbidden_paths | system & sensitive dirs | Denylist for paths outside the workspace/allowed-roots. A path under an allowed_roots entry takes priority and is permitted. |
allowed_commands | a curated executable list | Allowlist for the shell tool. |
block_high_risk_commands | true | Hard-blocks patterns like rm -rf, chmod 777, curl | sh. |
require_approval_for_medium_risk | true | Approval gate for medium-risk shell commands. |
non_cli_excluded_tools | [] | Tools removed entirely from non-CLI channels (invisible, not just denied). |
max_actions_per_hour | 20 | Per-policy hourly action budget. |
max_cost_per_day_cents | 500 | Per-policy daily spend guardrail. |