agentvalet
Call approved SaaS platforms from any Python agent. Your agent never holds the downstream credential — AgentValet signs a short-lived identity assertion, checks the call against the owner's grants and policy, injects the credential at call time, and writes an audit record.
This is the Python port of
@agentvalet/client. Same
endpoints, same approval semantics, same timings.
Install
pip install agentvalet
Python 3.9+. Two dependencies: httpx and pyjwt[crypto].
Get an agent identity
agentvalet register --code <invite-or-enrollment-code>
The RSA keypair is generated on your machine. Only the public half is sent;
the private key is written to ~/.agentvalet/agent.key (mode 0600) and never
crosses the wire. The code is either an invite's bind secret or the enrollment
code from the "Try it free" flow.
This writes the same files as npx @agentvalet/register, so the Node and Python
tooling are interchangeable — you do not need Node installed.
Use it
from agentvalet import AgentValet
av = AgentValet.from_env()
result = av.call(
platform="slack",
endpoint="/api/chat.postMessage",
method="POST",
scope="chat:write",
data={"channel": "#general", "text": "Deploy finished."},
)
from_env() reads AGENTVALET_AGENT_ID / AGENTVALET_OWNER_ID (or the bare
AGENT_ID / OWNER_ID) and finds the key via AGENT_PRIVATE_KEY_B64,
AGENT_PRIVATE_KEY_PATH, AGENT_PRIVATE_KEY, or ~/.agentvalet/agent.key. To
wire it explicitly:
av = AgentValet(
agent_id=os.environ["AGENT_ID"],
owner_id=os.environ["OWNER_ID"],
private_key=os.environ["AGENT_PRIVATE_KEY"],
proxy_url="https://api.agentvalet.ai", # default
)
Use it as a context manager to close the HTTP client cleanly:
with AgentValet.from_env() as av:
av.call(platform="github", endpoint="/user", scope="read")
Async
AsyncAgentValet is a method-for-method mirror. All decision logic is shared,
and the test suite runs the same behaviour table against both.
from agentvalet import AsyncAgentValet
async with AsyncAgentValet.from_env() as av:
result = await av.call(
platform="slack", endpoint="/api/chat.postMessage",
method="POST", scope="chat:write", data={...},
)
Approvals are just a slower call
When the owner has marked a scope as approval-gated, the proxy holds the action
and call() waits. If the owner approves, the proxy re-runs the call and you
get the result — from your code's point of view it simply took longer.
av = AgentValet.from_env(
on_approval_pending=lambda i: print(f"waiting… {i['elapsed_s']:.0f}s")
)
If nobody responds inside the budget (50s by default), you get an
ApprovalTimeoutError — not a failure. The action stays queued. Keep the
approval_id and resume whenever you like, in this process or another:
from agentvalet import ApprovalTimeoutError
try:
av.call(platform="stripe", endpoint="/v1/refunds", method="POST", scope="charge")
except ApprovalTimeoutError as err:
queue.put(err.approval_id) # hand off to a worker
# …later, elsewhere:
result = av.wait_for_approval(approval_id)
Pass approval_timeout_s=0 if you never want to block.
Errors you can branch on
Every exception is typed, so you never string-match an error envelope:
| Error | Means | What to do |
|---|---|---|
ConfigError |
Bad/missing identity or key | Fix config; raised before any network call |
NetworkError |
Transport failed | .hint diagnoses DNS / TLS / firewall / timeout |
AccessDeniedError |
No grant, or policy blocked it | request_access() — see below |
ApprovalDeniedError |
Owner said no | Terminal. Don't retry |
ApprovalExpiredError |
Aged out server-side | Re-issue the call |
ApprovalTimeoutError |
You stopped waiting | Resume via wait_for_approval() |
UpstreamError |
The SaaS returned non-2xx | .status / .data hold the upstream reply |
ProxyError |
Anything else from the broker | .status / .body |
Asking for access you don't have
Deny-by-default means a scope you were never granted raises
AccessDeniedError. Your agent can ask for it:
result = av.request_access(
platform="slack",
scope="chat:write",
reason="Post deploy notifications to #general",
)
if result["status"] == "approved":
... # retry the original call
Checking before you act
av.list_platforms() # what this agent is actually granted
av.pending_actions() # queued behind an approval
av.evaluate("stripe", "charge") # dry-run the decision, no side effect
evaluate() is worth calling before anything destructive.
Self-hosting
Point proxy_url at your own deployment. Everything else is identical.
License
MIT
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 agentvalet-0.1.0.tar.gz.
File metadata
- Download URL: agentvalet-0.1.0.tar.gz
- Upload date:
- Size: 20.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e284c95d801de9ea3bb4256e28d190b55ca9c01d7f49970d3e7d9e4c86d7740
|
|
| MD5 |
2833c1e0590ea6cbcfdc82307721c2a9
|
|
| BLAKE2b-256 |
586cac2b6128432038ee4e59e5554c4200b90f1d73458f409230483dcdbebe5a
|
Provenance
The following attestation bundles were made for agentvalet-0.1.0.tar.gz:
Publisher:
publish-python.yml on MCSEdwin/agentvalet
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agentvalet-0.1.0.tar.gz -
Subject digest:
8e284c95d801de9ea3bb4256e28d190b55ca9c01d7f49970d3e7d9e4c86d7740 - Sigstore transparency entry: 2434798670
- Sigstore integration time:
-
Permalink:
MCSEdwin/agentvalet@be506c03b4517a7c54c94917ea591e4a9aa66531 -
Branch / Tag:
refs/tags/agentvalet-v0.1.0 - Owner: https://github.com/MCSEdwin
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@be506c03b4517a7c54c94917ea591e4a9aa66531 -
Trigger Event:
push
-
Statement type:
File details
Details for the file agentvalet-0.1.0-py3-none-any.whl.
File metadata
- Download URL: agentvalet-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6b27a489ec1082cae9719e8742ed21aafb618be448b234302bad6fca1b68f757
|
|
| MD5 |
54ff87c22f2cce83b0db4d5fb6ae34e0
|
|
| BLAKE2b-256 |
c52d2be40607c31059e68c0250b1b571820c944f8e02de4695f11e71a50ee947
|
Provenance
The following attestation bundles were made for agentvalet-0.1.0-py3-none-any.whl:
Publisher:
publish-python.yml on MCSEdwin/agentvalet
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agentvalet-0.1.0-py3-none-any.whl -
Subject digest:
6b27a489ec1082cae9719e8742ed21aafb618be448b234302bad6fca1b68f757 - Sigstore transparency entry: 2434799592
- Sigstore integration time:
-
Permalink:
MCSEdwin/agentvalet@be506c03b4517a7c54c94917ea591e4a9aa66531 -
Branch / Tag:
refs/tags/agentvalet-v0.1.0 - Owner: https://github.com/MCSEdwin
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@be506c03b4517a7c54c94917ea591e4a9aa66531 -
Trigger Event:
push
-
Statement type: