Metadata-Version: 2.5
Name: buildathena-sdk
Version: 0.4.5
Summary: Athena Labs Python SDK for agentic ML workflow orchestration
Project-URL: Homepage, https://buildathena.dev
Project-URL: Documentation, https://buildathena.dev/docs/sdk
Author-email: Athena <richard@buildathena.dev>
License: Proprietary
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.11
Requires-Dist: aiofiles>=23.0.0
Requires-Dist: click<9.0.0,>=8.0.0
Requires-Dist: googleapis-common-protos<2.0.0,>=1.70.0
Requires-Dist: grpcio<2.0.0,>=1.82.1
Requires-Dist: httpx<1.0.0,>=0.26.0
Requires-Dist: hydra-core<2.0.0,>=1.3.2
Requires-Dist: protobuf<8.0.0,>=7.35.0
Requires-Dist: pydantic<3.0.0,>=2.12.0
Requires-Dist: pyyaml<7.0.0,>=6.0.0
Provides-Extra: dev
Requires-Dist: grpcio-tools==1.82.1; extra == 'dev'
Requires-Dist: mypy>=1.7.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
Requires-Dist: pytest-timeout>=2.2.0; extra == 'dev'
Requires-Dist: pytest-xdist>=3.0.0; extra == 'dev'
Requires-Dist: pytest>=7.4.0; extra == 'dev'
Description-Content-Type: text/markdown

<!-- Verified against codebase: 2026-08-24 -->

# buildathena-sdk

