v2.0.0next
Concepts

Why a permission gate

Why Kraft answers a worker's permission prompts itself, and how each harness lets it.

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; and
  • grants, named operations such as git-push that 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.

HarnessWhat Kraft usesReaches the gate
Claude CodeIts permission prompt tool, pointed at KraftYes
Cursor, CodexA hook Kraft installs, run before every tool callYes
OpenCode, AmpThe task's lists, written into the CLI's own permission rulesNo
Gemini, AntigravityNothing: the CLI runs in its most permissive modeNo

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 in auto, and the gate sees only what the classifier declines to settle.
  • With an allowed_tools list, Claude switches to manual, 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.

  • 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.
Copyright © 2026