Metadata-Version: 2.4
Name: techrevati-runtime
Version: 0.2.0
Summary: Async-aware runtime primitives for multi-step LLM agent loops.
Project-URL: Homepage, https://github.com/Techrevati/runtime
Project-URL: Documentation, https://Techrevati.github.io/runtime
Project-URL: Changelog, https://github.com/Techrevati/runtime/blob/main/CHANGELOG.md
Author: TechRevati doo
License-Expression: MIT
License-File: LICENSE
Keywords: agents,circuit-breaker,cost-tracking,orchestration,retry,runtime
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.11
Provides-Extra: dev
Requires-Dist: hypothesis==6.122.1; extra == 'dev'
Requires-Dist: mypy==1.18.2; extra == 'dev'
Requires-Dist: opentelemetry-api>=1.27; extra == 'dev'
Requires-Dist: opentelemetry-sdk>=1.27; extra == 'dev'
Requires-Dist: opentelemetry-semantic-conventions>=0.48b0; extra == 'dev'
Requires-Dist: pytest-asyncio==1.3.0; extra == 'dev'
Requires-Dist: pytest-cov==7.1.0; extra == 'dev'
Requires-Dist: pytest-randomly==4.0.1; extra == 'dev'
Requires-Dist: pytest==9.0.3; extra == 'dev'
Requires-Dist: ruff==0.14.5; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.25; extra == 'docs'
Provides-Extra: otel
Requires-Dist: opentelemetry-api>=1.27; extra == 'otel'
Requires-Dist: opentelemetry-sdk>=1.27; extra == 'otel'
Requires-Dist: opentelemetry-semantic-conventions>=0.48b0; extra == 'otel'
Description-Content-Type: text/markdown

# techrevati-runtime

