Skip to main content

Framework-agnostic schemas and validators for multi-agent turn routing plans

Project description

PlanContract — planner output in, validated plan out


Framework-agnostic schemas and validators for multi-agent turn routing plans.

Routing plan contracts — not behavioral agent safety contracts.


Python 3.11+ PyPI License: MIT Typed CI Ruff Pydantic v2


Install · Quick start · Topologies · CLI · Design · Contributing


Table of contents


Why PlanContract?

Most agent frameworks give you orchestration — LangGraph nodes, crew runners, tool loops — but leave plan validation as an exercise. Teams rebuild the same logic in every codebase:

Problem PlanContract answer
LLM emits invalid agent IDs PlanPolicy allowlists
Too many tasks per turn configurable max_tasks
Duplicate or missing task IDs structured ValidationReport
Cycles in depends_on Kahn topological sort + DEPENDENCY_CYCLE
Wrong parallel vs sequential label topology derived from the graph, not trusted from the LLM
Same agent scheduled twice merge_same_agent_tasks()

PlanContract is the contract between planner and dispatcher: typed schemas in, validated execution waves out.

Note: This is not agentcontract — that project covers behavioral/safety contracts for agent outputs. PlanContract covers routing plan structure for multi-agent turns.


How it works

sequenceDiagram
    autonumber
    participant U as User
    participant P as Planner LLM
    participant F as finalize_draft
    participant V as validate_plan
    participant D as Dispatcher

    U->>P: message
    P->>F: PlanDraft JSON
    F->>F: merge same agents
    F->>F: derive_topology()
    F->>V: AgentPlan
    V-->>F: ValidationReport
    F->>D: execution_waves()
    Note over D: wave 1: parallel agents<br/>wave 2: next in chain
Pipeline diagram (static)
flowchart LR
    A[PlanDraft] --> B[merge_same_agent_tasks]
    B --> C[derive_topology]
    C --> D[validate_plan]
    D --> E[AgentPlan]
    E --> F[execution_waves]
    F --> G[Dispatcher]

Install

From PyPI:

pip install plancontract
# or
uv add plancontract

Requires Python 3.11+.

From source (development):

git clone https://github.com/zamaniali1995/plancontract.git
cd plancontract
uv sync
make ci

Requires uv for development. Python 3.11+.


Quick start

from plancontract import PlanDraft, PlanPolicy, TaskDraft, finalize_draft, validate_plan

draft = PlanDraft(
    tasks=[
        TaskDraft(
            task_id="t1",
            agent_id="travel_agent",
            instruction="find flights to Tokyo",
        ),
        TaskDraft(
            task_id="t2",
            agent_id="billing_agent",
            instruction="check travel insurance coverage",
        ),
    ],
)

plan = finalize_draft(draft, policy=PlanPolicy.demo())

print(plan.topology)          # PlanTopology.PARALLEL
print(plan.execution_waves()) # one wave: [travel_agent, billing_agent]

assert validate_plan(plan, policy=PlanPolicy.demo()).ok

Run the bundled example:

uv run python examples/basic_usage.py
Example output
topology: parallel
agents: ['travel_agent', 'billing_agent']
waves:
  wave 1: ['travel_agent', 'billing_agent']
valid: True

Topologies at a glance

Topology is derived from dependencies during finalize_draft() — never copied blindly from LLM output.

Parallel vs sequential topology

Topology Tasks Dependency shape Waves
single 1 1
parallel 2–3 none (independent) 1
sequential 2–3 chain 1 per task
mixed 3 fan-in / fan-out 2+
deferred 0 0
fallback reserved
Sequential example — draft in, plan out

Input examples/data/plan_draft.sequential.json:

{
  "tasks": [
    {
      "task_id": "t1",
      "agent_id": "travel_agent",
      "instruction": "shortlist weekend destinations within 3 hours"
    },
    {
      "task_id": "t2",
      "agent_id": "billing_agent",
      "instruction": "estimate total cost for the selected destination",
      "depends_on": ["t1"]
    }
  ],
  "locale": "en",
  "summary": "Pick a destination, then estimate cost."
}
plancontract finalize examples/data/plan_draft.sequential.json --demo-policy
{
  "topology": "sequential",
  "locale": "en",
  "title": "",
  "tasks": [
    {
      "task_id": "t1",
      "agent_id": "travel_agent",
      "instruction": "shortlist weekend destinations within 3 hours",
      "depends_on": [],
      "extensions": {}
    },
    {
      "task_id": "t2",
      "agent_id": "billing_agent",
      "instruction": "estimate total cost for the selected destination",
      "depends_on": ["t1"],
      "extensions": {}
    }
  ]
}

Validation that CI can gate on

Structured errors — not just exceptions:

from plancontract import AgentPlan, PlanPolicy, ValidationCode, validate_plan

plan = AgentPlan.model_validate_json(open("plan.json").read())
report = validate_plan(
    plan,
    policy=PlanPolicy(allowed_agents=frozenset({"travel_agent", "billing_agent"})),
)

if not report.ok:
    for issue in report.issues:
        print(issue.code, issue.message)  # ValidationCode.UNKNOWN_AGENT, ...

