Opinion · Governance

Hold, don't refuse: why deny-only AI policy gets routed around.

Every access control system you have ever used has a third answer. The door says no, and then there is a desk where someone can say yes. The expense is over the limit, and then a manager signs it. The repo is private, and then an owner adds you for a week. Most AI-assistant guardrails only have two answers. That sounds strict, and strict sounds safe. In practice it is the fastest way to make sure your policy stops mattering.

What people do with a permanent no

Picture a rule: the restructuring plan is readable by the planning team only. A director who is not on that team, but who has been asked by the CFO to model two scenarios this week, asks their assistant to summarise it. Refused. The director has a legitimate need and a deadline. Here is what happens next, in roughly this order:

  1. They ask again with different words. Refused.
  2. They ask someone on the planning team to paste the relevant pages into a chat. Now the document is in a DM, outside every control you built.
  3. They ask IT for access to the underlying folder directly, get it because the request sounds reasonable, and read it with no assistant and no audit line at all.
  4. Someone senior complains that "the AI thing blocks real work," and the rule is switched to alert-only for everyone.

Not one of those outcomes is what the rule's author wanted. The rule was right. The answer was wrong, because "no" was the only other answer available. A control that cannot say "yes, for this, for now, on the record" will be bypassed by the people it was written for.

The third answer has four properties

A hold is not a softer deny. It is a different thing, and it only works if all four of these are true.

1

Nothing is fetched while it is held.

If the data is already in the model's context when the approval request goes out, the approval is theatre. The hold has to happen before the upstream is contacted, the same place a refusal happens.

2

A named person decides, and it is the right person.

Not "an admin." The clause has an owner: HR Privacy, Legal, the CFO. They know whether this request is the sanctioned exception or the thing the clause exists to stop. If the approver is whoever is on call in IT, you have moved the judgment to the person with the least context.

3

The yes expires.

An approval is for this purpose, for a few hours. It is not a group membership. When it lapses, the rule is back, with no ticket to close and no access review to find it in six months.

4

It is on the record, with the name.

Every retrieval made under the approval carries who approved it and why. That turns an exception from a hole in the policy into evidence that the policy works: the refusal, the request, the approval, the retrievals and the expiry, in one chain.

Miss any one and you have something worse than deny-only. No pre-fetch hold means leaks with paperwork. No named owner means rubber stamps. No expiry means permission creep. No record means a back door.

Why this is harder for assistants than for people

A human hitting a locked door waits. An assistant is in the middle of a tool call with a client that will time out in under a minute, and the person who could approve is in a meeting. So the hold cannot be a blocked request. It has to be an answer: "this is held, not refused; request 3f9c1a is with HR Privacy; nothing was fetched; retry when it is approved." The assistant relays that, the person goes and asks, and the retry succeeds.

There is a second trap: approval fatigue. If every call asks a human, the human clicks yes without reading, and you have built the consent dialog everyone ignores. Holds have to be rare. The policy still does most of the work deterministically, allow the ordinary and refuse the never, and approve is reserved for the narrow band where the honest answer really is "it depends on why."

What it looks like

A rule is still a clause, with its owner. The only change is the action:

- rule_id: COC-HR-040
  clause: "The restructuring plan may be read only with CHRO approval."
  owner: chro@example.com
  enforce:
    - type: wall
      action: approve          # not deny
      domains: [restructuring-plan]
      allowed_users: [cfo@example.com]

The director's assistant gets a hold. The CHRO gets a message with who, which rule, which tool, and the clause text. They run one command, or click once:

one afternoon, one held call
14:02 ▸drive__restructuring_plan  →  held COC-HR-040 · request 3f9c1a2b7e · nothing fetched
14:31 ▸$ aggrete approve 3f9c1a2b7e --by chro@example.com --ttl 4h
14:33 ▸drive__restructuring_plan  →  allowed purpose: approved by chro@example.com
18:31 ▸drive__restructuring_plan  →  held the approval expired; the wall is back

Compare that with the four outcomes above. The document never left the governed path. The person with the context made the call. Nobody's access grew. And the rule is still set to enforce.

The honest limits

An approver can be wrong, or pressured, or lazy. A hold moves the judgment to a person; it does not make the person good at it. What it gives you is that the judgment is visible, attributable and time-boxed, which is the most any control gives you. And holds do not fix rules that are wrong. If a clause blocks half the company every Tuesday, the answer is to rewrite the clause, not to hire approvers.

This shipped in Aggrete 0.10, an open-source MCP policy proxy: any rule can say approve, the call is held before the fetch, the clause owner decides from Slack, the terminal, the API or the console, and the approval is a time-limited grant with their name on every line. Try it with nothing installed at try.aggrete.com.
Open source

Deterministic policy for what AI can reach and do.

Aggrete is Apache-2.0. No model in the decision path.

Star on GitHub How it works