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.2.3.tar.gz (23.7 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.2.3-py3-none-any.whl (16.6 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for rulegate-0.2.3.tar.gz
Algorithm Hash digest
SHA256 bed009e1b0934a60ecf1e3ffc93073149711f2a5b64cab013cf48735c4e66cb5
MD5 6c145309f165176d7cb8c93c14c15215
BLAKE2b-256 9bebb6c0fc98e80634bda22979052a8a26d337cea22d2803b0499a845e6f5ea6

See more details on using hashes here.

File details

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

File metadata

  • Download URL: rulegate-0.2.3-py3-none-any.whl
  • Upload date:
  • Size: 16.6 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.2.3-py3-none-any.whl
Algorithm Hash digest
SHA256 a45bdb03a4cfb476004663495ab5e668cf959d33f853d94a39455f829f15eb2e
MD5 b555db3e8c00a5229774c011be93b07a
BLAKE2b-256 d0d658a0dce6240e9ba73ad06c85eb5aa45fd504b07e6e395cd50d139ded93d0

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