In your editor
CI is the last place a policy should fail. This page is about the loop before that — running Tirith on your own machine, while the code is still being written.
It matters most when an agent is drafting the policy for you. Generated policy JSON is plausible by construction: it parses, it looks right, and a policy that matches nothing is indistinguishable from one that works until you evaluate it.
The loop
tirith fmt .tirith/policies # layout
tirith lint .tirith/policies # shape
tirith -policy-path .tirith/policies -input-path plan.json --fail-on-error # meaning
Linting checks the shape: that every condition type exists, that no provider_args key
belongs to a different provider, that eval_expression names every evaluator, that
error_tolerance is inside condition where the engine reads it. Only evaluation checks the
meaning. Lint and format covers both commands in full.
Run both against a document that should fail. A guardrail only ever seen passing is a guardrail nobody has tested.
VS Code tasks
Drop this in .vscode/tasks.json and the loop becomes one keystroke:
{
"version": "2.0.0",
"tasks": [
{
"label": "Tirith: format policies",
"type": "shell",
"command": "tirith fmt .tirith/policies",
"problemMatcher": [],
"group": "test"
},
{
"label": "Tirith: lint policies",
"type": "shell",
"command": "tirith lint .tirith/policies",
"problemMatcher": [],
"group": "test"
},
{
"label": "Tirith: check the plan",
"type": "shell",
"command": "tirith -policy-path .tirith/policies -input-path plan.json --fail-on-error",
"problemMatcher": [],
"group": {"kind": "test", "isDefault": true},
"dependsOn": ["Tirith: lint policies"]
}
]
}
Tirith: check the plan is the default test task, so ⇧⌘B / Ctrl+Shift+B runs the whole
loop: lint first, then evaluate, because dependsOn will not run the second if the first exits
non-zero.
Catch it at commit time
Tirith publishes both commands as pre-commit hooks:
repos:
- repo: https://github.com/StackGuardian/tirith
rev: 1.2.1 # the first tag that publishes the hooks
hooks:
- id: tirith-lint
- id: tirith-fmt
pre-commit install
Both run only on the policy files a commit actually touches, need no network, and exit 3 on a
finding. See CI integration for why they lint rather than evaluate, and
lint and format for the flags and the files they read.
When an agent is writing the policy
Install the Tirith skill and your agent gets the closed condition list, the argument key each provider reads, and the instruction to run a policy before claiming it works:
curl -fsSL https://stackguardian.github.io/tirith/skill.sh | sh
Add --cursor for the Cursor rule. Agent Skills covers what is in the pack,
the other clients, and how to tell whether it took effect.
Two things make the difference between a drafted policy and a working one:
- The agent has
tirithon PATH. It is an ordinary command, so an agent with a shell can lint and evaluate its own work without any protocol server or plugin. - You give it a document that should fail. Ask for the policy and a plan that violates it,
then check that the exit code is
3. If it is0, the policy matched nothing.
Reading a failure without leaving the terminal
tirith --json -policy-path .tirith/policies -input-path plan.json > result.json
tirith ui --result result.json # needs the optional [tui] extra
The explorer names the resource behind each finding — its address, the planned action, and the attributes that changed — which the pretty printer does not show. See the interactive interface.