PlanSpec + lint + safety gates for agent plans.
Project description
sdf-plan
Tool safety gates for agent workflows.
30-Second Quickstart (ToolGate-first)
from sdf_plan import GateContext, confirm, propose
ctx = GateContext(workspace_id="demo-ws")
first = propose(
tool_name="filesystem.write",
args={"path": "/tmp/demo.txt", "content": "hello"},
ctx=ctx,
)
print(first.decision.value) # REQUIRE_CONFIRM
token = first.resume.token
_ = confirm(token, user_ok=True)
second = propose(
tool_name="filesystem.write",
args={"path": "/tmp/demo.txt", "content": "hello"},
ctx=ctx,
meta={"confirmed_token": token},
)
print(second.decision.value) # ALLOW
Expected flow: REQUIRE_CONFIRM -> CONFIRM -> ALLOW
Install
pip install sdf-plan
5-Minute First Success
python examples/tool_gate_quickstart.py
python examples/tool_gate_openai_input.py
python examples/plan_mode_preflight.py
CLI
sdf-plan lint path/to/plan.json
sdf-plan classify --tool filesystem.write
What You Get
- ToolGate runtime decisions (
ALLOW | REQUIRE_CONFIRM | WARN | BLOCK) - Signed confirmation tokens + resume flow
- Idempotency key derivation from scope + tool + canonical args
- Tool-mode lint rules + policy defaults
- PlanSpec lint and preflight (optional mode)
- LangGraph adapter (official thin wrapper for v0.2.0)
Support Matrix (v0.2.0)
- Official maintained adapter:
LangGraph - Thin adapters:
CrewAI,LangChain - Direct parser support: OpenAI-style tool calls, generic tool call JSON, PlanSpec
- BYO adapter support: any framework that can pass
(tool_name, args, meta, run_context)intopropose(...) - Deferred official adapters: additional framework-specific variants beyond thin wrappers
Public API Stability
Top-level imports are a stable facade:
from sdf_plan import propose, confirm
The facade remains stable while internals evolve; core logic stays in sdf_plan/gate, not in __init__.py.
Optional PlanSpec Mode
Plan mode remains supported for existing users.
from sdf_plan import lint_plan, policy_annotate, preflight_lint
plan = {
"steps": [
{
"id": "S1",
"type": "ACT",
"title": "send email",
"intent": "send email",
"inputs": [],
"outputs": ["ctx.sent"],
"depends_on": [],
"stop_condition": "Step S1 completed",
"fallback": "reduce_scope",
"idempotency_key": "idem-1",
}
]
}
plan, summary = policy_annotate(plan)
findings = lint_plan(plan, max_steps=12, safety_mode="safe")
preflight_lint(plan, max_steps=12, safety_mode="safe")
Guides
docs/API_REFERENCE.mddocs/ARCHITECTURE.mddocs/SECURITY_MODEL.mddocs/MIGRATION_PLANSPEC_TO_TOOLGATE.mddocs/PRODUCTION_HARDENING.mddocs/ADAPTER_TEMPLATE.mddocs/POLICY_TUNING.mddocs/TOOL_CLASSIFICATION.mddocs/COMPATIBILITY.mddocs/RELEASING.md
Examples
examples/tool_gate_quickstart.pyexamples/tool_gate_openai_input.pyexamples/plan_mode_preflight.pyexamples/adapter_minimal.pyexamples/langgraph_plangate_demo.pyexamples/crewai_plangate_demo.py(community-style example, not an official adapter contract in v0.2.0)
Testing (CI Parity)
Install dev/test dependencies:
pip install -e ".[dev]"
Fast local checks (matches PR path):
pytest -q -m "not slow" tests/unit
pytest -q tests/contract/test_gate_contract.py tests/contract/test_adapter_contract.py
pytest -q -m "not slow" tests/integration/test_openai_variants_normalization.py tests/integration/test_generic_toolcall_normalization.py tests/integration/test_planspec_to_ir.py tests/integration/test_tool_gate_flow.py tests/integration/test_tool_gate_concurrency.py tests/integration/test_plan_and_tool_mode_coexist.py tests/compat/test_planspec_roundtrip_best_effort.py
pytest -q tests/unit/test_token_security.py tests/integration/test_tool_gate_concurrency.py
Coverage gates:
pytest -q --cov=sdf_plan --cov-report=term-missing --cov-fail-under=70 tests/unit tests/integration
pytest -q --cov=sdf_plan.gate --cov-fail-under=70 tests/unit tests/integration
pytest -q --cov=sdf_plan.policy --cov-fail-under=70 tests/unit tests/integration
pytest -q --cov=sdf_plan.inputs --cov-fail-under=70 tests/unit tests/integration
Nightly/slow checks:
pytest -q -m slow tests/integration/test_fuzz_inputs.py tests/integration/test_perf_budget.py
Packaging smoke:
python -m build
twine check dist/*
pip install dist/*.whl
python -c "import sdf_plan; print('sdf_plan import ok')"
Compatibility
Use Cloud schema hash checks to detect contract drift:
from sdf_plan.compat import assert_schema_compat, package_version
assert_schema_compat(package_version(), "schema_hash_from_/v1/schema")
Releases
- Git tags use
vX.Y.Zformat. - GitHub Releases notes mirror
CHANGELOG.md. - PyPI releases are published from tagged workflow runs.
- See
docs/RELEASING.mdfor the exact process.
License
This project is licensed under the MIT License.
See LICENSE for the full text.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sdf_plan-0.2.7.tar.gz.
File metadata
- Download URL: sdf_plan-0.2.7.tar.gz
- Upload date:
- Size: 28.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
166c2066784effb2ab7418125d37e7010f24802f47aeb48d9c9fc4bd4c28fea6
|
|
| MD5 |
920f3e89d2807828d12ab3a264021932
|
|
| BLAKE2b-256 |
debcf0e0192fd46afeaf041a7eea7bb0e03c59bd062edb1ffffe8ef9c03ad548
|
Provenance
The following attestation bundles were made for sdf_plan-0.2.7.tar.gz:
Publisher:
release.yml on directiveproto/sdf-plan
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sdf_plan-0.2.7.tar.gz -
Subject digest:
166c2066784effb2ab7418125d37e7010f24802f47aeb48d9c9fc4bd4c28fea6 - Sigstore transparency entry: 954017150
- Sigstore integration time:
-
Permalink:
directiveproto/sdf-plan@fd56280a7dc589648aba1580bfc33643885cca78 -
Branch / Tag:
refs/tags/v0.2.7 - Owner: https://github.com/directiveproto
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fd56280a7dc589648aba1580bfc33643885cca78 -
Trigger Event:
push
-
Statement type:
File details
Details for the file sdf_plan-0.2.7-py3-none-any.whl.
File metadata
- Download URL: sdf_plan-0.2.7-py3-none-any.whl
- Upload date:
- Size: 37.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aa503f81b80ffb71c61503334ff9533328c95da63b858d4f1a66c3656816dd9f
|
|
| MD5 |
f1bd7d41696d4a4a81efe54cbf297c71
|
|
| BLAKE2b-256 |
211cee2ad238689a8321948d8c3c5304eb5c8168c3fb13f57d63254fae515ab6
|
Provenance
The following attestation bundles were made for sdf_plan-0.2.7-py3-none-any.whl:
Publisher:
release.yml on directiveproto/sdf-plan
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sdf_plan-0.2.7-py3-none-any.whl -
Subject digest:
aa503f81b80ffb71c61503334ff9533328c95da63b858d4f1a66c3656816dd9f - Sigstore transparency entry: 953995776
- Sigstore integration time:
-
Permalink:
directiveproto/sdf-plan@2ca14c23a4a553a2b6fb2ee5f7cefabf99c4545d -
Branch / Tag:
refs/tags/v0.2.7 - Owner: https://github.com/directiveproto
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2ca14c23a4a553a2b6fb2ee5f7cefabf99c4545d -
Trigger Event:
push
-
Statement type: