# Workspace Runs (/docs/sdk/workspace-runs)



`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 [#run-an-archive]

```ts
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.

<Callout type="info" title="Archive size">
  `uploadArchive` is the small direct path capped at 8 MiB. Use `createArchiveTransfer`,
  `uploadArchiveToTransfer`, and `finalizeArchive` for repo archives up to
  `transfer.maxSizeBytes`.
</Callout>

## Methods [#methods]

### runArchive [#runarchive]

```ts
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]

```ts
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]

```ts
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]

```ts
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]

```ts
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]

```ts
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]

```ts
start(
  workspaceRunId: `wsr_${string}`,
  input?: { idempotencyKey?: string },
): Promise<WorkspaceRun>
```

Starts extraction and command execution.

### streamEvents [#streamevents]

```ts
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]

```ts
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-list-cancel-evidence]

```ts
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 [#python-sdk]

The Python SDK exposes the same lifecycle through `client.workspace_runs` and
`await client.workspace_runs` on the async client.

```python
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 [#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 [#next-steps]

* [API Workspace Runs](/docs/api/workspace-runs) — REST endpoints and event
  shapes.
* [CLI Workspace Runs](/docs/cli/workspace-runs) — terminal commands.
* [Errors](/docs/sdk/errors) — `CrowNestApiError` and retry guidance.
