Continuous 1–5 risk scoring for AI agent actions.
An open-source alternative to binary allow/deny authorization for AI agents. Most agent governance today is "yes or no." Real production agents need more nuance: most actions should auto-execute, some should hold for human review, a few should be blocked outright. wave-engine gives you that as a small Python library with no runtime dependencies.
Wave 1 Routine → auto-execute, light logging
Wave 2 Logged → auto-execute, full audit trail
Wave 3 Evaluated → auto-execute, contextual logging
Wave 4 Review → hold for human approval
Wave 5 Critical → hold, escalate, full audit
If you're building an AI agent that takes real actions — sends emails, calls APIs, moves money, writes to production systems — you eventually need to answer: should this specific action actually happen right now?
The common answers all have problems:
- Hard-coded
ifblocks in agent code — unmaintainable as policies grow, impossible to audit. - Binary allow/deny authorizers — every ambiguous case falls into the wrong bucket. Either you block too much (agents become useless) or too little (incidents happen).
- Full-platform governance suites (Microsoft Agent 365, vendor-specific consoles) — too heavy for most teams, locked to one ecosystem.
wave-engine sits in the middle. A small, focused library that scores each action on a continuous scale and routes it to one of three outcomes: auto, review, or block. Bring your own policy. Plug it into any agent framework.
pip install wave-engine # core only, no dependencies
pip install "wave-engine[yaml]" # adds YAML policy loaderPython 3.9+. No required runtime dependencies for the core library.
from wave_engine import (
Action, WaveEvaluator, Rule,
match_system, match_action, match_context_gt, match_all,
)
# Define a policy.
evaluator = WaveEvaluator(rules=[
Rule(
name="high_value_refund",
matcher=match_all(
match_system("stripe"),
match_action("refund"),
match_context_gt("amount", 100),
),
score=30,
min_wave=4, # force at least Wave 4 (review)
reason="Refund over $100",
),
Rule(
name="billing_touch",
matcher=match_system("stripe"),
score=10,
reason="Billing system touched",
),
])
# Evaluate an action the agent wants to take.
decision = evaluator.evaluate(Action(
system="stripe",
action="refund",
destination="cus_12345",
context={"amount": 500, "env": "prod"},
actor_id="support-agent-v1",
))
print(decision.wave) # Wave.REVIEW (4)
print(decision.outcome) # "review" — hold for human approval
print(decision.reasoning) # "Refund over $100; Billing system touched"
# Then in your agent code:
if decision.is_auto:
run(action)
elif decision.is_held:
hold_for_human(action, decision.reasoning)
else:
reject(action, decision.reasoning)Compliance and security teams can write policy without touching agent code.
# policies/support_agent.yaml
rules:
- name: high_value_refund
match:
system: stripe
action: refund
context:
amount: { gt: 100 }
score: 30
min_wave: 4
reason: "Refund over $100"
- name: external_email_with_attachment
match:
system: gmail
action: send
destination_regex: "@(?!acme\\.com$)"
context:
has_attachment: true
score: 35
reason: "External recipient + attachment"from wave_engine import WaveEvaluator, load_rules_from_yaml
evaluator = WaveEvaluator(rules=load_rules_from_yaml("policies/support_agent.yaml"))Editing the YAML updates the policy. Restart the agent. No code change required.
Each rule contributes to a cumulative score when it matches the action. The total score maps to a Wave tier:
| Score | Wave | Outcome (default) |
|---|---|---|
< 20 |
1 Routine | auto |
20–39 |
2 Logged | auto |
40–59 |
3 Evaluated | auto |
60–79 |
4 Review | hold for human |
≥ 80 |
5 Critical | hold + escalate |
Rules can also:
min_wave: N— force the action to at least Wave N regardless of score. Useful for "anything touching production billing is at least Wave 4."force_outcome: "block"— reject the action outright. Useful for destructive operations.
Thresholds and outcome mapping are configurable; the defaults above are what wave-engine ships with.
The library ships with composable matchers so you don't have to write lambdas:
match_system("stripe", "stripe.*") # glob patterns
match_action("refund", "delete_*")
match_destination_regex(r"@(?!acme\.com$)") # any external email
match_context("amount", lambda v: v > 100) # arbitrary predicate
match_context_equals("env", "prod")
match_context_gt("amount", 100)
match_all(match_system("stripe"), match_context_gt("amount", 100))
match_any(match_system("aws"), match_system("gcp"))Or write your own — a matcher is just Callable[[Action], bool].
def run_agent_action(action_dict):
action = Action(**action_dict)
decision = evaluator.evaluate(action)
if decision.is_blocked:
return {"status": "rejected", "reason": decision.reasoning}
if decision.is_held:
approval_id = queue_for_human_review(action, decision)
return {"status": "pending", "approval_id": approval_id}
return execute_real_action(action)Pair wave-engine with an MCP proxy (such as mcp-governance-proxy) so any Claude Desktop, Cursor, or Cline agent gets governance applied automatically — no agent code changes.
Build your own DSL or natural-language compiler that emits Rule objects. The library is the runtime; you bring the authoring layer.
- Not a credential vault. Use Vault, AWS KMS, age, or sops.
- Not an audit log store. Persist
Decision.to_dict()to whatever you already use. - Not an enforcement runtime. It tells you what to do; you execute, hold, or reject.
- Not a content filter for model outputs. Use Guardrails AI, NeMo Guardrails, or Llama Firewall for that.
wave-engine is the scoring primitive. Compose it with the rest of your stack.
examples/01_support_agent.py— a customer-support agent with refund and email policies (Python rules)examples/02_yaml_policy.py— same policy expressed declaratively in YAMLexamples/support_policy.yaml— the YAML policy itself
Run them:
git clone https://github.com/andreas-altamirano/wave-engine
cd wave-engine
pip install -e ".[yaml]"
python examples/01_support_agent.py
python examples/02_yaml_policy.pypip install -e ".[dev]"
pytest -qApache 2.0. See LICENSE.
The Wave scoring primitive was originally developed inside Surfit, an AI agent governance platform. This library is the extracted, generalized version of that scoring engine — released so other teams can build governance into their own agents without committing to a full platform.
Companion libraries:
mcp-governance-proxy— MCP server that proxies tool calls through any evaluator (including this one)cross-system-correlator— pattern detection across multiple agent actions over time