Skip to main content

Deterministic, pre-execution policy enforcement for semantic actions in agent systems.

Project description

RuleGate

Deterministic, pre-execution policy enforcement for semantic actions in agent systems.

Source of Truth

The canonical source is github.com/actiongate-oss/rulegate. PyPI distribution is a convenience mirror.

Vendoring and forking are permitted under the terms of the BSL 1.1 license. If you vendor RuleGate, you must preserve the LICENSE file, preserve copyright headers in source files, and not remove or modify the BSL terms. The production use restriction applies to vendored copies. See SEMANTICS.md for the behavioral contract if you reimplement.


Quick Start

from rulegate import Engine, Rule, Ruleset, Context, PolicyViolation

engine = Engine()

def no_pii(ctx: Context) -> bool:
    return "ssn" not in str(ctx.kwargs.get("query", "")).lower()

@engine.guard(Rule("api", "search"), Ruleset(predicates=(no_pii,)))
def search(query: str) -> list[str]:
    return api.search(query)

try:
    results = search(query="find user")
except PolicyViolation as e:
    print(f"Blocked: {e.decision.violated_rules}")

Core Concepts

Rule

Identifies what's being policy-checked:

Rule(namespace, action, principal)

Rule("api", "search", "user:123")       # per-user policy
Rule("support", "escalate", "agent:42")  # per-agent policy
Rule("billing", "refund", "global")      # global policy

Ruleset

Ruleset(
    predicates=(no_pii, business_hours),  # all must pass (AND logic)
    mode=Mode.HARD,                        # HARD raises, SOFT returns decision
    on_store_error=StoreErrorMode.FAIL_CLOSED,
)

Predicates

A predicate is a callable that receives a Context and returns True (allow) or False (deny). Predicates must be pure functions — no I/O, no side effects, no mutations. All external state (time, configuration, session data) should be passed via meta:

def no_pii(ctx: Context) -> bool:
    return "ssn" not in str(ctx.kwargs.get("query", "")).lower()

def business_hours(ctx: Context) -> bool:
    return 9 <= ctx.meta["hour"] < 17

For diagnostics, wrap predicates in NamedPredicate:

from rulegate import NamedPredicate

no_pii_named = NamedPredicate("no_pii", no_pii)

If a predicate raises an exception, the action is blocked. A predicate that cannot execute cannot assert permission.

Context

Every predicate receives the full action context:

Context(
    rule=Rule("api", "search"),       # the rule being evaluated
    args=("hello",),                   # positional args to guarded function
    kwargs={"query": "find user"},     # keyword args to guarded function
    meta={"role": "admin", "hour": 14},# arbitrary metadata (time, session, etc.)
)

Decision

Every check returns a Decision:

decision.allowed           # bool
decision.blocked           # bool
decision.violated_rules    # tuple of predicate names that failed
decision.evaluated_count   # number of predicates evaluated
decision.reason            # BlockReason.POLICY_VIOLATION or None

Two Decorator Styles

@engine.guard(rule, ruleset)        # returns T, raises PolicyViolation
@engine.guard_result(rule, ruleset) # returns Result[T], never raises

Short-Circuit vs. Exhaustive

# Production path: stops at first failure
decision = engine.check(rule, ruleset)

# Diagnostic path: evaluates all predicates, reports every violation
decision = engine.check_all(rule, ruleset)

Determinism Guarantee

The allow/deny decision is always deterministic relative to the predicates and context. Specifically:

  • The store is write-only from the engine's perspective and is never consulted during evaluation. Predicate results are computed independently of store state.
  • Store audit write failures are counted and silently ignored. The on_store_error field on Ruleset is accepted for forward compatibility but is not consulted in v0.2 — the store is not in the decision path.
  • Given the same predicates and the same context, the same decision is produced every time.

Scope & Non-Goals

RuleGate does:

  • Pre-execution policy enforcement (all predicates must pass)
  • Stateless evaluation (decision depends only on predicates and context)
  • Short-circuit and exhaustive evaluation modes
  • Full decision explainability (which predicates failed and why)

RuleGate does not:

  • Make LLM or model inference calls
  • Perform rate limiting or throttling (use ActionGate)
  • Manage costs, budgets, or billing (use BudgetGate)
  • Provide authentication or authorization
  • Evaluate rules based on stored state or historical patterns
  • Make network calls or perform I/O during evaluation

See SEMANTICS.md for the formal behavioral contract.


Observability

engine.on_decision(lambda d: logger.info(f"{d.status}: {d.rule} {d.violated_rules}"))

