Permission gate
How Dispatch keeps writes to external systems and to its own policy under control.
Dispatch acts on real accounts, so a write that goes wrong has consequences: a draft sent to the wrong person, a calendar invite the user did not authorize, a permission rule loosened without consent. The permission gate is the mechanism that decides, per tool call, whether a write goes through.
Two pieces work together: a static risk annotation on the tool, and a runtime validator that compares the proposed action to the user's policy.
Risk annotations on tools
Every tool either carries a risk annotation or it does not. Tools without one are not gated. The gate is a no-op for reads, for the file-system tools when targeting a non-policy path, and for anything else that does not touch external state or the policy doc.
Two risk kinds exist:
external-write: the tool writes to a system outside Dispatch. Sending an email, creating a calendar event, posting to Slack.policy-write: the tool edits the permission policy document at/preferences/permissions.md. Gated because broadening the agent's own authority is a privilege-escalation surface: the validator checks that the user actually consented to this policy change.
The file-write tools (write_file, edit_file, delete_file) are generic and do not carry a static annotation. The gate upgrades them dynamically to policy-write whenever the path argument is the permissions file.
The validator
When a gated tool is about to execute, the gate runs the validator. The validator is itself a small LLM call that receives:
- The tool name and a structured action summary (each tool can supply a
describeActionfunction so the validator sees "send to jane@acme.com, subject Re: lunch" rather than raw JSON). - The conversation messages so far, so it understands intent.
- The user's policy document, which encodes their standing rules.
- The trigger prompt for automated runs, so the validator can check that the proposed action is in scope of what was authorized.
- Whether the user is present (interactive chat) or absent (autonomous run).
The validator returns one of two verdicts:
- Allow. The tool executes normally. An audit row is written; telemetry records the verdict, the model used, the latency, and the validator's confidence.
- Deny. The tool does not execute. The agent receives a structured
PermissionDeniedResultwith the validator's reasoning and awhatWouldAuthorizestring explaining what would be needed to proceed.
If the validator times out or errors, the gate fails closed: the tool call is treated as denied and surfaced with a failClosed: true flag so the agent can retry or surface the block to the user.
The retry-loop guard
A common failure mode for an AI agent is the ask → deny → ask loop, where the model keeps retrying the same action without new user input. The gate keeps a per-run memo of denials keyed by action signature; a second identical denied attempt with no new input results in a hard stop. The agent is told the action was already denied and must wait for the user, not propose it a third time.
The policy document
Each organization has a permission policy at /preferences/permissions.md. The policy is plain prose. It does not use a structured rule language. The validator reads it on every gated call and uses it as the user's standing intent. Typical statements look like:
- Don't send emails to anyone outside @acme.com without asking first.
- Always create calendar events as "tentative" unless the user confirmed the time.
- Slack messages in #leadership require user confirmation.Policy edits go through the gate themselves (as policy-write), so the agent cannot quietly broaden its own permissions. A successful policy edit requires the same kind of explicit consent as any other risky write.
The migration bypass
Dispatch runs prompt-content migrations periodically: small autonomous edits to the user's preferences files when a new feature or default ships. Migrations declare their target paths up front. A migration's writes to its declared paths short-circuit the validator and pass through with verdict migration-bypass, still writing an audit row and telemetry event. A migration that tries to reach an external-write tool is a contract violation; the gate surfaces it to error tracking and routes through the normal validator path, which will almost certainly deny.
Audit and telemetry
Every call that actually consults the validator (or short-circuits to migration-bypass) writes an audit row, capturing the action summary, verdict, validator reasoning, model used, latency, and whether the user was present. While the permissionAgent feature flag is off the gate still emits the permission_check analytics event for would-be-checked calls but does not write an audit row; audit rows resume for those calls once the flag is on for the org. The combination supports two things: an after-the-fact audit trail the user can inspect, and aggregate signal we use to evaluate the validator's accuracy over time.
Rollout status
The validator-driven enforcement path is gated behind the permissionAgent feature flag. With the flag off, gated tools still emit telemetry (so we can see the would-be-checked volume before flipping the flag on) but pass through without consulting the validator. The risk annotations, audit rows for migration bypasses, and the user-facing surfaces for the policy document and audit log are live regardless of the flag. The flag will flip on per-org as we complete the rollout.
For agents
- Tools carry an optional
riskannotation:external-write(touches external systems) orpolicy-write(edits the permission policy doc). - Tools without a
riskannotation are never gated. - File-write tools upgrade dynamically to
policy-writewhen theirpathargument is/preferences/permissions.md. - The validator is an LLM call that returns allow/deny with a structured reason. Inputs: action summary, conversation, policy document, trigger prompt for automated runs, user-presence flag.
- A second identical denied attempt in one run triggers a hard stop; the agent must surface the block and wait for the user.
- Every gated call that consults the validator (or short-circuits to
migration-bypass) writes an audit row. With thepermissionAgentflag off, gated calls still emit thepermission_checkanalytics event but do not write an audit row. - Validator enforcement is currently behind the
permissionAgentfeature flag; rollout is in progress. Telemetry is collected regardless of the flag.