Skip to main content

opsen

Run your agents. Know what each one cost.

You point a session at a task ID; we run it, meter it, and tell you what that task cost across compute and tokens together. Your agent code does not change — not one line, not one import.


Quickstart

pip install opsen
from opsen import Client

c = Client(api_key=os.environ["OPSEN_KEY"])

with c.session("job_8812", labels={"customer": "acme"}, budget_usd=2.00) as s:
    s.fs.write("agent.py", open("agent.py").read())
    s.exec("python3 agent.py")
    print(s.cost())
Cost(total=$0.019806 compute=$0.000006 tokens=$0.019800 calls=3)

agent.py is unmodified. Its Anthropic or OpenAI client picks up a base URL we inject into the session, so every model call is attributed without you instrumenting anything.

The query most people actually came for:

c.costs(group_by="labels.customer")
# [{"labels.customer": "acme", "total_usd": 412.55, "tasks": 1104}, ...]

Label sessions with whatever your business is organised by — customer, tenant, workflow, feature — and group on it. We do not model your business; you attach the labels and we join on them.


What you don't have to do

Manage session lifecycle. TTL is mandatory and enforced. Idle sessions suspend and resume transparently. Orphans are reaped. Wedged sessions are detectable (c.stuck_sessions()) rather than something you discover on an invoice.

Instrument your agent. No wrapper, no callback, no SDK inside the sandbox.

Hold provider keys in the sandbox. See below — this is the part worth reading even if you skip the rest.


Credentials

Register your provider key once:

k = c.add_provider_key("anthropic", "sk-ant-...")
s = c.session("job_1", provider_key_id=k["id"])

The key is sealed at rest and never enters the sandbox. Your agent gets a session token instead: scoped to one session, capped by that session's budget, dead when the session ends, revocable, useless anywhere else.

The token arrives as ANTHROPIC_API_KEY (and the OpenAI and Google equivalents) alongside a base URL, so your agent's SDK sends it the way it sends any API key and nothing in your code changes. It is a credential, so it travels in a header rather than the URL — a token in a path ends up in every access log and proxy record between you and us, and rotating it would mean rotating a URL you have embedded.

This matters because agent code is not trustworthy code. A prompt injection or a bad dependency that exfiltrates a provider key gets a credential with no spending limit and no expiry, usable from anywhere, against every project on your account. The same attack against a session token gets the remaining budget on one session.

Inbound credential headers from the sandbox are stripped, so an agent cannot route around metering by supplying its own key.


Budgets

s = c.session("job_1", budget_usd=2.00)

Enforced at the model call, not reported afterwards. When the cap would be breached the call returns 402 and the session is terminated.

One honest limitation. The cap is a pre-flight estimate. Output is bounded exactly by max_tokens; input is not — we can only see the request body, and the provider also counts system prompts, tool schemas and cache blocks that never reach us. We start pessimistic and calibrate against what the provider actually reports for your traffic. In practice this lands a few percent under the cap. It is not a guarantee that you will never exceed a budget by a cent; it is a guarantee that a runaway agent stops.

While a budgeted session is still calibrating, its first call runs alone and concurrent calls get 429 with budget_calibrating. You cannot bound N calls whose individual cost you cannot yet bound.

Tenant-level ceilings — spend per window, concurrent sessions, requests per minute — bound the account rather than the session, so a leaked API key cannot mint unlimited budgeted sessions.


Errors, and why the distinction matters

A 429 from us and a 429 from your model provider need opposite responses. Ours means slow down or raise a limit. Theirs means retry with backoff. Every response carries x-opsen-error-source, and the SDK turns it into types:

Exception Cause Retry?
BudgetExceeded your session cap Never. Retrying a cost control is how it becomes a cost leak
SessionGone terminated, expired, reaped No — start a new session
TenantLimited our ceiling Only if .retryable — true for rate, false for spend
ProviderError the model provider failed Yes, with backoff
try:
    result = s.call_model(payload)
except BudgetExceeded as e:
    alert(f"job {s.task_id} hit its cap at ${e.body['would_reach_usd']}")
except TenantLimited as e:
    if e.retryable: ...

