# How Warden decides

What Warden checks, the three verdicts, how rule files combine, and what changes when nobody is watching the run.

## What gets checked

Every tool call the agent makes passes through the policy engine before it runs: shell commands, file reads and writes, web fetches, git operations, MCP tool calls, and the Software Factory's own moves (start a build, review, merge, open a pull request).

The engine reads what a call *does*, not just its text:

- **Wrappers are unwrapped.** `sudo`, `env`, `nohup`, `nice`, `time` and similar are peeled off, and both the wrapper and the inner command are judged. `bash -c "..."`, `sh -c` and a heredoc fed to a shell are read command by command.
- **Chains are split.** `&&`, `;`, pipes, subshells and new lines are judged one command at a time. If any part is denied, the whole line is.
- **Paths are resolved.** `~`, `$HOME`, `..` and symlinks are followed, so `cat ../../app/.env` is the same as reading `.env`.
- **Flags are read as flags.** `deny: "rm -rf"` also catches `rm -fr`, `rm -r -f` and `rm --recursive --force`. Every form of force-push is recognised.
- **Unreadable commands are flagged.** `eval`, `$(...)`, backticks, `python -c`, and `curl ... | sh` can't be read in advance. Their inner commands are still checked, and the line as a whole counts as *unverifiable*, which your rule set decides: Open runs it, Guarded asks, Hardened blocks it.

The check is deterministic. The same call under the same rules always gets the same verdict, and every verdict names the rule behind it.

## Three verdicts

| Verdict | In a chat you're in | In a run nobody is watching |
|---|---|---|
| {{allow}} | The action runs. | The action runs. |
| {{ask}} | Warden pauses and asks you. Nothing runs until you choose. | The action is **held** for your review. The agent is told it was held and not to work around it, and the run carries on. |
| {{deny}} | The action doesn't run. The agent is told which rule stopped it, why, and not to retry or work around it. | The same. |

When rules overlap, **deny always wins**, then ask, then allow. An allow rule can never override a deny. Approving an ask never approves a deny that also matches. Rule order in the file doesn't matter.

## Rule sets and your own rules

Your rules come from a few places, combined into one decision:

- **A rule set**: Open, Guarded or Hardened, a starting point of ready-made protections that you then tune, each set to Allow, Ask or Block. See [Rule sets and protections](rule-sets).
- **Your own rules**: short YAML rules for anything the protections don't cover. See [Writing rules](writing-rules).
- **Rules for automated runs**: an extra file that can only tighten what an unattended run may do. See [Automated runs](automated-runs).
- **Company rules**, on Warden Team: set by your company, locked on your Mac. See [Warden Team](team).

Each can be set **Global** (every project) or for **one project**. A project can tighten Global, never loosen it.

## Attended and unattended

Warden knows whether a person is present. Chats you start are *interactive*. Automations, Software Factory steps and assurance checks are *automated*. A rule can tell them apart with `when: { trigger_mode: automated }`, and the agent can't change which one it is.

A **sandboxed run** (the Factory's Express lane, or an *Autonomous (sandboxed)* chat) works in a throwaway worktree, inside a kernel sandbox, with network only through a proxy. There, an action that would ask you goes ahead if its effect stays inside the sandbox. Anything that would reach outside, such as a push, a deploy, a publish or a write outside the run, is held for your review and never run. A deny is still a deny.

## When the engine can't answer

Warden fails closed. If the policy check can't run, actions in a chat need your manual approval, and automated runs are held. The same goes for a rule file with an error in it: rather than skipping its rules, Warden asks about every action until the file is fixed, and the reason names the file and the problem.

## Where you see it

- **In the chat**: each tool call shows its verdict as it happens, with the rule.
- **Decisions** (in each project): a live feed of every verdict, filterable by Blocked, Asked, Allowed and Automated, with the rule and a proof of the decision.
- **Mission Control → Needs you**: every ask and held action waiting on a person, across chats and runs.

> [!LIMIT] Rules check commands, not programs
> Rules check each command before it runs, not what a running program does. An allowed `python script.py` can still read `.env` from inside the script. See [Known limitations](limitations).
