Why a permission gate
A Kraft worker runs with nobody watching, so a permission prompt has nobody to answer it. This page explains why Kraft answers those prompts itself, and why it does that differently for each harness. For what the gate does and how to configure it, see Permission gate.
A permission prompt is the agent CLI asking whether it may run one tool call, such as a shell command or a file edit. The gate is Kraft's answer to it: the CLI asks, Kraft decides from the task's policy, and the decision lands on the work item's timeline.
The problem with an unattended prompt
Left to a harness's own judgment, an unattended agent does one of two things. It stalls on a tool it is unsure about, or it runs the tool anyway because nobody is watching. Neither is acceptable when you want to know what a worker was allowed to do.
The third option
Kraft's permission gate is a third option. The worker asks Kraft, Kraft answers from the task's own policy, and the answer, allow or deny and why, lands on the work item's timeline. You do not have to watch a session to know what it was let do.
The policy comes from three lists, which you set at any layer from the repository down to the task:
allowed_tools, the tools a task may use;deny_tools, the tools it may not; andgrants, named operations such asgit-pushthat a task may run (see Grant).
See How a policy resolves for how those layers combine.
Why it works differently per harness
The gate has to meet each CLI where that CLI offers a hook. The harnesses differ, so Kraft uses the strongest mechanism each one has.
| Harness | What Kraft uses | Reaches the gate |
|---|---|---|
| Claude Code | Its permission prompt tool, pointed at Kraft | Yes |
| Cursor, Codex | A hook Kraft installs, run before every tool call | Yes |
| OpenCode, Amp | The task's lists, written into the CLI's own permission rules | No |
| Gemini, Antigravity | Nothing: the CLI runs in its most permissive mode | No |
Permission gate has the full table. The rest of this section says why each row is what it is.
Claude Code settles most tool calls itself with its own classifier, such as a read-only shell command or a routine edit. What it will not settle, it hands to a permission prompt tool. Kraft points that at its own tool, so the CLI calls into Kraft instead of prompting a person who is not there.
How much reaches the gate follows from Claude's permission mode:
- With no
allowed_tools, Claude runs inauto, and the gate sees only what the classifier declines to settle. - With an
allowed_toolslist, Claude switches tomanual, and every call the built-in allowlist does not cover reaches the gate.
An allowed_tools list is what makes the gate worth configuring.
Cursor and Codex have no prompt tool, but each runs a hook before every tool call. Kraft installs a hook that asks the same gate. Kraft scopes the hook to the launch: Cursor's is written into the worktree and kept out of your commits, and Codex's is passed per launch and trusted by hash, never by bypassing Codex's hook trust, since that would trust every hook a repository ships.
Gemini and Antigravity have no hook Kraft can use and no way to enforce a
tool list. Gemini runs with --approval-mode yolo and Antigravity with
--dangerously-skip-permissions, so their calls never reach the gate.
In a stricter mode, each one would silently deny the calls a worker needs, such
as a shell command, because nobody is there to approve them. A task that sets
deny_tools or allowed_tools refuses to launch on either one instead of
running with a policy it cannot keep.
OpenCode and Amp offer no reliable per-call hook. Kraft writes the task's
deny_tools and allowed_tools into the CLI's own permission configuration
for that one launch, and the CLI enforces them itself. The trade-off is that
these calls never reach the gate, so they leave no permission_decision on
the timeline.
Why a deny is final but an allow may not be
Kraft can only decide; the CLI still runs the call. A deny is a deny on every
harness. An allow is the gate's decision, and on Cursor it does not override
the CLI's own --auto-review classifier, so a call the gate allows can still
be refused. This is why grants are described
as the gate's decision and not a guarantee that a command will run.
Why grants are names, not command patterns
A grant such as git-push is a named operation, not a pattern like
git push *. A pattern also matches git push x; rm -rf y. Kraft's grant
matcher accepts only a call that is exactly one plain git invocation of the
granted subcommand, and refuses anything that could run something else. That
is also why grants are not written into OpenCode's or Amp's rules, where only a
pattern is available.
An escalation turn holds git-commit,
git-rebase, and git-push unless you narrow it, because an escalation that
rebased the branch has to push it. A gate's reviewer gets no such default.
Related
- Permission gate: what the gate does per harness and how to configure it.
- Unattended runs: how each harness runs when nobody can answer a prompt.
- Security: the default posture this page leads up to.