CrowNest
TypeScript SDK

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.

FieldTypeDescription
commandstringShell command to run after archive extraction.
projectId`prj_${string}`Project to create the run in.
templatestringCrowNest template slug. python-node is the launch default.
templateVersionId`tplv_${string}`Specific CrowNest template version.
sandboxId`sbx_${string}`Warm sandbox to reuse.
keepSandboxbooleanKeep the sandbox after the run.
timeoutMsnumberCommand timeout in milliseconds.
metadataRecord<string,string>Small labels for list/evidence.
sourceMetadataRecord<string,string>Caller source labels such as repo or commit.
artifacts{ path: string; name?: string }[]Explicit relative paths to collect after command completion.
idempotencyKeystringRetry 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:

  • create
  • upload_archive
  • create_archive_transfer
  • upload_archive_to_transfer
  • finalize_archive
  • start
  • get
  • list
  • list_events
  • stream_events
  • cancel
  • evidence

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

On this page