Skip to main content

Bystro Think Python API

The Think SDK is a synchronous, typed client for durable Bystro agent workloads. It intentionally exposes a small surface: authenticate, upload artifacts, compose context, submit work, observe progress, answer pauses, and resume by run ID.

Install

Bystro 2.1.1 supports CPython 3.11 and 3.12. Activate the environment you want to use, confirm it with python --version, and install from PyPI:

python -m pip install "bystro>=2.1.1,<2.2"

Production endpoints use publicly trusted HTTPS certificates, so no custom CA bundle or TLS override is required. A private CA bundle is only needed for a local development deployment that uses its own certificate authority.

Authenticate

auth.login uses the same bystro.cloud dashboard account as the browser and caches its JWT in ~/.bystro/bystro_authentication_token.json. The directory is mode 0700, the file is atomically replaced at mode 0600, and tokens are never printed.

from bystro.api import auth
from bystro.think import ThinkClient

auth.login("you@example.com", "your-password")
client = ThinkClient.from_cached_login()

For short scripts, login and construction can be one call:

client = ThinkClient.login("you@example.com", "your-password")

If the deployment keeps Bystro's shared site-access gate enabled, present its code during the same login. The SDK establishes the gate cookie and performs dashboard login in one private session; the code is never written to the auth cache:

import os

client = ThinkClient.login(
    "you@example.com",
    "your-password",
    site_access_code=os.environ["BYSTRO_SITE_ACCESS_CODE"],
)

New accounts must explicitly provide the dashboard's signed legal assertions:

from bystro.api.auth import LegalConsent, signup

signup(
    "you@example.com",
    "your-password",
    "Your Name",
    legal_consent=LegalConsent.accepted("Your Name"),
    site_access_code=os.environ["BYSTRO_SITE_ACCESS_CODE"],
)

site_access_code is the Bystro application gate, not a Cloudflare credential. Cloudflare must still allow non-browser traffic to the dashboard authentication and Think API routes while retaining its challenge on browser pages.

The SDK exchanges that dashboard session through Think's existing cookie-auth admission endpoint. The application credential created by Think remains on the server; it is not copied into local code or exposed as a second API key.

Customer quickstart: complete examples

Start with this shared setup. Credentials come from environment variables, and the wait_for_result loop handles any number of durable clarification or plan review pauses while progress continues to stream. A billing pause must be resolved in the dashboard before refreshing the run.

import os

from bystro.think import (
    InputKind,
    NeedsInput,
    Run,
    RunResult,
    ThinkClient,
    show_progress,
)


def login() -> ThinkClient:
    return ThinkClient.login(
        os.environ["BYSTRO_EMAIL"],
        os.environ["BYSTRO_PASSWORD"],
        site_access_code=os.environ.get("BYSTRO_SITE_ACCESS_CODE"),
        on_event=show_progress,
    )


def wait_for_result(run: Run) -> RunResult:
    while True:
        outcome = run.wait(timeout=3600)
        if not isinstance(outcome, NeedsInput):
            return outcome

        print(f"\n{outcome.prompt}")
        if outcome.kind is InputKind.BILLING:
            input("Resolve billing in the dashboard, then press Enter: ")
            run.refresh()
            continue

        run.respond(input("> "))

Set credentials before running an example:

export BYSTRO_EMAIL="you@example.com"
export BYSTRO_PASSWORD="your-password"
export BYSTRO_SITE_ACCESS_CODE="your-site-code"  # omit when not required

1. Submit a question with live progress

submit_with_progress installs the concise progress renderer automatically. Passing show_progress at login also reports connection and reconnect events. The final answer remains available as the typed RunResult.output value.

with login() as client:
    run = client.submit_with_progress(
        "Research the latest CAR-T therapies and cite primary sources."
    )
    result = wait_for_result(run)

    print("\n--- Final response ---\n")
    print(result.output)

Progress is based on durable server lifecycle and status events; it is not a token-by-token stream of the final prose.

2. Submit a question with files

Paths passed through files are uploaded first and then attached to the same question. Large files automatically use the resumable, chunked upload path.

from bystro.think import UploadProgress


