Firedrill hosted API Python SDK
This package is the Python client for Firedrill Cloud. It complements the local framework; it does not run customer agents or replace their test runner.
python -m pip install firedrill-cloud
Start with the workflow that matches what you are building:
- Connect to an isolated hosted world when your agent needs synthetic Tools and deterministic state.
- Run a drill with your own agent when you need assertions, evidence, and a final verdict.
- Manage source and inspect live state when you are building or operating a hosted project.
Firedrill and AsyncFiredrill map the hosted control API one-to-one. connect()
and connect_async() add owned-session lifecycle and context-manager cleanup.
tail_evidence() and tail_evidence_async() follow a world's durable evidence
journal, ignore page-boundary duplicates, and fail if a sequence is missing.
The default control API is https://api.firedrill.run; use base_url= only when
targeting a different environment.
Explicit source and live control
HostedControl(client, project_id) and AsyncHostedControl(client, project_id)
add the latest public source and inspection routes without constructing another
HTTP client. They retain the supplied client's token supplier, custom transport,
headers and request options. These handwritten adapters live outside generated
source and return JSON dictionaries with the public camelCase field names.
from uuid import uuid4
from firedrill_cloud import Firedrill, HostedControl
client = Firedrill(token="YOUR_CONTROL_CREDENTIAL")
control = HostedControl(client, "prj_...")
entries = [control.tool_library_entry(name) for name in
("tool-mailbox", "tool-work-queue")]
request_key = str(uuid4()) # Persist this and the exact request before dispatch.
created = control.create_source_draft(
name="My selected Tools", idempotency_key=request_key,
tools=[{"libraryEntryId": entry["libraryEntryId"],
"expectedPackageArchiveDigest": entry["packageArchiveDigest"],
"initialState": "empty"} for entry in entries],
)
draft = created["draft"]
publication = control.publish_source_draft(
draft["sourceDraftId"], expected_source_digest=draft["sourceDigest"],
idempotency_key=request_key + "-publish",
)
attempt = control.build_attempt(publication["attempt"]["buildAttemptId"])
The explicit starter selection opts into the listed synthetic rows; empty
does not. Publication queues a build, not Tool approval or a running environment.
Follow the operation and diagnostics, approve exact Tool artifacts, then use
the existing environment/session API. Source mutation retries use the same key
and exact request; the helper never retries a mutation under a new key.
tool_library, source_drafts and export_local_reports return one bounded
page, with nextCursor when another page exists. source_draft reads exact
revision metadata; download_source verifies the original gzip's digest/size
(maximum 8 MiB) without extracting, installing or executing it. Deletion needs
the exact draft confirmation, version and key; retained build history blocks it.
run_activity(run_id, from_sequence=1, limit=50) reads provisional exact-run
activity, not sealed or signed evidence. Reset/expiry can make it unavailable.
export_local_reports(export_id, cursor=..., limit=50) pages original uploaded
archives separately from hosted bundles; use organization=True explicitly for
organization scope. Their provenance remains user-produced and unattested.
All corresponding AsyncHostedControl methods are awaited and use the same
supplied AsyncFiredrill client, including its asynchronous token supplier.
from firedrill_cloud import Firedrill, tail_evidence
client = Firedrill(token="fdc_...")
for entry in tail_evidence(
client,
project_id="prj_...",
session_id="ses_...",
max_entries=10,
):
print(entry.sequence, entry.kind)
Generated source lives in src/firedrill_cloud/generated/, carries its source commit and OpenAPI digest, and must not be edited by hand.
Run a drill with your own agent
An ordinary session, including one configured with a drill, does not itself run your agent or produce a completed drill Result. Create a run from a ready drill-configured session, or attach to an existing run:
For the full create/attach-and-complete workflow, the supplied client needs the
exact project's drill.run, run.read, session.read, and project.read
permissions. project.read is needed to observe the accepted completion operation
through sealing. Session creation and run cancellation require their separate
permissions; the helper does not broaden the credential.
import os
import httpx
from firedrill_cloud import Firedrill, run_hosted_drill
with httpx.Client() as transport:
client = Firedrill(base_url=os.environ["FIREDRILL_API_URL"],
token=os.environ["FIREDRILL_CREDENTIAL"], httpx_client=transport)
run = run_hosted_drill(
client, project_id=project_id, hosted_run_id=hosted_run_id,
# Use session_id instead of hosted_run_id to create a run.
wait_timeout_seconds=1800,
on_checkpoint=checkpoint_store.save,
run_interaction=lambda context: my_agent_runner.run(
task=context.interaction.task,
world=context.binding,
cancel_event=context.cancel_event,
deadline_monotonic=context.deadline_monotonic,
),
)
print(run.hosted_run_id, run.state) # Inspect Results for the verdict.
my_agent_runner and checkpoint_store are your own implementations. The runner
returns HostedTargetResult or its public JSON object, for example
{"schemaVersion": 1, "status": "completed", "output": {"answer": "actual agent output"}, "attachments": []}.
It receives world-only access scoped to that interaction, never your control
credential. The helper uses exactly your supplied client for orchestration and
never runs a model or Tool implementation. Do not fabricate an agent result to
mark a drill complete. File uploads are explicit through callback attach or
enabled capture methods described below; local paths in TargetResult are rejected.
Explicit files, logs, screenshots, and video
from firedrill_cloud import HostedCaptureFileInput, run_hosted_drill
def run_interaction(context):
result = my_agent_runner.run(context.interaction.task, context.binding)
context.attach(HostedCaptureFileInput("artifacts/output.json", "application/json"))
context.capture.log("Finished the caller-owned interaction")
context.capture.screenshot(HostedCaptureFileInput("artifacts/screen.png", "image/png"))
return result
run = run_hosted_drill(
client, project_id=project_id, hosted_run_id=hosted_run_id,
root=".", capture={"logs": "always", "screenshots": "retain-on-failure"},
run_interaction=run_interaction, on_checkpoint=checkpoint_store.save,
)
attach retains an explicitly selected file. Optional capture.file,
capture.screenshot, capture.video, and capture.log follow files,
screenshots, video, and logs policies: off by default, always, or
retain-on-failure. The server applies retention after the drill verdict. Use
capture.video(HostedCaptureFileInput("artifacts/recording.webm", "video/webm"))
with video enabled for an existing recording. No browser or recording is created
automatically, and global console output is not intercepted.
HostedCaptureDriver accepts screenshot, start_video, stop_video, and
dispose callbacks. Register with context.capture.register_driver(driver) for
the sync helper or await context.capture.register_driver_async(driver) for
the async helper. File callbacks return HostedCaptureFileInput; start_video
requires stop_video. Hooks receive a cancellation event and monotonic deadline
(default 5 seconds; driver_timeout_seconds maximum 60). Synchronous Python hooks
must cooperate: they cannot safely be forcibly stopped. Async hooks are awaited
with a timeout. Optional capture errors are recorded separately from drill checks.
Paths must be relative to the explicit root, nonempty regular files without
symlinks or additional hard links. Limits are 64 MiB/file, 120 MiB/run, 32 files;
explicit files accept a bare MIME type (up to 200 characters), including PDF,
CSV and ZIP. Logs require plain text, screenshots PNG/JPEG/WebP, and video WebM.
Unknown or active file types may download as opaque attachments, not previews. Explicit
logs are limited to 16 KiB/message and 1 MiB/4096 messages per interaction. Bytes
are copied verbatim; redact secrets before capture. redaction={"status": "applied_by_caller","note":"..."} is caller-provided metadata, not a server
redaction guarantee. Original files are never changed or removed.
The helper uploads private immutable snapshots through your exact configured
client and authenticated API origin, refusing byte-transfer redirects. Hosted
storage is Google Cloud Storage; configured local testing uses the filesystem
adapter. An upload checkpoint preserves the snapshot and exact keys so recovery
does not rerun the agent; retain its staging directory. A complete checkpoint
uses verified server attachment IDs, not local paths. Recovery also preserves
the capture root and policies. Caller-produced media is attached to the managed
run without claiming Firedrill independently observed your application. Uploading
a complete local report remains separate and explicitly unattested.
The high-level helper supplies upload headers automatically. If using the generated raw method after admission, pass exact admitted bytes (not JSON or base64), and retain the same upload key across retries:
client.run_attachments_upload_content(
project_id, hosted_run_id, attachment_id, request=content_bytes,
request_options={"additional_headers": {"Idempotency-Key": upload_key}, "max_retries": 0},
)
# A claim-scoped CI credential uses its separate endpoint:
ci_client.ci_attachments_upload_content(
ci_suite_id, ci_case_id, ci_claim_id, attachment_id, request=content_bytes,
request_options={"additional_headers": {"Idempotency-Key": upload_key}, "max_retries": 0},
)
The raw method sends application/octet-stream; storage retains the admitted
descriptor's media type. The required header is supplied through request options,
not a dedicated argument. Missing headers are rejected. Uploading alone does not
complete byte verification or the interaction. Async clients use the same
arguments with await.
run_hosted_drill_async accepts AsyncFiredrill, an async run_interaction, and
an optional async on_checkpoint. Both drivers share one orchestration plan:
they wait for each new interaction, submit the actual caller result, and wait
through final sealing. Attaching to a terminal run never invokes the agent.
Waits default to 30 minutes, bounded to 24 hours; polling defaults to one second.
Sync callbacks must honor their cancellation event and monotonic deadline;
Python cannot safely forcibly stop an arbitrary synchronous runner. Async
callbacks receive deadline cancellation and must cooperate with it.
Persist every checkpoint before its callback returns. After
HostedRunRecoveryError, call the helper with project_id, resume=error.checkpoint
and your callbacks. A saved complete checkpoint replays the exact TargetResult
and key, not the agent. A running checkpoint has an unknown caller outcome and
is deliberately not replayed; recover the actual result from your runner, then
submit it at the saved run/interaction/revision. Async cancellation retains
checkpoint on CancelledError and is not swallowed. Checkpoints contain no
world credential, but can contain sensitive agent output; store them privately.
Python checkpoints use Python request field names and should be resumed through
the Python helper, not passed to another SDK's checkpoint API.
For a manual terminal workflow, the separately installed public CLI extension
provides firedrill cloud run create, interaction, connect --format env,
complete --result FILE, and get. Use the exact interaction/revision and retry
command printed by the CLI; your runner still owns all agent execution.
Optional warm worlds
HostedProject(client, project_id).create_session(environment_id=..., seed=..., idempotency_key=..., warm_pool="prepare") prepares one short-lived exact world
without issuing access. warm_pool="prefer" checks out an exact prepared setup
or creates cold. AsyncHostedProject.create_session has the same async surface.
Omitting the option preserves ordinary cold creation. Keep build, seed,
scenario/drill and receiver selection identical for a matching checkout.
Prepared sessions remain warming until checkout; a warmPool.state of ready
does not authorize an agent connection. Inspect their returned expiry and metadata
(session.model_dump(by_alias=True)["warmPool"]). The server bounds lifetime and
capacity and rechecks authority at checkout. Owned connect/connect_async and
HostedProject.start accept only warm_pool="prefer", since they return usable
world access; preparing alone uses create_session.
Owned-session quickstart
Your existing runner owns the agent and the HTTP client. The helper creates one
exact hosted session, waits for its operation and ready state, and issues the
selected actor's short-lived binding. Omit scenario_id and drill_id to serve
the build baseline; supplying both is an error. No agent is run automatically.
import os
import httpx
from firedrill_cloud import Firedrill, connect
with httpx.Client() as http:
client = Firedrill(token=os.environ["FIREDRILL_CREDENTIAL"], httpx_client=http)
with connect(
client,
project_id="prj_...",
environment_id="env_...",
actor_id="operator", # an actor declared in your selected build
seed="42",
# scenario_id="busy", # optional named setup
# build_hash="sha256:...", # optional exact immutable build
operation_timeout_seconds=120,
) as world:
binding = world.binding
# Pass binding.world_http_url or binding.world_mcp_url and its credential
# to your agent's existing test configuration. Never pass client/token.
# Invoke your agent through your own runner here.
world.reset() # initial setup restored; use world.binding again afterward
The async equivalent has the same options and methods:
import os
import httpx
from firedrill_cloud import AsyncFiredrill, connect_async
async def test_world():
async with httpx.AsyncClient() as http:
client = AsyncFiredrill(token=os.environ["FIREDRILL_CREDENTIAL"], httpx_client=http)
async with await connect_async(
client, project_id="prj_...", environment_id="env_...",
actor_id="operator", seed="42",
) as world:
receipt = await world.advance_to(1_000_000, max_events=100)
# Inspect receipt.status: interrupted/failed is not completed.
assert receipt.status == "completed"
Context exit destroys the exact owned session on success, exceptions, and
cancellation. connect() without with (or connect_async() without async with) transfers cleanup responsibility to you: call world.destroy() or
await world.destroy(). The helper never closes the supplied client, installs
signal handlers, changes your runner, or cancels a server operation implicitly.
Stop or join agent work before leaving the context; retaining an old binding
does not retain an active world.
Control and recovery
| Handle method | Result |
|---|---|
reset(packages=...) or reset(hosted_snapshot_id=...) |
Restore selected state and rotate the binding; omit both for a full reset |
extend(ttl_ms) |
Return the server's session expiry; does not renew access credentials |
advance_to(target_us, max_events=...) |
Return the canonical durable time receipt, including partial/interrupted outcomes |
set_fault(package_id, fault_id, active) |
Return the canonical durable fault-control receipt |
destroy() |
Await destruction; repeated calls after success are no-ops |
resume_pending() |
Resolve the exact captured uncertain mutation and return its original result |
Every mutation optionally accepts idempotency_key and a threading.Event as
cancel_event. The helper generates a key when omitted. Mutations through one
handle are serialized. Cancellation of async tasks remains CancelledError;
synchronous cancellation uses cancel_event or the caller's ordinary
KeyboardInterrupt. Local cancellation does not mean the server stopped.
On an uncertain network error, timeout, cancellation, malformed receipt, or
selection mismatch, world.pending_mutation retains the exact request, key and
known operation ID. It contains no credentials. Other mutations and new binding
reads are blocked until that attempt is resolved. Call resume_pending() to
poll the known operation, or replay the original request with the same key if
its acknowledgement was lost. Never create a new key to retry uncertain work.
An initial canonical API rejection or terminal failed/cancelled operation is a
known outcome and releases that guard; later authorization failures cannot
erase an already uncertain attempt.
try:
world.extend(3_600_000)
except Exception:
if world.pending_mutation is not None:
print(world.pending_mutation.operation_id) # safe recovery identifier
world.resume_pending() # exact original key, input, lease, and version
else:
raise
Context exit first resolves a pending mutation, then attempts destruction under
a fresh cleanup deadline. It never overwrites a pending intent with a different
lease. Cleanup failure is raised on an otherwise successful exit; when your
code already raised, that primary exception is preserved and its
cleanup_error attribute, also available as world.cleanup_error, reports the
cleanup failure. Inspect it and recover explicitly; expiry is the final safety
net, not a guarantee that cleanup succeeded. Repeated async cancellation is
shielded while bounded cleanup finishes.
Acquisition failures expose error.pending_creation, including the original
key and request. If creation was acknowledged, cleanup is best effort by
default (cleanup_on_failure=False explicitly disables it). If the create
response was lost, no resource ID is known: the helper never lists sessions or
guesses what to destroy. Retry connect() with the original options and
idempotency_key=error.pending_creation.idempotency_key to recover that exact
server-side intent. Persist recovery details if your runner must survive
process termination; handles and pending attempts are in-memory only.
FiredrillLifecycleError.kind identifies SDK orchestration failures, and
.operation retains an available canonical operation/error/receipt. Generated
HTTP errors remain ApiError with their server envelope; transport exceptions
remain HTTPX exceptions. Recovery attributes are additive. Neither exception
text nor session readiness is an agent-test verdict.
Deadlines cover acquisition and each mutation, including polling (default 120 seconds, polling every 0.5 seconds). Every HTTP request disables generated automatic retries and receives the remaining timeout. Async requests are also bounded by the lifecycle clock. Sync cancellation is observed between requests and during polling; an in-flight sync request relies on HTTPX's phase timeouts. A custom synchronous transport that ignores timeouts, or a continuously streaming response, cannot be forcibly preempted. Keep the standard bounded control API/transport contract; abrupt process termination cannot run cleanup.
Start synthetic tools without a drill
sessions_create() accepts the build baseline (no selector), a named
scenario_id, or a drill_id. The selectors are mutually exclusive. Starting a
session serves the synthetic environment; it does not run or host your agent.
created = client.sessions_create(
"prj_...",
"env_...",
idempotency_key="tools-baseline-001",
seed="42",
# scenario_id="busy", # optional; omission uses the build baseline
)
This returns a session and an asynchronous operation, not a ready binding.
Poll operations_get(project_id, operation_id) with a bounded deadline; only
after it succeeds and sessions_get() returns ready, call
sessions_issue_access(..., actor_id=...) and pass that short-lived binding to
your agent. Failed operations carry their canonical error envelope. The same
flow is available with await on AsyncFiredrill.
Use sessions_reset() to restore the selected initial setup or an explicit
snapshot, then obtain a new binding: old credentials are fenced by the changed
lease generation. Destroy the exact session with sessions_destroy() in your
cleanup path. Mutations require stable idempotency keys; after a timeout, retry
the same key and arguments or inspect the known operation, never assume the
remote work stopped. These generated methods do not automatically provision,
poll, reset or clean up on behalf of the caller; use the handwritten lifecycle
helpers above when you want owned-session orchestration.
sessions_extend(project_id, session_id, idempotency_key=..., expected_lease_generation=session.lease_generation, expected_version=session.version, ttl_ms=3_600_000) requests a deadline one hour
from server admission time. The session must be ready and unexpired, and the
new deadline must be later than its current one and within your allowance.
The response contains the session and an already-succeeded operation. Retry the
same key and arguments after interruption: a replay does not extend time again.
Existing access credentials and drill timeouts keep their original expiry.
AsyncFiredrill exposes the same operation with await.
Saved browser tests and run evidence
These groups support AsyncFiredrill too (await, or async for for streams).
Use project-authorized control credentials, never world bindings. Retain each
mutation's exact key, body and expected versions before sending; retry uncertain
work unchanged. Example keys need replacing for new intents, and persisting for
cross-process recovery; generated methods retain no pending intent or cleanup.
Targets require public HTTPS on port 443. Keep credentials out of definitions,
URLs/tasks/messages. Agent input, runtime parameters (including resolved
parameterSecretRefs), fill actions and URL values suppress requested captures
unless the request explicitly sets captureConsent="include-sensitive-content"
(capture_consent in Python). Consent permits recording potentially sensitive
content; it is not a redaction guarantee. Screenshot masks also apply to live
frames, but do not redact video, traces, network traffic or values shown elsewhere.
from pathlib import Path
from firedrill_cloud.generated import CreateBrowserRunRequestTestId
from firedrill_cloud.generated.browser_tests import CreateBrowserTestRequestDefinition
project_id, browser = "prj_...", client.browser_tests
request_options = {"max_retries": 0, "timeout": 30}
# Your canonical browser-test JSON, using synthetic test data.
definition = CreateBrowserTestRequestDefinition.model_validate_json(Path("smoke.browser.json").read_text())
saved = browser.browser_tests_create(
project_id, idempotency_key="browser-save-001", definition=definition,
request_options=request_options,
).test
run_request = CreateBrowserRunRequestTestId(
test_id=saved.test_id, expected_test_version=saved.version, mode="replay", timeout_ms=120_000,
)
run = browser.browser_runs_create(
project_id, idempotency_key="browser-run-001", request=run_request,
request_options=request_options,
).run
run_id = run.run_id # retain for recovery
events = browser.browser_runs_list_events(project_id, run_id, after=0, limit=100)
for event in events.items:
print(event.sequence, event.type, event.message)
after = events.items[-1].sequence if events.items else 0
run = browser.browser_runs_get(project_id, run_id)
print(run.state, run.result)
if run.artifact is not None:
with Path(f"{run_id}.tar.gz").open("xb") as output:
for chunk in browser.browser_runs_download_artifact(project_id, run_id):
output.write(chunk)
Poll with a bounded deadline; retain the last event sequence across empty pages.
queued/running are active; other states terminal. completed is not a passing
assertion verdict, and browser results never verify world state. Downloads are
integrity-checked bundles. browser_tests_list() uses opaque next_cursor→after.
For follow-up, create a separate mode="agent", interactive=True run with a
saved task and authoring.run permission. Use that active run's ID here:
from firedrill_cloud.generated import BrowserRunMessageRequest_Message, BrowserRunMessageRequest_Finish
interactive_run_id = "brun_..." # exact active interactive run
browser.browser_runs_send_message(
project_id, interactive_run_id, idempotency_key="browser-follow-up-001",
request=BrowserRunMessageRequest_Message(message="Check the confirmation panel."),
request_options=request_options,
)
browser.browser_runs_send_message(
project_id, interactive_run_id, idempotency_key="browser-finish-001",
request=BrowserRunMessageRequest_Finish(), request_options=request_options,
)
# Alternatively, cancel this exact run, then poll its terminal state:
# browser.browser_runs_cancel(project_id, interactive_run_id, confirm=interactive_run_id,
# idempotency_key="browser-cancel-001", request_options=request_options)
Acceptance is not completion; finish closes input for independent assertions.
browser_runs_rerun(project_id, run_id, confirm=run_id, idempotency_key=...) pins
original inputs, excluding prior messages. A new rerun gets a new key; retries do not.
High-level hosted projects
HostedProject and AsyncHostedProject reuse the exact configured generated
client, including credentials, base URL, and transport. They never open a second
client or upload local files.
from firedrill_cloud import Firedrill, HostedProject, HostedHistoryFilters
client = Firedrill(base_url=api_url, token=control_credential)
project = HostedProject(client, project_id)
tools = project.tools(build_hash) # Contract metadata; no baseline records.
binding = project.connect(session_id=session_id, actor_id=actor_id)
page = project.history(HostedHistoryFilters(outcome="failed"), limit=20)
result = project.results(hosted_run_id)
connect_session(client, ...) and its async counterpart attach to an existing
ready session without creating/resetting/destroying it. Only give their returned
actor-scoped world binding to your external runner, never the control credential.
After WorldAccessUnresolvedError, retry with its exact idempotency_key and
original input. project.start(environment_id=..., actor_id=..., seed=...)
instead creates an owned lifecycle handle; use its context manager for cleanup.
Omit scenario/drill selectors for baseline. Selecting a drill setup does not run it.
History filters operate across all retained server history. Preserve them when
following cursors; timestamps use inclusive created_from_ms and exclusive
created_before_ms. Results preserve canonical verdict, runner, and orchestration
outcomes rather than treating admitted work as completed.
Compare retained runs
The same configured client supplies both summary comparison and one bounded page of original recorded details:
project = HostedProject(client, project_id)
summary = project.compare(
baseline_hosted_run_id=baseline_id, candidate_hosted_run_id=candidate_id,
)
page = project.compare_details(
baseline_hosted_run_id=baseline_id, candidate_hosted_run_id=candidate_id,
kind="assertions", limit=10,
)
AsyncHostedProject exposes the same methods with await. Lower-level clients
provide runs_compare and runs_compare_details. Kinds are assertions,
state_changes, and operations; page size defaults to 10 and is capped at 25.
Pass next_cursor as cursor with the same project, pair and kind to continue;
an expired cursor needs a fresh first-page request. Helpers never fetch every
page automatically or create a second credential context.
Matching summary measures do not guarantee identical recorded values. Missing entries do not prove deletion; oversized values are explicitly omitted, not partially displayed. Comparisons describe recorded differences without reevaluating the agent or declaring an improvement.
Prepare source for Python callers
This Python package does not provide the TypeScript prepareHostedDrill or
createHostedCliBridge convenience helpers. To use test-local setup, explicitly
publish a derived build with the shared CLI:
firedrill cloud publish --project "$PROJECT_ID" --root . \
--idempotency-key derived-review-001 --drill review-case --setup ./setup.json
Use your source's actual drill ID and canonical data-only setup JSON. Review and
explicitly approve any new Tool artifacts, then pass the returned exact build
hash to Python session creation (build_hash) or run_hosted_drills. Publication
does not automatically approve Tools or promote an environment. Your Python
agent can use the issued HTTP/MCP world binding directly; the caller-owned CLI
bridge is a separate TypeScript helper, not a Python API or agent source rewrite.
Run multiple hosted drills
run_hosted_drills and run_hosted_drills_async use your configured client and
existing caller-owned agent. They create ordinary sessions/runs, not a managed
suite resource, and never approve Tools or promote an environment.
from firedrill_cloud import run_hosted_drills
results = run_hosted_drills(
client, project_id=project_id, environment_id=environment_id,
build_hash=build_hash, drills=["review-message", "reject-write"],
trials=2, retries=1, concurrency=2, seed="42",
root=".", capture={"logs": "always"},
on_checkpoint=save_checkpoint_atomically,
run_interaction=run_my_existing_agent,
)
print(results["observed"])
Select suite="suite-id" instead of drills to use declared drill IDs/tags,
or omit both for every drill in the exact build. Explicit options override suite
defaults; otherwise trial count comes from the suite or drill. The selected build
must already be approved for the environment. Each attempt creates a fresh
session and destroys that owned session after its terminal result; all ordinary
run/report IDs remain in Results/history. Required project scopes are
build.read, project.read, session.create, session.read, session.destroy, drill.run
and run.read.
Retries occur only after known nonpassing terminal outcomes. Mixed retry
outcomes yield an inconclusive logical trial. Returned observed counts are
raw observations, not confidence estimates or signed suite verdicts. Within
each drill, seeds increment from the selected unsigned 64-bit seed modulo 2^64;
retries retain that trial's seed. Every attempt links its own evidence bundle.
Persist checkpoint callbacks atomically in private storage; callback saves are
serialized. HostedDrillsRecoveryError.checkpoint retains exact mutation keys,
staged uploads and pending session/run identities. Resume the same selection
with resume=checkpoint. An uncertain running callback is never replayed;
reconcile its real outcome through single-run recovery first. Terminal batch
resume does not invoke the agent. Python checkpoints are language-specific and
must not be passed to the TypeScript helper.
Sync callbacks can run concurrently on worker threads and must cooperate with their cancellation event/deadline; they cannot safely be forcibly killed. Async callbacks and checkpoint writers must be awaitable. Use the async helper inside an asyncio loop. The default wait limit is 30 minutes (maximum 24 hours). Planning is bounded to 1,000 logical trials, 10,000 possible attempts and concurrency 64; project quotas still apply. Your own runner owns file watching and scheduling. To shard jobs, pass disjoint explicit drill lists with separate checkpoints; intentional repeat runs use new batches and explicit build identities. No source upload, compilation, approval or background watch starts implicitly.
Local verification
Install with python -m pip install -e '.[dev]' and run python -m pytest.
The full repository test gate includes generated-client contracts, evidence
tailing, and sync/async lifecycle through a real loopback HTTP server. Browser,
CI and source-proposal contracts also exercise real HTTP, typed SSE, binary
downloads, exact-key retries and canonical failures. These fixtures script
control responses; they prove client serialization, parsing, polling, identity
checks, cleanup and recovery, not world-engine fidelity, customer-agent behavior,
live hosted authorization, or deployed availability. The same tests can run from
outside the checkout against a freshly installed wheel using
python -m pytest --import-mode=importlib /absolute/path/to/this/repository/tests.
Export scenario seed source
export_session_scenario(client, project_id, session_id, scenario_id="captured-state", expected_lease_generation=lease, include_sensitive_data=True) and
export_session_scenario_async(...) return canonical scenario JSON under scenario,
its digest and exact session/build/lease identity. Optional packages=[...] selects
Tools. The caller must explicitly acknowledge raw unredacted data and review it before
saving or sharing. These helpers do not write files or mutate/publish a world. Source
contains Tool records and baseline deletions, not actors, clock, faults, pending work
or history; unselected Tools inherit the destination baseline. Stale/unauthorized
reads and exports over 50,000 actions or 1 MiB fail without truncation.
list_hosted_snapshots(client, project_id, session_id=..., limit=..., cursor=...)
returns one bounded metadata page; get_hosted_snapshot(client, project_id, hosted_snapshot_id) resolves one checkpoint. Both have _async equivalents.
artifactAvailability: "not_checked" is explicit: metadata discovery does not
verify stored bytes, and restore/fork performs that validation. No storage paths
or credentials are included.
Apache-2.0 · Copyright Reload Tech Inc.
Release files for firedrill-cloud 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| firedrill_cloud-0.1.2.tar.gz | 791.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| firedrill_cloud-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 4.1 MB
Release files / firedrill_cloud-0.1.2.tar.gz
| Download URL | firedrill_cloud-0.1.2.tar.gz |
|---|---|
| Size | 791.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e5e9bb7591039b00fcead762033e80013f42dd6cc6417ef3485e7fd9d9197181
|
|
BLAKE2b-256 checksum How to use checksums |
59c941199378341899aa1d71004f515fbec6819ea3d482b155a0278fdf227263
|
| 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 Sep 21, 2026.
Transparency logRelease files / firedrill_cloud-0.1.2-py3-none-any.whl
| Download URL | firedrill_cloud-0.1.2-py3-none-any.whl |
|---|---|
| Size | 3.3 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d85aeecf9fb7d54f249704f798ad6770674af84a4ec8efb7d1cc4edf909b6650
|
|
BLAKE2b-256 checksum How to use checksums |
6fee0d714d03a306ad6fb88e9315036dce3954f9c62ef2051ea307f2080e5987
|
| 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 Sep 21, 2026.
Transparency log