Metadata-Version: 2.4
Name: tracengent
Version: 0.1.0
Summary: First-party Python SDK for Tracengent: instrument and govern agents (events, decision traces, authorization, tool governance, delegation).
Project-URL: Homepage, https://github.com/besarkutleshi/agentgov
Project-URL: Source, https://github.com/besarkutleshi/agentgov
Project-URL: Repository, https://github.com/besarkutleshi/agentgov.git
Author: Besar Kutleshi
License: MIT
License-File: LICENSE
Keywords: agent,authorization,governance,llm,observability,tracengent
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
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: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: typing-extensions>=4.7; python_version < '3.11'
Provides-Extra: dev
Requires-Dist: fastapi>=0.110; extra == 'dev'
Requires-Dist: flask>=3.0; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.110; extra == 'fastapi'
Provides-Extra: flask
Requires-Dist: flask>=3.0; extra == 'flask'
Description-Content-Type: text/markdown

# Tracengent — Python SDK

First-party Python SDK for [Tracengent](../../phases/phase-10-multi-language-sdks.md): instrument and
govern agents written in Python with parity to the .NET SDK ([`src/Tracengent.Sdk`](../../src/Tracengent.Sdk/)).

> **Status: feature-complete (Phase 10).** Full parity with the .NET SDK across telemetry, traces,
> authorization, tool governance, and delegation. The reference implementation is the .NET SDK; the
> contract is [§3 of the phase doc](../../phases/phase-10-multi-language-sdks.md#3-the-shared-wire-contract-the-spec-all-five-sdks-implement).

## Milestone status

| Milestone | Capability | State |
|---|---|---|
| **M1** | Options + validation, JWT/permission helpers, JSON redactor (pure logic) | ✅ done |
| **M2** | Event model, token provider, background dispatcher, `record` | ✅ done |
| **M3** | Decision traces + auto-tracking + ambient context + `authorize`/`ensure_authorized` | ✅ done |
| **M4** | `has_permission` (token-local) + framework bindings (FastAPI/Flask) | ✅ done |
| **M5** | Tool governance: egress gate, MCP/function gate, redaction | ✅ done |
| **M6** | Delegation: delegated session + attenuation (A2A) | ✅ done |
| **M7** | Packaging + sample + conformance + (gated) integration tests | ✅ done |

**Tests:** 152 unit + 7 conformance, 3 gated integration tests (`pytest -m integration`). The
conformance suite replays the shared [`wire_contract.json`](tests/conformance/wire_contract.json)
fixture and asserts the SDK's request shapes match the contract byte-for-byte (modulo volatile
ids/timestamps) — this same fixture is vendored into every Tracengent SDK. A runnable end-to-end
example lives in [`samples/demo_agent.py`](samples/demo_agent.py).

## What works today (M1–M3)

```python
from tracengent import TracengentClient, TracengentOptions, LlmCallResult, Money

opts = TracengentOptions.from_env(agent_id="travel-agent")
with TracengentClient(opts) as client:
    # Open a decision trace — groups steps, tool calls and actions into one timeline.
    with client.begin_trace("book a trip to Lisbon", actor_user_id="u_123") as trace:
        trace.step("decided to search flights")

        # Wrap tool calls / actions — timed and recorded automatically.
        flights = trace.track_tool_call("search-flights", lambda: search(...))

        # Govern an LLM call: reserve token budget before, reconcile actual usage after.
        answer = trace.track_llm_call(
            "summarize",
            lambda: LlmCallResult(value=call_model(...), tokens=900, model="gpt"),
            estimated_tokens=1000,
        )

        # Imperative authorization for a sensitive action (raises if denied).
        # Send the spend's currency too — the platform converts it into the policy/budget
        # currency before enforcing limits, so a USD charge is checked against a EUR cap.
        client.ensure_authorized("payments:refund", {"amount": 280, "currency": "USD"})
        trace.track_action("refund", lambda: do_refund(...), cost=Money.eur(280))
# leaving the trace flushes its completion; leaving the client flushes remaining events.
```

Outbound HTTP can be auto-recorded as `tool_call` events by routing it through a tracked client:

```python
from tracengent import tracked_client

http = tracked_client(client, base_url="https://api.example.com")
http.get("/things")   # recorded as a tool_call, attached to the active trace
```

Declarative gating for web-hosted agents (the equivalent of .NET's `[RequireAgentPermission]`):

```python
from fastapi import FastAPI, Depends
from tracengent.integrations.fastapi import install_tracengent, require_agent_permission

app = FastAPI()
install_tracengent(app, client)

@app.post("/refunds", dependencies=[Depends(require_agent_permission("payments:refund"))])
def refund(): ...        # 403s automatically when the agent lacks the permission
```

A Flask decorator (`tracengent.integrations.flask`) and a token-local coarse check
(`client.has_permission("flights:book")`, no round-trip) are also available.

**Tool governance (Phase 8).** Govern outbound tool calls the org doesn't own — block, hold for
approval, or redact before the request leaves:

```python
from tracengent import governed_client

# Every request through this client is authorized via /v1/tool-authorize first.
tools = governed_client(client, base_url="https://api.stripe.com")
tools.post("/v1/refunds", json={"amount": 280, "card": "4242"})
# -> denied/held → raises; allowed → forwarded with any redactions applied.

# Non-HTTP tools (MCP tools/call, LLM function calls):
client.tools.guard_function("transfer_funds", {"to": "...", "amount": 50}, invoke=do_transfer)
```

**Delegation / agent-to-agent (Phase 5).** Act under a delegated token and attenuate authority for a
downstream agent:

```python
session = client.begin_delegated_task(delegated_token)   # token granted out of band
session.authorize("flights:book", {"amount": 280})       # intersects base policy with the grant

# Mint a narrower child token to hand to another agent for an A2A hop:
child = session.attenuate_for_call("agt_hotel", ["hotels:book"], max_amount=500)
```

Pure helpers are also exported directly:

```python
from tracengent import permission_matches, Redaction, apply_redactions

permission_matches("flights:*", "flights:book")   # True
apply_redactions('{"amount":100,"card":"4242"}', [Redaction("$.card", "mask")])
# -> '{"amount":100,"card":"***REDACTED***"}'
```

## Requirements

- Python 3.10+
- `httpx` (HTTP transport)

## Development

```bash
cd sdk/python
pip install -e ".[dev]"
pytest -q          # unit tests
ruff check .       # lint
mypy               # type-check
```

CI runs lint + type-check + tests on every push (see [`.github/workflows/ci.yml`](../../.github/workflows/ci.yml), job `sdk-python`).

## Environment variables

| Var | Meaning |
|---|---|
| `TRACENGENT_URL` | Base URL of the Tracengent API |
| `TRACENGENT_CLIENT_ID` | Agent OAuth client id (`key_...`) — preferred |
| `TRACENGENT_CLIENT_SECRET` | Agent OAuth client secret (`ags_...`) |
| `TRACENGENT_INGEST_KEY` | Legacy org ingest key (`agk_...`) — fallback |
| `TRACENGENT_AGENT_ID` | Logical agent key reported with events |
| `TRACENGENT_ENVIRONMENT` | Environment tag (default `production`) |
