Metadata-Version: 2.4
Name: trelent-agents
Version: 0.2.5
Summary: Python SDK for the Agent Orchestration API.
Requires-Python: >=3.11
Requires-Dist: pydantic>=2.0
Requires-Dist: requests>=2.31
Description-Content-Type: text/markdown

# trelent-agents

Python SDK for the Trelent Agent Orchestration API.

## Installation

```bash
pip install trelent-agents
```

## Quick Start

### Without authentication

```python
from trelent_agents import Client

client = Client(api_url="http://localhost:8000")

sandboxes = client.sandboxes.list()
print(sandboxes)

run = client.runs.create(
    sandbox="example-agent:latest",
    prompt="Create hello.txt with a greeting",
)
print(run.id, run.status, run.harness.kind)
```

### With authentication

When the API has authentication enabled, provide your OAuth2 client credentials:

```python
from trelent_agents import Client

client = Client(
    api_url="https://agents.trelent.com",
    client_id="your-client-id",
    client_secret="your-client-secret",
)

sandboxes = client.sandboxes.list()
run = client.runs.create(
    sandbox="my-sandbox:latest",
    prompt="Do something useful",
)
```

The SDK handles token acquisition automatically — it exchanges your client credentials for a JWT via the API's `/token` endpoint before making authenticated requests.

If you need to call the token proxy directly, use `client.tokens.create(...)`.

The client also supports context manager usage:

```python
with Client(api_url="https://agents.trelent.com", client_id="...", client_secret="...") as client:
    runs = client.runs.list()
```

## Client Options

```python
client = Client(
    api_url="https://agents.trelent.com",  # API base URL (default: https://agents.trelent.com)
    client_id="...",                        # OAuth2 client ID (optional)
    client_secret="...",                    # OAuth2 client secret (optional)
    scope="...",                            # Override default OAuth2 scope (optional)
)
```

Both `client_id` and `client_secret` must be provided together, or both omitted.

## Resources

### `client.sandboxes`

```python
sandboxes = client.sandboxes.list()
# => list[RegistrySandbox] — [RegistrySandbox(name="my-sandbox", tags=["latest", "v1"])]
```

When auth is enabled, sandboxes are automatically filtered to your namespace. Sandbox names are returned without the namespace prefix.

### `client.tokens`

```python
token = client.tokens.create(
    client_id="your-client-id",
    client_secret="your-client-secret",
    scope="AgentOrchestrator:runs:list",
)
```

### `client.runs`

```python
from trelent_agents import ClaudeCodeHarnessSpec, LocalImporter, S3Exporter

# Create a run
run = client.runs.create(
    sandbox="my-sandbox:latest",
    prompt="Build a web server",
    harness=ClaudeCodeHarnessSpec(),  # optional, default: ClaudeCodeHarnessSpec()
    timeout_seconds=3600,          # optional, default: 3600
    imports=[LocalImporter(path="./data")],  # optional
    exports=[S3Exporter()],                  # optional
)

# List runs (optionally filter by sandbox)
runs = client.runs.list()
filtered = client.runs.list(sandbox="my-sandbox:latest")

# Get a specific run
run = client.runs.get("run-id")

# Check status
status = client.runs.get_status("run-id")

# Cancel a run
cancel = client.runs.cancel("run-id")

# Get checkpoints
chain = client.runs.get_checkpoint_chain("run-id")
checkpoint = client.runs.get_checkpoint("run-id", "checkpoint-id")
```

### Run operations

```python
# Fork (resume from checkpoint)
forked = run.fork(
    "Continue from where you left off",
    timeout_seconds=1800,
)

# Refresh run state
run.refresh()
print(run.status)  # updated status
```

## Streaming Events

Subscribe to real-time events from a run using Server-Sent Events (SSE). Events are streamed as the agent executes, providing live visibility into reasoning, messages, and tool usage.

### Basic streaming

