Metadata-Version: 2.5
Name: tama-sdk
Version: 0.1.1
Summary: Agent-friendly Python SDK for Tama
Project-URL: Documentation, https://tama.computer/docs/#python-sdk
Project-URL: Homepage, https://tama.computer
Project-URL: Repository, https://github.com/beam-cloud/tama
Author: Tama
Keywords: agents,gpu,sandbox,tama
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: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: grpcio<2,>=1.81.1
Requires-Dist: protobuf<7,>=6.33.5
Requires-Dist: pyyaml<7,>=6
Provides-Extra: dev
Requires-Dist: grpcio-tools==1.81.1; extra == 'dev'
Requires-Dist: mypy<2,>=1.14; extra == 'dev'
Requires-Dist: pytest<9,>=8.3; extra == 'dev'
Requires-Dist: ruff<1,>=0.9; extra == 'dev'
Requires-Dist: types-protobuf<7,>=5.29; extra == 'dev'
Description-Content-Type: text/markdown

# Tama Python SDK

Typed, synchronous, agent-friendly access to Tama machines. The SDK mirrors the
Tama CLI while keeping actions scoped to Python objects.

## Install

Python 3.10 or newer is required.

```bash
python -m pip install "tama-sdk==0.1.1"
```

The PyPI distribution is named `tama-sdk`; the Python import is `tama_sdk`.

## Authenticate

The simplest local setup is to install the Tama CLI, run `tama login`, and let
the SDK reuse the selected CLI profile:

```bash
curl -fsSL https://tama.computer/install | sh
tama login
```

```python
from tama_sdk import Tama

with Tama() as tama:
    print(tama.identity().account.id)
```

In CI, set `TAMA_TOKEN` instead. Set `TAMA_API_URL` only when using a non-default
gateway.

```bash
export TAMA_TOKEN="..."
export TAMA_API_URL="https://gateway.tama.computer"  # optional
```

You can also pass a token explicitly:

```python
import os

from tama_sdk import Tama

tama = Tama(api_key=os.environ["TAMA_TOKEN"])
```

Configuration precedence is explicit constructor values, `TAMA_*` environment
variables, then the selected CLI profile. `TAMA_PROFILE` selects a named CLI
profile. The Python SDK uses `TAMA_API_URL`; the CLI's endpoint override is named
`TAMA_API`.

## Safe end-to-end example

This creates a machine, runs a command and a Codex session, and always stops the
machine so billing pauses. `stop()` snapshots the machine and is reversible;
`delete()`/`rm()` destroys it.

```python
from tama_sdk import Tama

with Tama() as tama:
    machine = tama.new(name="python-sdk-example")
    try:
        result = machine.exec(["python", "--version"], check=True)
        print(result.stdout, end="")

        prompt = machine.prompt(
            "Inspect /workspace and write a concise README for the project.",
            agent="codex",
            check=True,
        )
        print(prompt.output, end="")
    finally:
        machine.stop()
```

`Tama` is a context manager because it owns a gRPC channel. The context manager
closes that local channel; it does **not** stop remote machines. Keep the
`try`/`finally` cleanup when your script creates or starts billable resources.

## Machines

The common workflows mirror the CLI: `new`, `list`, `get`, `rm`, `stop`,
`start`, `fork`, `exec`, `logs`, `prompt`, `expose`, `unexpose`, `ports`,
`desktop`, `terminal`, and `enable_ssh`.

```python
from tama_sdk import Tama

with Tama() as tama:
    for machine in tama.list(all=True):
        print(machine.id, machine.name, machine.status)

    machine = tama.get("worker")
    machine.stop()

    if machine.snapshot:
        print(machine.snapshot.disk_snapshot_id)
        print("warm checkpoint:", machine.snapshot.has_memory)

    machine.start()
```

`new()` and `start()` wait for the machine to become ready by default. Pass
`wait=False` for detached provisioning. `exec(..., check=True)` and
`prompt(..., check=True)` raise `CommandError` when the remote command exits
non-zero. A detached prompt has no exit code to check yet, so
`prompt(..., detach=True, check=True)` is rejected instead of silently ignoring
`check`. Long commands have no artificial RPC deadline; pass
`exec(..., timeout=300)` when the caller needs a five-minute bound.

## Complete public surface

An agent should prefer the object methods for one machine and the collections
for secondary resources:

