Action-level release control for AI agents: preview, approve, canary, halt, and audit tool actions before they touch production.
Project description
action-marshall
Action-level release control for AI agents. Preview, approve, canary, halt, and audit tool actions before they touch production.
action-marshall is the Python SDK for Action Marshall. Wrap the tools or functions your agent already calls — Action Marshall runs preview, policy, approval, canary, breaker, and signed audit before the action is released.
Status: 0.1.0 — pre-1.0 alpha. The API may change before 1.0.0.
Install
pip install action-marshall
Python 3.9+.
You also need a backend
action-marshall is the client SDK. It talks to an Action Marshall backend that runs the policy engine, signs receipts, and stores audit history. To try it end-to-end you have two paths:
Option A — local backend via Docker (recommended for evaluation)
git clone https://github.com/SatwikReddySripathi/action-marshall.git
cd action-marshall
docker compose up --build
Backend lives at http://localhost:8000, dashboard at http://localhost:3000. Wait until curl http://localhost:8000/ready returns 200. Default API key is am_test_demo_key_001.
Option B — point at a hosted backend (planned — hosted Action Marshall is not live yet)
Once hosted Action Marshall opens, you'll get an API key and a base_url. Join the waitlist.
Optional framework adapters:
pip install "action-marshall[langchain]" # available now (experimental)
pip install "action-marshall[langgraph]" # planned
pip install "action-marshall[crewai]" # planned
pip install "action-marshall[autogen]" # planned
pip install "action-marshall[mcp]" # planned
pip install "action-marshall[llamaindex]" # planned
pip install "action-marshall[openai]" # planned
pip install "action-marshall[all]"
Quickstart
Assuming the backend is up at http://localhost:8000:
from action_marshall import MarshallClient, Action, ActionParams
ks = MarshallClient(
api_key="am_test_demo_key_001",
base_url="http://localhost:8000",
)
result = ks.run(Action(
tool="servicenow",
action_type="bulk_update",
params=ActionParams(
connector="servicenow_sim",
query={"state": "open", "priority": {"op": "in", "value": ["P3", "P4"]}},
changes={"state": "in_progress"},
),
))
print(result.decision_value) # AUTO | CANARY | BLOCK | APPROVAL_REQUIRED
print(result.status) # completed | contained | blocked | awaiting_approval | observed
print(result.blast_radius) # 17
print(result.proof_url) # /v1/actions/<id>/proof
Open result.ui_urls["detail"] in your browser to see the full action in the dashboard.
Wrap an existing function
The killer pattern — every call becomes a governed action:
@ks.wrap_function(
tool="servicenow",
action_type="update_incident",
connector="servicenow_sim",
agent_id="incident-triage",
)
def update_incident(payload: dict) -> dict:
# your existing implementation
...
result = update_incident({"incident_id": "INC001", "status": "resolved"})
AUTO→ the wrapped function runs normally.BLOCK→ wrapped function is not called;MarshallDeniedis raised.APPROVAL_REQUIRED→ wrapped function is not called;MarshallApprovalRequiredis raised. Approve in Slack or the web UI, the next call goes through.
Pass on_denied=... / on_approval_required=... callbacks if you want to handle these without exceptions:
@ks.wrap_function(
tool="servicenow", action_type="update_incident",
connector="servicenow_sim", agent_id="incident-triage",
on_denied=lambda err: {"status": "blocked-by-policy", "reasons": err.result.decision.get("reasons")},
)
def update_incident(payload: dict) -> dict: ...
Preview without executing
preview = ks.preview(Action(...))
print(preview.decision_value, preview.blast_radius, preview.preview_hash)
Same lifecycle as run but no writes happen — the policy and canary phases evaluate and a receipt is signed for the dry run.
Wrap a LangChain tool
available now (experimental). Requires pip install "action-marshall[langchain]".
from langchain_core.tools import tool
from action_marshall import MarshallClient
from action_marshall.adapters.langchain import wrap_langchain_tool
@tool
def update_incident(payload: dict) -> dict:
...
ks = MarshallClient(api_key="am_...", base_url="http://localhost:8000")
protected = wrap_langchain_tool(
update_incident, ks=ks,
tool="servicenow", action_type="update_incident",
connector="servicenow_sim", agent_id="incident-triage",
)
# protected.invoke({...}) is now governed.
Verify a signed receipt
receipt = ks.verify_receipt("act_abc123")
print(receipt.verified) # True if the HMAC signature matches
print(receipt.signature)
Server-side verification — the backend re-signs the receipt body and compares with constant-time HMAC. For fully offline verification with a local PROOF_SECRET, see the security docs.
Decisions
| Decision | Meaning |
|---|---|
AUTO |
Allowed without approval. The wrapped function runs. |
CANARY |
Allowed, but a canary subset runs first; the breaker can halt. |
APPROVAL_REQUIRED |
Human approval needed before execution. |
BLOCK |
Disallowed. The wrapped function is not called. |
Decision hierarchy: BLOCK > APPROVAL_REQUIRED > CANARY > AUTO. Policies escalate, never de-escalate.
Connectors
| Connector | Use for | Status |
|---|---|---|
servicenow_sim |
Simulated ServiceNow incidents (demo / dev) | available now |
servicenow_real |
Live ServiceNow instance | available now |
email_generic |
Outbound email actions | available now |
jira_real |
Live Jira Cloud | planned |
Public API
from action_marshall import (
MarshallClient,
Action, ActionParams, Actor, Approval, Approver,
ActionResult, PreviewResult, Receipt,
MarshallError, MarshallAPIError, MarshallDenied, MarshallApprovalRequired,
)
The type-checker-friendly py.typed marker ships in the wheel — mypy and pyright will pick up our annotations automatically.
CLI
action-marshall is also installed as a CLI binary. Useful for one-off operations, CI smoke checks, and tools that aren't easily called from Python.
action-marshall --help
action-marshall init # write ~/.marshall/config.json
action-marshall preview action.json
action-marshall run action.json
action-marshall receipts list
action-marshall receipts verify act_abc123
Full reference: docs/cli.md.
Local development
git clone https://github.com/SatwikReddySripathi/action-marshall.git
cd action-marshall/sdk
pip install -e ".[dev]"
pytest
License
MIT. See LICENSE.
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 action_marshall-0.1.0.tar.gz.
File metadata
- Download URL: action_marshall-0.1.0.tar.gz
- Upload date:
- Size: 22.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e53c85dd2cbb89c60b3cabf7184beadd2c9fcee5c9bdaf483b677be6bfd8942c
|
|
| MD5 |
fd7289e93b55311643acd9682a399f91
|
|
| BLAKE2b-256 |
24b952be8b6f3020666a835a3eb44ba53a1cccf44634d5dc97024bab3a079b1b
|
Provenance
The following attestation bundles were made for action_marshall-0.1.0.tar.gz:
Publisher:
publish-python.yml on SatwikReddySripathi/action-marshall
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
action_marshall-0.1.0.tar.gz -
Subject digest:
e53c85dd2cbb89c60b3cabf7184beadd2c9fcee5c9bdaf483b677be6bfd8942c - Sigstore transparency entry: 1632045736
- Sigstore integration time:
-
Permalink:
SatwikReddySripathi/action-marshall@c2bf2f3492113f666be408902fe24679c2a558ff -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/SatwikReddySripathi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@c2bf2f3492113f666be408902fe24679c2a558ff -
Trigger Event:
push
-
Statement type:
File details
Details for the file action_marshall-0.1.0-py3-none-any.whl.
File metadata
- Download URL: action_marshall-0.1.0-py3-none-any.whl
- Upload date:
- Size: 16.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d2bfac6a6deb1d8651f8f074828b0ac509126cd78547aeb4f21018eef83d9822
|
|
| MD5 |
159e0e7c1b1e4b526c7f72cae6491060
|
|
| BLAKE2b-256 |
a156689a10329098c43e10f43b180cab8d868019cf188f36b040bed93d9a45ad
|
Provenance
The following attestation bundles were made for action_marshall-0.1.0-py3-none-any.whl:
Publisher:
publish-python.yml on SatwikReddySripathi/action-marshall
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
action_marshall-0.1.0-py3-none-any.whl -
Subject digest:
d2bfac6a6deb1d8651f8f074828b0ac509126cd78547aeb4f21018eef83d9822 - Sigstore transparency entry: 1632045765
- Sigstore integration time:
-
Permalink:
SatwikReddySripathi/action-marshall@c2bf2f3492113f666be408902fe24679c2a558ff -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/SatwikReddySripathi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@c2bf2f3492113f666be408902fe24679c2a558ff -
Trigger Event:
push
-
Statement type: