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" }
]
}
| Field | Type | Description |
|---|---|---|
default_action | "allow" | "deny" | Action for tool calls that match no rule. Defaults to "allow" if omitted. |
rules[].hook | string | Hook name (local_fs, local_shell, a remote hook name, …) |
rules[].tool | string | Tool 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.
| Name | Behaviour |
|---|---|
hitl-policy-default.json | Prompts for filesystem writes, sed, and shell commands; allows everything else |
hitl-policy-strict.json | Deny-by-default; only the rules listed are prompted |
hitl-policy-dev.json | default_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
- Open Admin → HITL Policies (
/hitl-policies). - Browse the policy list; the currently active policy is marked with an Active badge.
- 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:
- Read the
hitl-policy-namekey from the KV store. - If set, load that file from the VFS (
.contenox/). - If the key is empty or the file is missing, fall back to
hitl-policy-default.json. - If that file is also missing, use a built-in allow-all policy (equivalent to
hitl-policy-dev.json).