AgentMandate
What is your AI agent actually allowed to do?
AgentMandate is a design-time authority analyser for agentic AI systems. An AI agent is a goal-directed system, usually built around a language model, whose runtime supplies tools, identity, memory, policy, and the ability to cause effects. Those tools may each look safe on their own. AgentMandate finds the limits the agent can exceed by combining individually permitted calls, and tells you when a release widened what it can reach.
A mandate is the reviewed, bounded unit of work the agent is allowed to carry out while its issuer may be absent. The manifest is the repository's machine-readable description of that mandate. AgentMandate analyses authority, not whether the model is likely to choose a particular path.
Alpha. Apache-2.0.
Run the example
The examples live in this repository. Clone it and install into a virtual environment (Python 3.10 or newer):
git clone https://github.com/mrwersa/agentmandate.git
cd agentmandate
python -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[yaml]"
For use in your own project, install agentmandate[yaml] from PyPI instead;
the examples below assume you are in the repository root.
A payment-dispute agent has one reviewed mandate. Refunds are capped at 500 GBP per case, every refund needs human approval, and the mandate may move 500 GBP in total. It passes:
$ mandate lint examples/dispute-resolver.yaml
no single-manifest findings
The next release adds one tool. Read-only, moves no money, changes nothing:
- name: search_cases
effect: read
produces: case
unbounded: true
Nobody blocks that in review. It still passes lint, because no single tool is
wrong:
$ mandate reach examples/dispute-resolver-v2.yaml
BREACH cumulative value 1000 GBP exceeds limit 500 GBP
1. open_case(case#1)
2. search_cases(case#2)
3. issue_refund(case#1, 500 GBP)
4. issue_refund(case#2, 500 GBP)
The cap is measured per case. The new tool hands the agent cases it did not open, and there is no fixed number of them, so a per-case ceiling stops bounding the mandate. Four individually permitted calls, both refunds approved by a human, 1,000 GBP reachable.
Why a config diff is not an authority diff
That change is one read-only tool in a pull request. Here is what it did to the agent's effective authority:
$ mandate diff examples/dispute-resolver.yaml examples/dispute-resolver-v2.yaml
authority diff v1 -> v2
+ tool: gained search_cases
+ extractable value: 500 -> 2000 GBP
+ reachable breach: gained cumulative_value
verdict: WIDENING
a widening change needs named review before release
Exit code 1. A pull request shows what somebody typed. It does not show what the agent can now do, because reachability composes and text does not. Adding a read tool or removing a required scope can each open a path that did not exist, and none of them look like a permission change.
Review the configuration change together with the reachable behavior it enables.
Put it in CI
- uses: mrwersa/agentmandate@v0.8.0
with:
manifest: mandate.yaml
baseline: mandate-released.yaml # optional: did this widen authority?
source: src/agent # optional: has the manifest drifted?
Only the checks you give inputs for run, so a manifest alone is enough to
start. The counterexample renders in the job summary as a graph, and
sarif-file is an output you hand to github/codeql-action/upload-sarif so it
annotates the diff. fail-on: never reports without blocking, which is how to
turn this on over an existing repository without stopping everyone on day one.
Details, including why uploading the SARIF is deliberately your step and not the action's: docs/ci.md.
See the whole thing working: agent-release-gate takes one agent from Python source to a gate decision. Seven checks, one exit code, offline.
Adopt it in one repository
- Start with one agent and one bounded task. Write a manifest, or generate a
skeleton with
scanand review everyREVIEWfield. - Run
lintfor individual control gaps andreachfor combinations of calls that exceed a limit. Inspect the counterexample and the search depth. - Commit the reviewed manifest. Use
diffagainst the previous reviewed version anddriftagainst source in each pull request. - Add
verifywhen you have recorded calls with the fields your controls require. It checks those observations against the declaration.
A clean bounded search is evidence about the declared model. Runtime policy, identity, approvals, and accounting still need enforcement in the deployment. AgentMandate runs offline; it does not intercept requests.
The documentation map routes you to the relevant guide. The dated landscape survey explains the broader relationship to policy engines, scanners, and evaluation tools.
Starting from an existing agent
You do not have to write the first manifest by hand. From agent code:
$ mandate scan --source src/agent --agent dispute-resolver > mandate.yaml
That reads @tool, @function_tool, and @ai_function declarations and the
tools=[...] list they are passed to. It is a static read: nothing is
imported, nothing is executed, and the framework need not be installed.
One manifest describes one agent, so a source building two of them is refused
until you name the one you mean with --binding. A union would let reach
compose a path across tools that never share a run. What the read could not
enumerate is reported rather than dropped, because a tool missing from today's
manifest shows up in tomorrow's diff as authority that was never added. See
docs/inventory.md.
Or from an MCP catalogue:
$ mandate scan examples/mcp-tools.json --agent dispute-resolver > mandate.yaml
The skeleton is loadable straight away, and every judgement the catalogue could not supply is marked:
- name: issue_refund
# REVIEW: effect guessed from the name. read | write | irreversible
effect: irreversible
# REVIEW: does this spend the caller's authority or a service account?
principal: caller
requires: [case]
# REVIEW: amount looks like a value argument. A ceiling needs scope_key too.
# value_arg: amount
# scope_key: case
# ceiling: { amount: 0, currency: GBP }
requires_approval: true
Unrecognised verbs are proposed as irreversible, because under-calling an
effect is the more expensive mistake.
Use scan --format mcp|openapi|a2a for strict local JSON imports. OpenAPI
operations remain adapter candidates; an A2A card requires one explicit
--dispatch-tool and its advertised skills are not separate callable tools.
inventory import can also write an unreviewed declaration or its inventory IR.
The catalogue import guide has runnable examples
and explains the review needed before either output can inform a gate.
Keeping the manifest honest
A manifest is a reviewed claim about one mandate. Two things quietly falsify it: somebody adds a tool to the agent and nobody edits the YAML, or a signature changes and the argument a ceiling was counted against stops existing.
$ mandate drift mandate.yaml --source src/agent
UNDECLARED issue_credit_note
the agent is given this tool and the mandate does not declare it, so
every reach and diff run so far analysed a smaller graph than the real one
ARGUMENT issue_refund
value_arg names 'amount', which is not an argument this tool takes any
more (it takes: case_id, total, currency). A ceiling counted against an
argument that does not exist is not a ceiling.
REMOVED close_case
the mandate declares this tool and the agent is not given it in source,
so the analysis is defending authority nobody has.
The argument finding exposes a stale control: the manifest still parses, but its value argument no longer matches the implementation.
A tool list the read cannot enumerate, such as tools=load_tools(), is itself
a finding. Reporting no drift from evidence that could not see the whole list
would be the false assurance this package exists to prevent.
Reviewed dynamic inventories can discharge that uncertainty only when drift
also receives the declaration, captured bytes, deployment selection, and an
explicit evaluation date. See the
dynamic-inventory contract.
The manifest
Reachability needs three facts per tool that an ordinary tool schema does not carry: the effect class, which argument spends value, and which scope the limit is measured against.
version: 1
agent: dispute-resolver
identity: spiffe://bank/agents/dispute-resolver
limits:
total: { amount: 500, currency: GBP }
depth: 8
effects: # optional. an absent class is unbounded
irreversible: 3 # at most three irreversible calls in one run
tools:
- name: open_case
effect: read # read | write | irreversible
produces: case # mints a binding of scope "case"
- name: issue_refund
effect: irreversible
principal: caller # caller | service
requires: [case]
value_arg: amount
scope_key: case # the ceiling is per case
ceiling: { amount: 500, currency: GBP }
requires_approval: true
These annotations describe the bounded abstraction used by the search. They do not model arbitrary preconditions, postconditions, or tool implementations.
A cumulative constraint makes a decision depend on qualifying earlier
actions. Its limit is the configured bound; its consumed state is what the
runtime has already counted. In manifest v1, a ceiling is the maximum
cumulative value one tool may spend against one binding of its scope_key.
unbounded: true marks a tool that can be called repeatedly to mint fresh
bindings, which is what turns a per-scope limit into no mandate-wide bound at
all.
Commands
| Command | What it does |
|---|---|
mandate scan |
Derives a review-marked manifest skeleton from source or a local MCP/OpenAPI/A2A catalogue |
mandate drift |
Compares the declared mandate against the agent's source and fails when the two have separated |
mandate lint |
Single-manifest checks: separation of duties, ungated irreversible effects, service-account principals, invalid ceilings, and required scopes with no producer |
mandate reach |
Bounded search over a manifest or reviewed --ir snapshot for a legal call sequence that breaches a limit, reported as a counterexample |
mandate ir |
Exports a manifest as canonical Authority IR or structurally validates a snapshot without accepting its evidence as authority |
mandate inventory |
Imports local protocol catalogues as unreviewed declarations/IR, or validates declaration structure |
mandate conditions |
Structurally validates a tool-condition or condition-context artifact without accepting it as authority |
mandate delegations |
Structurally validates a delegation attachment or chain without accepting it as authority |
mandate producers |
Structurally validates a finite-producer boundary without accepting its sources as authority |
mandate continuity |
Structurally validates continuity evidence or reconciles whether consumed authority safely survives a named transition |
mandate cedar |
Structurally validates managed policy evidence, aligns exact decisions with reviewed authority, or compares matched requests across policy revisions |
mandate diff |
Effective-authority comparison of two manifests, including limits, preconditions, approvals, effects, and scope minting. --record emits a change record |
mandate verify |
Replays recorded tool calls against the manifest and fails closed when evidence required by a declared control is missing. Reads OpenTelemetry traces with --otel |
mandate obligations |
Derives reviewable test obligations from reachable authority, and renders reviewed ones as an AgentVerity decision suite |
mandate scenarios |
Exports reachable breach paths with blank environment, agent-input, and expected-control fields for human review and execution by an external evaluation harness |
Analysis commands support --json and return non-zero when findings or
unresolved required evidence need attention. scan, inventory import, and
ir export write artifacts to standard
output and are not gates. Exit codes and CI wiring: docs/ci.md.
When you need evidence attachments
Start with the ordinary manifest workflow above. Attachments are useful when source code or manifest fields cannot establish a specific boundary:
| Need | Guide |
|---|---|
| Exchange a canonical reviewed authority snapshot | Authority IR |
| Understand search depth, memory costs, and truncation | Search bounds |
| Resolve a dynamically supplied tool list | Dynamic inventory |
| Narrow a tool's effect using a reviewed input domain | Conditional authority |
| Relate a tool to an ordered delegation chain | Delegation |
| Compare managed policy decisions with reviewed intent | Managed Cedar |
| Establish a finite number of produced resources | Producer boundaries |
| Check whether consumed state survives a session or policy change | Refund walkthrough |
| Keep principal changes separate from session changes | Principal observations |
| Compare shared spend with a mandate budget under a reviewed binding | Principal accounting |
| Review policy-revision comparability and issuer treatment within an explicit scope | Revision review |
| Verify retained scalar state and next-request capacity at a handover | Scalar handover |
Each guide distinguishes structural validation from evidence eligible for analysis. Parsing an artifact does not accept its claims. Continuity remains experimental; principal observations currently leave mandate continuity unresolved even when their evidence is eligible.
For example, the GBP 1,000 refund mandate that has already spent GBP 600 should retain that consumption after reconnecting. The synthetic walkthrough compares a denied second GBP 600 refund with a reset that admits GBP 1,200 in total.
From authority to evaluation
AgentMandate produces two different test inputs:
obligationsnames consequential decision points that reviewed bounded decision tests should reachscenariospreserves compound counterexample paths that a multi-step scenario test should attempt
It does not execute either test. Promptfoo, LangSmith, AgentCore Evaluations, pytest, or an internal harness owns behaviour and outcome grading. AgentVerity can qualify the repeated bounded decisions after correctness passes.
reachability -> reviewed obligations and scenarios -> external evaluation
^ |
| v
manifest <- reviewed production incidents <- runtime policy and traces
Read the complete evaluation-loop workflow.
Scope
Read every finding as permitted by the reviewed mandate within this bounded abstraction. It is not proof that the model will choose the path or that an undeclared downstream invariant will accept it.
What this does not do, on purpose:
- No enforcement. No proxy, no runtime interception, no blocking.
- No data-flow reachability. Finding that a read tool feeds an exfiltration path needs taint labels the manifest does not carry. Cumulative value and scope minting are what the current model supports honestly.
- No model behaviour. Whether the agent would take a path is a different question from whether it may. This measures permitted authority.
- No session or budget broker. A runtime session is not automatically the reviewed mandate, and a configured cumulative limit is not enough unless its consumed state remains attached to that mandate. Continuity across sessions, handoffs, and policy revisions is evidence-backed roadmap work, not a claim made by the current reachability command.
- No inference of the fields that matter.
mandate scanreads agent source or an MCP catalogue and writes the skeleton, but it cannot know whether an effect is reversible or what a ceiling is measured against. It guesses conservatively and marks every guessREVIEW. Extract then annotate, never extract and trust.
Search is bounded by limits.depth. No breach at depth 8 is not proof that none exists at depth 20, and the report says when it truncated.
Documentation
- docs/README.md — a map of current contracts, experimental work, decision records, and evidence
- DESIGN.md — the authority model, why the search is shaped this way, and what was left out
- docs/evaluation-loop.md — how authority analysis, scenario evaluation, runtime policy, and production feedback remain distinct
- docs/test-obligations.md — decision-point obligations and the AgentVerity bridge
- CONTRIBUTING.md — branch and review workflow
- SECURITY.md — reporting, and what a manifest may contain
- STABILITY.md — what is guaranteed before 1.0
- docs/ci.md — the action, SARIF, the diff gate, and exit codes
- ROADMAP.md — adoption work, planned model extensions, and the path to 1.0
- CHANGELOG.md
Development
python -m pip install -e ".[dev]"
python -m pytest -q
python -m pytest -q --cov=agentmandate --cov-fail-under=100
ruff check .
main is protected. Every change lands through a pull request with CI green.
Status
Alpha. The version is in the badge above and on PyPI, so it is not repeated here where it would go stale. The authority model is the part most likely to change, because it has not yet been pointed at enough real tool graphs to know where it is too coarse. Issues describing a graph it models badly are the most useful thing you can file.
Metadata
Release files for agentmandate 0.22.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| agentmandate-0.22.0.tar.gz | 1.0 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| agentmandate-0.22.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.2 MB
Release files / agentmandate-0.22.0.tar.gz
| Download URL | agentmandate-0.22.0.tar.gz |
|---|---|
| Size | 1.0 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2205fc28e343e175ca7ccaaa854a5a5e1bbd3cc3f15b588bdc8ff33a31e13e98
|
|
BLAKE2b-256 checksum How to use checksums |
dab39158d5c5a7fe2a1a9cf1f4cf60d4c72f1c43080a03ff4fdfd999563edffd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / agentmandate-0.22.0-py3-none-any.whl
| Download URL | agentmandate-0.22.0-py3-none-any.whl |
|---|---|
| Size | 183.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d1c822aad21246053eff468f157051195686f5b88914a193b45af6ffb1066bb3
|
|
BLAKE2b-256 checksum How to use checksums |
f08ee74eee573a87d5ad777b5f81b98e2039936d19e3cbc0b47e0f407d259b08
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency log