Skip to main content
The Evaluator interprets a policy bundle against a tool-call request and returns allow / deny synchronously. It runs in your process, with no network round-trip on the hot path.

Constructor

evaluate(request)

EvaluationRequest is an arbitrary object with tool_name (required) and an optional agent_id. Add any other fields your policies reference as dot-paths (input.command, kwargs.amount, etc.). Returns an EvaluationResult:

Algorithm

  1. Frozen-agent kill-switch. If bundle.frozenAgentIds contains request.agent_id (case-insensitive), return deny / AGENT_FROZEN before any rule fires.
  2. Empty-bundle fail-closed. If no bundle is loaded or the bundle has no policies, return deny / NO_POLICIES.
  3. Per-policy compile check. If any policy contains a matches rule whose regex failed to compile, return deny / POLICY_COMPILE_ERROR when that policy is reached.
  4. Rule scan. For every (policy, rule) pair, AND all conditions. On match:
    • Record the rule as the current “matched” rule.
    • If the rule’s effect is deny, return immediately.
    • If allow, keep scanning — a later deny wins.
  5. Fall-through. If no rule matched, return the first policy’s spec.defaultEffect.
The summary: first-deny-wins; otherwise last-allow-wins; otherwise the first policy’s defaultEffect.

Operators

Field resolution

Dot-paths walk the request object:
Missing path components resolve to undefined, which compares unequal to anything a policy would eq against. __proto__, constructor, and prototype parts are rejected at the schema layer to prevent prototype-walking; resolveField also uses Object.hasOwn so inherited properties don’t resolve.

Wall-clock budget

Each evaluate() call is bounded to 50 ms of total work. If a pathologically large bundle would exceed that, evaluation bails out with deny / EVAL_TIMEOUT rather than blocking the event loop. The check fires at rule boundaries, so the actual upper bound is “current rule’s evaluation time + 50 ms.” For a healthy 1000-rule bundle on a modern laptop, evaluation typically completes in 1–2 ms — well under the budget.

Code paths that surface result codes