Metadata-Version: 2.4
Name: optio
Version: 0.2.0
Summary: Economic cost and outcome quality signals for agent runs, in OpenTelemetry GenAI vocabulary.
Project-URL: Homepage, https://github.com/Aniketh-74/Optio
Project-URL: Documentation, https://github.com/Aniketh-74/Optio/blob/main/docs/signals.md
Project-URL: Source, https://github.com/Aniketh-74/Optio
Project-URL: Issues, https://github.com/Aniketh-74/Optio/issues
Project-URL: Changelog, https://github.com/Aniketh-74/Optio/blob/main/CHANGELOG.md
Author: optio contributors
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: agents,cost,genai,governance,observability,opentelemetry
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: System :: Monitoring
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: opentelemetry-api>=1.27.0
Requires-Dist: opentelemetry-sdk>=1.27.0
Provides-Extra: all
Requires-Dist: claude-agent-sdk>=0.1.0; extra == 'all'
Requires-Dist: crewai>=0.60.0; extra == 'all'
Requires-Dist: langchain-core>=0.3.0; extra == 'all'
Requires-Dist: langgraph>=0.2.0; extra == 'all'
Requires-Dist: openai-agents>=0.0.1; extra == 'all'
Provides-Extra: claude
Requires-Dist: claude-agent-sdk>=0.1.0; extra == 'claude'
Provides-Extra: crewai
Requires-Dist: crewai>=0.60.0; extra == 'crewai'
Provides-Extra: dev
Requires-Dist: anthropic>=0.34.0; extra == 'dev'
Requires-Dist: cedarpy>=4.8.0; extra == 'dev'
Requires-Dist: hypothesis>=6.100.0; extra == 'dev'
Requires-Dist: import-linter>=2.0.0; extra == 'dev'
Requires-Dist: mypy>=1.11.0; extra == 'dev'
Requires-Dist: openai>=1.0.0; extra == 'dev'
Requires-Dist: pip-audit>=2.7.0; extra == 'dev'
Requires-Dist: pytest-benchmark>=4.0.0; extra == 'dev'
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
Requires-Dist: pytest-timeout>=2.3.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: ruff>=0.6.0; extra == 'dev'
Requires-Dist: typing-extensions>=4.12.0; extra == 'dev'
Provides-Extra: langgraph
Requires-Dist: langchain-core>=0.3.0; extra == 'langgraph'
Requires-Dist: langgraph>=0.2.0; extra == 'langgraph'
Provides-Extra: openai
Requires-Dist: openai-agents>=0.0.1; extra == 'openai'
Provides-Extra: optimize
Requires-Dist: tiktoken>=0.7.0; extra == 'optimize'
Description-Content-Type: text/markdown

# optio

**Economic cost and outcome quality signals for agent runs — in the OpenTelemetry GenAI vocabulary.**

