Workspace Runs
Use the TypeScript or Python SDK to upload an archive, run a command, replay events, stream events, and read evidence.
client.workspaceRuns in TypeScript and client.workspace_runs in Python
expose CrowNest Workspace Runs. Use them when you already have a .tar.gz or
.tgz archive and want CrowNest to execute a command inside the python-node
template, then keep a durable Evidence Bundle for later inspection.
The SDKs handle one-shot archive runs, create, archive upload, staged archive transfer, start, bounded event replay, event streaming, cancel, list, and evidence reads. Treat the event stream as live progress and the Evidence Bundle as the output another agent or CI job can trust after the sandbox is gone. Build the archive yourself or use a tool such as Crabbox for dirty checkout sync.
Run an archive
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import { createCrowNestClient } from "@crownest/sdk";
const client = createCrowNestClient();
const bytes = await readFile("repo.tgz");
const sha256 = createHash("sha256").update(bytes).digest("hex");
const run = await client.workspaceRuns.runArchive({
archive: {
body: bytes,
headers: { "content-length": String(bytes.byteLength) },
sha256,
sizeBytes: bytes.byteLength,
},
template: "python-node",
command: "pnpm test",
artifacts: [{ path: "test-results.xml", name: "test-results" }],
metadata: { suite: "unit" },
timeoutMs: 120_000,
});
for await (const event of client.workspaceRuns.streamEvents(run.id)) {
if (event.type === "stdout") process.stdout.write(event.data);
if (event.type === "stderr") process.stderr.write(event.data);
if (event.type === "error") throw new Error(event.message);
if (event.type === "terminal") {
process.exitCode = event.workspaceRun.exitCode ?? 0;
break;
}
}
const evidence = await client.workspaceRuns.evidence(run.id);
console.log(evidence.status, evidence.exitCode, evidence.artifactIds);uploadArchiveToTransfer authenticates transfer URLs on the configured
CrowNest API origin and strips the CrowNest bearer token for external upload
targets.
Archive size
uploadArchive is the small direct path capped at 8 MiB. Use createArchiveTransfer,
uploadArchiveToTransfer, and finalizeArchive for repo archives up to
transfer.maxSizeBytes.
Methods
runArchive
runArchive(input: {
archive: { body: BodyInit; headers?: HeadersInit; sha256: string; sizeBytes: number };
command: string;
// plus create fields such as template, projectId, timeoutMs, metadata, artifacts
}): Promise<WorkspaceRun>Creates the run, creates a staged archive transfer, uploads the archive body, finalizes the archive, and starts execution. It returns the started run.
create
create(input: CreateWorkspaceRunInput): Promise<WorkspaceRun>Creates a run record in awaiting_archive.
| Field | Type | Description |
|---|---|---|
command | string | Shell command to run after archive extraction. |
projectId | `prj_${string}` | Project to create the run in. |
template | string | CrowNest template slug. python-node is the launch default. |
templateVersionId | `tplv_${string}` | Specific CrowNest template version. |
sandboxId | `sbx_${string}` | Warm sandbox to reuse. |
keepSandbox | boolean | Keep the sandbox after the run. |
timeoutMs | number | Command timeout in milliseconds. |
metadata | Record<string,string> | Small labels for list/evidence. |
sourceMetadata | Record<string,string> | Caller source labels such as repo or commit. |
artifacts | { path: string; name?: string }[] | Explicit relative paths to collect after command completion. |
idempotencyKey | string | Retry key for create. |
Artifact collection requires the API key to include artifact:create and
file:read.
uploadArchive
uploadArchive(
workspaceRunId: `wsr_${string}`,
input: { bytes: Uint8Array; sha256: string; sizeBytes: number; idempotencyKey?: string },
): Promise<{ archive: WorkspaceRunArchive; workspaceRun: WorkspaceRun }>Uploads a small archive directly through the CrowNest API.
createArchiveTransfer
createArchiveTransfer(
workspaceRunId: `wsr_${string}`,
input: { sha256: string; sizeBytes: number; idempotencyKey?: string },
): Promise<WorkspaceRunArchiveTransfer>Creates a staged upload target with uploadUrl, method, headers,
expiresAt, and maxSizeBytes. Production targets may upload directly to
storage.
uploadArchiveToTransfer
uploadArchiveToTransfer(
transfer: WorkspaceRunArchiveTransfer,
input: { body: BodyInit; headers?: HeadersInit },
): Promise<void>Uploads bytes or a stream to the staged transfer target. External upload URLs are sent without CrowNest API authentication.
finalizeArchive
finalizeArchive(
workspaceRunId: `wsr_${string}`,
input: { uploadId: `upl_${string}`; sha256: string; sizeBytes: number; idempotencyKey?: string },
): Promise<{ archive: WorkspaceRunArchive; workspaceRun: WorkspaceRun }>Verifies and attaches the staged upload to the run.
start
start(
workspaceRunId: `wsr_${string}`,
input?: { idempotencyKey?: string },
): Promise<WorkspaceRun>Starts extraction and command execution.
streamEvents
streamEvents(
workspaceRunId: `wsr_${string}`,
input?: { afterSeq?: number; reconnect?: boolean },
): AsyncIterable<WorkspaceRunStreamEvent>Streams status, archive_progress, stdout, stderr,
artifact_collected, artifact_error, heartbeat, terminal, and
error events. Use afterSeq to resume after the last sequence number
you processed.
listEvents
listEvents(
workspaceRunId: `wsr_${string}`,
input?: { afterSeq?: number; limit?: number },
): Promise<ListWorkspaceRunEventsResponse>Returns a bounded replay window without opening an SSE stream. Use it from polling agents and MCP hosts that need request/response progress checks.
get, list, cancel, evidence
get(workspaceRunId: `wsr_${string}`): Promise<WorkspaceRun>
list(input?: {
limit?: number;
metadata?: Record<string, string>;
projectId?: `prj_${string}`;
status?: WorkspaceRunStatus;
}): Promise<ListWorkspaceRunsResponse>
cancel(workspaceRunId: `wsr_${string}`): Promise<WorkspaceRun>
evidence(workspaceRunId: `wsr_${string}`): Promise<WorkspaceRunEvidenceBundle>evidence returns durable run proof after the run is terminal and the Evidence
Bundle has been persisted. The bundle is the stable handoff object: status,
exit code, archive checksum, artifact IDs, artifact collection errors, timing,
metadata, source metadata, and failure details.
Python SDK
The Python SDK exposes the same lifecycle through client.workspace_runs and
await client.workspace_runs on the async client.
from crownest import CrowNest
client = CrowNest()
run = client.workspace_runs.create(
template="python-node",
command="pytest -q",
timeout_ms=120_000,
)
transfer = client.workspace_runs.create_archive_transfer(
run["id"],
sha256=sha256,
size_bytes=len(archive_bytes),
)
client.workspace_runs.upload_archive_to_transfer(
transfer,
body=archive_bytes,
)
client.workspace_runs.finalize_archive(
run["id"],
upload_id=transfer["id"],
sha256=sha256,
size_bytes=len(archive_bytes),
)
client.workspace_runs.start(run["id"])
for event in client.workspace_runs.stream_events(run["id"]):
if event["type"] == "stdout":
print(event["data"], end="")
if event["type"] == "terminal":
break
events = client.workspace_runs.list_events(run["id"], limit=100)
evidence = client.workspace_runs.evidence(run["id"])Python method names follow snake_case:
createupload_archivecreate_archive_transferupload_archive_to_transferfinalize_archivestartgetlistlist_eventsstream_eventscancelevidence
Dashboard inspection
The signed-in dashboard can inspect SDK-created Workspace Runs at
/workspace-runs. It lists runs, links to a detail page, displays Evidence
Bundle fields after terminal completion, and offers owner/admin cancellation
for active runs.
Next steps
- API Workspace Runs — REST endpoints and event shapes.
- CLI Workspace Runs — terminal commands.
- Errors —
CrowNestApiErrorand retry guidance.