Agent patterns
Recipes for stateful execution, artifacts, retries, and cleanup across MCP, CLI, and SDK surfaces.
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
Use Code Runs when you want interpreter state. Variables and imports persist inside a Code Context for later runs in the same Sandbox.
MCP
Call run_code without sandbox_id to lazily create the default Sandbox.
The response includes sandbox_id and, when available, context_id.
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.
get_usage({})CLI
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
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
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
Use Commands for process execution and Artifacts for durable outputs. Keep
working files under /workspace.
MCP
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
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
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
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
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
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
crownest workspace-runs run-archive repo.tgz --template python-node -- pnpm testTypeScript SDK
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
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
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
The CLI exposes an idempotency key on Code Runs.
crownest code run "$SANDBOX" \
--idempotency-key "agent-step-42" \
--code "print('retry-safe')"TypeScript SDK
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
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
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
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
crownest sandboxes extend "$SANDBOX" --ttl-ms 1800000
crownest sandboxes kill "$SANDBOX"TypeScript SDK
await sandbox.extend({ ttlMs: 1_800_000 });
await sandbox.kill();Python SDK
sandbox.extend(ttl_ms=1_800_000)
sandbox.kill()Discover the current surface
The Capability matrix 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.