Skip to main content

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 referenceTypeDescription
context.step_typestringThe type of step: llm_call, tool_call, connector_call, or human_task
context.step_namestringHuman-readable name of the step (e.g., "generate_code", "deploy_to_staging")
context.modelstringLLM model identifier (e.g., "gpt-4", "claude-opus-4-20250514", "amazon.titan-text-express-v1")
context.providerstringLLM provider (e.g., "openai", "anthropic", "bedrock")
context.step_indexintThe sequential position of this step in the workflow

Workflow Context Fields (under context.)

Policy field referenceTypeDescription
context.workflow_idstringThe unique workflow identifier
context.workflow_namestringHuman-readable workflow name
context.sourcestringOrchestrator 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 referenceTypeDescription
context.tool_namestringName of the specific tool being invoked (e.g., "web_search", "sql_query")
context.tool_typestringTool category: "function", "mcp", or "api"
tool_input.*variesIndividual 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.

FieldTypeDescription
step.gate_countintNumber of /gate calls for this (workflow_id, step_id), including the current one
step.completion_countintNumber of successful /complete calls (0 or 1 in normal flows)
step.prior_completion_statusenum"none", "completed", or "gated_not_completed"
step.prior_output_availablebooltrue iff a prior /complete landed
step.last_decisionenum"allow", "block", or "require_approval" from the prior gate call
step.first_attempt_age_secondsintSeconds since the first gate call on this step
step.idempotency_keystringThe 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:

  • conditions is 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 (not eq, gt, lt).
  • actions is a plural array with each action wrapped in {type, config}. The reason (and severity for approvals) lives inside config.
  • type must be one of content, user, risk, cost, context_aware, media, rate-limit, budget, time-access, role-access, mcp, connector. Most WCP examples use context_aware.
  • category must start with dynamic- or media-. dynamic-compliance, dynamic-security, dynamic-cost are 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.