How the gate decides

Last updated October 5, 2026

On this page

Draft for review

Everything an agent does goes through the gate: every step of its plan (read a collection, write a record, call a tool, send an email, issue a refund) is checked before it runs. The gate gives one of three answers: go ahead, wait for a named person to approve, or refuse. People's own actions pass the same gate.

This page explains the checks in the order the gate makes them, what each answer means, and how to ask the gate what it would decide before anything happens. By the end you can predict why the demo refund waits for Ada, and check your prediction with Try an action.

To follow along, start the demo company and sign in as described in the Quickstart: each person signs in by pasting the token ok demo serve prints for them. The demo runs without a model unless you set a provider key; a stub planner then answers the walkthrough's asks. Nothing on this page needs a real model.

This page describes the gate as it works today on the golive branch. What is not possible yet, and known problems, are listed at the end.

The three answers#

Answer What happens Shown in Try an action as
Allow The step runs now. Allow · Allowed
Require approval The step does not run. An approval with the exact payload (the email, the refund amount and invoice) goes to the approver's inbox, and the run waits. Approving runs that step and the run carries on; rejecting ends it. Require approval · Needs approval from ada
Deny The step is refused with a reason. The run is told why. Nobody is asked, because there is nothing to approve. Deny · Denied: ...

Explain adds a fourth answer, Conditional (It depends), when the result hinges on something it cannot see, such as whether the run has read a value, and lists each case. An approval that nobody could be found for, or for an action that could not run later, becomes a refusal instead.

The checks, in order#

The gate works through these checks for every step. The names in the first column are the labels you see in Try an action and on an approval's Why am I asked?.

# Check What it asks If it fails
1 Exists Is this a tool the company has, or a collection that exists? For a tool it also states the tool's risk, its data class, and whether it reaches outside the company. Denied ("stripe.charge is not a tool the kernel knows").
2 Route Can the tool be reached: is its connection set up, unlocked and holding working credentials? Denied before anyone is asked.
3 Arguments Do the tool's arguments pass its argument rules? See Argument rules on tools. Denied before anyone is asked.
4 Delegation held For an agent acting for someone: does it hold a delegation from that person? Denied ("churn-watcher holds no delegation from alice").
5 Kill switch Is a switch held on the whole company, on this agent or person, on their team, or on this tool? A paused module holds switches on its agents and tools. The global switch stops people's actions too, such as Tom reading tickets ("global kill switch engaged"). Denied ("Actor Churn-risk watcher is paused: held by the admin").
6 Active Is the caller still active (not offboarded)? Denied.
7 Skill When the step runs inside a skill: did the skill declare this tool, this collection (read or write), this data class, delegation, or this memory scope? Denied ("skill churn-watch did not declare tool gmail.send").
8 Delegation The limits of the delegation chain: not expired, covers this tool, collection and data class, allows this risk, and the cost fits its spending limit. Denied ("delegation does not cover tool support-ops.billing.refund").
9 Model For a model call: may this model see data of this class? Denied ("no allowed model for data class pii.customer").
10 Budget Every budget the step draws on: the agent's, its team's, the task's (and the task that started it), and the thread's. Denied ("budget agent:... exhausted").
11 Rule Company, pack and kernel rules that match. The highest priority wins. Whatever the winning rule says.
11 Default When no rule matches: the defaults, which compare the action's risk with the caller's trust. See Risk and trust. As the default says.
12 Module rule A separate layer of rules a module can set, checked after the rules above. They can only tighten: deny or require approval, never allow. The demo has none, so this step never appears there. Denied, or approval.
13 Approver When approval is needed: who would be asked, and whether a run would wait. Denied when nobody can be found.

The first refusal decides. In Try an action, the checks after it are still shown, greyed and marked (after the decision), so you can see everything you would have to change, not just the first thing.

Some checks are made again at the moment the step really runs, even after an approval: the kill switches, the argument rules (with what the run has read by then), the tool's rate limit, and what the caller may read in the collection. Explain lists these under Checked again when the call runs. Approving also checks the policy again first; see Inbox and approvals.

Risk tiers and trust tiers#

Risk belongs to the action, never to the agent. Trust belongs to the agent and caps how much risk it may take on its own.

Risk tier Examples
Read Reading a collection, recalling memory
Low Writing a record you own, a model call, private memory, posting in a thread, creating a thread, most read-only tools
Medium Writing a record someone else owns, deleting or rolling back a record, team or company memory, logging a thread decision, a schema proposal, delegating "act as"
High Tools with outside effects: sending email, replying to a customer, creating a calendar event, commenting on GitHub; promoting a skill
Critical Applying a schema change, changing policy, changing an actor, changing a connection; tools marked critical, such as support-ops.billing.refund

