Skip to main content

plan-linter is a static analysis toolkit for LLM agent plans

Project description

🛡️ plan-linter

"Fail your agent's flight-plan in CI—before it fails production."

plan-linter is an open-source static analysis toolkit for LLM agent plans.

It parses the machine-readable plan emitted by a planner/brain, validates it against schemas, policy rules, and heuristics, and returns Pass / Fail with an annotated risk-score JSON.

CI License PyPI version Python Versions

✨ Features

Capability Description
Schema validation JSON Schema/Pydantic v2 types for every step & arg
Policy engine YAML/OPA rules: allow/deny tools, value bounds, scopes
Data-flow analysis Detect raw secrets, PII, un-escaped SQL moving to dangerous sinks
Loop detection Cycle check on step graph; flag unbounded recursions
Risk scoring Heuristic risk score (0-1); fail threshold configurable
Plugin rules Drop-in Python files → auto-discovered via entry_points
CLI & JSON output Exit code for CI; detailed JSON for orchestrators

📦 Installation

Using pip

pip install plan-lint

From source

git clone https://github.com/your-organization/plan-linter.git
cd plan-linter
pip install -e .

🚀 Quick Start

The simplest way to use plan-linter is to run it on a plan JSON file:

plan-lint path/to/plan.json

For a more advanced usage, you can provide a policy file:

plan-lint path/to/plan.json --policy path/to/policy.yaml

📝 Example Plan Format

{
  "goal": "Update product prices with a discount",
  "context": {
    "user_id": "admin-012",
    "department": "sales"
  },
  "steps": [
    {
      "id": "step-001",
      "tool": "sql.query_ro",
      "args": {
        "query": "SELECT product_id, current_price FROM products"
      },
      "on_fail": "abort"
    },
    {
      "id": "step-002",
      "tool": "priceAPI.bulkUpdate",
      "args": {
        "product_ids": ["${step-001.result.product_id}"],
        "discount_pct": -20
      }
    }
  ],
  "meta": {
    "planner": "gpt-4o",
    "created_at": "2025-05-15T14:30:00Z"
  }
}

📋 Example Policy Format

# policy.yaml
allow_tools:
  - sql.query_ro
  - priceAPI.bulkUpdate
bounds:
  priceAPI.bulkUpdate.discount_pct: [-40, 0]
deny_tokens_regex:
  - "AWS_SECRET"
  - "API_KEY"
max_steps: 50
risk_weights:
  tool_write: 0.4
  raw_secret: 0.5
  loop: 0.3
fail_risk_threshold: 0.8

🔍 Command Line Options

Usage: plan-lint [OPTIONS] PLAN_FILE

Options:
  --policy, -p TEXT     Path to the policy YAML file
  --schema, -s TEXT     Path to the JSON schema file
  --format, -f TEXT     Output format (cli or json) [default: cli]
  --output, -o TEXT     Path to write output [default: stdout]
  --fail-risk, -r FLOAT Risk score threshold for failure (0-1) [default: 0.8]
  --help                Show this message and exit

🧩 Adding Custom Rules

You can create custom rules by adding Python files to the plan_lint/rules directory. Each rule file should contain a check_plan function that takes a Plan and a Policy object and returns a list of PlanError objects.

Here's an example of a custom rule that checks for SQL write operations:

from typing import List

from plan_lint.types import ErrorCode, Plan, PlanError, Policy

def check_plan(plan: Plan, policy: Policy) -> List[PlanError]:
    errors = []
    
    for i, step in enumerate(plan.steps):
        if step.tool.startswith("sql.") and "query" in step.args:
            query = step.args["query"].upper()
            write_keywords = ["INSERT", "UPDATE", "DELETE"]
            
            for keyword in write_keywords:
                if keyword in query:
                    errors.append(
                        PlanError(
                            step=i,
                            code=ErrorCode.TOOL_DENY,
                            msg=f"SQL query contains write operation '{keyword}'",
                        )
                    )
    
    return errors

🤝 Contributing

We welcome contributions from the community! To get started:

  1. Check the open issues or create a new one to discuss your ideas
  2. Fork the repository
  3. Make your changes following our contribution guidelines
  4. Submit a pull request

Please read our Code of Conduct to keep our community approachable and respectable.

🏗️ Development

To set up a development environment:

# Clone the repository
git clone https://github.com/your-organization/plan-linter.git
cd plan-linter

# Create a virtual environment
python -m venv .venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

# Install development dependencies
pip install -e ".[dev]"

# Install pre-commit hooks
pre-commit install

📄 License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

🙏 Acknowledgements

Thanks to all contributors who have helped shape Plan-Linter!

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

plan_lint-0.0.1.tar.gz (19.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

plan_lint-0.0.1-py3-none-any.whl (16.7 kB view details)

Uploaded Python 3

File details

Details for the file plan_lint-0.0.1.tar.gz.

File metadata

  • Download URL: plan_lint-0.0.1.tar.gz
  • Upload date:
  • Size: 19.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.5.24

File hashes

Hashes for plan_lint-0.0.1.tar.gz
Algorithm Hash digest
SHA256 e093af6ce7f5cd3cb4a3455fcad295d63260a3f46c423c8807b4a230b00ca706
MD5 9159b0d2cce3218b7762b7ad15a904e9
BLAKE2b-256 11e8ca78e6b483f1ca0b702feec0db6041c9c46b784c7916bb2d9a4ed11c4e72

See more details on using hashes here.

File details

Details for the file plan_lint-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: plan_lint-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 16.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.5.24

File hashes

Hashes for plan_lint-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 9b56e3ea451dea4b0fe0627b2edcdcb816c02f1eab12fd88d79f98f421a2e583
MD5 2729db9eebec145776ac35b1175e30c8
BLAKE2b-256 8f38db5ced42806823c2c2581728f35f5a78e156f3c7b65aac118c1d539338a7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page