call_model implements these rules already, including honouring retry-after and jittering backoff. Most callers never touch it — your agent's own SDK goes through the injected base URL — but it exists so the rules are written down somewhere executable.

Failed calls are never billed and are reported separately as failed_calls, so your cost-per-call has the right denominator.


One worker, many jobs

A long-lived worker can be re-pointed. Cost stays with the job that incurred it:

w = c.session("job_A")
w.exec("python3 worker.py")
w.retag("job_B")
w.exec("python3 worker.py")

c.task_cost("job_A")   # only job_A's compute and tokens
c.task_cost("job_B")

Idle suspension

Agents spend most of their wall clock waiting on model responses, and billing that as active compute is the default failure of every full-duration provider. We suspend on idle and resume transparently.

Resuming costs roughly 180 ms on the next call, so this is a trade rather than a free win. Named policies, because the right answer depends on how you value latency:

policy idle window for
interactive (default) 15 min user-facing agents where resume latency shows
balanced 3 min
batch 30 s long-idle background work
never

The default is conservative on purpose. At 2 vCPU / 4 GiB, suspending over a two-minute idle window saves about half a cent and costs 180 ms of first-token time — a bad trade for anything a human is waiting on.


What happens if opsen is down

Your model calls go through our gateway, so this is a fair question to ask before you route production traffic through anyone. The answer depends on whether you asked for a cost guarantee.

No budget set — we fail open. The call is forwarded and served. Usage for that window may not be recorded, and the response carries x-opsen-degraded: accounting so a client that cares can buffer and reconcile. You never asked us to guard anything, so we do not stand in the way.

Budget set — we fail closed. The call is refused with 503 cost_control_unavailable rather than made without the cap you asked for.

That second one is deliberate and it is the less convenient choice, so here is the reasoning. Outages correlate with runaway spend — provider degradation, retry storms, agents looping on errors are the same conditions that break us and the same ones that empty a budget. A cap that disappears exactly when it is needed is not a cap. And the security model depends on it: your sandbox holds a session token instead of your provider key precisely BECAUSE the token is bounded by the cap. Failing open would quietly hand back an unbounded credential.

So: set a budget on sessions where a wrong number costs you more than a paused minute, and leave it unset where availability matters more. You choose per session, and the behaviour is the same whether we are healthy or not.


Runtimes

runtime="auto" picks a backend. You can pin one. Nothing else about the backend is visible through the API, which is deliberate: the same code runs against any of them, and you are not writing against a particular provider's semantics.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

opsen-0.1.6.tar.gz (13.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

opsen-0.1.6-py3-none-any.whl (13.5 kB view details)

Uploaded Python 3

File details

Details for the file opsen-0.1.6.tar.gz.

File metadata

  • Download URL: opsen-0.1.6.tar.gz
  • Upload date:
  • Size: 13.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for opsen-0.1.6.tar.gz
Algorithm Hash digest
SHA256 5a3b131c35298a4fead3c6e9da8e83bd905287af1ba8ac07e1d490218b9ea9fe
MD5 9b66326ad619e58b0253d29973d646b1
BLAKE2b-256 48abfb34c74d31a37635d3ba4dfaf958798b0ae6402df1b075f3d054e7347e8a

See more details on using hashes here.

Provenance

The following attestation bundles were made for opsen-0.1.6.tar.gz:

Publisher: release.yml on md322613/opsen

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file opsen-0.1.6-py3-none-any.whl.

File metadata

  • Download URL: opsen-0.1.6-py3-none-any.whl
  • Upload date:
  • Size: 13.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for opsen-0.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 b127c4a168a64582c2719792e30268b7f6c0fba9528395acf26c62552ec969db
MD5 5e31da6a6a67bd6e1642b915d683f453
BLAKE2b-256 ee9687281aca447d8254bcb4a03b1f30ccfed7126e4efd3daa29e8a2b8895dfe

See more details on using hashes here.

Provenance

The following attestation bundles were made for opsen-0.1.6-py3-none-any.whl:

Publisher: release.yml on md322613/opsen

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.6 This release

2 files

0.1.5

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page