def report_upload(progress: UploadProgress) -> None:
    print(
        f"[upload:{progress.phase.value}] {progress.fraction:.0%}",
        flush=True,
    )


with login() as client:
    run = client.submit_with_progress(
        "Analyze the cohort using the attached phenotype table.",
        files=["cohort.vcf.gz", "phenotypes.tsv"],
        on_upload_progress=report_upload,
    )
    result = wait_for_result(run)
    print(result.output)

3. Submit with genetic, conversation, and artifact context

The add_*_context helpers accept a string or an existing MessageWithContext, so context can be built one layer at a time. Existing references are resolved under the authenticated user's ownership.

import os

from bystro.think import (
    add_artifact_context,
    add_genetic_context,
    add_previous_conversation_context,
)


with login() as client:
    artifact = client.upload_artifact("study-notes.pdf")

    message = "Re-evaluate the strongest phenotype associations."
    message = add_genetic_context(
        os.environ["BYSTRO_JOB_ID"],
        message,
        name="Case cohort",
        assembly="hg38",
    )
    message = add_previous_conversation_context(
        os.environ["BYSTRO_PRIOR_THREAD_ID"],
        message,
        name="Previous analysis",
    )
    message = add_artifact_context(artifact, message)

    run = client.submit_with_progress(message)
    result = wait_for_result(run)
    print(result.output)

For a reusable higher-order context pipeline:

from bystro.think import (
    artifact_context,
    compose_context,
    genetic_context,
    previous_conversation_context,
)

add_study_context = compose_context(
    genetic_context("annotation-job-id", assembly="hg38"),
    previous_conversation_context("prior-thread-id"),
    artifact_context("existing-artifact-id"),
)

message = add_study_context("Compare the strongest signals.")

4. List conversations, browse results, and download files

list_conversations returns the authenticated user's conversations newest first and transparently follows every cursor page. Pass search to filter by conversation name. Resume a returned ID to browse or download its protected output files through the same authenticated session.

from pathlib import Path


with login() as client:
    conversations = client.list_conversations(search="CAR-T")
    for conversation in conversations:
        print(conversation.id, conversation.name)

    if not conversations:
        raise RuntimeError("No matching conversations")

    previous_run = client.resume(conversations[0].id)
    files = previous_run.output_files()
    for output_file in files:
        print(output_file.path, output_file.size)

    if files:
        downloaded = previous_run.download_file(
            files[0],
            Path("downloads") / files[0].path,
        )
        print("Downloaded:", downloaded)

    archive = previous_run.download_all(
        Path("downloads") / f"{previous_run.id}.tar"
    )
    print("Archive:", archive)

download_file and download_all stream to a temporary file and only publish the destination after the authenticated download completes. Existing targets are not overwritten unless overwrite=True is passed explicitly.

Upload files and submit them with a question

Passing paths to submit uploads each file to personal input artifacts first, then attaches the resulting artifact records to the same user message:

run = client.submit(
    "Find variants associated with the case phenotype.",
    files=["cohort.vcf.gz", "phenotypes.tsv"],
)

Uploads use the production resumable protocol: bounded 10 MiB chunks, per-chunk SHA-256 checksums, idempotent retries with exponential backoff, and polling for asynchronous server finalization. The chunk size and retry policy can be configured on ThinkClient. An artifact_path is relative, has at most 64 components, and must end in the local file's exact name; invalid paths fail locally before authentication or upload begins.

Use upload_artifact when the artifact should be created before the question:

def report_upload(progress):
    print(progress.phase.value, f"{progress.fraction:.0%}")

artifact = client.upload_artifact(
    "cohort.vcf.gz",
    artifact_path="study/cohort.vcf.gz",
    on_progress=report_upload,
)
run = client.submit("Run QC on this cohort", files=[artifact])

upload is an equivalent shorter alias.

Compose genetic, conversation, and artifact context

Context helpers accept either a plain string or an immutable MessageWithContext, so calls compose naturally:

from bystro.think import (
    add_artifact_context,
    add_genetic_context,
    add_previous_conversation_context,
)

message = "Compare the strongest signals"
message = add_genetic_context(
    "annotation-job-id",
    message,
    name="Case cohort",
    assembly="hg38",
)
message = add_previous_conversation_context(
    "prior-thread-id",
    message,
    name="Earlier analysis",
)
message = add_artifact_context(artifact, message)

run = client.submit(message)

Reusable higher-order transforms are also available:

from bystro.think import (
    artifact_context,
    compose_context,
    genetic_context,
    previous_conversation_context,
)

study_context = compose_context(
    genetic_context("annotation-job-id", name="Case cohort", assembly="hg38"),
    previous_conversation_context("prior-thread-id"),
    artifact_context("existing-artifact-id"),
)

run = client.submit(study_context("Re-evaluate the phenotype association"))

message.to_xml() returns a safely escaped preview of the semantic context. On the wire, references remain structured metadata. Think resolves artifacts against the authenticated user before creating canonical input-file context; dataset and conversation retrieval tools independently enforce ownership before returning referenced data. Raw user-authored XML is never treated as an ownership boundary.

Handle needs_input

wait() returns exactly one of two values: RunResult or NeedsInput. Clarifications and plan review are durable checkpoint states, not transient socket prompts.

from bystro.think import InputKind, NeedsInput

outcome = run.wait(timeout=3600)
if isinstance(outcome, NeedsInput):
    if outcome.kind is InputKind.PLAN_REVIEW:
        run.respond("accept")
    else:
        run.respond("Use case_control as the phenotype column")
    outcome = run.wait(timeout=3600)

The first live pause notification can arrive just before its checkpoint is committed, so checkpoint_id may initially be None. run.respond() requests the durable replay automatically and will not upload attachments or dispatch the response until the checkpoint is present. Once answered, replays of that same or an older checkpoint are ignored, preventing reconnect races from reopening a stale question. If synchronization fails, the pause remains intact and RunProtocolError asks you to call run.refresh() and retry. A billing pause is also represented as NeedsInput, but must be resolved through the billing action in the dashboard; then call run.refresh().

Progress and reconnects

submit_with_progress uses show_progress; use submit for silent workloads:

from bystro.think import ThinkClient, show_progress

with ThinkClient.login(
    "you@example.com", "your-password", on_event=show_progress
) as client:
    run = client.submit_with_progress("Perform a GWAS", files=["cohort.vcf.gz"])
    result = run.wait()

Browse and download generated results

conversations = client.list_conversations(search="CAR-T")
for conversation in conversations:
    print(conversation.id, conversation.name)

files = run.output_files()
print([(output_file.path, output_file.size) for output_file in files])
image = run.download_file(files[0], "downloads/duck.png")
archive = run.download_all("downloads/all-results.tar")

The durable run ID is available immediately after admission:

print(run.id)

Another process can attach to it later:

client = ThinkClient()
run = client.resume("run-or-thread-id")
outcome = run.wait()

run.messages provides the hydrated transcript and run.history provides bounded SDK progress history. Socket reconnects automatically replay the current overlay state. When a replayed needs_input overlay arrives before its transcript, wait() holds the outcome until transcript hydration restores the clarification or plan-review prompt.

Follow-up turns and errors

After a successful result, start another turn in the same conversation:

run.follow_up("Now stratify the result by ancestry")
next_result = run.wait()

Transport, HTTP, billing, admission, timeout, and protocol failures have typed exceptions under bystro.think. A ThinkClient owns one foreground run at a time; use separate clients for concurrently controlled conversations. Closing a client disconnects local transports but does not cancel durable server work.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bystro-2.1.1.tar.gz (22.0 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

bystro-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (22.3 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

bystro-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (22.3 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ ARM64

bystro-2.1.1-cp312-cp312-macosx_11_0_arm64.whl (22.3 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

bystro-2.1.1-cp312-cp312-macosx_10_12_x86_64.whl (22.3 MB view details)

Uploaded CPython 3.12macOS 10.12+ x86-64

bystro-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (22.3 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ x86-64

bystro-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (22.3 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ ARM64

bystro-2.1.1-cp311-cp311-macosx_11_0_arm64.whl (22.3 MB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

bystro-2.1.1-cp311-cp311-macosx_10_12_x86_64.whl (22.3 MB view details)

Uploaded CPython 3.11macOS 10.12+ x86-64

File details

Details for the file bystro-2.1.1.tar.gz.

File metadata

  • Download URL: bystro-2.1.1.tar.gz
  • Upload date:
  • Size: 22.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: maturin/1.14.1

File hashes

Hashes for bystro-2.1.1.tar.gz
Algorithm Hash digest
SHA256 ad3aa69586b7448f739ee6eb0f4563c3deda2fb64c094785ba42555faea011d9
MD5 a3de18ea7cc1b88fed9261357d38e413
BLAKE2b-256 0d79736f3b0cba13e8ebaf3aa16493b3092ce7cc027941b2fc2d2506a79a21a4

See more details on using hashes here.

File details

Details for the file bystro-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for bystro-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 1e8645004cb2f5ce674a925407aa22a7e11aecbbb32aec57ec1f39ee2dabd1d4
MD5 a609ba34f1bd1a4502014d967bf16aaf
BLAKE2b-256 aa39cfd414dbb845f3d66ffee8346259efb7da126c4ee5d56b805ff83d1b0c67

See more details on using hashes here.

File details

Details for the file bystro-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for bystro-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 ab1123bc40ffb92c88a93c8d5361c9a580811709e1c5a6d948314abd8b039ea8
MD5 3602b4b4fb751d1df90903e447f1ad1e
BLAKE2b-256 ee1a96d33ff57495fd85b31e996cbb50d1d3d3818aee1af4b473e6dddc2b783d

See more details on using hashes here.

File details

Details for the file bystro-2.1.1-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for bystro-2.1.1-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 35b15d0095b6b79a03ad6ddbb355785506fc7135353f7422396c4ef7d08671c9
MD5 c8b57d6a336075c40fb2917cc75e4387
BLAKE2b-256 bdac113a7c02e56da13d63b4cd1f3a5855c4756207e83a1dbb93d69442f9183d

See more details on using hashes here.

File details

Details for the file bystro-2.1.1-cp312-cp312-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for bystro-2.1.1-cp312-cp312-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 3f9b823c7fc0c48abc71180e45162f1e1faf37d5ac2a9c6d67f84fc38dc08cf0
MD5 e6a9267d1b723576df8fb8371477a2c6
BLAKE2b-256 b0ed3e42191838c33416bdefb18bdaf1dd583952714f4cc24b33ddb376e537b8

See more details on using hashes here.

File details

Details for the file bystro-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for bystro-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 276202ffdbf7c54e5a83a28e91ab345beac3a4190f24700b6bdb080a57bd3f27
MD5 f5978ec864f117e0f2eac55f7eba3e9b
BLAKE2b-256 984853bdf8c2cb4aa515ae8176b630a92c56621bd72cbf69a70c1d5c27f8563a

See more details on using hashes here.

File details

Details for the file bystro-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for bystro-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 3c14341483439d2a707ec9149b076a42083711113e21881918af5971da43f850
MD5 fbaa8db040957c016e9e6b387d10280d
BLAKE2b-256 d16207e9d24be430bac08a688433bf5faa3239697b1446b8b78ac9c69c59dfda

See more details on using hashes here.

File details

Details for the file bystro-2.1.1-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for bystro-2.1.1-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 6c1fcdbd5c8fb990166e5dd25213c7146551899c6fbdbe3f3c10752938adc8f8
MD5 1c3c0298393bc80fd3ec7e1f67e8693a
BLAKE2b-256 a331192148e79109f8bb684fc1becdf7bc1451bfb2136f6392f1e8f37b5dc487

See more details on using hashes here.

File details

Details for the file bystro-2.1.1-cp311-cp311-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for bystro-2.1.1-cp311-cp311-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 6d5807fad507d24debe9f82786ba83e2f0d3e061c878e54af3dd403573d52fbd
MD5 078a28e24abc87efcd8499b1a87c31f4
BLAKE2b-256 3ee0da085d957f89d8c76a12e119a4d2b42b33f397ffab4127e730d5e3c102b9

See more details on using hashes here.

Release history Release notifications | RSS feed

2.1.3

9 files

2.1.2

9 files

This release

2.1.1 This release

9 files

2.1.0

9 files

2.0.0

8 files

1.0.4

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page