```python
tama.identity()                       # account/workspace identity
tama.usage(days=30)                  # credit balance and metered usage
tama.offers()                         # available images, CPU, memory, and GPUs
tama.new(...)                         # create a machine
tama.list(all=True)                  # list, including stopped machines
tama.get("worker")                    # get by name or id
tama.rm("worker")                     # permanently delete
tama.stop("worker")                   # snapshot and stop
tama.start("worker")                  # start and wait until ready
tama.fork(snapshot_id, name="copy")   # fork an immutable snapshot
tama.exec("worker", ["pytest", "-q"], check=True)
tama.prompt("worker", "Fix the tests", agent="codex", check=True)
tama.logs(session_id)                # durable agent-session transcript
tama.logs(machine_id, pid)           # low-level process log stream
tama.expose("worker", 8000)           # publish a port; returns its URL
tama.unexpose("worker", 8000)
tama.ports("worker")                  # {port: public_url_or_none}
tama.desktop("worker")                # start/get browser desktop URL
tama.terminal("worker")               # start/get browser terminal URL
tama.enable_ssh("worker", public_key)
```

The same machine-scoped actions are available on `Machine`: `refresh`, `exec`,
`prompt`, `stop`, `start`, `delete`, `expose`, `unexpose`, `desktop`,
`terminal`, and `enable_ssh`. Its most useful properties are `id`, `name`,
`status`, `status_detail`, `data`, and the restore point returned by a stop in
`snapshot`.

Every secondary collection is explicit:

```python
tama.machines.create(...)            # also get/list/delete/stop/start
tama.snapshots.create("worker", label="baseline")
tama.snapshots.list(machine="worker", automatic=False)
tama.snapshots.fork(snapshot_id, name="experiment")
tama.templates.create("worker", name="base", description="...", public=False)
tama.templates.list()
tama.templates.delete(template_id)
tama.secrets.set("OPENAI_API_KEY", value)  # values are never returned
tama.secrets.list()
tama.secrets.delete("OPENAI_API_KEY")
created = tama.tokens.create("ci")         # created.secret is shown once
tama.tokens.list()
tama.tokens.revoke(created.id)
tama.sessions.list("worker")
tama.sessions.logs(session_id)
tama.files.list("worker", "/workspace")
```

`start_credit_purchase(amount_cents)` returns a Stripe checkout URL and
`confirm_credit_purchase(session_id)` refreshes the balance after the browser
returns. Most agents should send a human to the console rather than operating a
payment flow. `tama.raw` exposes the generated gRPC stub for forward
compatibility; normal code should use the typed helpers above.

## Detached agent session

```python
from tama_sdk import Tama

with Tama() as tama:
    machine = tama.get("worker")
    session = machine.prompt(
        "Run the test suite, fix failures, and summarize the patch.",
        agent="codex",
        detach=True,
    )

    print("session:", session.id)
    for event in tama.logs(session.id):
        print(event.data, end="")
```

Closing the local script does not stop a detached agent session. Its transcript
is durable and can be followed later with `tama.logs(session.id)`.

## Snapshots, forks, templates, secrets, and tokens

Secondary resources live on discoverable collections:

```python
from tama_sdk import Tama

with Tama() as tama:
    snapshot = tama.snapshots.create("worker", label="baseline")
    if snapshot is not None:
        fork = tama.snapshots.fork(snapshot.id, name="experiment-1")
        fork.stop()

    tama.snapshots.list(machine="worker")
    tama.templates.list()
    tama.secrets.set("OPENAI_API_KEY", "...")
    tama.tokens.create("ci")
```

Snapshots and templates pin the machine's complete root filesystem as one
immutable disk snapshot. A normal stop may also seal a memory checkpoint against
that disk state, allowing a warm resume. Secret values are never returned by the
SDK.

## Errors and retries

Catch `TamaError` for the SDK's complete error family, or a specific subclass
such as `AuthenticationError`, `NotFoundError`, `ValidationError`, or
`CommandError`.

Read-only RPCs retry short `UNAVAILABLE` and `DEADLINE_EXCEEDED` failures with
bounded exponential backoff. Mutations are never retried automatically: a
timed-out create, exec, or snapshot may already be running server-side. After an
ambiguous mutation failure, inspect state with `list(all=True)` or `get()` before
trying it again.

The `timeout=` on `Tama(...)` bounds ordinary control-plane RPCs. Operations
that legitimately seal or move machine state—stop, snapshot, template capture,
delete, and exec—do not inherit that short deadline. `exec(timeout=...)` is the
explicit opt-in bound for a remote command.

The generated protobuf schema is available as `tama_sdk.proto`, and the raw
generated service stub is available as `tama.raw` when a new RPC lands before a
convenience wrapper.

Full documentation: https://tama.computer/docs/#python-sdk

## Development

From `sdks/python` in the Tama repository:

```bash
uv sync --extra dev
uv run --extra dev python scripts/generate.py
uv run --extra dev pytest
uv run --extra dev ruff check .
uv run --extra dev mypy
```

The generated protobuf modules are committed, so installing the wheel does not
require `protoc`.
