Stances

A rule does one of three things, and the middle one is the point:

stanceeffect
observerecords the firing and says nothing at all
adviseputs the reason into the model's context; refuses nothing
denyrefuses the tool call, with the reason and the remedy

observe and advise are not two ways of saying "not blocking yet". additionalContext enters the model's context and therefore changes its behaviour, which contaminates the rate the observation exists to measure. A rule that talks is intervening. That is why the two are named differently and why backtest numbers from an advise rule are not comparable with the numbers that justified promoting it.

That contamination is also what makes it measurable. amont-agent backtest --compliance reads the two stances against each other: what a model did after an advise fired, beside what it did after an observe fired and said nothing. The second is the rate the habit corrects on its own, and an advise that does not beat it is costing context tokens for nothing. See measuring and graduating.

Changing one

Takes effect on the next command; nothing to restart.

git config --global amont.agent.pipe-to-tail.stance observe
git config --global amont.agent.stance observe          # every rule

The ladder, most specific first:

rule.default_stance  <  amont.agent.stance  <  amont.agent.<id>.stance

then capped at the rule's own ceiling, and clamped to observe if the guard is switched off.

Ceilings

Every rule declares the loudest stance it may ever take. For most rules that is deny, and the ceiling changes nothing. A rule whose finding is an estimate — "this loop may make hundreds of requests" — is capped at advise: refusing a command on an estimate would claim a certainty the analysis does not have.

The cap is applied after every configured key, so neither amont.agent.stance deny nor the rule's own key can pass it, and graduate --to deny refuses a capped rule with the reason. amont-agent rules prints each rule's ceiling beside its stance.

Why git config and not a committed file

Promotion power stays on the machine, with the person. A rule that a committed file could promote to deny would mean cloning a repository hands it the power to refuse your shell commands.

This is enforced three times, because neither of the first two was enough.

  1. amont.conf's parser will not name these rules, so a committed manifest cannot reach them.
  2. amont's config reader lets a repository's committed policy set lines outrank system and global git config — right for a hook manager, wrong for a guard — so this project reads git config itself, without that ladder.
  3. Git's own search order ends at --local and --worktree, and the last file wins. A .git/config therefore outranked your --global answer without any of amont's machinery being involved — and the agent whose command was just refused can write one, since git config is not a command any rule here objects to. So the reader takes --global, then --system, and nothing else.

A stance answers to your own git config and to nothing a repository carries or a process standing in one can write. A file your global config includes or includeIfs counts as your own: git skips those for a scoped read unless asked, and this reader asks. graduate and demote write --global for the same reason.

One consequence worth stating plainly: git config amont.agent.<rule>.stance run inside a repository writes --local by default, and this tool will not read it. Pass --global, which is what every example here does.