Metadata-Version: 2.5
Name: ergoda
Version: 0.1.0
Summary: Measure which steps of your agent can run on a cheaper model, and keep measuring
Author: Ergoda
License-Expression: FSL-1.1-ALv2
License-File: LICENSE.md
Requires-Python: >=3.11
Requires-Dist: click>=8.1
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.7
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.7
Requires-Dist: sqlalchemy>=2.0
Provides-Extra: all
Requires-Dist: fastapi>=0.111; extra == 'all'
Requires-Dist: litellm<2,>=1.40; extra == 'all'
Requires-Dist: psycopg[binary]>=3.1; extra == 'all'
Requires-Dist: python-multipart>=0.0.9; extra == 'all'
Requires-Dist: uvicorn>=0.30; extra == 'all'
Provides-Extra: hosted
Requires-Dist: psycopg[binary]>=3.1; extra == 'hosted'
Requires-Dist: sentry-sdk>=2.0; extra == 'hosted'
Provides-Extra: litellm
Requires-Dist: litellm<2,>=1.40; extra == 'litellm'
Provides-Extra: postgres
Requires-Dist: psycopg[binary]>=3.1; extra == 'postgres'
Provides-Extra: server
Requires-Dist: fastapi>=0.111; extra == 'server'
Requires-Dist: python-multipart>=0.0.9; extra == 'server'
Requires-Dist: uvicorn>=0.30; extra == 'server'
Description-Content-Type: text/markdown

# ergoda

Measures which steps of your agent can run on a cheaper model, and keeps
measuring once they do. Your code keeps calling your own provider with your
own key. Ergoda decides which model a step uses, and anything it cannot
decide goes to the model you would have called anyway.

```sh
pip install ergoda
ergoda onboard --sample    # a first report on bundled sample data, offline
```

The ten-minute path is the [quickstart](https://ergoda.com/docs/quickstart/);
every term is defined in [concepts](https://ergoda.com/docs/concepts/).

## Start: measure before anything moves

```sh
ergoda init --frontier anthropic/claude-opus-5 --step resolve_ticket
```

This writes a `routes.yaml` that routes nothing: every step keeps serving
the model your code already asks for. It prints the two lines to paste:

```python
from ergoda.sdk import Client

ergoda = Client.from_env()
create = ergoda.wrap({"anthropic": client.messages.create}, step="resolve_ticket")

# unchanged from here: same arguments, same response object
response = create(model="claude-opus-5", max_tokens=1024, messages=msgs, tools=TOOLS)
```

Add `--control-plane https://api.ergoda.com --email you@company.com` to get
a site key, so the metrics reach a control plane where they accumulate, and a
dashboard at https://app.ergoda.com/ (sign in with the email address you
signed up with; until email sign-in is switched on, the page links a sign-in
with the site key). Only derived
metrics are sent (hashes, counts, token totals). Prompts and responses stay in
your process. The control plane is a hosted service: it is not in this
package, and running your own is not offered.

## Then: find what can move

No trace export yet? Wrap your create call as
`record(client.messages.create)` (`from ergoda import record`) and it writes
one to `traces.jsonl` on your own disk as your agent runs. Langfuse, LangSmith
and OpenTelemetry exports are read as they are.

```sh
ergoda onboard traces.jsonl --step resolve_ticket \
  --serve anthropic/claude-haiku-4-5 --escalate-to anthropic/claude-opus-5
ergoda analyse traces.jsonl --model anthropic/claude-haiku-4-5 --html report.html
```

`onboard` checks that the export can be replayed, lists the tools whose
effects can be undone, and prints the policy it would write. It writes
nothing until a named person approves: add `--approved-by "Your Name"`.
`analyse` replays your traces against the cheaper model with your own key and
reports agreement per step with an interval. Without that key it stops and
names the variable; a replay the provider refuses is counted as a failure,
never as a disagreement.

## What it will and will not do

- **Doubt goes to the expensive model.** An unknown step, an unreachable
  control plane, a changed prompt or a refused proposal all serve your own
  frontier model.
- **A route to a cheaper model starts with a person's name on the record.**
  After that, autopilot may loosen an approved route's checks where your own
  traffic shows at most a 2% added failure rate at 95% confidence. That
  limit is a published default, not one a person on your team accepted, so
  its moves are recorded as `ergoda:default`. You can switch it off for your
  site on the dashboard's Switch autopilot off page, or with
  `POST /v1/sites/SITE/autopilot` and `{"by": "your name", "enabled": false}`;
  every move toward the cheaper model then waits for a named person. Moves it
  already made stay until you revert them or an escalation takes them back;
  only a route with no traffic for 90 days goes back on its own. Setting a route's
  `enabled: false` sends that step back to your frontier model.
- **Routing is not meant to cost more than not routing.** In the Python SDK,
  a route whose spend passes what the frontier model alone would have cost is
  pinned back to the frontier. The TypeScript SDK and the proxy do not
  enforce this yet.
- **The numbers are estimates with intervals.** Agreement is measured on your
  traffic and reported with its bound. Parity is about task completion; refusal
  behaviour is not measured.

## Latency

The decision is in-process, with no network hop on the request path, and
takes about a millisecond. A step the cheaper model gets wrong is redone by
the frontier model, so that step makes two calls in a row. Shipped
with your deployment, `routes.yaml` is in force from the first call, and the
control plane is consulted behind it. In a Lambda or Cloud Run handler, build
the client at module scope and wrap the body in `with ergoda.request():` so
queued metrics are delivered before the process is frozen.

Python 3.11+. OpenAI, Anthropic, Gemini and Bedrock request and tool-call
shapes are read natively. Other providers go through the optional `litellm`
extra. `pip install 'ergoda[server]'` adds what the proxy needs, for agents
that reach their provider through a base URL (`ergoda dataplane serve`).

Documentation: https://ergoda.com/docs/quickstart/

Licence: [FSL-1.1-ALv2](https://fsl.software/), source-available. Use it for
anything, in production and commercially, except a product or service that
competes with Ergoda; each version becomes Apache 2.0 two years after it is
published.
