End-to-End Workflow Guide¶
This guide walks through the complete machine lifecycle — from initialization through provisioning to return — across all four ORB interfaces: CLI, Python SDK, REST API, and MCP.
Lifecycle Overview¶
| Stage | Description | Terminal? |
|---|---|---|
| Initialize | Configure ORB and discover infrastructure | — |
| List templates | Browse available compute templates | — |
| Create request | Submit a provisioning request | — |
| Poll status | Wait for request to reach a terminal state | — |
| Extract machines | Read machine IDs from the completed request | — |
| Return machines | Submit a return request | — |
| Poll return | Wait for return to complete | — |
Request Status Reference¶
| Status | Terminal | Description |
|---|---|---|
pending |
No | Request received, not yet processed |
in_progress |
No | Machines are being provisioned |
complete |
Yes | All machines provisioned successfully |
partial |
Yes | Some machines provisioned |
failed |
Yes | Provisioning failed |
cancelled |
Yes | Request was cancelled |
Configuration¶
orb init¶
orb init writes config.json and default_config.json into the ORB config directory. The config directory location depends on how ORB is installed:
| Install type | Config directory |
|---|---|
| uv tool install | ~/.orb/config/ |
| mise-managed Python | ~/.orb/config/ |
User install (pip install --user) |
~/.orb/config/ |
| Standard virtualenv | <venv-parent>/config/ |
System install (/usr or /opt) |
<sys.prefix>/orb/config/ |
| Development (pyproject.toml found) | <project-root>/config/ |
ORB_CONFIG_DIR env var |
value of ORB_CONFIG_DIR |
ORB_ROOT_DIR env var |
$ORB_ROOT_DIR/config/ |
Run orb config show to see the exact path in use.
Interactive wizard (recommended for first-time setup):
Non-interactive (for scripted or CI environments):
# Basic — provider discovers infrastructure automatically
orb init --non-interactive --provider aws
# Reinitialize over an existing config
orb init --force
Non-interactive flags available:
| Flag | Description |
|---|---|
--non-interactive |
Skip the interactive wizard |
--provider <type> |
Provider type (e.g. aws) |
--scheduler <type> |
Scheduler type (default or hostfactory) |
--config-dir <path> |
Custom config directory |
--force |
Overwrite existing config |
AWS-specific non-interactive flags:
--region,--profile,--subnet-ids,--security-group-ids,--fleet-roleare AWS-only. For other providers, use the interactive wizard (orb init) which adapts to the selected provider's requirements.
The resulting config file looks like:
{
"scheduler": {"type": "default"},
"provider": {
"providers": [
{
"name": "aws_myprofile_us-east-1",
"type": "aws",
"enabled": true,
"config": {"profile": "myprofile", "region": "us-east-1"},
"default": true,
"template_defaults": {
"subnet_ids": ["subnet-abc", "subnet-def"],
"security_group_ids": ["sg-123"]
}
}
]
}
}
Inspect and edit config after init:
orb config show # print current config
orb config get provider # get a single key
orb config set scheduler.type default # set a single key
SDK config modes¶
The SDK supports four initialization modes:
from orb import ORBClient as orb
# Mode 1 — default: reads env vars (ORB_CONFIG_FILE, ORB_PROVIDER, ORB_REGION, …)
# or built-in defaults
async with orb() as sdk: ...
# Mode 2 — SDK config dict: tune provider, region, timeout, log_level, etc.
async with orb(config={"provider": "aws", "region": "us-west-2"}) as sdk: ...
# Mode 3 — config file on disk
async with orb(config_path="/etc/orb/config.json") as sdk: ...
# Mode 4 — full app config in memory (Lambda, CI, notebooks — no filesystem)
app_cfg = {
"scheduler": {"type": "default"},
"provider": {
"providers": [
{"name": "default", "type": "aws", "enabled": True,
"config": {"region": "us-east-1"}, "default": True}
]
}
}
async with orb(app_config=app_cfg) as sdk: ...
REST API and MCP¶
Neither the REST API nor the MCP server exposes config endpoints. Configuration must be set via orb init or a config file before starting the server.
Template Management¶
CLI¶
# List all templates (optionally filter by provider API)
orb templates list
orb templates list --provider-api EC2Fleet
# Inspect a single template
orb templates show my-tmpl
# Create from a JSON file
orb templates create --file template.json
orb templates create --file template.json --validate-only # dry-run validation
# Update fields from a JSON file (only keys present in the file are changed)
orb templates update my-tmpl --file changes.json
# Delete (--force skips confirmation prompt)
orb templates delete my-tmpl
orb templates delete my-tmpl --force
# Validate an existing template or a file
orb templates validate my-tmpl
orb templates validate --file template.json
# Generate a starter template scaffold
orb templates generate
orb templates generate --provider-api EC2Fleet
orb templates generate --provider-specific # include provider-specific fields
orb templates generate --generic # minimal provider-agnostic scaffold
# Refresh the template cache
orb templates refresh
SDK¶
All template methods are auto-discovered via CQRS.
async with orb() as sdk:
# List templates
result = await sdk.list_templates(active_only=True)
# -> list of template dicts
# Get a single template
tmpl = await sdk.get_template(template_id="my-tmpl")
# -> template dict
# Create a template (template_id, provider_api, image_id are required)
result = await sdk.create_template(
template_id="my-tmpl",
provider_api="EC2Fleet",
image_id="ami-0abcdef1234567890",
name="My Template",
instance_type="t3.medium",
subnet_ids=["subnet-abc"],
security_group_ids=["sg-123"],
tags={"env": "dev"},
)
# -> {"created": True}
# -> {"created": False, "validation_errors": ["..."]} on failure
# Validate a template
result = await sdk.validate_template(template_id="my-tmpl")
# -> {"valid": True, "validation_errors": []}
# Update fields (only supplied kwargs are changed)
result = await sdk.update_template(
template_id="my-tmpl",
name="Updated Name",
instance_type="t3.large",
)
# -> {"updated": True}
# -> {"updated": False, "validation_errors": ["..."]} on failure
# Delete a template
result = await sdk.delete_template(template_id="my-tmpl")
# -> {"deleted": True}
# Refresh the template cache
await sdk.refresh_templates()
REST API¶
# List templates
curl -s http://localhost:8000/api/v1/templates/ | jq .
# Filter by provider API
curl -s "http://localhost:8000/api/v1/templates/?provider_api=EC2Fleet" | jq .
# Get a single template
curl -s http://localhost:8000/api/v1/templates/my-tmpl | jq .
# Create a template
curl -s -X POST http://localhost:8000/api/v1/templates/ \
-H "Content-Type: application/json" \
-d '{
"template_id": "my-tmpl",
"provider_api": "EC2Fleet",
"image_id": "ami-0abcdef1234567890",
"name": "My Template",
"instance_type": "t3.medium",
"subnet_ids": ["subnet-abc"],
"security_group_ids": ["sg-123"],
"tags": {"env": "dev"}
}'
# Update a template (only supplied fields are changed)
curl -s -X PUT http://localhost:8000/api/v1/templates/my-tmpl \
-H "Content-Type: application/json" \
-d '{"name": "Updated Name", "instance_type": "t3.large"}'
# Delete a template
curl -s -X DELETE http://localhost:8000/api/v1/templates/my-tmpl
# Validate a template config
curl -s -X POST http://localhost:8000/api/v1/templates/validate \
-H "Content-Type: application/json" \
-d '{"template_id": "my-tmpl", "provider_api": "EC2Fleet", "image_id": "ami-0abcdef1234567890"}'
# Refresh the template cache
curl -s -X POST http://localhost:8000/api/v1/templates/refresh
MCP¶
The MCP server exposes read-only template tools only. There are no create, update, or delete tools.
To create, update, or delete templates from an MCP-driven workflow, use the REST API or CLI directly.
CLI¶
Full workflow script¶
#!/usr/bin/env bash
set -euo pipefail
# 1. Initialize ORB (first-time setup — discovers AWS infrastructure and writes config)
orb init
# 2. Generate example templates for your provider
orb templates generate
# 3. List available templates and pick one
orb templates list
TEMPLATE_ID="aws-basic" # replace with a template ID from the list above
COUNT=3
# 4. Request machines
orb machines request "$TEMPLATE_ID" "$COUNT"
# Output includes a request ID, e.g.: req-abc123
REQUEST_ID="req-abc123" # replace with the request ID from the output above
# 5a. Manual poll loop — check status until terminal
while true; do
STATUS=$(orb requests status "$REQUEST_ID" --format json | jq -r '.status')
echo "Status: $STATUS"
case "$STATUS" in
complete|partial|failed|cancelled)
break
;;
esac
sleep 15
done
# 5b. --wait shortcut (equivalent to the loop above)
orb machines request "$TEMPLATE_ID" "$COUNT" --wait --timeout 600
# 6. Extract machine IDs from the completed request
MACHINE_IDS=$(orb requests status "$REQUEST_ID" --format json \
| jq -r '.machines[].machine_id')
echo "Machines: $MACHINE_IDS"
# 7. Return machines when done
# shellcheck disable=SC2086
orb machines return $MACHINE_IDS
# 8. Poll return status
RETURN_ID="ret-xyz789" # replace with the return request ID from step 7 output
while true; do
STATUS=$(orb requests status "$RETURN_ID" --format json | jq -r '.status')
echo "Return status: $STATUS"
case "$STATUS" in
complete|failed|cancelled)
break
;;
esac
sleep 15
done
Python SDK¶
Full lifecycle example¶
import asyncio
from orb import ORBClient as orb
async def full_lifecycle() -> None:
# ORBClient as a context manager handles initialize() and cleanup() automatically.
async with orb() as sdk:
# 1. List available templates
result = await sdk.list_templates(active_only=True)
templates = result if isinstance(result, list) else result.get("templates", [])
print(f"Found {len(templates)} template(s)")
template_id = templates[0].get("template_id") or templates[0].get("id")
# 2. Create a provisioning request
request_result = await sdk.create_request(template_id=template_id, count=3)
request_id = (
request_result.get("created_request_id")
or request_result.get("request_id")
or request_result.get("id")
)
print(f"Request created: {request_id}")
# 3. Wait for the request to reach a terminal status.
# wait_for_request polls every poll_interval seconds until timeout.
final = await sdk.wait_for_request(
request_id,
timeout=600.0,
poll_interval=15.0,
)
print(f"Request status: {final.get('status')}")
# 4. Extract machine IDs from the completed request
machines = final.get("machines", [])
machine_ids = [m.get("machine_id") or m.get("id") for m in machines]
print(f"Machines: {machine_ids}")
# 5. Return machines when done
return_result = await sdk.create_return_request(machine_ids=machine_ids)
return_request_id = (
return_result.get("created_request_id")
or return_result.get("request_id")
or return_result.get("id")
)
print(f"Return request created: {return_request_id}")
# 6. Wait for the return to complete
return_final = await sdk.wait_for_return(
return_request_id,
timeout=300.0,
poll_interval=10.0,
)
print(f"Return status: {return_final.get('status')}")
if __name__ == "__main__":
asyncio.run(full_lifecycle())
wait_for_request / wait_for_return signatures¶
await sdk.wait_for_request(
request_id: str,
*,
timeout: float = 300.0, # seconds before TimeoutError is raised
poll_interval: float = 10.0, # seconds between status checks
) -> dict
await sdk.wait_for_return(
return_request_id: str,
*,
timeout: float = 300.0,
poll_interval: float = 10.0,
) -> dict
Both methods raise TimeoutError if the request does not reach a terminal status within timeout seconds.
REST API¶
Base URL¶
Full workflow¶
# 1. List templates
curl -s http://localhost:8000/api/v1/templates | jq .
# 2. Request machines
curl -s -X POST http://localhost:8000/api/v1/machines/request \
-H "Content-Type: application/json" \
-d '{"template_id": "aws-basic", "count": 3}'
# Response includes request_id, e.g.: "req-abc123"
# 3. Poll request status until terminal
REQUEST_ID="req-abc123"
while true; do
RESPONSE=$(curl -s "http://localhost:8000/api/v1/requests/${REQUEST_ID}")
STATUS=$(echo "$RESPONSE" | jq -r '.status')
echo "Status: $STATUS"
case "$STATUS" in
complete|partial|failed|cancelled)
break
;;
esac
sleep 15
done
# 4. Extract machine IDs
MACHINE_IDS=$(curl -s "http://localhost:8000/api/v1/requests/${REQUEST_ID}" \
| jq -r '.machines[].machine_id')
# 5. Return machines
curl -s -X POST http://localhost:8000/api/v1/machines/return \
-H "Content-Type: application/json" \
-d "{\"machine_ids\": $(echo "$MACHINE_IDS" | jq -R . | jq -s .)}"
# Response includes return request_id, e.g.: "ret-xyz789"
# 6. Poll return status
RETURN_ID="ret-xyz789"
while true; do
STATUS=$(curl -s "http://localhost:8000/api/v1/requests/${RETURN_ID}" \
| jq -r '.status')
echo "Return status: $STATUS"
case "$STATUS" in
complete|failed|cancelled)
break
;;
esac
sleep 10
done
Response envelope¶
All REST responses use this envelope:
MCP¶
The MCP server exposes ORB operations as tools callable by AI assistants. The tool sequence for the full lifecycle is:
1. List templates¶
2. Create a request¶
3. Poll request status¶
Call get_request_status repeatedly until the status is terminal:
Repeat until status is one of: complete, partial, failed, cancelled.
4. Return machines¶
{
"name": "return_machines",
"arguments": {
"machine_ids": ["machine-1", "machine-2", "machine-3"]
}
}
5. Poll return status¶
Repeat until status is complete, failed, or cancelled.
Error Handling¶
CLI¶
Non-zero exit codes indicate failure. Use --format json and check the error field for structured error output. Common issues:
orb initnot run — runorb initbefore any other command- Invalid template ID — run
orb templates listto see available templates - Timeout on
--wait— increase--timeoutor poll manually
SDK¶
from orb.sdk.exceptions import ConfigurationError, MethodExecutionError, ProviderError, SDKError
try:
async with orb() as sdk:
result = await sdk.create_request(template_id="aws-basic", count=3)
except ConfigurationError as e:
# ORB not initialized or config file missing
print(f"Config error: {e}")
except ProviderError as e:
# AWS provider unreachable or misconfigured
print(f"Provider error: {e}")
except MethodExecutionError as e:
# A specific SDK method failed (e.g. invalid template ID)
print(f"Method error: {e.message}")
except TimeoutError as e:
# wait_for_request / wait_for_return exceeded timeout
print(f"Timed out: {e}")
except SDKError as e:
print(f"SDK error: {e}")
REST API¶
| HTTP Status | Meaning |
|---|---|
400 |
Bad request — check request body |
404 |
Resource not found — check IDs |
409 |
Conflict — request already in terminal state |
500 |
Internal server error — check ORB logs |
MCP¶
MCP tool errors are returned in the content field with isError: true. Application-specific error codes:
| Code | Meaning |
|---|---|
1001 |
Unknown tool name |
1002 |
Tool execution failed |
1003 |
Unknown resource URI |
1004 |
Resource access failed |