A governor for risky AI/agent tool calls with a tamper-evident audit trail. Upholdr governs tool calls; it never connects to SaaS systems.
Project description
Upholdr
A governor for risky AI/agent tool calls — with a tamper-evident audit trail.
Upholdr governs tool calls; it never connects to SaaS systems. It is the control layer that decides what an agent may do automatically, what needs human approval, and what is only logged. It is not an integration / iPaaS platform — it never ships connectors and never talks to Shopify, Slack, HubSpot, and so on. You (or an aggregator like MCP / Composio / Pipedream) bring the tools; Upholdr decides what's allowed.
Upholdr gives an agent graduated autonomy: an action starts in report_only
(logged, never executed), and a category of action earns its way up the ladder to
suggest and finally auto only after it accumulates clean, logged evidence —
per risk category, never globally. Novel or high-risk actions are escalated even
inside an otherwise-automated category, and the engine fails closed when data is
missing or a tool is unknown. The audit trail is the product — a SQLite ledger
sealed by a tamper-evident hash chain.
How it compares
- vs OPA / Cedar — those are stateless-per-decision policy engines (allow/deny now). Upholdr adds the temporal layer they don't model: autonomy earned across sessions, promotion/demotion evidence, and a reproducible ledger. It can sit alongside a policy engine, not replace it.
- vs LangSmith / Langfuse — those observe agent runs; Upholdr gates them.
- vs human-in-the-loop libraries — those ask forever; Upholdr's point is to ask less over time, with receipts to justify it.
What's in the box
- Earned autonomy, per category. Actions climb
report_only → suggest → autoon clean, logged evidence; novel or high-risk calls escalate even inside anautocategory (the "auto ceiling"). Reachingautorequires routine clean evidence — elevated categories can earnsuggestbut never auto-execute. - Deterministic replay.
ordered action log + policy → decisions + final ladder state, reproducible run-to-run. The same derivation drives the live path, the CLI, and the report — one source of truth. - Operator overrides with cooldown.
reject/reversedemote a category and let it re-earn trust over a cooldown instead of locking it forever; overrides are recorded in the ledger and feed the same derived ladder. - Tamper-evident ledger. A SHA-256 hash chain over a single append sequence,
append-only SQLite triggers, and
upholdr verify— receipts you can check. - Pluggable gate-packs, selected by policy.
access_data(reversibility, exposure, blast-radius, data-confidence, evidence-layer novelty) is the default;pack: refundsscores refund amount — proof the engine isn't domain-specific. - Operator surface.
upholdr status/upholdr categories, and a static HTMLupholdr reportshowing promotion-readiness, recent decisions, overrides, and ledger-verify status. - Live MCP governance.
Governor.govern()routes each call at its earned level; an SDK-free MCP proxy core forwards a tool call only when the verdict isexecuted_auto, returning "approval required" for the rest.
Quickstart
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]" # add the proxy extra with: pip install -e ".[dev,mcp]"
# Evaluate one action against a policy and append a sealed ledger row:
upholdr evaluate \
--file examples/access_actions.jsonl \
--policy examples/policies/access_data.policy.yaml \
--db ./upholdr.db
# Verify the ledger hash chain (exits non-zero on tampering):
upholdr verify --db ./upholdr.db
# Watch one category climb report_only -> suggest -> auto (deterministic):
upholdr replay examples/ladder_actions.jsonl \
--policy examples/policies/tiny_ladder.policy.yaml
# Same engine, a different domain — a $9,500 refund escalates even at auto:
upholdr replay examples/refunds/actions.jsonl \
--policy examples/refunds/policy.yaml
# Render the static operator report:
upholdr report --db ./upholdr.db \
--policy examples/policies/access_data.policy.yaml \
--out report.html
pytest -q
A known tool resolves to reported_only (a new category starts at the bottom of the
ladder); an unknown tool fails closed to escalated. upholdr evaluate writes a
sealed ledger row; upholdr replay derives ladder state from the ordered log.
The tool descriptor registry
Upholdr judges risk from declared metadata about each tool — never by calling it.
See examples/policies/access_data.policy.yaml:
tools:
hubspot.export_contacts:
direction: read
surface: internal
data_class: customer_pii
default_risk: high
data_class is declared by you, not detected — mislabeling it is a governance
gap you own. An unregistered tool is treated as worst-case and fails closed. A policy
may select a domain gate-pack with pack: (default access_data; the refunds recipe
uses pack: refunds). The MCP proxy SDK is an optional extra: pip install "upholdr[mcp]".
License & security
Apache-2.0 (LICENSE). Security policy: SECURITY.md.
The ledger is tamper-evident, not tamper-proof — see the threat model in
docs/threat-model.md.
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 upholdr-0.1.0.tar.gz.
File metadata
- Download URL: upholdr-0.1.0.tar.gz
- Upload date:
- Size: 50.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1dccd975ef1dc8bf00e08c5c6835b6d7fa7cf92cb978539f000fd076cfc7c556
|
|
| MD5 |
5216fae0848cdb5de37221f64e717ad6
|
|
| BLAKE2b-256 |
29f409d707b7ad9ffcc0cfea4e2df925f24b0f8fc291b5bb27fc406173e2b465
|
Provenance
The following attestation bundles were made for upholdr-0.1.0.tar.gz:
Publisher:
release.yml on slimbiggins007/upholdr
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
upholdr-0.1.0.tar.gz -
Subject digest:
1dccd975ef1dc8bf00e08c5c6835b6d7fa7cf92cb978539f000fd076cfc7c556 - Sigstore transparency entry: 1959942282
- Sigstore integration time:
-
Permalink:
slimbiggins007/upholdr@e648a9d8056571b6f2e504051e753a2031ec5dcd -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/slimbiggins007
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e648a9d8056571b6f2e504051e753a2031ec5dcd -
Trigger Event:
release
-
Statement type:
File details
Details for the file upholdr-0.1.0-py3-none-any.whl.
File metadata
- Download URL: upholdr-0.1.0-py3-none-any.whl
- Upload date:
- Size: 34.8 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 |
e93be004097087c5aa8797daccf759b358436cb92ee789b59f89e49e92eb9397
|
|
| MD5 |
95c8309de5a233867c7626aacd8aaaba
|
|
| BLAKE2b-256 |
90057344c52a85fdbf24d741a80cfcc89be18ed7c6ff57ba5af75504c6441baf
|
Provenance
The following attestation bundles were made for upholdr-0.1.0-py3-none-any.whl:
Publisher:
release.yml on slimbiggins007/upholdr
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
upholdr-0.1.0-py3-none-any.whl -
Subject digest:
e93be004097087c5aa8797daccf759b358436cb92ee789b59f89e49e92eb9397 - Sigstore transparency entry: 1959942460
- Sigstore integration time:
-
Permalink:
slimbiggins007/upholdr@e648a9d8056571b6f2e504051e753a2031ec5dcd -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/slimbiggins007
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e648a9d8056571b6f2e504051e753a2031ec5dcd -
Trigger Event:
release
-
Statement type: