Metadata-Version: 2.5
Name: nexgenomics
Version: 0.8.0
Summary: Python SDK for fabric guest agents — originate v1.cap.* capability calls.
Project-URL: Homepage, https://github.com/nexgenomics/py-nexgenomics
Author: NexGenomics
License-Expression: Apache-2.0
Keywords: agents,fabric,inference
Requires-Python: >=3.11
Provides-Extra: test
Requires-Dist: pytest-asyncio>=0.24; extra == 'test'
Requires-Dist: pytest>=8; extra == 'test'
Description-Content-Type: text/markdown

# nexgenomics

The Python SDK for fabric guest agents. Agent authors originate `v1.cap.*`
capability calls — `model.generate`, `model.stream`, `dataset.search`,
`turn.delta` — without
hand-rolling the wire protocol or ever touching the platform token.

Requires Python **3.11+**. Async-native.

```python
from nexgenomics import connect, PermissionDenied

async with connect() as agent:
    reply = await agent.generate(
        model="mistral-7b-instruct", version="v0.3",
        messages=[{"role": "user", "content": "hello"}],
        max_tokens=64,
    )
    print(reply.content, reply.usage)

    async for chunk in agent.stream(
        model="mistral-7b-instruct", version="v0.3",
        messages=[{"role": "user", "content": "write a haiku"}],
    ):
        print(chunk, end="", flush=True)

    try:
        hits = await agent.search(dataset="papers", space="v1", query="genomics")
    except PermissionDenied:
        ...
```

## Records

Search returns the passages that best match a query. To read what a dataset
holds, list its records and read one whole:

```python
async for rec in agent.records(dataset="catalog"):
    if rec.readable:
        data = await agent.records_read(
            dataset="catalog", batch_id=rec.batch_id, expected_size=rec.size
        )
```

`records_read` returns the record's bytes, whole, or raises. Pass
`expected_size`: the stream's final frame carries no length, so the listed size
is what proves nothing was lost. A refusal raises a `FabricError`; a lost or
malformed chunk raises `ProtocolError`. Records are bytes, not text.

## Published datasets

A published dataset is a frozen copy its owner published: a catalogue, a
reference corpus. A run reads one when its workspace holds a license to it and
the request for the work named it. It is named by the id of one published
version, which the request hands the agent as a reach entry of kind
`publication`.

Read the whole version in one call:

```python
bundle = await agent.published_read_all(publication=pub_id)
for rec, data in bundle.records:
    ...  # rec.batch_id, rec.sha256, rec.size; data is the record's bytes
```

That is one call whatever the number of records, so a catalogue does not spend
the run's call budget. Every record returned has been checked against the
size and SHA-256 the platform listed for it; anything else raises
`ProtocolError` and returns nothing. `bundle.digest` identifies the version.

A version too large for one call raises `FailedPrecondition`. Read it record by
record instead:

```python
pub = await agent.published_list(publication=pub_id)
for rec in pub.records:
    data = await agent.published_read(
        publication=pub_id, batch_id=rec.batch_id,
        expected_size=rec.size, expected_sha256=rec.sha256,
    )
```

Without a license, each of the three raises `PermissionDenied`. Nothing here
reads a dataset of the run's own workspace, and `records_read` cannot read a
publication.

## Storing a result

A run asked for one piece of work stores its one result in the dataset the
request named for writing, and says which records the result rests on:

```python
stored = await agent.result_write(
    dataset=results_id,
    data=ledger_bytes,
    cites=[{"dataset": pub_id, "batch_id": rec.batch_id} for rec, _ in bundle.records],
)
```

The platform compares `cites` with the records it actually served this run. If
the result cites one it did not serve, nothing is stored and the call raises
`FailedPrecondition`. `stored.check` is `passed`, or `cited_nothing` when the
result cited no record. For a record of a publication, `dataset` is the
publication's id. A result is at most 8 MiB and cites at most 1024 records.

## How it works

The SDK drives a small Go transport helper (`cap-helper`) as a subprocess over a
length-prefixed-JSON stdio protocol. The helper owns the vsock framing, the
credential handshake, per-call correlation, and the v1.1 stream-frame decode —
one authority shared with the host, so the SDK cannot drift from the wire
contract. The SDK owns the ergonomic surface: the call methods, the exception
hierarchy, and the streaming async iterators.

**Identity is ambient.** The helper reads the credential from env (populated by
the platform at boot); the SDK never sees or accepts the token. Your code does
not change when the platform changes how the token is delivered.

## Errors

Two disjoint branches under `FabricSDKError`:

- **`FabricError`** — the host returned a verdict with a wire code:
  `Unauthenticated`, `PermissionDenied`, `NotFound`, `InvalidArgument`,
  `AlreadyExists`, `FailedPrecondition`, `Unavailable`, `Internal`. A denial on a
  stream is raised from the `async for`.
- **`TransportError`** — the call never reached a verdict: `NoReply` (deadline —
  **never** a denial), `SendFailed`, `ProtocolError`, `Draining`, `HelperExited`.

Catch `FabricError` for "the platform said no", `TransportError` for "no answer".

## Streaming and cancel

`stream(...)` returns an async iterator that yields text deltas; on normal
completion it stops and exposes `.finish_reason` and `.usage`. Breaking out of
the loop, or `await stream.aclose()` / leaving an `async with stream`, sends an
advisory cancel for the in-flight generation.

## The helper binary

At runtime the `cap-helper` binary is delivered by the platform image (it is
version-locked to the deployed host and built in the monorepo), so in-guest the
SDK finds it automatically. For **off-platform development** against the fake
host, point the SDK at a local build:

```bash
export CAP_HELPER_BIN=/path/to/cap-helper
```

## Development

```bash
pip install -e '.[test]'
pytest
```

The tests run the SDK against a fake helper (`tests/_fakehelper.py`) that speaks
the same stdio protocol with an independent codec — no VM, no real host.

## License

Apache-2.0. Releases from 0.4.0 onward are Apache-2.0 licensed; earlier 0.x
releases were BSD-3-Clause.
