Intent Plans
Understanding plan structure, lifecycle, and validation
Intent Plans
An Intent Plan is a structured document that declares all actions an agent intends to execute. Think of it as a "pre-approved checklist" that gets cryptographically signed.
What is an Intent Plan?
An intent plan is:
- Declarative: States what to do, not how
- LLM-Generated: Created dynamically by the agent's reasoning
- Immutable: Cannot be changed once signed
- Verifiable: Cryptographically bound to execution
Plan Structure
Basic Plan Format
{
"steps": [
{
"action": "fetch_data",
"mcp": "data-mcp",
"description": "Get user data from database"
},
{
"action": "analyze",
"mcp": "analytics-mcp",
"description": "Calculate risk score"
}
]
}Plan with Metadata
{
"steps": [
{
"action": "process_payment",
"mcp": "finance-mcp",
"description": "Process customer payment",
"metadata": {
"priority": "high",
"timeout_seconds": 30
}
}
],
"metadata": {
"purpose": "payment_processing",
"version": "1.2.0",
"tags": ["finance", "critical"]
}
}Plan Templates vs LLM Generation
Primary: LLM-Generated Plans (Recommended)
# Agent uses LLM to generate plan from natural language
captured = client.capture_plan(
llm="gpt-4",
prompt="Fetch user data, calculate credit score, and store result"
)
# LLM autonomously decides which MCPs and actions to useBenefits:
- Maximum flexibility
- Agent autonomy
- Adapts to context
- Natural language interface
Alternative: Plan Templates (Fixed Structure)
# For debugging, testing, or strict workflows
plan_template = {
"steps": [
{"action": "fetch_data", "mcp": "data-mcp"},
{"action": "analyze", "mcp": "analytics-mcp"}
]
}
captured = client.capture_plan(
llm="gpt-4",
prompt="Execute predefined workflow",
plan=plan_template # Use fixed structure
)Use Cases:
- Testing and debugging
- Regulatory compliance (fixed workflows)
- Performance-critical scenarios (skip LLM planning)
- Template-based execution
Note: Plan templates are more restrictive than LLM-generated plans. They're useful for specific scenarios but sacrifice agent flexibility.
Plan Validation
When you submit a plan, ArmorIQ validates:
Validation happens in two places, and they fail differently.
1. Structure validation, in the SDK
capture_plan checks the plan before any network call:
planis present- it is a dict
- it contains a
stepskey
Each failure raises a plain ValueError with a message naming the problem. It
is not an ArmorIQException, so a bare except ArmorIQException will not
catch it:
client.capture_plan(llm="gpt-4", prompt="...") # no plan
# ValueError: Plan structure is required. You must provide an explicit plan
# with the MCP and actions you want to execute.
client.capture_plan(llm="gpt-4", prompt="...", plan={}) # no steps
# ValueError: Plan must contain 'steps' keyPer-step fields are not validated locally. A step missing action or mcp
reaches the platform and is rejected there.
2. Plan and MCP validation, on the platform
Token issuance and enforcement check what the SDK cannot: that the MCP is registered and reachable for your org, that the action exists on it, and that the plan satisfies policy.
These surface as exceptions, not as a response body you inspect:
| Failure | Exception |
|---|---|
| Plan or step rejected at issuance | InvalidTokenException |
| Invoked action is not in the token's plan | IntentMismatchException (carries action, plan_hash) |
| Policy denies the plan | PolicyBlockedException |
| Policy requires approval first | PolicyHoldException |
| MCP unreachable or erroring | MCPInvocationException (carries mcp, action, status_code) |
See Error Handling for the attributes on each and the
proxy status codes they map from. To check an MCP is registered before you plan
against it, use client.list_mcps() or client.get_mcp_tool_schemas(mcp_name).
Plan Lifecycle
Phase 1: Capture
captured_plan = client.capture_plan(
llm="gpt-4",
prompt="Fetch and analyze data"
)What Happens:
- Prompt sent to ArmorIQ
- Plan generated (by LLM or template)
- Structure validated
- Plan stored with unique ID
- PlanCapture object returned
Phase 2: Canonicalization
token = client.get_intent_token(captured_plan)What Happens:
- Plan converted to canonical form (CSRG)
- Deterministic hash generated
- Hash signs the token
- Token includes plan hash + policy + expiration
Canonical Form (CSRG):
{
"nodes": [
{"id": "n1", "action": "fetch_data", "mcp": "data-mcp"},
{"id": "n2", "action": "analyze", "mcp": "analytics-mcp"}
],
"edges": [
{"from": "n1", "to": "n2"}
]
}Phase 3: Verification
result = client.invoke(
mcp="data-mcp",
action="fetch_data",
intent_token=token
)What Happens:
- Proxy receives request
- Token signature verified
- Plan hash extracted
- Action checked against plan
- If match: request forwarded to MCP
- If mismatch: request rejected
Phase 4: Audit
Automatically Logged:
- Plan creation time
- Token generation time
- All action invocations
- Success/failure status
- Execution times
Plan Examples
Example 1: Data Pipeline
# Natural language prompt
captured = client.capture_plan(
llm="gpt-4",
prompt="Fetch customer data, validate it, and store in warehouse"
)
# Generated plan:
{
"steps": [
{"action": "fetch_customers", "mcp": "data-mcp"},
{"action": "validate_schema", "mcp": "validation-mcp"},
{"action": "store_data", "mcp": "warehouse-mcp"}
]
}Example 2: Financial Analysis
# Natural language prompt
captured = client.capture_plan(
llm="gpt-4",
prompt="Analyze Q4 revenue, compare with forecast, generate report"
)
# Generated plan:
{
"steps": [
{"action": "fetch_revenue", "mcp": "finance-mcp"},
{"action": "fetch_forecast", "mcp": "finance-mcp"},
{"action": "compare_metrics", "mcp": "analytics-mcp"},
{"action": "generate_report", "mcp": "reporting-mcp"}
]
}Example 3: Multi-Service Orchestration
# Complex workflow
captured = client.capture_plan(
llm="gpt-4",
prompt="Get user profile, check permissions, fetch data, apply transformations, send notification"
)
# Generated plan:
{
"steps": [
{"action": "get_profile", "mcp": "auth-mcp"},
{"action": "check_permissions", "mcp": "auth-mcp"},
{"action": "fetch_data", "mcp": "data-mcp"},
{"action": "transform", "mcp": "etl-mcp"},
{"action": "send_notification", "mcp": "notification-mcp"}
]
}Best Practices
1. Use Descriptive Prompts
# ✓ Good: Specific and clear
prompt = "Fetch sales data for 2024, calculate YoY growth, and generate PDF report"
# ✗ Bad: Vague
prompt = "Do some data stuff"2. Include Context in Metadata
captured = client.capture_plan(
llm="gpt-4",
prompt="Process refund",
metadata={
"transaction_id": "txn_123",
"reason": "customer_request",
"priority": "high"
}
)3. Validate Plans Before Execution
try:
captured = client.capture_plan(llm="gpt-4", prompt=user_input, plan=plan_dict)
print(f"Plan has {len(captured.plan['steps'])} steps")
# Review plan before getting token
for step in captured.plan['steps']:
print(f"- {step['mcp']}/{step['action']}")
# Proceed if plan looks good
token = client.get_intent_token(captured)
except ValueError as e:
# capture_plan raises ValueError when plan is missing or has no 'steps'
print(f"Plan validation failed: {e}")plan is required. capture_plan raises a plain ValueError, not an
ArmorIQException, when it is missing or has no steps key, so a bare
except ArmorIQException will not catch it.
4. Use Plan Templates for Critical Workflows
# For regulatory compliance or safety-critical operations
compliance_template = {
"steps": [
{"action": "verify_identity", "mcp": "kyc-mcp"},
{"action": "check_sanctions", "mcp": "compliance-mcp"},
{"action": "approve_transaction", "mcp": "approval-mcp"}
]
}
captured = client.capture_plan(
llm="gpt-4",
prompt="Execute compliance workflow",
plan=compliance_template
)Common Issues
Issue: Plan Too Large
Problem: Plan has > 100 steps, causing timeouts
Solution: Break into multiple plans
# Split large workflows
plan1 = client.capture_plan(llm="gpt-4", prompt="Fetch and validate data")
plan2 = client.capture_plan(llm="gpt-4", prompt="Transform and load data")Issue: Action Not in Registry
Problem: MCP or action doesn't exist
Solution: Check available MCPs first
# Verify MCP exists
available_mcps = client.list_mcps()
print(available_mcps)Issue: Plan Hash Mismatch
Problem: Token verification fails
Solution: Don't modify captured plan after getting token
# ✓ Good
captured = client.capture_plan(...)
token = client.get_intent_token(captured)
client.invoke(..., intent_token=token)
# ✗ Bad: Modifying plan
captured = client.capture_plan(...)
captured.plan['steps'].append(...) # Don't do this!
token = client.get_intent_token(captured) # Hash won't matchNext Steps
- Token Lifecycle - How tokens work
- Security Model - Verification details
- Policy Management - Control execution