Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Envai Teams Python SDK

Typed synchronous and asynchronous clients for the current Envai Teams API and Agent Team Protocol (ATP3 with strict historical ATP2 replay). Python 3.11+. The published alpha prerelease is 1.0.0a2 on PyPI. Prerelease APIs may change; pin the version and review upgrades before production use.

Install from PyPI

python -m venv .venv
# Activate .venv using the command for your shell, then:
python -m pip install "envai-teams-sdk==1.0.0a2"

Compatibility note: this branch adds a breaking contract3 migration. The published 1.0.0a2 baseline below predates it; use the normal release produced from this branch for Wiki V1/contract3, or the documented source install while reviewing.

The explicit version selects the alpha prerelease; no --pre flag is needed with this exact pin. The distribution is envai-teams-sdk; import envai. GitHub release v1.0.0-alpha.2 uses Python package version 1.0.0a2. For source installation and development, see getting started.

HTTP uses HTTPX, models use Pydantic, schema validation uses the pinned JSON Schema, and client-tool signatures use cryptography Ed25519 plus the rfc8785 implementation. No generated code or schema validator downloads remote references at runtime.

Quick start

import os
from envai import EnvaiClient, models

with EnvaiClient(os.environ['ENVAI_API_KEY'], base_url=os.environ['ENVAI_BASE_URL']) as client:
    workspace = client.workspaces.create(body=models.WorkspaceCreateRequest(name='Research'))
    # Choose a team already configured for this account.
    chat = client.chats.create(workspace.id, body=models.ChatCreateRequest(team_id=os.environ['ENVAI_TEAM_ID']))
    result = client.chats.send_and_wait(chat.id, 'Summarize the project', timeout=120)
    print(result.text)
    print(result.turn_id, result.last_seq, result.terminal_event.type)
    if result.terminal_event.type != "turn.completed":
        raise RuntimeError(f"Turn ended with {result.terminal_event.type}")

The quickstart creates remote resources and starts a potentially billable turn. Set ENVAI_BASE_URL, ENVAI_API_KEY, and an existing ENVAI_TEAM_ID. See the complete quickstart, stream example, and tool example.

No credentials are read implicitly or printed in client representations. Configure an API key explicitly. The example reads an environment variable only in application code. IDs are opaque strings, never UUID-coerced.

Async and individual events

from envai import AsyncEnvaiClient
from envai.event_models import AgentTextDeltaEvent

async def run(api_key: str, chat_id: str):
    async with AsyncEnvaiClient(api_key, base_url='https://your-edge.example') as client:
        async for event in client.chats.send_and_stream(chat_id, 'Hello', timeout=120):
            if isinstance(event, AgentTextDeltaEvent) and event.agent and event.agent.role == 'orchestrator':
                print(event.data.delta, end='')
            if event.terminal:
                print(event.type)

send_and_stream opens SSE and waits for the first ping before sending once. An HTTP 200 stream response alone does not establish live readiness. Waiting for a ping may take one heartbeat interval. Only matching turn_id events are returned; agent.done is not a turn terminal. send_and_wait aggregates only the orchestrator's text. Failed/cancelled terminal events remain explicit in TurnResult.terminal_event; they are not turned into successful text-only results.

For existing turns or explicit lifecycle control, use client.chats.events(chat_id, after=checkpoint) and client.chats.send_message(...) separately. Streams are context managers, have one reader, expose last_seq, live, and reconnects, and support max_reconnects, max_duration, max_frame_bytes and a threading.Event cancel. Async streams also support ordinary task cancellation. Closing an iterator releases the HTTP response. Sync cancellation is cooperative at reads/backoff; stream_read_timeout bounds an idle read. Remote chats.cancel is a separate, explicit mutation; closing a local stream never cancels a remote turn.

API surface

Services: workspaces, tools, chats, completions, files, agents, teams, models, api_keys, images, capabilities. Resources and ordinary collections are unwrapped from data; completion pages preserve data, links, and meta. completions.iterate(workspace_id, chat_id) follows numeric pages on the configured origin, never a response-supplied URL.

The generated primitive layer retains all 65 operation IDs in src/envai/_contracts/operations.json; every primitive has a snake_case method (e.g. send_chat_message for sendChatMessage) in both clients. See the generated API inventory. Health uses /api/v1/up; JWKS is public and defaults to the origin /.well-known/jwks.json. jwks_url can be overridden. The provider-webhook and local image-test primitives are included for inventory completeness and are experimental integration surfaces, not normal application workflows.

Generated request and response models are in envai.models; 28 typed event classes and their payloads are in envai.event_models / envai.models. Known events are validated strictly against the current contract. A malformed known event raises ProtocolError with the original data in .raw; a valid future type returns UnknownEvent with data unchanged. Unknown capability names are governed by the pinned HTTP contract, while unknown valid ATP event types are accepted independently of the capabilities discovery list.