```python
from trelent_agents import Client, EventType

client = Client(api_url=api_url, client_id=client_id, client_secret=client_secret)

run = client.runs.create(
    sandbox="my-sandbox:latest",
    prompt="Build a simple web server",
)

# Subscribe to the event stream
for event in run.stream():
    match event.type:
        case EventType.MESSAGE_DELTA:
            print(event.data.delta.text, end="", flush=True)
        case EventType.TOOL_CALL_STARTED:
            print(f"\nTool: {event.data.name}")
        case EventType.SESSION_COMPLETED:
            print(f"\nCompleted with exit code: {event.data.exit_code}")
```

### Streaming via client

You can also stream using the run ID directly:

```python
for event in client.runs.stream_events("run-abc123"):
    print(event.type, event.seq)
```

### Event types

| Event Type | Description |
|------------|-------------|
| `SESSION_STARTED` | Agent session initialized |
| `SESSION_COMPLETED` | Session finished (includes `exit_code` and `usage`) |
| `TURN_STARTED` | New conversation turn began |
| `TURN_COMPLETED` | Turn finished |
| `TURN_FAILED` | Turn failed with error |
| `MESSAGE_STARTED` | Assistant message started |
| `MESSAGE_DELTA` | Incremental text content |
| `MESSAGE_COMPLETED` | Full message content available |
| `REASONING_STARTED` | Extended thinking started |
| `REASONING_DELTA` | Incremental reasoning text |
| `REASONING_COMPLETED` | Reasoning block finished |
| `TOOL_CALL_STARTED` | Tool invocation started (includes `name` and `kind`) |
| `TOOL_CALL_INPUT_DELTA` | Streaming tool input JSON |
| `TOOL_CALL_INPUT_COMPLETE` | Full tool input available |
| `TOOL_CALL_OUTPUT_DELTA` | Streaming tool output |
| `TOOL_CALL_COMPLETED` | Tool execution finished (includes `exit_code`) |
| `ERROR` | Error occurred |

### Event structure

Every event has a common envelope:

```python
class CommonEvent(BaseModel):
    seq: int           # Sequence number (monotonically increasing)
    timestamp: str     # ISO 8601 timestamp
    type: EventType    # Event type discriminator
    data: EventData    # Type-specific payload
```

### `client.health()`

```python
health = client.health()
# => HealthResponse(status="ok")
```

## Connectors

### Importing local files

```python
from trelent_agents import LocalImporter

run = client.runs.create(
    sandbox="my-sandbox:latest",
    prompt="Process the data",
    imports=[LocalImporter(path="./my-data-dir")],
)
```

The `LocalImporter` tarballs and base64-encodes the local path, sending it inline with the request.

### Exporting to S3

```python
from trelent_agents import S3Exporter

run = client.runs.create(
    sandbox="my-sandbox:latest",
    prompt="Generate a report",
    exports=[S3Exporter(bucket="my-bucket", path="reports/")],
)
```

## Docker Registry Setup

When auth is enabled, push sandbox images to your user namespace on the registry:

```bash
# Login with your OAuth2 credentials
docker login registry.example.com -u <client_id> -p <client_secret>

# Push to your namespace
docker tag my-sandbox:latest registry.example.com/<client_id>/my-sandbox:latest
docker push registry.example.com/<client_id>/my-sandbox:latest
```

When creating runs via the SDK, just use the sandbox name without the namespace prefix:

```python
run = client.runs.create(
    sandbox="my-sandbox:latest",  # not "<client_id>/my-sandbox:latest"
    prompt="...",
)
```

The API resolves the full registry path automatically based on your authenticated identity.

## Types

The SDK exports the following types:

```python
from trelent_agents import (
    Client,
    ClaudeCodeHarnessSpec,
    CodexHarnessSpec,
    CommonEvent,
    EventType,
    GeminiHarnessSpec,
    Run,
    RunStatus,
    RunResult,
    RunStatusResponse,
    RegistrySandbox,
    CheckpointResponse,
    CancelRunResponse,
    ChatHistoryEntry,
    FileOutput,
    OutputFile,
    HealthResponse,
    HarnessInfo,
    HarnessKind,
    HarnessSpec,
    TokenRequest,
    TokenResponse,
    ToolKind,
    WorkflowIds,
    LocalImporter,
    S3Exporter,
    APIError,
    NotFoundError,
    ValidationError,
)
```
