Skip to content

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.

SurfaceModeWhat happens on a tool that needs approval
CLIInteractiveYou get a [Y]es / [N]o / [A]lways prompt
Channels (Telegram, Discord, …)Non-interactiveThere 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.

A single file write passes through two independent checks. They fail with different messages and have different fixes:

  1. Tool-approval gatemay this tool be called at all? Driven by [autonomy] level, auto_approve, and always_ask. Failure message: “Denied by non-interactive channel approval policy.”

  2. Path sandboxwhere may a file tool read or write? Driven by [autonomy] workspace_only, allowed_roots, and forbidden_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.

For each tool call on a channel, Revka evaluates, in order:

  1. level = "full" → never requires approval (everything allowed).
  2. level = "read_only" → side-effecting tools are blocked downstream.
  3. always_ask contains "*" or the tool name → always requires approval → auto-denied on channels (this overrides everything below).
  4. The shell tool → governed by its own command allowlist (allowed_commands / block_high_risk_commands), not the outer gate.
  5. MCP-namespaced tools (name contains __, e.g. revka-operator__run_workflow, kumiho-memory__kumiho_memory_engage) → auto-approved on channels.
  6. auto_approve contains "*" or the tool name → allowed.
  7. Otherwise (the supervised default) → requires approval → auto-denied on channels.

This rule (step 5 vs step 7) is the crux of most channel issues:

  • Built-in tools have bare namesfile_read, file_write, shell, tool_search, browser, etc. They are not auto-approved on channels. Only the read-only ones in the default auto_approve list 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.

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 tools

The end-to-end channel flow then completes:

tool_searchfile_write (YAML into ~/.revka/workflows/) → run_workflowget_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.

ApproachConfigTrade-off
Targeted (recommended)level = "supervised" + allowlist only the specific toolsLeast privilege; you add a tool the first time you genuinely need it. The path sandbox still confines all file writes.
Fulllevel = "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.

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.

  • “Denied by non-interactive channel approval policy” → a bare-named tool isn’t allowlisted. To see which tool, set [channels_config] show_tool_calls = true and look for the ❌ <tool>: … line, then add <tool> to auto_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 that level isn’t read_only.
  • Edits seem ignored → restart the daemon; config.toml is read at startup.
  • always_ask set to "*" → this overrides auto_approve and the __ rule, so everything is denied on channels. Remove it.
KeyDefaultEffect
level"supervised"read_only | supervised | full. On channels, full never prompts; supervised auto-denies anything not otherwise allowed.
auto_approveread-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_onlytrueConfine file paths to workspace_dir + allowed_roots.
allowed_roots["~/.revka/workflows"]Extra roots file tools may read/write outside the workspace.
forbidden_pathssystem & sensitive dirsDenylist for paths outside the workspace/allowed-roots. A path under an allowed_roots entry takes priority and is permitted.
allowed_commandsa curated executable listAllowlist for the shell tool.
block_high_risk_commandstrueHard-blocks patterns like rm -rf, chmod 777, curl | sh.
require_approval_for_medium_risktrueApproval gate for medium-risk shell commands.
non_cli_excluded_tools[]Tools removed entirely from non-CLI channels (invisible, not just denied).
max_actions_per_hour20Per-policy hourly action budget.
max_cost_per_day_cents500Per-policy daily spend guardrail.