Every decision includes: status, rule, ruleset, reason, violated_rules, evaluated_count. The store records evaluation outcomes for audit purposes but is never in the decision path.


Relation to ActionGate and BudgetGate

RuleGate is one of three composable primitives in the agent execution layer:

Primitive Limits Use case
ActionGate calls/time Rate limiting
BudgetGate cost/time Spend limiting
RuleGate policy predicates Policy enforcement

All three are deterministic, pre-execution, and decorator-friendly. They compose via stacking:

from decimal import Decimal

@actiongate_engine.guard(Gate("api", "search"), Policy(max_calls=100))
@budgetgate_engine.guard(Ledger("api", "search"), Budget(max_spend=Decimal("1.00")), cost=Decimal("0.01"))
@rulegate_engine.guard(Rule("api", "search"), Ruleset(predicates=(no_pii, business_hours)))
def search(query: str) -> list:
    ...

Benchmarks

python -m rulegate.bench

Single-thread latency, CPython 3.12, default GC, no PYTHONOPTIMIZE. Measured on Linux (container, 2 vCPU). Run bench_rulegate.py on your target hardware — Docker, VM, and bare metal will produce different tail profiles:

Scenario p50 p95 p99
1 trivial predicate ~4μs ~7μs ~12μs
5 enterprise predicates ~4.5μs ~7μs ~12μs
10 predicates (all pass) ~4.5μs ~7μs ~12μs
NullStore (no audit) ~3μs ~3μs ~5μs

Predicate count adds ~30–50ns per predicate. The MemoryStore audit write is the dominant fixed cost (~1.5μs). Decision logic is bounded at 3–6μs regardless of composition.


API Reference

Type Purpose
Engine Core policy evaluation
Rule Action identity tuple
Ruleset Policy configuration (predicates + mode)
Context Immutable context passed to predicates
Decision Evaluation result with full diagnostics
Result[T] Wrapper for guard_result
PolicyViolation Exception from guard
NamedPredicate Predicate with human-readable name
MemoryStore Single-process audit backend
Enum Values
Mode HARD, SOFT
StoreErrorMode FAIL_CLOSED, FAIL_OPEN
Status ALLOW, BLOCK
BlockReason POLICY_VIOLATION, STORE_ERROR

License

RuleGate is licensed under the Business Source License 1.1.

Licensor:             actiongate-oss
Licensed Work:        RuleGate
Additional Use Grant: None
Change Date:          2030-02-25 (four years from initial publication)
Change License:       Mozilla Public License 2.0

What this means: You may copy, modify, create derivative works, redistribute, and make non-production use of RuleGate. The Additional Use Grant is "None", which means any use in a live environment that provides value to end users or internal business operations — including SaaS, internal enterprise deployment, and paid betas — requires a commercial license from the licensor. On the Change Date, RuleGate becomes available under MPL 2.0 and the production restriction terminates. Each version has its own Change Date calculated from its publication.

If you vendor RuleGate: Preserve the LICENSE file and copyright headers. Do not remove or modify the BSL terms. The production restriction applies to all copies, vendored or otherwise.

Licensing difference from siblings: ActionGate and BudgetGate are Apache 2.0. RuleGate is BSL 1.1. If composing all three, ensure your use complies with both license terms.

See LICENSE for the legally binding text.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rulegate-0.3.0.tar.gz (27.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rulegate-0.3.0-py3-none-any.whl (17.4 kB view details)

Uploaded Python 3

File details

Details for the file rulegate-0.3.0.tar.gz.

File metadata

  • Download URL: rulegate-0.3.0.tar.gz
  • Upload date:
  • Size: 27.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for rulegate-0.3.0.tar.gz
Algorithm Hash digest
SHA256 0ed13899cd650d19e0222aa31d574f7e53c1b84fe4097e5c2ccec73b1e38ffa8
MD5 93ed1d3208d4476396690eda5ec50529
BLAKE2b-256 535ddb1ac5439669209a48b238b111ac6a473ca34bcae823ec91de32cbeb6be3

See more details on using hashes here.

File details

Details for the file rulegate-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: rulegate-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 17.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for rulegate-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a85d296d6ab9d043049a9aaff3c507917b45256906cb25bf8498b362d1fc2759
MD5 fcf329c3cbc5c4afeab869f80fc8eaf4
BLAKE2b-256 a72adfa681cd3e8f765786a55ff57fd85ad1fa234f83523bf860e5c2a3713f62

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page