Skip to main content
Policy decides who may act. Risk decides how much scrutiny the action needs. The RiskEngine assigns every tool call one of four tiers, and escalate_at turns that tier into a gate: any permitted action at or above the threshold is routed to a human approver before it runs.

The four tiers

RiskTier is a string enum defined in infy/governance/types.py. The tiers are ordered by severity. Severity ordering is exposed through tier_at_least(tier, threshold), which returns True when tier is at or above threshold. The engine and the escalation gate both rely on this ranking.

How a tier is assigned

RiskEngine.assess(tool, args) resolves a tier from the tool’s static metadata. It checks three sources in order and returns the first that applies.
1

Explicit risk_tier wins

If the tool declares risk_tier, that value is used verbatim: RiskTier(tool.risk_tier). This is the override. A tool author who knows the risk states it directly.
2

Verb mapping

Otherwise, if the tool declares a verb and that verb is known, the engine maps it to a tier (see the table below). The lookup is case-insensitive: tool.verb.upper() is matched.
3

Side-effect default

If neither is set, the engine falls back to the tool’s side_effect flag. A side-effecting tool becomes HIGH. A pure or read-only tool becomes LOW.
The relevant Tool fields (from infy/tools.py) are all optional governance metadata: risk_tier, verb, and side_effect. If none are set on a tool, side_effect defaults to False, so an unannotated tool assesses as LOW.
The conservative default only kicks in when a tool is marked side-effecting but left unprofiled. An unprofiled side-effecting tool (side_effect=True, no risk_tier, no known verb) is treated as HIGH. That is deliberate: an action with unknown blast radius should not slip through as low risk.

The verb table

When a tool sets verb but not risk_tier, the engine uses this fixed mapping from infy/governance/risk.py. A verb outside this table is ignored, and resolution falls through to the side_effect default.

Profiling a tool

Attach the metadata when you define the tool. Any of the three inputs works. Pick the most specific one you can.
When risk_tier and verb disagree, risk_tier wins because it is checked first. In transfer_funds above, the explicit "critical" is used, not the DB verb’s HIGH.

Risk gates the decision

Assessing a tier is only half the story. On its own, the tier is an annotation on the audit record. escalate_at, set on the Governance object, is what makes risk gate the run. Inside Governance._authorize, the flow is:
  1. tier = self.risk.assess(tool, args) scores the call.
  2. The policy engine evaluates the request. A FORBID blocks it outright.
  3. For a permitted request, approval is required when the policy attached a REQUIRE_APPROVAL obligation or when escalate_at is not None and tier_at_least(tier, self.escalate_at).
escalate_at defaults to RiskTier.HIGH. So out of the box, every HIGH and CRITICAL action is sent to the approver, and the default approver is DenyAll. The practical effect: an unprofiled, side-effecting tool assesses as HIGH, hits the gate, and is denied by default unless a human approves it.
Set escalate_at=None to disable risk-based escalation entirely. Policy-driven REQUIRE_APPROVAL obligations still apply, so you keep explicit approvals while dropping the blanket tier gate.

Where to go next

Policy

Deny-by-default authorization that runs before risk is scored.

Approval

Human-in-the-loop review, including durable approval that suspends a run.

Audit

The tamper-evident, hash-chained record where each tier lands.

Governance overview

How policy, risk, approval, and audit compose on the agent loop.