[![PyPI version](https://img.shields.io/pypi/v/buildathena-sdk)](https://pypi.org/project/buildathena-sdk/)
[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)
[![Status: Alpha](https://img.shields.io/badge/status-alpha-orange)](https://buildathena.dev)

Python SDK for building blocks and workflows on the [Athena Labs](https://buildathena.dev) ML orchestration platform. Athena Labs makes ML workflows reproducible, observable, interruptible, and composable through a DAG execution engine with a built-in session that can build, run, monitor, and repair pipelines.

> **Alpha software** — APIs may change between releases. Pin your version in production.

## Installation

```bash
ATHENA_VERSION="$(athena --version | awk '{print $2}')"
pip install "buildathena-sdk==${ATHENA_VERSION}"
```

Requires Python 3.11+.

## Quick Start

Define a block, read resolved config from `ctx.config`, emit metrics and progress, and register an explicit artifact when you want a durable named asset:

```python
from athena import BlockContext, ConfigRef, block

@block(
    name="TrainModel",
    outputs=["checkpoint"],
    config=ConfigRef(search_path="conf", config_name="train"),
)
async def train_model(ctx: BlockContext) -> dict:
    epochs = int(ctx.config.get("epochs", 100))
    learning_rate = float(ctx.config.get("learning_rate", 1e-3))

    for epoch in range(epochs):
        loss = train_epoch(lr=learning_rate)
        await ctx.emit_metric("loss", loss, step=epoch)
        await ctx.emit_progress(epoch + 1, epochs)
        await ctx.check_pause()  # cooperative pause point

    checkpoint = await ctx.artifacts.register(
        "model.pt",
        name="checkpoint",
        format="pickle",
        mime_type="application/octet-stream",
        tags=["training", "final"],
    )
    return {"checkpoint": checkpoint.as_ref()}
```

Portable resource requests may include CPU, system memory, `/dev/shm` shared
memory, ephemeral local storage, and accelerator requirements. `memory` is the
total container RAM reservation/limit. `shared_memory` is only the `/dev/shm`
mount-size ceiling: it may be used without `memory` or be larger than `memory`,
and it does not create an additional physical RAM pool. When `memory` is
omitted, RAM remains unreserved.

```python
from athena import BlockEnvironment, ResourceSpec

environment = BlockEnvironment(
    backends=["docker", "kubernetes"],
    image="us-docker.pkg.dev/acme/athena/train:2026-08-24",
    resources=ResourceSpec(
        cpu="2",
        memory="4Gi",
        shared_memory="8Gi",
        ephemeral_storage="16Gi",
    ),
)
```

Docker supports `shared_memory` and intentionally does not match requests that
contain `ephemeral_storage`. Kubernetes supports both.
Process accepts resource-bearing work but does not enforce or reserve those
fields. Omit `process` from `BlockEnvironment.backends` when enforcement is
required.

Docker and Kubernetes mount the exact run code bundle at `/athena/workspace`,
set `ATHENA_WORKSPACE=/athena/workspace`, and execute with that directory as the
working directory. Install dependencies and keep image-owned files elsewhere;
the runtime mount replaces anything the image placed under the reserved path.
Ensure the declared executable resolves through `PATH` or use an absolute path.
Process workers set `ATHENA_WORKSPACE` to a fresh detached, attempt-owned worktree
at the exact `code_ref` commit.

## Consuming Inputs

Block inputs come from the Python signature after `ctx`. Athena hydrates those
arguments from upstream outputs before invoking the block:

```python
@block(name="Evaluate", outputs=["report"])
async def evaluate(ctx: BlockContext, checkpoint) -> dict:
    checkpoint_ref = checkpoint
    checkpoint_path = await ctx.artifacts.resolve(checkpoint_ref)
    model = load_model(checkpoint_path)
    score = run_eval(model)
    await ctx.emit_metric("accuracy", score)
    return {"report": {"accuracy": score}}
```

## Credentials

Declare required secrets in the `@block` decorator and access them at runtime via `ctx.secrets`. Credentials are encrypted at rest and injected only during execution:

```python
@block(name="FetchData", outputs=["dataset"], secrets=["API_KEY"])
async def fetch_data(ctx: BlockContext) -> dict:
    key = ctx.secrets["API_KEY"]
    data = await download(api_key=key)
    return {"dataset": data}
```

Declare outputs on the decorator and return a mapping with exactly those keys.

## Cooperative Pause

Call `check_pause()` inside long-running loops to let Athena Labs pause the block between iterations without losing progress:

```python
for epoch in range(epochs):
    train_step()
    await ctx.check_pause()  # yields control if a pause was requested
```

## BlockContext API

| Method / Accessor | Description |
|---|---|
| `ctx.secrets["KEY"]` | Access declared secrets |
| `await ctx.emit_metric(name, value, step=, labels=)` | Emit one scalar metric |
| `await ctx.emit_metrics({"loss": loss, "accuracy": acc}, step=, labels=)` | Emit multiple scalar metrics |
| `await ctx.emit_progress(current, total, message=)` | Emit progress (current/total) |
| `await ctx.emit_log(message, level=, source=)` | Emit a structured log event |
| `await ctx.check_pause()` | Cooperative pause checkpoint |
| `await ctx.artifacts.register(source, format=, mime_type=, name=, tags=, metadata=)` | Create a durable artifact |
| `artifact.as_ref()` / `artifact.as_data()` | Choose pointer or hydrated downstream delivery |
| `await ctx.artifacts.resolve(ref)` | Resolve a managed artifact to a local path |
| `await ctx.artifacts.load(ref)` | Load and deserialize a formatted managed artifact |
| `ctx.athena` | Attempt-scoped Repo, Session, Chat, Workflow, and Run resources |

`as_data()`, `resolve()`, and `load()` apply to managed artifacts. URI-backed external artifacts
use `as_ref()` and expose their location through `ref.uri` for code that can access the bound store
or filesystem.

## Block-Scoped Athena Client

Athena injects the canonical Repo, Session, and private Chat client into an
executing block. Chat collections paginate transparently, sending returns after
durable acceptance, and receipt waits target only the submitted turn:

```python
repo = await ctx.athena.repos.get("https://github.com/acme/training.git")
session = await ctx.athena.sessions.create(
    title="Candidate search",
    repos=[repo.at("candidate", new_branch=True)],
)

proposal_branch = session.repos[0].branch_name
chat = await session.chats.create(model_key="claude-opus-5")
receipt = await chat.send(
    f"""Athena prepared `{proposal_branch}` as a fresh proposal branch seeded from the
exact current head of `candidate`. Work directly in the prepared branch. After the
edit, use athena_attachment_git first with action `commit` and a concise message,
then with action `publish` and no message.""",
)
turn = await receipt.wait(timeout=None)

async for existing_chat in session.chats.list():
    print(existing_chat.id)

async for item in chat.items():
    consume(item)

async for item in turn.items():
    consume_turn_item(item)

# Stop interrupts current work but leaves the Chat open.
stop = await chat.stop(reason="operator requested")

# Close only after the Chat is quiescent, then read current state explicitly.
await chat.close()
closed = await session.chats.get(chat.id)
assert closed.closed_at is not None

# Exact lookup is the durable pointer; lookup alone does not reopen the Chat.
same_chat = await session.chats.get(closed.id)
await same_chat.resume()
resumed = await session.chats.get(same_chat.id)
assert resumed.closed_at is None
```

`model_key` is the stable model identity shown in Athena. With no `connection`,
Athena uses its platform connection. To use a team connection, pass its permanent
name, for example `connection="Acme inference"`. Athena resolves that pair to an
exact route when the Chat is created and keeps the route pinned on the Chat.

`title` is optional. If it is omitted, the server assigns a stable friendly title such as
`quiet-snail-cafe`; the generated title is returned on the `Session` object and in browser
projections. Titles are trimmed and limited to 255 characters.

Block code does not author runtime call keys or application idempotency keys.
Invoke a `Block` or nested `Workflow` directly with `child(...)`; there is no
separate `.call(...)` authoring path. Session, Chat, and independent
`Workflow.run()` operations likewise derive their internal replay identity. Ordinary
mutations use a stable logical call slot; a response to a pending Chat request uses
that exact request's identity. Keep construction order deterministic so the same
source slot continues to represent the same logical operation after a controller retry.
Browser-generated user intents and transport-level request keys are separate
internal boundaries, not block SDK arguments.

`repo.at(parent, new_branch=True)` creates a fresh proposal branch from the exact
current head of `parent`; the returned attachment reports the generated branch
name. Work directly in that prepared branch. Agent-authored changes are committed
with a message and then published without a message; reconciliation is only for a
published remote head that changed after preparation.

`chat.items()` and `turn.items()` expose only the typed, durable, user-visible
transcript projection. They are not live watches or raw event/trace access.
Canonical values decode to ordinary Python primitives where lossless; inline
bytes, opaque blobs, encoded values, and artifacts remain explicit
`InlineBytes`, `BlobRef`, `EncodedValue`, and `ArtifactRef` wrappers rather than
generated Protobuf messages or implicitly fetched bulk data.

`chat.stop()` durably requests interruption of work active at admission time;
it does not close the Chat or prevent later messages and wakes. `chat.close()`
is a synchronous lifecycle mutation that requires the Chat to have no active or
queued agent work. While closed, that exact Chat rejects new ingress and cannot
receive gate wakes, but it remains readable through `session.chats.get(chat_id)`
and `chat.items()`. Only explicit `chat.resume()` reopens it. Closing one Chat
does not disable a Session policy that is defined to create a different fresh
Chat.

### Independent Runs

`Workflow.run()` returns a durable `Run` handle. Lifecycle reads return an
immutable `RunSnapshot`; outputs and metrics have focused accessors:

```python
run = await candidate.workflow("workflows/evaluate.py:evaluate").run(profile=None)

snapshot = await run.wait(timeout=3600)
outputs = await run.result()
fitness = await run.metric("fitness")

print(snapshot.status, snapshot.is_terminal)
print(snapshot.source.repo_url)
print(snapshot.source.branch_name, snapshot.source.commit_sha)
```

This evaluation passes `profile=None` because the controller polls the Run directly and does not
need profile gates or agent wakes.

`RunSnapshot` carries `id`, `lab_id`, `workflow_key`, `status`, `is_terminal`,
`failure`, `held_node_ids`, `created_at`, `started_at`, `completed_at`, and
`source`. Its `source` contains `repo_url`, `branch_name`, and `commit_sha` for
that observation. `run.source` is the latest source observed by the handle.

Use `status()` for a current snapshot, `wait()` for a terminal snapshot,
`result()` for hydrated named outputs, `metric(name)` for the latest metric,
and `kill()` or `release_held_nodes(...)` for lifecycle controls.

## Config

Blocks use Hydra-backed `ConfigRef` values. Hydra owns composition and interpolation; launch
policy remains separate in Run Profiles.

```python
from athena import ConfigRef, block

@block(
    config=ConfigRef(
        loader="hydra",
        search_path="configs",
        config_name="train",
    )
)
async def train(ctx):
    epochs = ctx.config["epochs"]
```

See the [config docs](https://buildathena.dev/docs/features/config) for discovery and launch layers.

## Documentation

- [Getting Started](https://buildathena.dev/docs/setup) — installation and first workflow
- [SDK Reference](https://buildathena.dev/docs/sdk) — decorators, config, and discovery
- [Generated Python API Reference](./API_REFERENCE.md) — exact public signatures, types, and defaults
- [BlockContext API](https://buildathena.dev/docs/sdk/block-context) — full method reference
- [Evolutionary Controllers](https://buildathena.dev/docs/sdk/evolutionary) — candidate branches, private Chats, evaluation Runs, and lineage
- [Features](https://buildathena.dev/docs/features) — gates, inspectors, caching, and more
- [knowledge-base/current/architecture/sdk.md](../../../knowledge-base/current/architecture/sdk.md) — internal architecture and implementation reference

## License

Proprietary - Copyright (c) 2026 Athena Labs Research Inc. All rights reserved.