report.raise_if_invalid()  # raises PlanValidationError for CI

GitHub Actions:

- run: pip install plancontract
- run: plancontract validate plans/candidate.json --mode draft --demo-policy
Example validation failure (CLI)
plancontract validate bad-plan.json --mode draft --demo-policy
{
  "ok": false,
  "error_count": 1,
  "issues": [
    {
      "code": "unknown_agent",
      "message": "unknown or disallowed agent_id 'unknown_agent'",
      "task_id": "t1",
      "field_name": "agent_id",
      "context": {}
    }
  ]
}

Use --mode draft for LLM planner JSON; omit it (default plan) for finalized AgentPlan files.


CLI

Command Purpose
plancontract validate <file> Validate a plan or draft JSON
plancontract finalize <draft> Merge, derive topology, validate
plancontract schema draft | plan Print JSON Schema
# Validate LLM draft with demo agent allowlist
plancontract validate examples/data/plan_draft.parallel.json --mode draft --demo-policy

# Finalize draft → AgentPlan
plancontract finalize examples/data/plan_draft.sequential.json --demo-policy

# Export schemas (also committed under schemas/)
plancontract schema draft > plan_draft.schema.json
plancontract schema plan   > agent_plan.schema.json

Core concepts

Type Role
TaskDraft One LLM-proposed agent task
PlanDraft Planner output (tasks + locale + summary)
PlanTask Runtime task with validated fields
AgentPlan Finalized plan with derived PlanTopology
PlanPolicy Allowlists, limits, extension rules
ValidationReport Pass/fail with machine-readable ValidationCode

Public API:

from plancontract import (
    AgentPlan,
    PlanDraft,
    PlanPolicy,
    PlanTopology,
    ValidationCode,
    derive_topology,
    finalize_draft,
    merge_same_agent_tasks,
    topological_waves,
    validate_plan,
)

See docs/design.md for architecture notes and schemas/ for committed JSON Schema.


Project structure

plancontract/
├── assets/                    # README visuals (SVG source + PNG for GitHub)
├── docs/
│   └── design.md              # Architecture notes
├── examples/
│   ├── basic_usage.py         # Runnable quick-start script
│   └── data/
│       ├── plan_draft.parallel.json
│       └── plan_draft.sequential.json
├── schemas/
│   ├── plan_draft.schema.json # Committed JSON Schema
│   └── agent_plan.schema.json
├── src/plancontract/
│   ├── __init__.py            # Public API
│   ├── __main__.py            # python -m plancontract
│   ├── py.typed               # PEP 561 typing marker
│   ├── models.py              # Pydantic schemas (PlanDraft, AgentPlan, …)
│   ├── policy.py              # PlanPolicy constraints
│   ├── topology.py            # Graph scheduling + topology derivation
│   ├── merge.py               # Same-agent task merge
│   ├── validate.py            # Structured validation
│   ├── finalize.py            # Draft → plan pipeline
│   ├── errors.py              # ValidationReport and codes
│   └── cli.py                 # plancontract CLI
└── tests/
    ├── conftest.py            # Shared fixtures
    ├── helpers.py             # Test task builders
    ├── test_topology.py       # Graph + derive_topology
    ├── test_validate.py       # Validation rules
    ├── test_finalize.py
    ├── test_merge.py
    ├── test_models.py
    ├── test_errors.py
    ├── test_cli.py
    ├── test_schemas.py
    └── test_integration.py

Roadmap

  • LangGraph adapter (PlannerNode, DispatchNode)
  • RouteBench scorer integration
  • JSON Schema publishing (schemas/)
  • plancontract diff for eval regression

Contributing & license

Contributions welcome — see CONTRIBUTING.md. Run make ci before opening a PR.

License: MIT

Disclaimer: Independent open-source project. Not affiliated with any employer or downstream product.



If PlanContract saves you from rewriting planner validation for the third time, consider starring the repo.


Built for teams shipping multi-agent assistants with explicit planner → dispatcher contracts.

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

plancontract-0.1.3.tar.gz (22.5 kB view details)

Uploaded Source

Built Distribution

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

plancontract-0.1.3-py3-none-any.whl (20.9 kB view details)

Uploaded Python 3

File details

Details for the file plancontract-0.1.3.tar.gz.

File metadata

  • Download URL: plancontract-0.1.3.tar.gz
  • Upload date:
  • Size: 22.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for plancontract-0.1.3.tar.gz
Algorithm Hash digest
SHA256 9001dfefaf7238ad71d6fe6231d25810fa6d9c77b241b797870e1eb7b2c7d7e2
MD5 c70c5728164a955d1e31f345353d55df
BLAKE2b-256 2db0457f8f04bb4bde2486aeb0ae2f0cfa758300cb9de60ab060fc061dc0dd07

See more details on using hashes here.

File details

Details for the file plancontract-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: plancontract-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 20.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for plancontract-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 402632e492a5ab63844a4f670e4438f4b3bf7a2f66d304452bfa6423582ae34f
MD5 e3a22b76b1836a1c1f4b36715d50ee5a
BLAKE2b-256 bb19d4b5edfa240bba144f74f55909aa3e1072e4d7014ddb13540ce8261d20db

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