Policy Configuration
Workflow policies govern what happens when an external orchestrator (LangGraph, Temporal, Airflow, or any custom system) reaches a step gate. Instead of writing governance logic inside your orchestrator code, you define declarative policies in AxonFlow. The policy engine evaluates them at every step gate check and returns one of three decisions: allow the step, block it, or require human approval before proceeding.
How Workflow Policies Differ from Gateway Policies
AxonFlow supports two policy scopes. Gateway policies (scope: gateway) evaluate individual LLM requests flowing through the Agent proxy, inspecting prompts and responses in real time. Workflow policies (scope: workflow) operate at a higher level. They evaluate the intent of a workflow step before it executes, using structured metadata like the step type, model, provider, and tool context rather than raw prompt text.
Both scopes share the same tenant policy engine and condition evaluation logic. If you already have gateway policies configured, the syntax for workflow policies is identical. The difference is what context is available for conditions: gateway policies see prompt text and token counts, while workflow policies see the step gate context described below.
Condition Fields
When a step gate request arrives, the WCP policy adapter flattens the step context into a key-value map that the policy engine evaluates against your conditions. Fields fall into a few dotted-path namespaces — use the prefix shown in the tables below when you reference a field in a policy.
Core Step Fields (under context.)
Step fields come from the adapter's context map and are accessed with the context. prefix.
| Policy field reference | Type | Description |
|---|---|---|
context.step_type | string | The type of step: llm_call, tool_call, connector_call, or human_task |
context.step_name | string | Human-readable name of the step (e.g., "generate_code", "deploy_to_staging") |
context.model | string | LLM model identifier (e.g., "gpt-4", "claude-opus-4-20250514", "amazon.titan-text-express-v1") |
context.provider | string | LLM provider (e.g., "openai", "anthropic", "bedrock") |
context.step_index | int | The sequential position of this step in the workflow |
Workflow Context Fields (under context.)
| Policy field reference | Type | Description |
|---|---|---|
context.workflow_id | string | The unique workflow identifier |
context.workflow_name | string | Human-readable workflow name |
context.source | string | Orchestrator source: langgraph, langchain, crewai, or external |
Step Input Fields (under step_input.)
Any key in the step_input object is accessible with the direct step_input. prefix — no context. wrapping. For example, if your step gate request includes "step_input": {"environment": "production", "region": "us-east-1"}, you can write conditions against step_input.environment and step_input.region.
Tool Context Fields (Per-Tool Governance)
When per-tool governance is used, these fields are added from the tool_context object.
| Policy field reference | Type | Description |
|---|---|---|
context.tool_name | string | Name of the specific tool being invoked (e.g., "web_search", "sql_query") |
context.tool_type | string | Tool category: "function", "mcp", or "api" |
tool_input.* | varies | Individual keys from the tool's input arguments, prefixed directly with tool_input. (limited to 50 keys, no context. wrapping) |
Retry & Idempotency Fields (Evaluation tier and above)
Policies that reason about retry state and caller-supplied idempotency keys can read these step.* fields, populated from the step's retry_context before each policy evaluation. Authoring a policy that uses any of them requires an Evaluation or Enterprise license — creating such a policy on a Community license is rejected with 403 FEATURE_REQUIRES_EVALUATION_LICENSE. The underlying wire fields themselves are populated on every tier, so clients on any license can read retry_context from the gate response even though Community cannot author policies that evaluate it.
| Field | Type | Description |
|---|---|---|
step.gate_count | int | Number of /gate calls for this (workflow_id, step_id), including the current one |
step.completion_count | int | Number of successful /complete calls (0 or 1 in normal flows) |
step.prior_completion_status | enum | "none", "completed", or "gated_not_completed" |
step.prior_output_available | bool | true iff a prior /complete landed |
step.last_decision | enum | "allow", "block", or "require_approval" from the prior gate call |
step.first_attempt_age_seconds | int | Seconds since the first gate call on this step |
step.idempotency_key | string | The caller-supplied key; empty string when none was supplied |
Retry-aware policies only re-fire on subsequent /gate calls when the caller passes retry_policy: "reevaluate". Default-idempotent retries hit the cached decision without consulting the policy engine. See Retry Semantics & Idempotency for the full wire shape and SDK Integration for the cache semantics.
Actions
Every policy specifies one of three actions that the engine returns as the step gate decision.
allow is the default. When no policies match a step gate request, or when all matching policies specify allow, the step proceeds. You rarely need to create explicit allow policies unless you want to override a broader block.
block prevents the step from executing. The gate response includes the blocking policy's name and reason, which your orchestrator should use to abort the workflow or skip the step. This is the right action for hard prohibitions like "never use GPT-4 in production" or "block destructive SQL operations."
require_approval pauses the workflow and creates a human-in-the-loop (HITL) approval request. The gate response includes an approval_id (always populated) and an approval_url (populated only when the orchestrator is configured with a PORTAL_BASE_URL env var). The workflow cannot proceed until a human approves or rejects the step. On rejection, the workflow is automatically aborted. This action is available in the Evaluation tier and above.
Examples
Every example below is a complete POST /api/v1/policies body. The wire shape has a few rules to know up front:
conditionsis a flat array, ANDed implicitly — every condition in the array must match for the policy to fire.- Operators are
equals,not_equals,contains,greater_than,less_than,regex,in(noteq,gt,lt). actionsis a plural array with each action wrapped in{type, config}. Thereason(andseverityfor approvals) lives insideconfig.typemust be one ofcontent,user,risk,cost,context_aware,media,rate-limit,budget,time-access,role-access,mcp,connector. Most WCP examples usecontext_aware.categorymust start withdynamic-ormedia-.dynamic-compliance,dynamic-security,dynamic-costare the most common.
Block a Specific Model in Workflows
This policy prevents GPT-4 from being used in any workflow step that makes an LLM call.
{
"name": "block-gpt4-in-workflows",
"description": "Block GPT-4 in production workflow LLM calls",
"type": "context_aware",
"category": "dynamic-security",
"priority": 900,
"enabled": true,
"conditions": [
{"field": "context.step_type", "operator": "equals", "value": "llm_call"},
{"field": "context.model", "operator": "equals", "value": "gpt-4"}
],
"actions": [
{"type": "block", "config": {"reason": "GPT-4 not allowed in production workflows"}}
]
}
Require Approval for Deployment Steps
This policy targets connector calls where the step name indicates a deployment action. When matched, the workflow pauses and a HITL approval request is created.
{
"name": "require-approval-for-deploy",
"description": "Deployment connector calls require human approval",
"type": "context_aware",
"category": "dynamic-compliance",
"priority": 900,
"enabled": true,
"conditions": [
{"field": "context.step_type", "operator": "equals", "value": "connector_call"},
{"field": "context.step_name", "operator": "contains", "value": "deploy"}
],
"actions": [
{"type": "require_approval", "config": {"reason": "Deployment steps require human approval", "severity": "high"}}
]
}
Block PII in Step Inputs
If your orchestrator passes structured metadata in step_input, you can write policies against those fields directly with the step_input. prefix.
{
"name": "block-pii-in-workflow-inputs",
"description": "Block steps flagged as containing PII",
"type": "context_aware",
"category": "dynamic-compliance",
"priority": 900,
"enabled": true,
"conditions": [
{"field": "step_input.contains_pii", "operator": "equals", "value": true}
],
"actions": [
{"type": "block", "config": {"reason": "PII detected in workflow step input"}}
]
}
Restrict by Provider
This policy blocks any workflow step that uses a specific LLM provider, regardless of which model is selected.
{
"name": "block-external-providers",
"description": "Block OpenAI on compliance-sensitive workflows",
"type": "context_aware",
"category": "dynamic-compliance",
"priority": 900,
"enabled": true,
"conditions": [
{"field": "context.step_type", "operator": "equals", "value": "llm_call"},
{"field": "context.provider", "operator": "equals", "value": "openai"}
],
"actions": [
{"type": "block", "config": {"reason": "External LLM providers not allowed; use Bedrock"}}
]
}
Tool-Specific Policies
With per-tool governance, you can write policies that target specific tools within a workflow. When a step gate request includes tool_context, the adapter propagates tool_name, tool_type (under context.), and tool_input.* (direct prefix) into the evaluation context.
Block a Specific Tool
{
"name": "block-code-executor",
"description": "Block the code_executor tool in production",
"type": "context_aware",
"category": "dynamic-security",
"priority": 900,
"enabled": true,
"conditions": [
{"field": "context.tool_name", "operator": "equals", "value": "code_executor"}
],
"actions": [
{"type": "block", "config": {"reason": "Code execution not allowed in production"}}
]
}
Block Dangerous Tool Inputs
You can inspect individual fields within the tool's input arguments using the direct tool_input. prefix.
{
"name": "block-dangerous-sql",
"description": "Block destructive SQL commands via sql_query tool",
"type": "context_aware",
"category": "dynamic-security",
"priority": 900,
"enabled": true,
"conditions": [
{"field": "context.tool_name", "operator": "equals", "value": "sql_query"},
{"field": "tool_input.query", "operator": "contains", "value": "DROP TABLE"}
],
"actions": [
{"type": "block", "config": {"reason": "Destructive SQL operations blocked"}}
]
}
Require Approval for MCP Tools
{
"name": "approve-mcp-writes",
"description": "Route MCP tool calls through the HITL queue",
"type": "context_aware",
"category": "dynamic-compliance",
"priority": 900,
"enabled": true,
"conditions": [
{"field": "context.tool_type", "operator": "equals", "value": "mcp"},
{"field": "context.step_type", "operator": "equals", "value": "tool_call"}
],
"actions": [
{"type": "require_approval", "config": {"reason": "MCP tool calls require human approval", "severity": "medium"}}
]
}
Policy Evaluation Behavior
All active workflow-scoped policies are evaluated on every step gate check. When policies restrict a step, the engine checks if any matching policy specifies require_approval. If so, the decision is require_approval (giving a human the opportunity to override). Otherwise, the decision is block. Only when no matching policies restrict the step is the decision allow.
The step gate response always includes two lists: policies_evaluated (all policies that were checked) and policies_matched (policies whose conditions matched and contributed to the decision). This transparency helps with debugging why a step was allowed or blocked.
