# Agent patterns (/docs/guides/agent-patterns)



CrowNest is built around a live Sandbox, a Workspace at `/workspace`, and
durable Artifacts for outputs you want to keep. These recipes cover the same
patterns across MCP, CLI, and SDK surfaces.

## Stateful code execution [#stateful-code-execution]

Use Code Runs when you want interpreter state. Variables and imports persist
inside a Code Context for later runs in the same Sandbox.

### MCP [#mcp]

Call `run_code` without `sandbox_id` to lazily create the default Sandbox.
The response includes `sandbox_id` and, when available, `context_id`.

```text
run_code({ "code": "x = 40" })
run_code({ "code": "print(x + 2)" })
```

Use `get_usage` when you need compute, spend, quota, and current MCP
session Sandbox state before continuing.

```text
get_usage({})
```

### CLI [#cli]

```bash title="Terminal"
SANDBOX=$(crownest sandboxes create --template python-node --json | jq -r .data.id)
crownest code run "$SANDBOX" --code "x = 40"
crownest code run "$SANDBOX" --code "print(x + 2)"
```

### TypeScript SDK [#typescript-sdk]

```ts
import { createCrowNestClient } from "@crownest/sdk";

const client = createCrowNestClient();
const sandbox = await client.sandboxes.create({ template: "python-node" });

await sandbox.code.run({ code: "x = 40" });
const result = await sandbox.code.run({ code: "print(x + 2)" });

console.log(result.stdout);
```

### Python SDK [#python-sdk]

```python
from crownest import CrowNest

client = CrowNest()
sandbox = client.sandboxes.create(template="python-node")

sandbox.code.run("x = 40")
result = sandbox.code.run("print(x + 2)")

print(result["stdout"])
```

## Create, run, collect, kill [#create-run-collect-kill]

Use Commands for process execution and Artifacts for durable outputs. Keep
working files under `/workspace`.

### MCP [#mcp-1]

```text
run_command({ "command": "python3 - <<'PY'\nfrom pathlib import Path\nPath('/workspace/report.txt').write_text('ready')\nPY" })
create_artifact({ "source_path": "/workspace/report.txt", "name": "report.txt" })
list_artifacts({})
kill_sandbox({ "sandbox_id": "sbx_..." })
```

### CLI [#cli-1]

```bash title="Terminal"
SANDBOX=$(crownest sandboxes create --template python-node --json | jq -r .data.id)
crownest commands run "$SANDBOX" -- python3 -c "from pathlib import Path; Path('/workspace/report.txt').write_text('ready')"
crownest artifacts create "$SANDBOX" /workspace/report.txt --name report.txt
crownest sandboxes kill "$SANDBOX"
```

### TypeScript SDK [#typescript-sdk-1]

```ts
const sandbox = await client.sandboxes.create({ template: "python-node" });

await sandbox.commands.run(
  "python3 -c \"from pathlib import Path; Path('/workspace/report.txt').write_text('ready')\"",
);

const artifact = await sandbox.artifacts.create({
  name: "report.txt",
  path: "/workspace/report.txt",
});

await sandbox.kill();
```

### Python SDK [#python-sdk-1]

```python
sandbox = client.sandboxes.create(template="python-node")
sandbox.commands.run(
    "python3 -c \"from pathlib import Path; Path('/workspace/report.txt').write_text('ready')\""
)
artifact = sandbox.artifacts.create(path="/workspace/report.txt", name="report.txt")
sandbox.kill()
```

## Workspace Run create, replay, evidence [#workspace-run-create-replay-evidence]

Use Workspace Runs for archive-based repo work that needs durable status,
event replay, cancellation, Artifacts, and an Evidence Bundle. MCP exposes the
primitive lifecycle; the CLI `run-archive` command is a CLI convenience
wrapper for local archives.

### MCP [#mcp-2]

```text
create_workspace_run({ "template": "python-node", "command": "pnpm test" })
create_workspace_run_archive_transfer({ "workspace_run_id": "wsr_...", "sha256": "...", "size_bytes": 12345 })
upload_workspace_run_archive_transfer({ "workspace_run_id": "wsr_...", "upload_id": "upl_...", "content_base64": "..." })
finalize_workspace_run_archive({ "workspace_run_id": "wsr_...", "upload_id": "upl_...", "sha256": "...", "size_bytes": 12345 })
start_workspace_run({ "workspace_run_id": "wsr_..." })
replay_workspace_run_events({ "workspace_run_id": "wsr_...", "after_seq": 0, "limit": 100 })
get_workspace_run_evidence({ "workspace_run_id": "wsr_..." })
```

### CLI [#cli-2]

```bash title="Terminal"
crownest workspace-runs run-archive repo.tgz --template python-node -- pnpm test
```

### TypeScript SDK [#typescript-sdk-2]

```ts
const run = await client.workspaceRuns.create({
  command: "pnpm test",
  template: "python-node",
});

const transfer = await client.workspaceRuns.createArchiveTransfer(run.id, {
  sha256,
  sizeBytes: bytes.byteLength,
});

await client.workspaceRuns.uploadArchiveToTransfer(transfer, { body: bytes });
await client.workspaceRuns.finalizeArchive(run.id, {
  uploadId: transfer.id,
  sha256,
  sizeBytes: bytes.byteLength,
});
await client.workspaceRuns.start(run.id);

const events = await client.workspaceRuns.listEvents(run.id, { limit: 100 });
const evidence = await client.workspaceRuns.evidence(run.id);
```

### Python SDK [#python-sdk-2]

```python
run = client.workspace_runs.create(template="python-node", command="pytest -q")
transfer = client.workspace_runs.create_archive_transfer(
    run["id"],
    sha256=sha256,
    size_bytes=len(bytes_),
)
client.workspace_runs.upload_archive_to_transfer(transfer, body=bytes_)
client.workspace_runs.finalize_archive(
    run["id"],
    upload_id=transfer["id"],
    sha256=sha256,
    size_bytes=len(bytes_),
)
client.workspace_runs.start(run["id"])
events = client.workspace_runs.list_events(run["id"], limit=100)
evidence = client.workspace_runs.evidence(run["id"])
```

## Retry with idempotency keys [#retry-with-idempotency-keys]

Use idempotency keys for mutating API/SDK calls that may be retried after a
network failure. The API stores idempotent replay results for 24 hours.

### CLI [#cli-3]

The CLI exposes an idempotency key on Code Runs.

```bash title="Terminal"
crownest code run "$SANDBOX" \
  --idempotency-key "agent-step-42" \
  --code "print('retry-safe')"
```

### TypeScript SDK [#typescript-sdk-3]

```ts
await client.sandboxes.create({
  idempotencyKey: "agent-sandbox-42",
  template: "python-node",
});

await sandbox.code.run({
  code: "print('retry-safe')",
  idempotencyKey: "agent-code-42",
});
```

### Python SDK [#python-sdk-3]

```python
sandbox = client.sandboxes.create(
    template="python-node",
    idempotency_key="agent-sandbox-42",
)

sandbox.code.run(
    "print('retry-safe')",
    idempotency_key="agent-code-42",
)
```

MCP Workspace Run mutations expose `idempotency_key` for create, direct
archive upload, staged transfer creation/upload/finalize, and start. Other
replay-sensitive mutating MCP steps that do not accept an idempotency key
should use natural idempotency when available or route exact-replay recovery
through the CLI or SDK.

## Cleanup with TTL and kill [#cleanup-with-ttl-and-kill]

Every live Sandbox has a Sandbox TTL. Extending a Sandbox resets its TTL
from the call time; it does not pause, persist, or revive expired
Sandboxes. Kill Sandboxes explicitly when the work is complete.

### MCP [#mcp-3]

```text
set_sandbox_ttl({ "sandbox_id": "sbx_...", "ttl_ms": 1800000 })
kill_sandbox({ "sandbox_id": "sbx_..." })
```

Sandboxes created by the MCP server are best-effort killed when the MCP
stdio process exits, but explicit `kill_sandbox` is still the cleanest
handoff.

### CLI [#cli-4]

```bash title="Terminal"
crownest sandboxes extend "$SANDBOX" --ttl-ms 1800000
crownest sandboxes kill "$SANDBOX"
```

### TypeScript SDK [#typescript-sdk-4]

```ts
await sandbox.extend({ ttlMs: 1_800_000 });
await sandbox.kill();
```

### Python SDK [#python-sdk-4]

```python
sandbox.extend(ttl_ms=1_800_000)
sandbox.kill()
```

## Discover the current surface [#discover-the-current-surface]

The [Capability matrix](/docs/api/capabilities) maps operations to MCP, CLI,
and SDK surfaces. MCP hosts also receive startup instructions, the
`get_agent_context` tool, resource `crownest://agent/context`, and prompts
`crownest_workspace_run` and `crownest_sandbox_session` so they can choose the
smallest surface that fits the task.