Optional request fields preserve omission. In particular, ApiKeyCreate(name='x') omits expires_at; only explicit expires_at='never' means no expiration. Never log the plaintext token returned by key creation. Nullable legacy USD cost numbers retain floating-point precision from the wire without rounding to cents or estimating billing from local price tables.

Multipart methods accept HTTPX-style file tuples. Binary methods return bytes; no download is automatically written to disk. For a returned artifact URL use images.download_signed_url(url): it preserves query bytes/order, allows only the configured origin and current /api/v1/artifacts/download path, and never sends Bearer authentication. The raw artifact primitive uses the current artifact, expires, generation, signature query fields in canonical alphabetical order. Legacy path= artifact URLs are intentionally unsupported.

Transport policy and observability

Both clients accept timeout, connect_timeout, stream_read_timeout, retry, transport, observer, base_url, and jwks_url. An injected HTTPX transport supports tests, proxies, custom TLS and application-specific routing. Environment proxy discovery is disabled to avoid silently routing secrets through process-wide proxy settings. Use a configured transport when a proxy is desired.

Only GET requests are automatically retried on transport failure or 429/502/503/504, with bounded exponential jitter and bounded Retry-After. POST, PUT and DELETE are never automatically retried, even when a status or network failure looks transient. Per-call RequestOptions supports timeout and cooperative cancellation. A mutation with an ambiguous network result must be reconciled by the application. Redirects are not followed, including same-origin redirects.

observer(name, details) receives small structural events such as http.retry, stream.reconnect, stream.gap, stream.duplicate, and tool.error. It never receives credentials, URLs, tool arguments, output, or response bodies. Observer exceptions are isolated from delivery behavior. Typed HTTP exceptions expose http_status, type, code, message, details, and request_id; their string representation deliberately omits potentially sensitive server messages/details.

Opt-in client tools

No local tools, filesystem access, shell commands, or handlers are installed by default. Server registration (client.tools.create) and agent authorization (external_tools_enabled=[{'name': 'your_tool'}]) are separate from local handler registration. Both are required for a server-visible client tool.

See examples/client_tools.py for a complete bounded async runner. The runner requires explicit deployment/account scope, chat_id, turn_id, and a state store:

from envai import ClientToolRegistry, SQLiteToolState, AsyncClientToolRunner

registry = ClientToolRegistry()
async def echo(args, context):
    context.check_cancelled()
    return {'echo': args['text']}
registry.register('echo', echo,
    {'type': 'object', 'properties': {'text': {'type': 'string'}}, 'required': ['text']},
    timeout=120)

# Explicitly select a persistent database path. Nothing is created implicitly.
state = SQLiteToolState('/application-owned-data/envai-claims.sqlite3')
runner = AsyncClientToolRunner(client, registry, store=state,
    scope='production/account-id', chat_id=chat_id, turn_id=accepted_turn_id,
    max_concurrency=4)

Every request is snapshotted, validated, canonicalized with RFC 8785 and verified with Ed25519 before any handler runs. Integers outside the safe exact range (±(2^53−1)) are normalized to binary64 before both verification and handler execution, matching the server and JavaScript numeric domain. Overflow and non-finite values are rejected. Use strings when exact huge integers matter. JWKS caches have configurable TTL and one refresh on unknown kid/verification failure. Unsupported algorithms and invalid signatures fail closed. Locally registered argument schemas are cloned and cannot retrieve remote references. An explicit $schema supports Draft 3, 4, 6, 7, 2019-09, or 2020-12 through the installed jsonschema validator. An omitted dialect defaults to Draft 2020-12; unknown dialects are rejected. Prefer a modern declared dialect for shared tools; TypeScript supports Draft 6 and newer. Literal const/enum values are preserved as data. Unknown/invalid local tools produce controlled error outputs. Structured results are JSON-serialized exactly once; outputs are capped at 128 KiB. Empty strings become a controlled error because the current API rejects them despite the OpenAPI schema lacking minLength.

ToolStateStore is a pluggable protocol. Its atomic claim must commit before returning true, and claims must not automatically expire. SQLite uses atomic insertion and FULL synchronous commits. The key is (scope, chat_id, call_id); use a stable, non-secret deployment/account scope, and preserve the database. MemoryToolState() is an explicit ephemeral option only. Stores retain hashes and status, never arguments/outputs/credentials. A conflicting payload for the same claim is rejected. Results observed before requests create resolved tombstones. Both built-in stores reject malformed existing records before reading or changing them; corruption is never treated as a missing claim or automatically repaired. A status transition also rejects a missing claim, preventing output delivery if local state disappears during handler execution. This protects against pre-existing local state damage, not malicious access to a database that an attacker can rewrite or delete. Resolve corrupt state explicitly without discarding the deduplication history.