[![PyPI](https://img.shields.io/badge/pypi-techrevati--runtime-blue.svg)](https://pypi.org/project/techrevati-runtime/)
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
[![Type Safe](https://img.shields.io/badge/py.typed-yes-brightgreen.svg)](https://peps.python.org/pep-0561/)
[![Zero Dependencies](https://img.shields.io/badge/dependencies-zero-green.svg)](#design-goals)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

Production-grade runtime primitives for multi-step LLM agent loops — sync **and** async, with retry classification, circuit-breaker protection, per-model cost tracking, opt-in budget enforcement, role-based tool gating, content guardrails, agent-to-agent handoffs, declarative policy, and OpenTelemetry GenAI semantic conventions out of the box. **Beta — 0.1.x; minor breaking changes possible until 0.2.0.**

```bash
pip install techrevati-runtime
# Or with OpenTelemetry:
pip install 'techrevati-runtime[otel]'
```

## Quick start

```python
from techrevati.runtime import (
    Orchestrator, UsageSnapshot, ModelPricing, register_pricing,
)

register_pricing("model-a", ModelPricing(input_per_million=3.0, output_per_million=15.0))

orch = Orchestrator(
    role="writer", phase="draft", project_id=1,
    budget_usd=10.0, enforce_budget=True, max_iterations=25,
)
with orch.session() as session:
    result, usage = session.run_turn(
        lambda: call_model(prompt),
        model="model-a",
        usage=UsageSnapshot(input_tokens=5000, output_tokens=1200),
        timeout=30.0,
    )
print(session.summary())
```

The session walks the worker through `INITIALIZING → RUNNING → COMPLETED`, classifies any exception that bubbles up into a typed failure scenario, attempts recovery once, enforces the budget, gates tool calls behind permissions and guardrails, and emits structured events to any sink you configure — without you wiring any of it by hand.

**Async sibling**: replace `with` with `async with`, `session()` with `asession()`, `run_turn` with `arun_turn`. Same parameters. `asyncio.CancelledError` cleanly transitions the worker to `CANCELLED`.

For an end-to-end example exercising every primitive (permissions + breaker + budget + guardrail + handoff + policy + OTel), see [`examples/tiny_agent.py`](examples/tiny_agent.py) and the [end-to-end tutorial](docs/tutorials/end-to-end.md).

## Design goals

- **Zero runtime dependencies.** Imports are stdlib only. OpenTelemetry is an optional `[otel]` extra.
- **Type-safe.** `py.typed` marker shipped; clean under `mypy --strict`.
- **Composable.** Every primitive (`CircuitBreaker`, `AsyncCircuitBreaker`, `RetryContext`, `QualityGate`, `PolicyEngine`, `UsageTracker`, `PermissionEnforcer`, `Guardrail`, `Handoff`) is usable standalone. The `Orchestrator` is just the wiring.
- **Thread-safe and async-safe.** `threading.Lock` in sync paths, `asyncio.Lock` in async paths. State is per-instance.
- **Configuration-free at the edges.** Pricing data is empty by default; phase thresholds are not hardcoded; permission roles are caller-defined. The runtime stays opinion-free about what your numbers mean.

## Primitives

| Module | Provides |
|---|---|
| `orchestrator` | `Orchestrator`, `OrchestrationSession`, `AsyncOrchestrationSession`, `AgentSession` |
| `circuit_breaker` | `CircuitBreaker`, `AsyncCircuitBreaker` (CLOSED/OPEN/HALF_OPEN with configurable probe permits) |
| `retry_policy` | `classify_exception`, `attempt_recovery` (sync + async), `backoff_delay` with full/equal/decorrelated jitter |
| `usage_tracking` | `UsageTracker`, `register_pricing`, `load_pricing_from_file`, `BudgetExceededError`, `has_pricing` |
| `agent_lifecycle` | `AgentRegistry`, `AgentWorker` with validated state machine including `CANCELLED` |
| `agent_events` | Typed lifecycle events + OpenTelemetry attribute bridge |
| `permissions` | Role × tool authorization, deny-first |
| `guardrails` | Pre-call + post-call content gating around `run_tool` / `arun_tool` |
| `handoffs` | `Handoff` value + `session.handoff_to()` agent-to-agent delegation |
| `policy_engine` | Composable conditions and rule evaluator with auto-elapsed time |
| `sinks` | `EventSink` / `UsageSink` Protocols + ring-buffered defaults |
| `otel` *(optional)* | `OpenTelemetrySink` + `OpenTelemetryUsageSink` emitting GenAI semconv spans/metrics |

## Showcase

### Async with handoff and guardrails

```python
import asyncio
from techrevati.runtime import (
    AllowAllGuardrail, AsyncCircuitBreaker, Orchestrator, UsageSnapshot,
)

cb = AsyncCircuitBreaker("model-api", failure_threshold=3, recovery_timeout_seconds=30.0)

async def main():
    orch = Orchestrator(
        role="writer", phase="draft",
        async_circuit_breaker=cb,
        guardrails=[AllowAllGuardrail()],
        max_iterations=10,
    )
    async with orch.asession() as session:
        text, _ = await session.arun_turn(
            lambda: acall_model(prompt),
            model="model-a",
            usage=UsageSnapshot(input_tokens=5000, output_tokens=1200),
            timeout=30.0,
        )
        handoff = session.handoff_to("editor", reason="review", context={"draft": text})
        print(f"handed off to {handoff.target_role}")

asyncio.run(main())
```

### OpenTelemetry observability

```python
from techrevati.runtime import Orchestrator
from techrevati.runtime.otel import OpenTelemetrySink, OpenTelemetryUsageSink

orch = Orchestrator(
    role="writer", phase="draft",
    event_sink=OpenTelemetrySink(agent_id="writer-001"),
    usage_sink=OpenTelemetryUsageSink(),
)
# Every AgentEvent now appears as an OTel span with gen_ai.operation.name,
# gen_ai.agent.id, gen_ai.usage.{input,output}_tokens. Drop-in compatible
# with any APM ingest that already understands GenAI semconv.
```

See [`docs/api/otel.md`](docs/api/otel.md) for the full attribute list and span name mapping.

### Standalone primitives

Pick just what you need. Each primitive is usable on its own without `Orchestrator`.

```python
from techrevati.runtime import (
    CircuitBreaker, CircuitOpenError,
    UsageTracker, UsageSnapshot,
    classify_exception, attempt_recovery, RecoveryContext,
)

cb = CircuitBreaker("downstream", failure_threshold=5, recovery_timeout_seconds=60.0)
result = cb.call(fetch, url, timeout=10)  # raises CircuitOpenError if tripped

ctx = RecoveryContext()
scenario = classify_exception(my_error)
recovery = attempt_recovery(scenario, ctx)  # returns RecoveryResult with steps to retry

tracker = UsageTracker()
tracker.record_turn("model-a", UsageSnapshot(input_tokens=5000, output_tokens=1200))
print(tracker.format_cost())
```

## Why not LangGraph / OpenAI Agents SDK?

`techrevati-runtime` is intentionally smaller and narrower than either:

- **LangGraph** is a *workflow engine* with durable execution, checkpointer protocols, and a graph model. Use it when your agent flow is a graph that needs to survive restarts and you're OK with the LangChain ecosystem footprint.
- **OpenAI Agents SDK** is a *cohesive runtime* tied to OpenAI's models, with default tracing through their dashboards. Use it when you're committed to OpenAI and want the smoothest path.
- **`techrevati-runtime`** is a *zero-dep primitive set*. Sync + async. Vendor-neutral. Emits OpenTelemetry GenAI semantic conventions so the same APM dashboards that consume OpenAI Agents SDK telemetry will pick us up too. Bring your own model client and your own persistence — the runtime stays opinion-free.

The runtime is **not** a durable workflow engine. Sessions are in-memory; a pluggable checkpointer is on the 0.2.0 roadmap. If you need restart-resumable workflows today, pair this with [Temporal](https://temporal.io/), [dbos](https://www.dbos.dev/), or LangGraph's checkpointer.

## Limitations (be honest with yourself before adopting)

- **Pricing must be registered.** The bundled `pricing.json` is intentionally empty. Without `register_pricing()` or `load_pricing_from_file()`, every cost calculation returns $0.00 (you will see a one-time warning per model).
- **Budget enforcement is opt-in.** Set `Orchestrator(enforce_budget=True)` to raise `BudgetExceededError`; the default merely records an event and continues.
- **Permissions are advisory.** `OrchestrationSession.run_tool()` enforces; `run_turn()` does not gate model calls. There is no sandbox — pair with OS-level isolation if needed.
- **No durable execution.** Sessions are in-memory and ephemeral. Pair with Temporal/dbos for restart-resumable workflows.
- **Default sinks are in-memory ring buffers.** Long-running sessions need a durable `EventSink` and `UsageSink` (e.g. `OpenTelemetrySink`, or your own).
- **`CircuitBreaker` state is per-process.** Each replica counts its own failures. Add a shared coordinator if you need fleet-wide breaker state.

## Status

`techrevati-runtime` is at version **0.1.0** (beta). This release ships async-first execution, the four standard primitives (Sessions, Tools, Handoffs, Guardrails), `max_iterations` cap, and OpenTelemetry GenAI semantic conventions. Minor breaking changes are possible between 0.1.x and 0.2.0 — they will be documented in [docs/migrating-from-0.0.x.md](docs/migrating-from-0.0.x.md) and gated by deprecation warnings. Pinning Python 3.11+ for `from __future__ import annotations` ergonomics and modern asyncio.

See [CHANGELOG.md](CHANGELOG.md) for the per-sprint release notes and [docs/tutorials/end-to-end.md](docs/tutorials/end-to-end.md) for a guided tour of every primitive.

Issues and PRs welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) and [SECURITY.md](SECURITY.md).

## License

MIT — copyright © 2026 [TechRevati doo](https://techrevati.com). See [LICENSE](LICENSE).
