Python SDK¶
The Python package does three different jobs: it describes workflows and Actions as data, controls a running platform over its HTTP API, and executes your own business logic in a worker. This reference is for integration developers who call Weave from a Python application. It explains which entry point fits which job, how the client signs in and reports errors, and which operations it offers.
New to Weave? Follow the Python SDK tutorial first: it has complete scripts and expected output, from a local simulation to a durable run. Use this page afterward. For other languages, use the HTTP API; the full API reference describes the same contracts.
You need Python 3.12 or later. Calls to a platform also need a running platform, a way to sign in, and grants in a workspace; the sections below say which.
Choose the right Python tool¶
| Your job | Python entry point | Needs a running platform? | Start here |
|---|---|---|---|
| Build a workflow definition | firefly_weave.sdk.builder.WorkflowBuilder |
No; it creates definition data | Build the same workflow in YAML and Python |
| Check a definition | firefly_weave.compiler.api.compile_source |
No; supply an explicit dependency catalog | Compile and simulate locally |
| Describe an HTTP API call as an Action, without code | firefly_weave.sdk.http_actions.author_http_action (new in 0.1.0a7) |
No; publishing the Action does | Embedding: generate a no-code HTTP Action |
| Publish, activate, start, or read runs | firefly_weave.sdk.client.WeaveClient |
Yes: its address, a sign-in, and a workspace | Start with an existing API |
| Reuse the sign-in and workspace of the CLI | firefly_weave.sdk.profiles.ProfileStore with firefly_weave.sdk.sign_in.session_for (new in 0.1.0a7) |
Yes | Reuse your saved platform |
| Manage people and roles | firefly_weave.sdk.members.MembersClient |
Yes, as an administrator | Give people the right access |
| Assign and complete human work | HumanTaskStep and the WeaveClient human-task methods |
Authoring is local; assignment and completion use the API | Human tasks and approvals |
| Send or reply to email | firefly_weave.sdk.email.EmailClient |
Yes, with a scoped mail connection | Email conversations and triggers |
| Run your business code for an Action | firefly_weave.sdk.worker.Worker with WorkerTransport |
Yes; an operator admits the release and authorizes its worker | Build and run a worker |
| Upload or download verified files | firefly_weave.sdk.files.upload_file and download_file |
Yes; scoped file grants | Files, step by step |
| Transfer files inside a leased task | upload_task_file and download_task_file, with WorkerTransport |
Yes; a current task lease | Worker file transfers |
| Author or evaluate reusable decision rules | DecisionTableDefinition, DecisionTableStep, WeaveClient.evaluate_decision |
Author locally; published evaluation uses the API | Decision tables |
| Configure or ask Lumi | WeaveClient.configure_lumi, lumi_status, ask_lumi |
Yes; a configured private gateway and explicit Lumi roles | Lumi assistant |
| Add a reusable connector | A connector package and its registration | Authoring is local; live calls need an installed executor and a connection | Build a custom integration |
"New in 0.1.0a7" marks what alpha6 and earlier packages do not have: saved platforms and the no-code HTTP Action builder. The human-task, email, membership, and run-lifecycle clients are also in alpha6; upgrade the client and the server together.
Python code never becomes a workflow step. WorkflowBuilder produces the
same portable definition data you could write as YAML or JSON. Your own Python
function runs only in a worker handler, selected through an admitted Action.
WeaveClient sends HTTP requests; it never runs that handler inside your
product.
Install in your application's environment¶
Install the SDK where your application runs. The CLI installer
puts the weave command in its own environment, so it does not add imports to
your application.
- The base
firefly-weavewheel includes the compiler, the models, andWorkflowBuilder. - Calls to a platform also need the
clientextra. Server and database dependencies are not needed. - From this source checkout, run
uv sync --locked --no-editable --extra client, then run scripts withuv run --locked --no-editable --extra client python path/to/script.py.uv syncremoves packages that the extras you name do not need, so add every other extra this checkout uses (for example--extra studio) to the same command. - For an application outside the checkout, install the matching release wheel
with its
clientextra in that application's environment.
Start with an existing API¶
WeaveClient needs three things: the platform's API origin, a token
source (a function or object that returns a current access token), and a
scope (the tenant, project, and environment UUIDs of your workspace). Choose
how to provide them:
| Your situation | Use |
|---|---|
You already ran weave auth setup on this computer |
Reuse your saved platform: no token or IDs to copy |
| A CI job or your operator supplies a token | Use a supplied access token |
| Your application signs people in itself | Device login, PKCE and stores |
Reuse your saved platform¶
Since 0.1.0a7, the SDK can use the same saved platform as the CLI,
Studio, and the desktop app: its server, its sign-in, and its workspace. First
connect once with weave auth setup
and choose a workspace. Then run this script in your application's environment:
import asyncio
from firefly_weave.contracts.access import Scope
from firefly_weave.sdk.client import WeaveClient
from firefly_weave.sdk.profiles import ProfileStore
from firefly_weave.sdk.sign_in import session_for
async def main():
# The active saved platform; pass a name to resolve() to pick another one.
profile = ProfileStore().resolve()
if profile.workspace is None:
raise SystemExit("Choose a workspace first: weave auth workspace")
workspace = profile.workspace
scope = Scope(tenant_id=workspace.tenant_id, project_id=workspace.project_id,
environment_id=workspace.environment_id)
# The session reads and renews the saved sign-in in the platform's credential store.
async with WeaveClient(profile.server, session_for(profile), scope) as client:
await client.catalog()
print("Catalog read succeeded on", profile.name)
asyncio.run(main())
Expected: Catalog read succeeded on NAME, where NAME is your saved
platform. The script reads profiles.json from the folder described in
What is saved, and where,
and uses the credential store that platform was saved with. It never prints a
token. When you are signed out, or the sign-in has expired and cannot renew,
the request raises firefly_weave.sdk.auth.AuthError with code
WV-AUTH-REQUIRED: run weave auth login, then retry. With no active
platform, resolve() raises ProfileError with code WV-PROFILE-NONE.
Use a supplied access token¶
Use this in CI, or when your operator supplies a token. Keep WEAVE_BASE_URL,
the three scope IDs, and WEAVE_ACCESS_TOKEN in the same shell, as described in
Use a supplied access token:
import asyncio
import os
from firefly_weave.contracts.access import Scope
from firefly_weave.sdk.client import WeaveClient
scope = Scope.model_validate({
"tenant_id": os.environ["WEAVE_TENANT_ID"],
"project_id": os.environ["WEAVE_PROJECT_ID"],
"environment_id": os.environ["WEAVE_ENVIRONMENT_ID"],
})
def access_token():
# Called once per request; supply a current token from your approved source.
return os.environ["WEAVE_ACCESS_TOKEN"]
async def main():
# The context manager closes the HTTP client after this read-only request.
async with WeaveClient(os.environ["WEAVE_BASE_URL"], access_token, scope) as client:
catalog = await client.catalog()
print("Catalog read succeeded")
asyncio.run(main())
Expected: Catalog read succeeded. This callback rereads the environment but
never renews the token: when it expires, requests fail with HTTP 401 until you
supply a new one. Production hosts plug in their own acquisition and renewal,
as described in client lifecycle.
Publishing, activating, and starting runs need more grants. The lifecycle example lists them. Do not copy local tutorial IDs to another platform.
Compile the tutorial workflow through the API¶
This section continues the manual standalone tutorial and reuses its
private receipt and token file. If you used weave platform setup, follow the
shorter SDK tutorial's local-platform option
instead: it has the whole publish-to-result script. With an existing API, keep
the scope and token source from the previous section.
-
Restore the standalone session. Complete the standalone tutorial and keep its
WEAVE_WORK_DIRandWEAVE_API_URLvariables and a currenthost-token.json. In a new terminal, use its resume steps first. The host principal needscatalog.readandcompilein the project. -
Refresh the token. Run this in terminal 1, with the original identity and runtime configuration loaded:
# Point at the host-facing API address, not the container address used by workers. export WEAVE_API_URL="http://127.0.0.1:${WEAVE_API_PORT:?Restore the standalone session}" # Acquire and verify a new weave-host token; it replaces host-token.json and prints no token. "$WEAVE_PYTHON" "$WEAVE_WORK_DIR/refresh-token.py"The helper from standalone step 3 changes no grants.
-
Save and run the script. Save this as
.local/tutorial/host-client.pyand run it with"$WEAVE_PYTHON" .local/tutorial/host-client.py, so it uses the same installed package as the local API. If you completed the quickstart elsewhere, copy itsecho.workflow.yamlinto.local/tutorial/first:import asyncio import json import os from pathlib import Path from firefly_weave.contracts.access import Scope from firefly_weave.sdk.client import WeaveClient root = Path(os.environ["WEAVE_WORK_DIR"]) receipt = json.loads((root / "first-run.json").read_text()) scope = Scope.model_validate(receipt["scope"]) def access_token(): return json.loads((root / "host-token.json").read_text())["access_token"] async def main(): source = Path(".local/tutorial/echo.workflow.yaml").read_text() async with WeaveClient(os.environ["WEAVE_API_URL"], access_token, scope) as client: catalog = await client.catalog() result = await client.compile(source=source, format="yaml", catalog=catalog, filename="echo.workflow.yaml", strict=True) for diagnostic in result.diagnostics: print(diagnostic.code, diagnostic.path) assert result.ok and result.artifact is not None print(result.artifact.digest) asyncio.run(main())Expected: a 64-character artifact digest. The server compiled the source; nothing was saved, published, or executed. The callback rereads the token file on every request but does not renew an expired token: refresh it as in step 2.
Read downward and carry each returned ID into the next request. Saving a draft is optional; the example below starts at publication and uses a separate idempotency key for each change. Open diagram at full size.
Extend the host to publish, activate, and run¶
Put this block inside the async with above, after a successful compilation.
It changes the platform and needs definition.publish, release.activate,
run.start, and run.read. Add from firefly_weave.contracts.catalog import
ActivationRequest and from firefly_weave.contracts.runtime import
StartRunRequest at the top of the script.
version = await client.publish("workflows", source, "yaml",
idempotency_key="tutorial-echo-publish-1")
# Bind that exact immutable version to the selected environment.
activation = await client.activate(
ActivationRequest(version_id=version.id, artifact_digest=version.digest,
scope=scope),
idempotency_key="tutorial-echo-activate-1",
)
# Each intentional execution gets its own run request and idempotency key.
run = await client.start_run(
StartRunRequest(activation_id=activation.id, input={"message": "Hello, Weave"}),
idempotency_key="tutorial-echo-run-1",
)
# A start receipt is admission, so read the run to observe execution progress.
current = await client.read_run(run.id)
print(current.id, current.state.status, current.state.output)
Expected: the run ID, then succeeded and {'message': 'Hello, Weave'} once
the run has finished; read it again if it has not. The version identifies
immutable source, the activation selects it for this environment, and the run
records one execution. Echo needs no worker or connection.
Reuse a key only to retry the same request. Use a new run key for each new run. If you change the published source, choose a new workflow version and new publication and activation keys: a changed body with an old key conflicts. A timeout does not prove that a change failed; keep its key and read the resources before you decide to retry.
Client lifecycle and errors¶
Use it asynchronously. WeaveClient is asynchronous: open it with
async with and await each operation. Using it as a synchronous context
manager fails with a clear error. On exit, it closes its HTTPX client and any
transport you supplied, and it cannot be reused.
Token sources. The client accepts either kind:
- a synchronous function, called once per request, so your host can rotate
tokens without creating a new client (an
asyncfunction is rejected here); - an object with
async get_access_token(target), which receives the exact API origin.OAuthSessionand the session of a saved platform are such objects.
Scope. Pass a Scope with your tenant, project, and environment. None is
allowed only for operations without a scope, such as
await client.invoke("identity.read"), which returns who you are and the
workspaces you can use.
Network rules.
- The base URL is an exact HTTPS origin; HTTP works only on loopback
(
localhost,127.0.0.1,::1) for local development. - Requests have bounded timeouts and response sizes.
- The client never follows redirects, inherits proxy settings from the environment, accepts compressed responses, or retries on its own. A transport error during a change leaves its outcome unknown.
- It always uses the canonical
/api/v1paths and quoted revisions, validates request and response models exactly, and rejects unsupported wire or artifact versions.
Errors.
| Exception | Meaning | What to do |
|---|---|---|
WeaveError |
The platform answered with an error. It exposes status, code, request_id, diagnostics, and the safe problem |
Read code; the HTTP API failure table lists the common ones |
PreconditionFailed |
A WeaveError for HTTP 412: your revision is stale |
Read the resource again and resend with the current revision |
TransportError |
The request may or may not have reached the platform | For a change, keep the key and check the resource before retrying |
ContractError |
The answer does not match the expected contract or version | Check that client and server versions match |
PreconditionFailed, TransportError, and ContractError are subclasses of
WeaveError, so catch them before it. Exception text never includes provider
or transport bodies. Results that can be
"unavailable" (some runs, tasks, and resources) stay typed instead of being
turned into successful payloads.
Operation families¶
Every method is async and needs the matching capability in the client's
scope. The operation inventory lists each
capability.
| Methods | Purpose |
|---|---|
compile, validate, catalog, schemas, capabilities, language |
Compile and validate; read the catalog, the supported language, limits, and connectors, and the language manifest (language() returns a LanguageManifest) |
list_connector_descriptors, read_connector_descriptor |
Installed connectors and the exact manifest to publish (new in 0.1.0a7) |
save_draft, read_draft, export_draft, delete_draft, list_definitions |
Append-only drafts, their retirement, and catalog discovery |
publish, read_definition, export_definition, retire_definition |
Immutable workflow, Action, and Connector versions |
activate, read_activation, export_activation, retire_activation, list_activations |
Pin versions to an environment |
create_connection, read_connection, test_connection, list_connections |
Immutable integration connections that hold only secret handles, and explicit checks |
start_run, read_run, signal, cancel, retry, list_runs |
Durable runs |
pause_run, resume_run, run_lifecycle, archive_run, restore_run, purge_run |
Run lifecycle management |
history, export_run, replay, run_incidents, list_incidents, resolve_incident |
Bounded evidence and audited recovery |
human_tasks, read_human_task, claim_human_task, release_human_task, complete_human_task, reassign_human_task |
Human task inbox and decisions |
assignment_bindings, put_assignment_binding, put_human_group |
Who may work on which human task |
register_release, read_release, list_releases, register_worker, read_worker, list_workers, revoke_worker, grant_connection |
Trusted worker releases and instances |
drain_worker, resume_worker |
Revision-checked worker controls; existing leases can settle while drained |
invoke("deployment_targets.…"), invoke("deployments.…"), invoke("deployment_observations.…"), invoke("deployment_plans.…"), invoke("deployment_jobs.…"), invoke("deployment_runners.…") |
Typed, environment-scoped Operations contracts; operation names and required authority are in the API inventory |
claim_tasks, heartbeat, complete_task, fail_task, request_credentials |
The typed worker protocol; WorkerTransport remains supported |
create_trigger, read_trigger, list_triggers, disable_trigger |
Immutable signed webhook triggers |
save_schedule, read_schedule, list_schedules, schedule_history, change_schedule |
UTC schedules with revisions and occurrence pages |
create_broker_trigger, read_broker_trigger, list_broker_triggers, disable_broker_trigger, retry_broker_trigger, list_broker_incidents |
Start runs from broker (Kafka) messages |
save_subscription, read_subscription, list_subscriptions, disable_subscription, read_delivery, list_deliveries, retry_delivery, delivery_attempts |
Outgoing event subscriptions and deliveries |
list_source_bindings, read_source_binding, revoke_source_binding |
Connection source bindings |
create_provider_source, read_provider_source, list_provider_sources, disable_provider_source, read_provider_receipt, list_provider_receipts, retry_provider_receipt |
Incoming messaging-platform events |
read_teams_reference, list_teams_references, revoke_teams_reference, reactivate_teams_reference, whatsapp_delivery_state, list_whatsapp_status_facts |
Teams conversation references and WhatsApp delivery status |
read_compatibility, check_compatibility, plan_retention, read_retention_plan, apply_retention |
Compatibility reports and retention plans |
create_debug, inspect_debug, command_debug |
Effect-free debug sessions with virtual time |
create_tenant, create_project, create_environment, read_environment, grant |
Provisioning, separately authorized |
MembersClient(client) and EmailClient(client) wrap a WeaveClient for
people administration and email. invoke(operation_id, ...) is the
registry-checked adapter that the CLI uses; prefer the typed methods in your
code. Paginated methods return a typed Page, except run history, whose
EventPage also carries its high-water mark. Resource IDs and cursors must
belong to the client's scope. Secret leases use the CredentialLease model,
whose ordinary serialization leaves out the resolved value.
Deployment Operations through the SDK¶
Use WeaveClient.invoke with models from
firefly_weave.contracts.deployments. It resolves only registered operations,
validates request and response contracts, and uses the client's existing
authentication and exact environment scope. For example, inside an active
client context:
from uuid import UUID
from firefly_weave.contracts.deployments import ObserveRequest
job = await client.invoke(
"deployment_observations.create",
body=ObserveRequest(target_id=UUID(target_id)),
idempotency_key=request_id,
)
Retain request_id when retrying that same request. Poll deployment_jobs.read
with identifier=job.id; successful observation jobs identify the persisted
snapshot. Build a plan using its observation ID and the desired deployment's
exact revision. Read and review the returned plan before approving its digest
and applying it with a separate retained idempotency key. Updates use
revision= for If-Match; lists accept bounded query filters and cursors.
Approval and apply require explicit grants. The outbound runner uses a separate application identity and local provider configuration. Do not put cloud credentials, command strings, or raw manifests in API bodies. See Operations authority and reconciliation for the checks that still apply after a plan was approved.
Device login, PKCE and stores¶
Use this section when your own application signs people in, instead of receiving a token or reusing a saved platform. Three terms:
- Device sign-in (also called device login) shows a code that the person approves in a browser, possibly on another device.
- PKCE (Proof Key for Code Exchange) binds a browser sign-in to the client that started it; S256 is its SHA-256 challenge method.
- A credential store keeps the resulting tokens between runs of your program.
The CLI sign-in commands use these same building blocks.
Sign in from your application¶
OAuthSession(LoginConfig(...), store) signs in and supplies tokens to
WeaveClient. The connection file fields describe
every LoginConfig field.
import asyncio
from firefly_weave.sdk.auth import LoginConfig, OAuthSession
from firefly_weave.sdk.client import WeaveClient
from firefly_weave.sdk.credentials import NativeCredentialStore
# Non-secret sign-in settings from your operator; "account" names this credential record.
login = LoginConfig(
provider_id="corp-oidc",
issuer="https://login.example.com/realms/weave",
client_id="weave-cli",
target="https://weave.example.com",
account="my-app",
)
session = OAuthSession(login, NativeCredentialStore())
async def main():
if session.status()["state"] != "signed_in":
# Opens the browser on this computer; use flow="device" to show a code instead.
await session.login(flow="browser")
# No scope is needed to ask who you are; the session renews the token when needed.
async with WeaveClient(login.target, session, None) as client:
identity = await client.invoke("identity.read")
print("Signed in as principal", identity.principal_id)
asyncio.run(main())
Expected: the browser opens your identity provider's sign-in page, then
Signed in as principal and a UUID. Replace every LoginConfig value with
your operator's; these are illustrative, not a preset for a named provider.
What the session does.
- It needs no server settings, database, or Weave authorization of its own. Discovery checks the exact issuer, and every provider endpoint must be on the issuer's origin or an explicitly trusted origin.
- The credential record is bound to the provider, issuer, client ID, API
origin, and
account.accountis a local storage name, not a claim about who signed in. - It sends access tokens only. ID tokens are never used to call the API.
Flows. login(flow=...) accepts:
| Flow | Behavior |
|---|---|
"auto" (default) |
Device sign-in when the provider advertises it, otherwise browser sign-in |
"browser" or "pkce" |
Browser sign-in with PKCE S256 and a callback on a random loopback port |
"device" |
Device authorization; it never falls back to another flow after a denial |
The CLI chooses differently for saved platforms: its auto prefers the browser
when one can open. Browser sign-in binds its loopback listener before opening
the browser, checks the exact callback host, path, state, and the issuer when
the provider advertises it, rejects repeated or ambiguous callbacks, and closes
everything on cancellation or timeout. Pass browser= or instructions= to
show the address or code in your own interface. For browser sign-in,
prompt="login" or prompt="select_account" makes the provider ask for an
account again. Every
wait is bounded by login_timeout; denial and expiry save no credentials. The
result also holds identity: an unverified display hint from the ID token, for
showing and linking only.
status() reads only local data and never contacts the provider. It returns
state (signed_in, expired, signed_out, or in_progress),
authenticated, refresh_available, expires_at, and
reauthentication_required. logout(revoke=True) removes the local record and
reports remote_revocation as confirmed, unconfirmed, or not_requested.
Choose a credential store¶
NativeCredentialStore() (default choice). It uses the operating system's
store: macOS Keychain, Windows Credential Locker, Linux Secret Service, or
KWallet. It refuses an unknown or insecure backend, and it never falls back to
a plaintext file silently. The Windows and Linux stores are covered by unit
tests only.
FileCredentialStore(absolute_path), for macOS and Linux only. Windows
refuses it. Its folder must already exist, belong to you, and have mode 0700.
The record and its lock files must be regular files you own, with mode 0600 and
a single link. Symbolic links are refused anywhere in the path. Each
replacement writes an exclusive temporary file, syncs it, renames it
atomically, and syncs the folder.
How sign-in, renewal, and sign-out stay consistent¶
Several processes, or several tasks in one process, can share one credential record. The session uses a lock per record and a fresh generation UUID for each change, so that a late answer never overwrites newer state:
- Sign-in first saves a token-free
authenticatingrecord. Tokens are saved only if that exact attempt is still pending. A sign-out, a deletion, or a newer sign-in makes a late completion save nothing. A new sign-in replaces the previous credentials, so cancelling it can leave you signed out. - Renewal holds the same lock. Before sending the refresh token, it saves a
token-free
refreshingfence; only memory keeps the old refresh token. If the fence cannot be saved, no request is sent. The new tokens must replace the record before an access token is returned. After a crash, an ambiguous answer, or a failed save, the fence remains and the next process asks for a new sign-in; old tokens are never restored. A renewal that provably never reached the provider keeps the previous sign-in and fails withWV-AUTH-OFFLINE. A wider scope or a missing rotation is rejected. - Sign-out saves a token-free signed-out record and removes the local record under the lock. A failed removal is an error. Revocation cannot shorten an access token that was already issued, or undo its effects.
On POSIX, cooperating processes lock through a shared lock file that is never
replaced or deleted while in use. On Windows, the native store adds a
per-record in-process lock before the per-user named mutex, because the mutex
alone lets one thread enter twice. Both stages share one 60-second deadline.
"Durable" means the store acknowledged an atomic write; guarantees against
power loss depend on your operating system. Tokens and verifiers never appear
in repr, CLI JSON, lock files, or errors.
Provider note. The bundled local environment uses Keycloak, whose stale-refresh check compares whole-second timestamps. This is an observation about that local provider, not a requirement to deploy Keycloak: the client fence stops cooperating processes from reusing an old record regardless of the provider. A rotated token alone does not prove stronger replay protection on the provider side. See identity and secrets for compatible OIDC settings.
Next steps¶
- Run the complete flow from Python in the SDK tutorial.
- Execute your own code for an Action with a worker, and look up its contract in the worker protocol.
- Generate workflows or no-code HTTP Actions from your product with embedding.
- Check any operation's request and response in the full API reference.