# How the DeepSeek-Reasonix Permission Layer Implements Per-Call Gating with Allow/Ask/Deny Rules

> Discover how DeepSeek-Reasonix implements per-call gating with its permission layer. Learn about Deny Ask Allow rules and the Gate runtime for secure tool invocation.

- Repository: [YHH/DeepSeek-Reasonix](https://github.com/esengine/DeepSeek-Reasonix)
- Tags: internals
- Published: 2026-06-22

---

**The DeepSeek-Reasonix permission layer implements per-call gating through a deterministic precedence system where Deny rules override Ask rules, which override Allow rules, using a pure `Policy` structure evaluated by a runtime `Gate` that intercepts every tool invocation.**

The permission system in the DeepSeek-Reasonix repository provides fine-grained access control for AI agent tool calls. It evaluates every invocation against configurable allow, ask, and deny rules before execution. This article examines the implementation details found in the source code, specifically how the `Gate.Check` method serves as the single entry point for permission decisions.

## Core Architecture: Decision, Policy, and Gate

The system rests on three tightly-coupled concepts that separate configuration from runtime logic.

### The Decision Enum

The `Decision` type defines three possible outcomes for any policy evaluation: `Allow`, `Ask`, and `Deny`. These constants represent the final verdict on whether a specific tool call may execute immediately, requires user confirmation, or is forbidden entirely.

### Policy as Pure Configuration

The `Policy` struct is a pure, I/O-free data structure defined in [`internal/permission/permission.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/permission/permission.go). It contains a fallback `Mode` and three rule lists:

```go
type Policy struct {
    Mode  Mode   // fallback when no rules match
    Allow []Rule // auto-allow patterns
    Ask   []Rule // prompt user patterns  
    Deny  []Rule // auto-deny patterns
}

```

Because `Policy` contains no I/O dependencies, it can be unit-tested in isolation without mocking external systems.

### Gate as the Runtime Enforcer

The `Gate` struct wraps a `Policy` and an optional interactive `Approver`. It provides the `Check` method—the only function the agent calls to decide whether a tool may run. This design ensures that all permission logic funnels through a single controlled entry point.

## Parsing Permission Rules

Rules are textual specifications such as `Bash(go test:*)` or `Edit`. The `ParseRule` function converts these strings into structured `Rule` objects containing `Tool`, `Subject`, and `Literal` fields.

The parser supports three syntax variants:

- **ToolName** – matches any subject for that tool (e.g., `Edit`)
- **ToolName(glob)** – matches a glob pattern against the subject (e.g., `Bash(ls *)`)
- **ToolName=literal** – matches the subject exactly (legacy form)

See the parsing logic in `ParseRule` located at [`internal/permission/permission.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/permission/permission.go) lines 72-94.

## Policy Evaluation and Precedence Logic

When a tool call arrives, the evaluation chain follows strict deterministic precedence.

### The Decision Chain

The `Policy.Decide` method extracts subjects from JSON arguments and delegates to `DecideSubjects`. The precedence hierarchy is explicit:

```

Deny > Ask > Allow > (read-only ? Allow : Mode)

```

This means a single deny rule matching any subject immediately blocks the call, regardless of allow rules present.

### Subject Extraction

For multi-endpoint tools like `move_file`, the `Subjects` function extracts all relevant paths (e.g., `source_path` and `destination_path`). The `DecideSubjects` method ensures that multi-subject tools are allowed only when **every** endpoint satisfies the same decision threshold, preventing partial permission loopholes.

The core evaluation logic lives in `Policy.DecideSubject` (lines 38-53) and the `matchAny` helper (lines 75-92) in [`internal/permission/permission.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/permission/permission.go).

## Per-Call Gating with Gate.Check

`Gate.Check` serves as the definitive per-call gate. Its signature accepts the tool name, raw JSON arguments, and a read-only flag:

```go
func (g *Gate) Check(ctx context.Context, toolName string,
    args json.RawMessage, readOnly bool) (bool, string, error)

```

### Execution Flow

1. **Read-only override**: If the tool is `bash` but the command is read-only (e.g., `git status`), the call is treated as `readOnly = true` (lines 64-68).

2. **Policy consultation**: The gate calls `g.Policy.Decide(toolName, readOnly, args)` to determine the preliminary decision.

3. **Decision handling**:
   - **Deny**: Immediately returns `allow = false` with a canned denial message.
   - **Ask**: If no `Approver` is configured (non-interactive runs), the gate auto-allows to maintain autonomy. Otherwise, it invokes `Approver.Approve` to solicit user input. If the user selects "remember," the rule is appended to `g.Policy.Allow` and the optional `OnRemember` callback fires (lines 84-99).
   - **Allow** (or fallback): Returns `true` to permit execution.

The complete implementation resides in `Gate.Check` at lines 60-102 of [`internal/permission/permission.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/permission/permission.go).

## Subject Extraction and Pattern Matching

### Glob Implementation

The `matchGlob` function (lines 105-133) implements shell-style pattern matching supporting `*` and `?` wildcards, where `*` matches across path separators. This enables rules like `Bash(go test:*)` to match complex command strings.

### Remember-Rule Generation

When users opt to persist an allowance, `RememberRuleForScope` (lines 140-176) constructs the appropriate rule string for the current scope—whether that's `Bash(go test:*)`, `Edit`, or the bare tool name.

## Practical Implementation Examples

### Constructing a Policy

```go
p := permission.New(
    "ask",                               // default mode for writers
    []string{"Bash(ls *)", "Edit"},      // allow list
    []string{"Bash(git push *)"},        // ask list  
    []string{"Bash(rm -rf *)"},          // deny list
)

```

### Initializing the Gate (Interactive Mode)

```go
gate := permission.NewGate(p, myApprover) // myApprover implements Approver interface

```

### Querying a Specific Call

```go
allowed, reason, err := gate.Check(ctx, "bash",
    []byte(`{"command":"git push origin main"}`), false)

```

In this example, `Policy.Decide` identifies `Bash(git push *)` in the Ask list, triggering the approver flow. If the user selects "always allow," the rule is dynamically added to `p.Allow` for the session duration.

### Non-Interactive Auto-Allow

For automated environments, implement a nil approver that treats all Ask decisions as Allow without persistence:

```go
type NilApprover struct{}
func (a *NilApprover) Approve(ctx context.Context, tool, subject string,
    args json.RawMessage) (bool, bool, error) {
    return true, false, nil // allow, don't remember
}

```

## Summary

- The permission layer uses a **three-tier precedence system** (Deny > Ask > Allow) enforced at [`internal/permission/permission.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/permission/permission.go).
- **`Gate.Check`** is the sole entry point for per-call gating, handling read-only overrides and interactive approval flows.
- **Policy structs are pure data** without I/O, enabling deterministic unit testing of permission logic in isolation.
- **Multi-subject tools** require all endpoints to satisfy the same decision level, preventing security gaps in operations like file moves.
- Rules support **glob patterns** with `*` and `?` wildcards, parsed by the `ParseRule` function using shell-style matching semantics.

## Frequently Asked Questions

### What happens if a tool call matches both Allow and Deny rules?

Deny always takes precedence. The `DecideSubjects` method evaluates deny rules first via the `matchAny` helper, ensuring that a single deny match immediately returns `Deny` regardless of any matching allow rules.

### How does the system handle multi-parameter tools like move_file?

The `Subjects` function extracts all relevant paths (source and destination), and `DecideSubjects` requires that **every** subject passes the same permission threshold. This prevents scenarios where the source is allowed but the destination is denied, or vice versa.

### Can the permission policy be modified at runtime?

Yes. When a user selects "remember" during an interactive approval, the gate dynamically appends the new rule to the in-memory policy's Allow list via `g.Policy.Allow = append(g.Policy.Allow, rule)`. The optional `OnRemember` callback persists this change to storage if configured.

### What is the difference between Mode and the explicit rule lists?

`Mode` serves as the fallback decision when no explicit rules match. If a call is read-only and no rules apply, it defaults to `Allow`. For write operations with no matching rules, it follows the configured Mode (typically "ask" or "deny"). Explicit Allow/Ask/Deny rules always override the Mode.