Statuses are running, ambiguous, submitted, and resolved. The runner stores ambiguous before POSTing output, marks submitted only on HTTP success, and marks resolved on the corresponding stream result. It never retries delivery or reexecutes a duplicate call; a later 404/409 does not prove earlier success. Inspect .get(...) and reconcile ambiguous states externally. A crash between side effect and durable completion cannot be made exactly-once by an SDK: handlers must use context.call_id as an idempotency key, transactions, or explicit reconciliation. A signature authenticates the call ID, tool name, and canonical arguments. It does not sign the chat, turn, account, timestamp, or freshness. Only feed the runner trusted authenticated stream events for its configured turn.

Concurrency is bounded with no unbounded handler queue. Saturation returns a controlled error. Handler deadlines use a conservative margin below the registered timeout, since the server timer starts before delivery and no absolute deadline is transmitted. Observed tool result/turn terminal cancels local work. Async runners require native async handlers; sync runners use worker threads and cooperative ToolContext cancellation. Python cannot forcibly terminate arbitrary threads: a timed-out sync handler retains its concurrency slot until it exits, and must check cancellation before every effect. Runner lifecycle is bound to one known turn; it does not infer permission to execute unrelated turns.

Current alpha limitations

  • Replay is only best-effort while the same chat runtime stays resident. The sequence can reset after restart/eviction, live events can be lost, and the API supplies no epoch/cursor-expired signal. Upward gaps trigger one replay attempt, then ReplayGapError; the SDK cannot detect every reset or promise durable replay. Do not silently reuse a checkpoint after a known restart. Reconcile or explicitly start a new observation epoch.
  • Replay/subscribe is not atomic. The first-ping workaround reduces the known attach race; it is not a server-level durability guarantee.
  • A successful steer acknowledgement does not guarantee changed execution. Approval resolution currently returns HTTP 409 and is unavailable as a completed workflow. Neither operation is automatically invoked.
  • Context persistence/rehydration is best-effort and may lose history; the SDK cannot assert durable context restore from a successful acknowledgment.
  • A second tool output is 409 while the turn is active and may be 404 after cleanup. Neither response alone reconciles an ambiguous earlier delivery.

Development and contract provenance

The bundled contract3 HTTP and ATP contracts include strict immutable numeric ATP2 replay schemas. Default clients select contract3 and fail closed against older servers. See migration and Wiki V1. tests/fixtures/contract3-provenance.json records the five current shared contract snapshots. The immutable baseline tests/fixtures/provenance.json separately records two historical byte-exact snapshots from five SDK-authored fixtures. Guards verify the source commit/path, byte count, SHA-256, and Git blob identity. Public guides are SDK documentation rather than copied contract files. Building or reading the SDK does not require access to a separate source repository.

python scripts/generate.py --check
ruff check src scripts tests examples
mypy src
pytest --cov=envai --cov-report=term-missing
python -m build

Regenerate only the typed models and REST primitives with python scripts/generate.py; SSE/tool behavior is handwritten. CI runs Python 3.11–3.13, lint, type checks, contract drift checks, wheel build/import, all mocked HTTP operations, shared ATP/SSE/JCS/Ed25519 vectors and concurrency/replay/error regressions. Combined runtime line/branch coverage is gated at 90%; generated models/primitives are excluded from coverage but independently covered by the operation inventory and response-schema tests. Tests never call Envai production endpoints or require credentials. MIT, Copyright (c) 2026 DigItalyGo.

Wiki V1 and contract3 validation results, database crash/concurrency scope and release limits: validation report.

Metadata

Release files for envai-teams-sdk 1.0.0a3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for envai-teams-sdk 1.0.0a3
File Size Uploaded Provenance
envai_teams_sdk-1.0.0a3.tar.gz 291.2 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for envai-teams-sdk 1.0.0a3
File Interpreter ABI Platform
envai_teams_sdk-1.0.0a3-py3-none-any.whl Python 3 none any Details

Total release size: 502.2 kB

Release files / envai_teams_sdk-1.0.0a3.tar.gz

Download URL envai_teams_sdk-1.0.0a3.tar.gz
Size 291.2 kB
Tags Source
SHA-256 checksum
How to use checksums
5997e4ade75a929a9dc72b7feb7013e6370a8cf0c8d608c77ff41cd3d77eab9a
BLAKE2b-256 checksum
How to use checksums
d86670d22d8b0048a90f9ba6d50de51be70f145672301e7294accd475c8f2b74
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

Release files / envai_teams_sdk-1.0.0a3-py3-none-any.whl

Download URL envai_teams_sdk-1.0.0a3-py3-none-any.whl
Size 211.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9e382bd6f47b605afe89e77ed4f8e5bc3172ed377bed1aba8cddbf19bc129de5
BLAKE2b-256 checksum
How to use checksums
388c1f2d384c284882a126d9e338d54d739f42256354c7242c386f1d8d191ceb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

Security for envai-teams-sdk 1.0.0a3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for envai_teams_sdk-1.0.0a3.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

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