[![CI](https://github.com/Aniketh-74/Optio/actions/workflows/ci.yml/badge.svg)](https://github.com/Aniketh-74/Optio/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/optio.svg)](https://pypi.org/project/optio/)
[![Python](https://img.shields.io/pypi/pyversions/optio.svg)](https://pypi.org/project/optio/)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/Aniketh-74/Optio/blob/main/LICENSE)

Your agent framework tells you what happened. `optio` tells you **what it cost, whether it was
going in circles, and whether the answer was any good** — as ordinary OTel span attributes, so
every backend you already run can chart and alert on them.

```python
from optio import instrument

instrument(agent)  # one line; spans now carry cost + behavior signals
```

That is the whole integration. `optio` never touches a request
([ADR-001](https://github.com/Aniketh-74/Optio/tree/main/docs/design/adr/)) — it reads the spans
your framework already emits and writes numbers back onto them.

---

## Install

```bash
pip install optio                # core: signals only, never touches a request
pip install "optio[langgraph]"   # + framework adapter
pip install "optio[optimize]"    # + optio_optimize: acts on the signals (see below)
```

Adapters: `langgraph`, `openai`, `crewai`, `claude`. Python **3.10 – 3.14**, Linux/macOS/Windows.

## Usage

```python
from optio import meter, RunContext


@meter(budget="$0.50")
def run_agent(prompt: str) -> str: ...


# or scope it yourself
with RunContext(budget="$0.50") as run:
    ...  # spans emitted inside are governed
```

Enabling or disabling lanes never changes agent behavior, nothing blocks the agent, and
configuration errors raise at setup — never on the hot path.

## The signals

These attribute names *are* the integration contract — mirrored as constants in `optio.semconv`
and asserted by contract tests.
[`docs/signals.md`](https://github.com/Aniketh-74/Optio/blob/main/docs/signals.md) is authoritative.

| Signal | Type | Lane |
|---|---|---|
| `gen_ai.run.actual_cost` | double (USD) | cost |
| `gen_ai.run.projected_cost` | double (USD) | cost |
| `gen_ai.run.budget_remaining` | double (USD) | cost |
| `gen_ai.run.cost_per_successful_task` | double (USD) | cost × quality |
| `gen_ai.run.loop_state` | enum string | behavior |
| `gen_ai.run.repeat_count` | int | behavior |
| `gen_ai.run.quality.groundedness` | double [0,1] | quality (opt-in) |
| `gen_ai.run.quality.task_success` | double [0,1] | quality (opt-in) |
| `gen_ai.run.success` | bool | quality |

Policies match the exact names, so they stay portable across backends:

```rego
deny if input.attributes["gen_ai.run.projected_cost"] > 0.50
deny if input.attributes["gen_ai.run.loop_state"] == "looping"
```

## Overhead

Published rather than promised — generated by the CI benchmark job, not written by hand.

| Measure | Result | Budget |
|---|---|---|
| Per-step, cost + behavior lanes | mean **~70 µs**, p99 **~130 µs** | < 5 ms p99 |
| Behavior classification at 10k steps | ~40 µs → ~50 µs | flat in run length |
| Classification vs *window size* | 37 µs at 50, 38 µs at 1000 | flat in window |
| Ledger snapshot, 10k open reservations | ~100 µs | O(1)-ish |
| Both lanes disabled | ~18 µs p99 | — |
| `import optio` | ~158 ms cold | < 500 ms |

Roughly **40× inside budget**. Measured on one developer machine with an in-memory store and no
exporter attached; read them as an order of magnitude, not a promise for your hardware. The
window-size row is a structural guarantee rather than a measurement — classification is O(1) in the
window, so raising `behavior_window_size` to catch longer cycles costs memory but not latency. It
used to cost both, at 370 µs per step
([docs/testing.md](https://github.com/Aniketh-74/Optio/blob/main/docs/testing.md)).

Detector accuracy on the synthetic corpus, both CI-gated:

| Measure | Result |
|---|---|
| False positives — healthy runs flagged | **0 / 1200** (0.000%) |
| Detection — pathological runs caught | **600 / 600** (100.0%) |

The detection rate is gated *alongside* the false-positive rate, because a zero false-positive rate
is trivially achievable by never detecting anything.

## `optio_optimize` — acting on the signals

A separate, opt-in package that sits **in** the request path: caching, prefix markers, history
trimming, deduplication, retrieval pruning
([ADR-013](https://github.com/Aniketh-74/Optio/blob/main/docs/design/adr/adr-013-optimization-lives-in-a-separate-package.md)).
Installing `optio` alone never pulls it in.

```python
from optio_optimize import Optimizer

optimizer = Optimizer()  # lossless by default
response = optimizer.call(request, provider_fn)  # or await optimizer.acall(...)
```

Cost reduction, **live-API measured**, not simulated:

| Workload | `gpt-4o-mini` | `claude-sonnet-4-5` |
|---|---|---|
| `retry_storm` | **93.5%** | **97.8%** |
| `tool_loop` | **80.8%** | **93.7%** |
| `multi_turn_chat_long` | — | **86.2%** |
| `mcp_agent` | — | **84.0%** |
| `fan_out` | **68.2%** | **83.7%** |
| `multi_turn_chat` | **8.4%** | **82.2%** |
| `rag_queries` | **16.5%** | **76.2%** |

**Those two columns differ by 10× on `multi_turn_chat`, and that gap is the point.** OpenAI
populates its prefix cache automatically, so the biggest lever — placing explicit cache breakpoints
— has nothing to do there. Anthropic caches *only* what you mark, which is where marking correctly
is worth 80%+ on ordinary conversation traffic.

Three Anthropic figures come with **100% byte-identical output**, and two of them reduce *zero
tokens* — prefix caching changes the rate tokens are billed at, not the volume. A tool that reports
only "tokens saved" scores those at 0.0%.

Full method, dates and per-stage detail:
[`docs/optimize-benchmarks.md`](https://github.com/Aniketh-74/Optio/blob/main/docs/optimize-benchmarks.md).

## How it decides what to claim

Every number above is measured, dated and re-runnable — and several exist because a measurement
contradicted an earlier claim and the claim lost:

- **Stages ship off when the evidence says so.** 14 of 24 optimizations are disabled by default,
  including one that measured a **cost increase** of 14.8%.
- **Corrections stay in the record.** `optimize-benchmarks.md` rewrites itself in place when a live
  run contradicts a simulated one — it has, twice.
- **Unfavourable numbers are published too.** `semantic_cache`'s live false-positive rate at the
  shipped default is recorded at 85.7%.

The reasoning behind each decision is in
[46 ADRs](https://github.com/Aniketh-74/Optio/tree/main/docs/design/adr/).

## Status

> **alpha (0.2.0).** All three lanes work end to end and every signal in the contract is
> implemented, on 99% coverage with 100% on the ledger and the fail-open guard.
>
> - **State is in-process only.** `store_backend="redis"` is rejected at setup rather than silently
>   ignored ([ADR-005](https://github.com/Aniketh-74/Optio/tree/main/docs/design/adr/)).
> - **The signal names may still move.** They are pinned to OTel GenAI semconv 1.37.0, which is
>   itself marked Development-stability upstream
>   ([ADR-002](https://github.com/Aniketh-74/Optio/tree/main/docs/design/adr/)).
>
> `1.0.0` waits for that vocabulary to stabilise — the reasoning and the three conditions are in
> [ADR-046](https://github.com/Aniketh-74/Optio/blob/main/docs/design/adr/adr-046-1-0-0-waits-for-the-vocabulary-underneath.md).

## What is public

The top-level package, and nothing else
([ADR-012](https://github.com/Aniketh-74/Optio/blob/main/docs/design/adr/adr-012-the-public-api-is-the-top-level-package-only.md)):

```python
from optio import instrument, meter, RunContext, current_run, Config, BudgetPolicy
from optio import __version__, GENAI_SEMCONV_VERSION
```

| | |
|---|---|
| `instrument` | attach to a framework object |
| `meter` | decorator: scope a run, optionally with a budget |
| `RunContext` | the same thing as a context manager |
| `current_run` | the run in scope, or `None` |
| `Config` | everything configurable, validated at setup |
| `BudgetPolicy` | the limits a run is governed by |
| `__version__`, `GENAI_SEMCONV_VERSION` | this release, and the semconv release it tracks |

Plus `optio.semconv`, which holds the attribute names as constants.

`optio.runtime`, `optio.lanes` and friends are internal and may change without a major bump — if
you need something from them, please open an issue rather than importing it. A test asserts every
name above appears in this file, so an undocumented export cannot ship.

## Docs

| | |
|---|---|
| [Signals contract](https://github.com/Aniketh-74/Optio/blob/main/docs/signals.md) | every attribute, authoritative |
| [Behavior detection](https://github.com/Aniketh-74/Optio/blob/main/docs/behavior.md) | what the corpus does and does not model |
| [Testing](https://github.com/Aniketh-74/Optio/blob/main/docs/testing.md) | how the numbers above are produced |
| [Optimize benchmarks](https://github.com/Aniketh-74/Optio/blob/main/docs/optimize-benchmarks.md) | per-stage, per-workload, dated |
| [ADRs](https://github.com/Aniketh-74/Optio/tree/main/docs/design/adr/) | why every decision went the way it did |
| [Contributing](https://github.com/Aniketh-74/Optio/blob/main/CONTRIBUTING.md) | the gate you need to pass |
| [Changelog](https://github.com/Aniketh-74/Optio/blob/main/CHANGELOG.md) | what changed, and why |

## Development

```bash
pip install -e ".[dev]"
pytest                       # 2,210 tests
ruff check . && mypy         # lint + types
lint-imports                 # architecture boundaries
```

## License

Apache-2.0 — see [LICENSE](https://github.com/Aniketh-74/Optio/blob/main/LICENSE).
