Policy & Governance
The policy engine is the governance kernel of Formael. Every intent passes through it before any external action is taken - no exceptions, no bypass paths. Each verdict is computed once, atomically, and pinned to the immutable rule versions that produced it.
There are exactly three legal outcomes:
| Verdict | Meaning |
|---|---|
APPROVED | Pass the intent to execution |
DENIED | Reject and record per-axis reasoning |
PENDING_APPROVAL | Pause and route to a human approver |
The four axes - four orthogonal questions
The four axes are not four buckets of similar conditions. They are four different questions with different inputs and different stakeholders. The verdict is the conjunction of the four - every axis must pass for execution to proceed, and no axis can compensate for another.
| Axis | Question | Owns |
|---|---|---|
| Identity | Who, in what context, may invoke this? | Time-of-day, environment fencing, cool-downs, agent groups, credential context |
| Semantic | What does this intent mean - and is that allowed? | Slot-based content checks: value, counterparty, content, classification, volume |
| Fiscal | Can the organization (and this agent) afford this? | Two separate ledgers: platform spend (what Formael bills) and action value (what the agent moves) |
| Risk | If this goes wrong, how bad is it? | Intrinsic risk profile, org thresholds, per-capability HITL pins |
Evaluation is short-circuit, cheapest-first:
Identity → Semantic → Fiscal → RiskA fast Identity denial skips the remaining axes.
Capability slots - the governance contract
Policy rules are written against semantic slots, not raw parameter field paths. A slot is a typed role that a parameter plays in governance - the same slot exists across many capabilities, so one rule applies portably wherever the slot is declared.
| Slot | Meaning | Example fields |
|---|---|---|
monetary_value | The financial magnitude of the action | invoice total, refund amount, wire principal |
counterparty | The external party the action affects | customer, vendor, beneficiary, recipient |
principal | The internal subject the action is about (not the agent) | employee being deprovisioned, user being granted a role |
content | Free-text content the action transmits | message body, email body, ticket description |
destination | Where the action delivers | Slack channel, email address, webhook URL |
classification | Discrete labels that change risk | priority=P1, role=OWNER, severity=critical |
volume | A count that scales blast radius | line items, accounts affected, recipients |
Each capability declares which of its parameters populate which slots. Once a capability emits monetary_value, a rule like "require approval when monetary value exceeds $10,000" applies to it - without rewriting field paths. The same rule covers finance.create.invoice, support.issue.refund, and contracting.create.contract identically.
See Capability Catalog & Definition Packs for how slots are declared on a capability.
Identity axis
Question: Who, in what context, may invoke this?
Per-agent grants - which capabilities is this agent allowed to call at all - belong on Agent Scope, not on policies. See Agent Authentication. The Identity axis handles cross-cutting contextual rules that apply across agents or change with runtime context.
| Rule kind | What it asks | Example |
|---|---|---|
time_window | Is this within an allowed time-of-day / day-of-week window? | No identity.deprovision between 22:00 and 06:00 |
environment_fence | Does the agent's environment match? | Agents tagged staging may not call finance.* |
cooldown | Is the agent inside a minimum interval since its last call? | At most one finance.create.invoice per 60 seconds per agent |
agent_group_restriction | Is the agent in a permitted group? | Agents tagged experimental cannot call monetarySensitive=true capabilities |
credential_context_required | Does the credential context (JWT claims) satisfy a requirement? | Require device_trust=verified for capabilities flagged requiresHitlDefault |
{
"axis": "identity",
"kind": "time_window",
"condition": {
"daysOfWeek": ["MON", "TUE", "WED", "THU", "FRI"],
"hours": "09:00-18:00",
"timezone": "Europe/London",
"applyTo": { "capabilityFlags": ["monetarySensitive"] }
},
"action": "deny",
"description": "Block monetary actions outside business hours"
}Agent groups are configured via Agent.tags in Agents → Scope, and referenced by agent_group_restriction rules in policy.
Semantic axis
Question: What does this intent mean, and does it comply with your rules?
Semantic rules operate on slots, not parameter paths. The vocabulary is small and stable:
| Condition | Reads slot | Example |
|---|---|---|
value_above | monetary_value | Refund > $500 |
value_currency_in | monetary_value | Currency must be USD or EUR |
counterparty_not_in_allowlist | counterparty | Beneficiary not in approved vendor list |
content_matches | content | Message contains an SSN regex |
destination_external | destination | Slack channel is not internal |
classification_is | classification | Priority = P1, Role = OWNER |
volume_above | volume | Line items > 100 |
{
"axis": "semantic",
"kind": "value_above",
"slot": "monetary_value",
"threshold": { "amount": 10000, "currency": "USD" },
"action": "require_approval",
"description": "Require approval for monetary actions above $10,000"
}Because the rule targets a slot, it applies to every capability that emits monetary_value - no per-capability rewrite required.
The reasoning trace
IntentEnvelope.context.reasoning - the agent's natural-language explanation of why it wants to act - is captured and stored as evidence. It is never gating. A policy verdict cannot fire solely on the content of the reasoning trace, because the trace is non-deterministic. Reasoning feeds audit and AI suggestion drafting only; the policy engine ignores it during evaluation.
Fiscal axis - two ledgers, one axis
The Fiscal axis answers "can the organization afford this?" - but "afford" has two unrelated meanings, and Formael keeps them separate.
Platform spend - what Formael bills your organization
- Denomination: USD, always.
- Inputs: the per-IEC platform fee (and AI inference cost when AI features are on).
- Stakeholder: the CFO doing FinOps on Formael itself.
- Configured at: Settings → Budgets → Platform Spend.
Rules cap your monthly/weekly/daily Formael bill: alert at 80% utilization, deny when the org-daily Formael budget is exhausted, etc.
Action value - what your agents move externally
- Denomination: multi-currency. The amount is read from the
monetary_valueslot in its native currency. Each currency is enforced independently - there is no FX conversion at decision time. - Inputs: the
monetary_valueslot of the inbound intent, summed into the relevant action-value ledger row (org-wide, per-agent, per-domain, per-period). - Stakeholder: AP/AR, compliance, fraud, finance ops - not Formael billing.
- Configured at: Settings → Budgets → Action Value. Off by default for new organizations; turn it on when you need it.
{
"axis": "fiscal",
"kind": "action_value_budget",
"scope": { "domain": "finance", "agentId": null },
"period": "daily",
"limit": { "amount": 100000, "currency": "EUR" },
"action": "deny",
"description": "Cap daily AP outflow at €100k across all agents"
}A $1,000,000 wire transfer has a platform-spend cost of roughly $0.002 (one IEC) and an action value of $1,000,000. These are different categories of harm, paid for by different budgets, owned by different people, and gated by different rules - never conflated.
Remaining budget is returned in every fiscal denial:
{
"denial": {
"axisResults": {
"fiscal": {
"verdict": "DENIED",
"ledger": "action_value",
"scope": "domain=finance",
"period": "daily",
"currency": "EUR",
"currentSpend": 97250,
"limit": 100000,
"projected": 102100
}
}
}
}Multi-currency rule. Per-currency enforcement only. An intent denominated in EUR is never converted to USD to check a USD ledger - if no matching ledger row exists, the axis passes. FX rollup is a dashboard feature; it never affects a policy decision.
Non-monetary quotas (e.g. "tickets per hour") are not Fiscal. They belong on Identity as
cooldown/rate-shaping rules.
Risk axis
Question: What is the blast radius if this action goes wrong?
Risk is composed from three independent inputs:
| Input | Source |
|---|---|
| Intrinsic risk | The capability's riskProfile (reversibility, visibility) - installed from the pack, adjustable in your registry |
| Org thresholds | A single risk_threshold rule per org: require approval above X, deny above Y |
| Per-capability HITL pin | A field on your organization's capability override: INHERIT_PACK / PIN_ON / PIN_OFF |
Composite formula:
risk_score = (1 − reversibility) × 0.40 + visibility × 0.35 + precedent × 0.25Precedent quietly reflects platform learning: an agent that has executed this capability many times successfully is intrinsically less risky than one trying it for the first time. The first attempt scores 1.0; prior success drives the term toward 0.2; prior failure pushes it back up.
Evaluation order at runtime
- Did the org pin HITL on this capability? →
PASS + requiresApproval=true. Done. - Otherwise, compute the score from the capability's risk profile and this agent's precedent.
- Apply the org's thresholds (or the platform defaults if unset).
Tier defaults
| Tier | requireApprovalAbove | denyAbove |
|---|---|---|
| Starter | 0.5 | - |
| Business | 0.7 | - |
| Enterprise | 0.9 (configurable) | configurable |
Risk-axis rules are threshold-only. There is no
allow/denyaction selector on a Risk-axis rule - a threshold rule can only force approval or denial. Per-capability HITL pins are configured on the capability itself in Capabilities → [capability] → Risk Profile, not as a policy rule.
Risk Posture - opinionated defaults
Onboarding asks each organization to pick one Risk Posture. The posture applies a fixed set of templates and threshold defaults so a new org has working governance the moment a pack is installed.
| Posture | Posture templates applied | requireApprovalAbove |
|---|---|---|
| Conservative | 7 | tighter than tier default |
| Balanced | 3 | tier default |
| Permissive | 0 | tier default |
Most SMEs never need to leave Balanced. You can override any individual template or threshold after onboarding - the posture is a starting point, not a lock-in.
Policy templates
Templates are pre-authored bundles named in compliance English, mapped to slot-based rules so they work portably across packs.
| Template | Axis | What it configures |
|---|---|---|
finance-high-value-approval | Semantic | Require approval when monetary_value.amount > $10,000 |
ap-outflow-daily-cap | Fiscal (action value) | Cap daily AP outflow per organization |
contracting-sign-approval | Risk (HITL pin) | Always require approval before contracting.sign.* |
identity-deprovision-approval | Risk (HITL pin) | Always require approval before deprovisioning accounts |
identity-privileged-hitl | Semantic + Risk | Require approval when granting admin roles (classification.role=OWNER) |
support-refund-threshold | Semantic | Require approval for refunds above $500 (via monetary_value) |
support-pii-block | Semantic | Deny outbound content matching PII patterns (SSN, PAN, etc.) |
messaging-external-recipients | Semantic | Require approval when destination.address is external |
after-hours-monetary-block | Identity | Block monetary actions outside business hours (time_window) |
experimental-agent-guardrails | Identity | Agents tagged experimental cannot call monetary-sensitive capabilities |
To apply a template, navigate to Policies → Apply Template. Generated rules can be reviewed and edited before activation.
Policy simulation - replay against your own history
Before activating any rule, simulate it against your organization's last 30 days of IEC history. Simulation runs the full four-axis evaluation deterministically; it writes no audit record and executes no action.
Policies → Simulate returns:
- The number of IECs the rule would have matched
- Per-IEC verdict deltas - actual verdict vs counterfactual verdict
- A false-positive estimate - successful transactions that would have been blocked
- The HITL load delta per approval group
- Aggregate change in transaction value for blocked IECs
- The five most-affected agents
Simulation is the single best way to evaluate a policy change before it ships. Use it on every non-trivial rule edit.
Policy versioning
Every rule update creates an immutable PolicyRuleVersion snapshot before taking effect. The live engine reads the active version; the historical chain stays intact forever. Every verdict in your audit trail references the exact policyRuleVersionId that produced it - enabling full point-in-time policy auditability.
Navigate to Policies → [rule] → Version History to browse snapshots, diff versions, and revert.
Human-in-the-loop approval
When any axis returns PASS + requiresApproval=true, the IEC enters PENDING_APPROVAL. The approver sees the full context: the agent's submitted intent, the agent's reasoning trace, the slot projections, the risk score breakdown, and the proposed action in plain language.
| Action | Outcome |
|---|---|
| Approve | Execution proceeds with the original parameters |
| Deny | IEC closes with a denied outcome; agent is notified |
Approval requests expire after a configurable window (default: 24 hours). Expired requests close as denied.
Approval groups
Approval groups route HITL requests to the right team. Each domain is assigned an approval group - when an intent from that domain is deferred, only that group is notified.
| Domain | Example group |
|---|---|
finance | Finance Approvers |
contracting | Legal |
identity | IT Operations |
Approval groups are managed in Settings → Approval Groups; domain assignments live in the Domains section of the console.
Default behavior when no rules match
| Axis | If no rules match |
|---|---|
| Identity | Allow (agent scope already gated capability access) |
| Semantic | Allow |
| Fiscal | Allow (no budget configured → no cap to exceed) |
| Risk | Evaluated against your org thresholds; HITL pin still applies |
To switch any axis to default-deny, configure the explicit catch-all rule on that axis. Per-agent capability access is not a policy concern - manage it on Agent Scope.
Policy verdicts in the audit trail
Every policy evaluation is recorded permanently as a PolicyVerdict row, immutable and append-only:
- The four per-axis verdicts (
PASS/FAIL/PASS+requiresApproval) - The specific
policyRuleVersionIds that contributed - The computed risk score and its inputs (reversibility, visibility, precedent)
- The slot projections used in evaluation
- The evaluation timestamp
This record is the source of truth for compliance reporting, point-in-time replay, and post-hoc analysis.