A tool's risk is set by the pack that defines it. Try an action shows it in the first step: "support-ops.billing.refund is a tool: risk critical, handles financial data, reaches outside the company".

Trust tier Highest risk on its own
Sandboxed (new agents) Low
Standard Medium
Trusted High

Every agent in the demo is standard.

The defaults, when no rule matches#

Caller Action Default
A person Critical, and the person is not an admin (roles admin or data_owner) Needs an admin
A person Anything else Allowed
An agent Critical Denied: agents propose critical changes and the change pipeline applies them
An agent Reaches outside the company Needs the person the agent acts for
An agent Above its trust tier Needs the person it acts for (a sandboxed agent: its manager)
An agent Within its trust tier Allowed

So, in the demo, the Churn-risk watcher may read Northwind's billing account and raise a ticket's severity on its own, and a calendar invite from the Sales Assistant waits for Alice because it reaches outside.

Rules#

Rules come from three places, shown in the Owner column of the rules table on Governance › Policy:

Owner Where they come from
Kernel Installed by OrchKernel itself, such as "Only a human admin changes a module".
A module (shown by the module's name, such as Support operations) Installed with a module, such as Support operations' refund rules. These are ordinary rules and are checked at the Rule step.
Company Added by your admins through the change pipeline or the policy repository. See Policy, rules and kill switches.

How they combine:

  • A rule matches on who (agents only, people only, named actors, teams, roles), what (actions, tools, collections, data classes, a minimum risk or sensitivity) and, for money, an amount cap.
  • Among the matching rules, the highest priority decides. At the same priority, deny beats require approval, which beats allow.
  • A matching rule replaces the default. That is how a module makes a critical tool usable by agents at all: without a rule, an agent's critical action is denied.
  • An amount cap fails closed: an amount the gate cannot read (missing, or text such as "$250") counts as over the cap.
  • Module rules (step 12), a separate layer, only tighten: they can turn an allow into approval or a deny, never the other way. They are not the same as the rules a module installs, which appear in the rules table.

A rule's effect is allow, deny, or require approval from one of these:

Approver Who is asked
The person the agent acts for (acting_for) The person who asked, else the task's owner, else the agent's escalation contact
Owner The thread's owner, else the task's owner
Manager The agent's escalation contact (its team lead)
A role, such as admin Anyone with that role; any of them may decide
A named actor That person

The agent itself is never its own approver.

Argument rules on tools#

A tool can carry rules on its arguments. They are checked before the gate's policy checks, so nobody is asked to approve a call that could not run, and again when the call runs. They apply to people and agents alike.

Rule The argument must Demo example
in_read_results Appear in a record or tool result the run has read, so a recipient or invoice the model made up is refused The refund's invoice; the support reply's ticket
company_domain Be an address on one of the company's domains The founder's email to, as an alternative to in_read_results
listed_in_connection Be listed under a key of the module's connection settings repo for GitHub comments, listed under repos
not_repeated Not have gone through this tool already, or be waiting for approval One refund per invoice

Rules on the same argument are alternatives (any one passing is enough), except not_repeated, which must always pass. Rules on different arguments must all pass.

In the demo the GitHub connection lists no repositories, so explain shows every review comment from the PR Reviewer refused at Arguments: "argument repo is not listed under repos in the tool's connection config".

Worked example: the demo refund#

Ticket T-1042: Northwind was charged twice and wants 250.00 back. Maya asks the Churn-risk watcher to deal with it. Predict the answer before you check it.

The watcher acts for Maya with her delegation, inside its skill churn-watch, and wants to call support-ops.billing.refund (critical, financial, outside) with 250.00 on invoice inv-2026-09-1042.

  1. Arguments. The invoice must be one the run read. The watcher read Northwind's account first, so it passes.
  2. Delegation held, kill switch, active, skill, delegation, budget. Maya delegated to the watcher up to critical risk and $50.00; nothing is paused; churn-watch declares the tool and financial data; the budgets have room.
  3. Rule. Two Support operations rules match, both for agents only: support-ops-refunds-need-admin (over 100.00, priority 20) and support-ops-credits-need-approval (every credit, priority 15). The higher priority decides: approval from the admin role.
  4. Approver. Ada is the only admin. On the real approval, Why am I asked? says "Ada Park would be asked, and the run would wait until they decide."

So the refund waits for Ada, and Maya, although the watcher acts for her, cannot approve it.

What changes the answer:

Change Answer Why
A 50.00 credit instead Still waits for Ada support-ops-credits-need-approval matches every agent credit. In the demo, no refund by an agent goes through on its own.
No rule at all Denied The default for an agent's critical action.
The Knowledge-base writer asks instead Denied Maya's delegation to it does not cover the tool, and the rule support-ops-writers-and-reviewers-never-reach-customers (priority 30) denies it too.
The same invoice again, after Ada approved Denied not_repeated: "Invoice inv-2026-09-1042 was already refunded (re_3Rk0a1Lq on 2026-10-05)". Explain does not show this today; see Known problems.
Maya calls the refund herself Waits for Ada Default: a critical action by a person who is not an admin. The invoice rule applies to people too, so explain still shows it as It depends.
Ada calls it herself Allowed by policy An admin's critical action needs no one else. Again It depends on the invoice having been read.
The watcher reads the billing account Allowed Low risk, within a standard agent's trust, no rule.

For a small refund to go through on its own, an admin would add a company allow rule for the refund tool with a priority between 15 and 20 (say 18). For 50.00 the allow rule then outranks "every credit"; for 250.00 the over-the-cap rule at 20 still wins and the refund waits for Ada. You can test this without changing anything: propose the rule and explain against the proposal (see For developers). We did, and got Allowed for 50.00 and Needs approval from ada for 250.00 (each still It depends on the invoice having been read). The proposal itself lands in Ada's inbox as "Change proposed: 1 policy rules (...)" until someone decides it; nothing changes until it is applied.

To see the real thing, follow the refund in the Quickstart. On the approval, Why am I asked? shows the same steps the gate took when the watcher asked.

Asking the gate first: Try an action#

Try an action on Governance › Policy asks the gate what it would decide for one step, and why. Nothing runs, nothing is recorded, nobody is asked.

You are You can ask for
An admin Anyone: any person, any agent, acting for anyone
Anyone else Yourself, and the agents that act for you (acting for you)

The list of people and agents under Who shows only those you may choose.

Explain the demo refund#

  1. Sign in as ada (or as maya: the watcher acts for her). Open Governance, then the Policy tab (Governance opens on Approvals). On a phone, the sidebar with Governance is behind the menu button at the top left. Try an action sits under Source of truth.

  2. Who: Churn-risk watcher (agent). An Acting for list appears, set to No one; choose Maya Patel.

  3. Action: Use a tool (the default). Tool: Issue a refund or credit · support-ops.billing.refund. Do not pick Issue refund · billing.refund, a different tool from the Support module. The tool's arguments appear as fields: Amount *, Invoice * and Reason *.

  4. Fill Amount 250, Invoice inv-2026-09-1042, Reason double charge, and press Explain.

  5. You should see a yellow Conditional badge, It depends, the rule support-ops-refunds-need-admin (pack:support-ops), and two cases:

    • If the run has read the value: Needs approval from ada
    • If the run has not read it: Denied: argument invoice passes if the run has read it

    Below come the steps (Exists, Route, Arguments, Delegation held, Kill switch, Active, Delegation, Budget, Rule, Approver: "Ada Park would be asked"), then Checked again when the call runs (switches, argument rules, "5 calls a minute allowed; 0 used in the last minute"), and the line Under the live policy 01806d4e0eb8 · ... · Nothing was executed or recorded.

  6. Change Amount to 50 and press Explain again. The rule is now support-ops-credits-need-approval, and the answer is still Needs approval from ada.

  7. Change Who to Knowledge-base writer (agent). Acting for goes back to No one; choose Maya Patel again. The tool and its fields keep their values. Press Explain. You should see a red Deny, Denied: delegation does not cover tool support-ops.billing.refund, failing at Delegation, with the later steps (the second delegation limit, the budget, the deny rule) marked (after the decision).

The answer is conditional because Try an action has no run, so it cannot know whether the agent read the invoice first. The API can explain inside a real run, and then answers plainly (below).

More to try in the demo#

Who Acting for Action You should see
Churn-risk watcher Maya Use a tool: Billing account Allowed; Default: "within what the agent's trust tier allows unattended"
Churn-risk watcher Alice Use a tool: Billing account Denied at Delegation held: Alice never delegated to it
Sales Assistant Alice Use a tool: Create calendar event · calendar.create Needs approval from alice: "an agent's external side effect needs the person it acts for"
Me, as tom Read a collection: tickets Allowed; under Checked again, which teams may read the tickets
Me, as maya Apply a schema change: tickets Needs approval from ada: a critical action by a person who is not an admin

To see a kill switch in an answer, as Ada:

  1. On the same page, in Kill switches, press Pause an agent, choose Churn-risk watcher under Agent and press Pause. The card now reads "Paused agents: Churn-risk watcher (an admin)".
  2. Explain any of its tools, for example Billing account acting for Maya. You should see Deny, Denied: actor churn-watcher is paused, and the step "Kill switch: Actor Churn-risk watcher is paused: held by the admin". The policy hash in the last line changes while the switch is held.
  3. Press Resume an agent, choose Churn-risk watcher and press Resume. "Paused agents: none" again.

Allowed for a read does not mean you see every row: the collection's own access decides that, and the note under Checked again says who may read it. An answer holds under the policy hash shown; the gate decides again when the step really runs.

Not possible yet#

  • Explaining inside a run, or against a proposed policy, from the screen. Try an action always explains outside any run and under the live policy; use the API or ok policy explain with --run or --proposal.
  • Explaining every kind of step from the screen. The screen offers the common ones; rollback, artifacts, thread decisions, playbook proposals and promotions are API and CLI only.

Known problems on this page#

  • Explain misses a repeated refund. Explain does not check not_repeated. After Ada approved the T-1042 refund, explaining the same refund still answered Needs approval from ada, while the watcher's real second attempt was refused ("Invoice inv-2026-09-1042 was already refunded").
  • The stub claims a refund it did not make. With no model, that refused second attempt still ends Done with the note "Refunded after an admin approved it."
  • The global switch stops people too. Its confirmation says "People can still work", but people's reads and writes are refused while it is on.
  • Why am I asked? starts at Delegation held. The steps stored on an approval leave out Exists, Route and Arguments, which explain shows.
  • Approver shown by id. In Try an action's answer the cases read Needs approval from ada rather than Ada Park.
  • "you would see only any row". Explaining a read of a collection open to every row of a team ends with this broken phrase.

For developers#

API#

POST /api/policy/explain with Authorization: Bearer <token>.

json
{
  "actor": "churn-watcher",
  "for": "maya",
  "skill": "churn-watch",
  "run": "<run id>",
  "policy": { "proposal": "<proposal id>" },
  "do": { "op": "tool", "tool": "support-ops.billing.refund",
          "args": { "invoice": "inv-2026-09-1042", "amount": 250, "reason": "double charge" } }
}

Everything but do is optional; actor defaults to the caller. do is one plan op as an agent writes it (tool, query, aggregate, upsert, delete, rollback, llm, post, artifact, decide, delegate, remember, recall, task, propose_playbook) or a syscall no op reaches (schema_apply, policy_change, actor_change, promote, connection_change, thread_create). For llm, data_class is a label string such as "pii.customer".

The answer holds decision (kind: allow, deny, require_approval with approvers and parks, or conditional with branches), matched_rule, steps and at_dispatch (each step: check, result, says, after_decision), policy (label, hash), at and executed: false.

With run set to a parked run you can see, the arguments are judged with what the run read, and the answer is plain: require_approval with "parks": true and "ada would be asked, and the run would wait until they decide".

With policy.proposal set to a Rules or Policy proposal you can see (admins and its proposer), the candidate decides and policy.label is proposal. A Rules proposal is made with POST /api/changes, {"kind": "rules", "rules": [...], "rationale": "..."}.

Status and code When
403 denied Explaining for someone you may not ("maya may not explain for sales-assistant")
404 not_found An unknown or hidden actor, run, task, thread or proposal
422 invalid A {{template}} value ("values are literal") or a malformed op
422 not_governed ask or output, which pass no gate
429 rate_limited An agent explaining more than 60 times a minute

GET /api/approvals/:id/explain returns { asked, now, changed }: the steps stored when the approval was asked, explain for the same call now, and whether the decision, approver rule or matched rule has changed since.

CLI#

sh
OK_TOKEN=<ada's token> ok policy explain --server http://127.0.0.1:8080 \
  --actor churn-watcher --for maya \
  tool support-ops.billing.refund \
  --args '{"invoice": "inv-2026-09-1042", "amount": 250, "reason": "double charge"}'

It prints the same answer as text, starting "churn-watcher for maya: it depends:". --op takes the whole op as JSON; --skill, --task, --thread, --run and --proposal fill the request's fields; --json prints JSON.

Events#

Explain records nothing. The real gate records a policy_evaluated event for every decision, budget_alert when a budget crosses its alert threshold, and approval_requested when it asks someone. See Events and the audit log.