Plan Structure¶
This page explains the structure of plans that can be validated by Plan-Lint.
Overview¶
A plan in Plan-Lint represents a sequence of steps that an AI agent intends to execute. The plan is structured as a JSON object with specific fields that allow Plan-Lint to analyze it for potential security and operational issues.
Plan Format¶
Plans are represented as JSON objects with the following structure:
{
"goal": "Human-readable description of what the plan aims to accomplish",
"steps": [
{
"id": "unique-step-identifier",
"tool": "tool_to_execute",
"parameters": {
"param1": "value1",
"param2": "value2"
},
"on_fail": "abort",
"depends_on": ["previous-step-id"]
}
],
"context": {
"key1": "value1",
"key2": "value2"
},
"meta": {
"planner": "model-name",
"created_at": "timestamp"
}
}
Required Fields¶
- steps: An array of execution steps that make up the plan (required)
Optional Fields¶
- goal: A human-readable description of what the plan aims to accomplish
- context: Additional context information relevant to the plan
- meta: Metadata about the plan such as which model generated it
Step Structure¶
Each step in the plan represents an individual action to be executed. Steps have the following structure:
{
"id": "step1",
"tool": "tool_name",
"parameters": {
"param1": "value1",
"param2": "value2"
},
"on_fail": "abort",
"depends_on": ["step0"]
}
Required Fields¶
- id: A unique identifier for the step (string)
- tool: The name of the tool or function to execute (string)
- parameters: An object containing the parameters for the tool execution (object)
Optional Fields¶
- on_fail: What to do if this step fails (options: "abort", "continue")
- depends_on: Array of step IDs that must complete before this step can execute
Parameter References¶
Parameters can reference the outputs of previous steps using the syntax {{step_id.result}} or ${step_id.result}. For example:
{
"id": "step2",
"tool": "send_email",
"parameters": {
"body": "The account balance is {{step1.result.balance}}",
"to": "${step1.result.email}"
},
"depends_on": ["step1"]
}
This allows steps to use the outputs of previous steps as inputs, creating a workflow.
Special Tool Patterns¶
Plan-Lint recognizes special patterns in the tool names to apply specific validations:
- db.query: Database query operations
- db.query_ro: Read-only database queries
- db.write: Database write operations
- payments.: Payment operations (e.g.,
payments.transfer) - notify.: Notification operations (e.g.,
notify.email) - file.: File operations
These patterns help Plan-Lint apply the appropriate security checks based on the type of operation.
Example Plan¶
Here's a complete example of a plan that queries a database and sends an email:
{
"goal": "Send monthly account statement to user",
"steps": [
{
"id": "step1",
"tool": "db.query_ro",
"parameters": {
"query": "SELECT balance, email FROM accounts WHERE user_id = $1",
"args": ["user-123"]
}
},
{
"id": "step2",
"tool": "notify.email",
"parameters": {
"to": "{{step1.result.email}}",
"subject": "Your Monthly Statement",
"body": "Your current balance is ${{step1.result.balance}}"
},
"depends_on": ["step1"]
}
],
"context": {
"user_id": "user-123",
"month": "January 2025"
},
"meta": {
"planner": "gpt-4o",
"created_at": "2025-01-01T00:00:00Z"
}
}
Best Practices¶
When working with plans, follow these best practices:
- Use Unique IDs: Ensure each step has a unique ID.
- Explicit Dependencies: Always specify step dependencies using the
depends_onfield. - Minimal Permissions: Use the most restrictive tool possible (e.g.,
db.query_roinstead ofdb.query). - Parameter Sanitization: Ensure user inputs are properly sanitized before including them in step parameters.
- Clear Goal Description: Include a clear, human-readable goal to make the plan's purpose obvious.
API Usage¶
Here's how to create and validate a plan using the Plan-Lint API:
from plan_lint import validate_plan
from plan_lint.types import Plan, PlanStep
# Create a plan programmatically
plan = Plan(
goal="Send notification to user",
steps=[
PlanStep(
id="step1",
tool="db.query_ro",
parameters={
"query": "SELECT email FROM users WHERE id = $1",
"args": ["user-456"]
}
),
PlanStep(
id="step2",
tool="notify.email",
parameters={
"to": "{{step1.result.email}}",
"subject": "Notification",
"body": "This is a notification"
},
depends_on=["step1"]
)
],
context={"user_id": "user-456"}
)
# Validate the plan
result = validate_plan(plan)
if result.valid:
print("Plan is valid!")
else:
print("Plan validation failed:")
for violation in result.violations:
print(f"- {violation.rule}: {violation.message}")