claude as a worker: your app or CI submits a prompt, Claude edits files inside the sandbox, and you collect stdout / artifacts.
Prerequisites
Reuse an Identity provider + network policy from get-started, or create them inline as below.export GRAVIXLAYER_API_KEY="your-gravixlayer-api-key"
export ANTHROPIC_API_KEY="sk-ant-..."
export GRAVIXLAYER_CLAUDE_TEMPLATE="agent-claude"
# Optional if already created:
# export ANTHROPIC_PROVIDER_ID=...
# export CLAUDE_NETWORK_POLICY_ID=...
Permission model
For unattended runs:claude --dangerously-skip-permissions -p "..."
timeout on every run_cmd / runtime exec. The Claude template sets IS_SANDBOX=1 so bypass mode is allowed.
Minimal driver
Prefercommand= + args= so user-supplied prompts cannot break shell quoting.
import os
import uuid
from gravixlayer import GravixLayer
TEMPLATE = os.environ["GRAVIXLAYER_CLAUDE_TEMPLATE"]
ANTHROPIC_API_KEY = os.environ["ANTHROPIC_API_KEY"]
client = GravixLayer()
provider_id = os.environ.get("ANTHROPIC_PROVIDER_ID")
policy_id = os.environ.get("CLAUDE_NETWORK_POLICY_ID")
if not provider_id:
provider = client.identity.providers.create(
name=f"anthropic-{uuid.uuid4().hex[:8]}",
provider_type="api_key",
secrets=[{"key": "ANTHROPIC_API_KEY", "value": ANTHROPIC_API_KEY}],
)
provider_id = provider.id
if not policy_id:
policy = client.network_policies.create(
name=f"claude-egress-{uuid.uuid4().hex[:8]}",
egress_mode="allowlist",
rules=[
{"destination": "api.anthropic.com", "port": 443, "protocol": "tcp"},
],
)
policy_id = policy.id
runtime = client.runtime.create(
template=TEMPLATE,
providers=[provider_id],
network_policy_ids=[policy_id],
env_vars={"ANTHROPIC_API_KEY": ANTHROPIC_API_KEY},
timeout=1800,
)
prompt = (
"Create a hello-world HTTP server in Go under /workspace/hello "
"and print the file tree when done."
)
result = runtime.run_cmd(
command="claude",
args=[
"--dangerously-skip-permissions",
"-p",
prompt,
"--output-format",
"text",
],
working_dir="/workspace",
timeout=900,
)
print(result.stdout)
print("exit_code:", result.exit_code)
tree = runtime.run_cmd(
command="find",
args=["/workspace/hello", "-type", "f"],
timeout=60,
)
print(tree.stdout)
runtime.kill()
# Assumes PROVIDER_ID and POLICY_ID already exist (see get-started)
RT=$(gravixlayer runtime create \
--template "$GRAVIXLAYER_CLAUDE_TEMPLATE" \
--provider "$ANTHROPIC_PROVIDER_ID" \
--network-policy "$CLAUDE_NETWORK_POLICY_ID" \
--env "ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY" \
--timeout 1800 \
--wait \
--output json | jq -r '.runtime_id')
gravixlayer runtime exec "$RT" --timeout 900 --workdir /workspace -- \
claude --dangerously-skip-permissions \
-p 'Create a hello-world HTTP server in Go under /workspace/hello' \
--output-format text
gravixlayer runtime exec "$RT" -- find /workspace/hello -type f
gravixlayer runtime kill "$RT" -y
API="https://api.gravixlayer.ai/v1"
AUTH=(-H "Authorization: Bearer $GRAVIXLAYER_API_KEY" -H "Content-Type: application/json")
RT=$(curl -sS -X POST "$API/agents/runtime" "${AUTH[@]}" \
-d "{
\"template\": \"$GRAVIXLAYER_CLAUDE_TEMPLATE\",
\"cloud\": \"azure\",
\"region\": \"eastus2\",
\"timeout\": 1800,
\"providers\": [\"$ANTHROPIC_PROVIDER_ID\"],
\"network_policy_ids\": [\"$CLAUDE_NETWORK_POLICY_ID\"],
\"env_vars\": {\"ANTHROPIC_API_KEY\": \"$ANTHROPIC_API_KEY\"}
}" | jq -r '.runtime_id')
# Wait for running (same loop as get-started), then:
curl -sS -X POST "$API/agents/runtime/$RT/commands/run" "${AUTH[@]}" \
-d '{
"command": "claude",
"args": [
"--dangerously-skip-permissions",
"-p", "Create a hello-world HTTP server in Go under /workspace/hello",
"--output-format", "text"
],
"working_dir": "/workspace",
"timeout": 900000
}' | jq .
curl -sS -X DELETE "$API/agents/runtime/$RT" \
-H "Authorization: Bearer $GRAVIXLAYER_API_KEY"
Structured JSON + session resume
--output-format json returns machine-readable output including session_id. Keep the same runtime and pass --resume for follow-ups.
import json
import os
from gravixlayer import GravixLayer
client = GravixLayer()
runtime = client.runtime.create(
template=os.environ["GRAVIXLAYER_CLAUDE_TEMPLATE"],
providers=[os.environ["ANTHROPIC_PROVIDER_ID"]],
network_policy_ids=[os.environ["CLAUDE_NETWORK_POLICY_ID"]],
env_vars={"ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"]},
timeout=1800,
)
first = runtime.run_cmd(
command="claude",
args=[
"--dangerously-skip-permissions",
"--output-format",
"json",
"-p",
"Analyze /workspace and propose a one-step refactor plan.",
],
timeout=900,
)
assert first.success, first.stderr
payload = json.loads(first.stdout)
session_id = payload["session_id"]
print("session_id:", session_id)
follow = runtime.run_cmd(
command="claude",
args=[
"--dangerously-skip-permissions",
"--resume",
session_id,
"-p",
"Implement step 1 of that plan.",
],
timeout=900,
)
print(follow.stdout)
print("exit_code:", follow.exit_code)
runtime.kill()
Streaming events (stream-json)
Use Claude’s stream-json format with Gravix Layer’s on_stdout callback (SSE under the hood). The SDK still returns aggregated stdout / exit_code.
import json
import os
from gravixlayer import GravixLayer
client = GravixLayer()
runtime = client.runtime.create(
template=os.environ["GRAVIXLAYER_CLAUDE_TEMPLATE"],
providers=[os.environ["ANTHROPIC_PROVIDER_ID"]],
network_policy_ids=[os.environ["CLAUDE_NETWORK_POLICY_ID"]],
env_vars={"ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"]},
timeout=1800,
)
def on_stdout(chunk: str) -> None:
for line in chunk.splitlines():
if not line.strip():
continue
try:
event = json.loads(line)
except json.JSONDecodeError:
print(line, end="")
continue
kind = event.get("type")
if kind == "assistant":
usage = (event.get("message") or {}).get("usage") or {}
print("assistant tokens:", usage.get("output_tokens"))
elif kind == "result":
print("done:", event.get("subtype"), "ms=", event.get("duration_ms"))
result = runtime.run_cmd(
command="claude",
args=[
"--dangerously-skip-permissions",
"--output-format",
"stream-json",
"-p",
"List the top-level files under /workspace.",
],
timeout=900,
on_stdout=on_stdout,
)
print("exit_code:", result.exit_code)
runtime.kill()
gravixlayer runtime exec "$RT" --stream --timeout 900 -- \
claude --dangerously-skip-permissions \
--output-format stream-json \
-p 'List the top-level files under /workspace'
Patterns
| Pattern | Approach |
|---|---|
| One-shot task | Single run_cmd with -p and --output-format text |
| Pipeline / CI | --output-format json, assert success, download artifacts via runtime.file |
| Multi-turn agent | Same runtime + --resume <session_id> |
| Parallel tasks | Separate runtimes — Long-running agents |