HITL Policies

Human-in-the-loop (HITL) lets you intercept tool calls before they execute and decide — approve, block, or let them pass automatically — based on a named policy file.

How it works

When HITL is enabled, the engine wraps every tool invocation in a HITLWrapper. Before each call executes, the wrapper evaluates the active policy and takes one of three actions:

  • approve — pause and prompt the user; execution continues only after explicit approval
  • allow — pass through silently (no prompt)
  • deny — reject the call immediately without prompting

In the CLI, approval prompts appear inline in the terminal (TTY). In Beam, a card appears in the chat interface for the user to approve or reject.

Policy file format

A policy is a JSON file with an optional default_action and a list of rules:

{
  "default_action": "deny",
  "rules": [
    { "hook": "local_fs",    "tool": "write_file",  "action": "approve" },
    { "hook": "local_fs",    "tool": "sed",         "action": "approve" },
    { "hook": "local_shell", "tool": "local_shell", "action": "approve" }
  ]
}
FieldTypeDescription
default_action"allow" | "deny"Action for tool calls that match no rule. Defaults to "allow" if omitted.
rules[].hookstringHook name (local_fs, local_shell, a remote hook name, …)
rules[].toolstringTool name within that hook (write_file, sed, local_shell, …)
rules[].action"approve" | "allow" | "deny"What to do when this rule matches

Rules are evaluated top-to-bottom; the first match wins.

Built-in presets

Contenox ships three policy presets. They are written to .contenox/ on first run and can be edited in the Beam UI.

NameBehaviour
hitl-policy-default.jsonPrompts for filesystem writes, sed, and shell commands; allows everything else
hitl-policy-strict.jsonDeny-by-default; only the rules listed are prompted
hitl-policy-dev.jsondefault_action: allow — silent pass-through; useful for local development

Enabling HITL

Pass --hitl on any command that runs chains:

contenox run   --hitl --chain .contenox/my-chain.json "refactor main.go"
contenox chat  --hitl "move the config loading to its own function"
contenox plan next --hitl

Or set the environment variable to make it permanent for a shell session:

export CONTENOX_HITL_ENABLED=true
contenox run "do the thing"

Selecting the active policy

CLI

contenox config set hitl-policy-name hitl-policy-strict.json
contenox config get hitl-policy-name   # verify

This writes to the KV store and takes effect immediately — no restart required. The setting applies to all subsequent --hitl invocations in the same data directory.

Beam UI

  1. Open Admin → HITL Policies (/hitl-policies).
  2. Browse the policy list; the currently active policy is marked with an Active badge.
  3. Hover a policy and click Set active to switch.

The active policy updates immediately for all new requests.

Managing custom policies

In Admin → HITL Policies you can:

  • Create — click Create new; a draft policy is opened in the Monaco JSON editor.
  • Edit — select a policy from the list; edit inline; press Ctrl+S (or Cmd+S) or click Save.
  • Delete — hover a policy and click the delete icon; confirm the prompt.

Custom policies are stored in the VFS alongside the built-in presets and are immediately available for selection.

Policy resolution order

When HITL is enabled and a tool call needs evaluation, the engine resolves the policy as follows:

  1. Read the hitl-policy-name key from the KV store.
  2. If set, load that file from the VFS (.contenox/).
  3. If the key is empty or the file is missing, fall back to hitl-policy-default.json.
  4. If that file is also missing, use a built-in allow-all policy (equivalent to hitl-policy-dev.json).