The patterns on this page (retry decorators, an audit-log schema, a rate
limiter) are illustrative, adapt them to your own stack. What’s specific to
Parmana (the client construction, the exception taxonomy, which call raises
what) is the real, verified API, see Python SDK. For how to
actually run the server itself in production (Fly.io, Docker, environment
variables the server reads), see Production
deployment and Deploy
patterns, this page is about the client side,
calling an already-deployed Parmana instance.
Deployment shape
Configuration
ParmanaConfig object to pass around, ParmanaClient takes endpoint and api_key
directly. Keep your own settings object (like ParmanaSettings above) if you want centralized
validation; just unpack it into the two real constructor kwargs.
Error handling
Fail-closed by default
A denial should be a denial, not an exception a caller has to remember to interpret correctly. Wrap the client so each failure resolves to an explicit outcome. A policy rejection means “did not execute”. A timeout or network error does not: an approved request can take tens of seconds and may have run, so it means “unknown, read the record before anything else”:Error types
The exception taxonomy is real and verified, see Errors, correctly mapped to real conditions for the complete, tested table. The ones worth handling explicitly in production:Retries
ParmanaClient already retries idempotent GETs with backoff on 429/502/503/504; POSTs
(which execute() is) are never retried automatically, since retrying an execution isn’t safe.
Do not wrap execute() in a generic retry. Sending the same transaction again (same
business_transaction_id) returns ConflictError (409), never the original result; sending it
with a new id asks for the action a second time. Use the server code instead:
timeout=120 on the client (the default is 30 seconds), and after a timeout read the
record with the same id before anything else.
Audit logging
Parmana already produces a signed Execution Trust Record for every approved execution, and a Refusal Record for a rejected one, both independently verifiable. What you typically still want on your own side is a queryable local log tying those records back to your application’s own request context:Monitoring
Track outcomes and latency around everyexecute() call, however your stack already does
metrics (Prometheus shown here as one example):
NetworkError/ServerError rate
above zero for more than a few minutes, and p99 latency past whatever your own SLA is.
Rate limiting
If you’re calling Parmana from a high-throughput path and want to shed load client-side rather than let the server’s own limits reject you:Smoke-testing a deployment
Run something like this after every deploy, it should complete without raising anything other than an expectedExecutionRejectedError:
Deployment checklist
-
PARMANA_ENDPOINTandPARMANA_API_KEY(or equivalent) set from your secrets manager, not hardcoded - Fail-closed behavior confirmed for
NetworkError/ServerError(see above) -
ExecutionRejectedErrorhandled as an expected, logged outcome, not swallowed silently - Your own audit log wired up, keyed by
business_transaction_id - Basic metrics (outcome counts, latency) exported
- Smoke test run against the target environment after deploy
- You’ve read Limitations, know what Parmana does and doesn’t guarantee before you make claims of your own downstream
Next
Production deployment
Running the Parmana server itself in production, the other half of this
page.
Python SDK for AI Agents
The integration patterns this guide’s error handling wraps around.
Limitations
What Parmana does and doesn’t guarantee, read before you write your own SLA
on top of it.
Python SDK reference
The complete, verified API surface: every method, every error, the test
suite backing it.