Skip to main content
Deploy agents as always-on services that run continuously in the cloud. Services are ideal for Discord bots, webhook listeners, background workers, and any long-running process that needs to stay alive 24/7.

Quick Start

# Deploy a service (secrets auto-resolved from agent's required_secrets)
orch service deploy acme/discord-bot

# Check status
orch service list

# Update env vars on a running service
orch service env set <service> KEY=VALUE

# View logs
orch service logs <service-id>

# Restart a service
orch service restart <service-id>

# Delete a service
orch service delete <service-id>

Deploying a Service

orch service deploy <org/agent[@version]> \
  [--name <service-name>] \
  [--min-instances <n>] \
  [--max-instances <n>] \
  [--env KEY=VALUE] \
  [--secret <NAME>] \
  [--command <cmd>] \
  [--arg <value>] \
  [--workspace <slug>] \
  [--json]
FlagDescriptionDefault
--name <name>Service nameAgent name
--min-instances <n>Minimum running instances1
--max-instances <n>Maximum running instances1
--env KEY=VALUEEnvironment variable (repeatable)
--secret <NAME>Extra workspace secret (repeatable). Agent required_secrets are auto-included.
--command <cmd>Override entrypoint commandAuto-detected
--arg <value>Command argument (repeatable)
--workspace <slug>Workspace slugCurrent workspace
--jsonOutput as JSON
Only tool and agent types (those with runtime.command or loop config) with a published code bundle are eligible for service deployment. prompt types (direct LLM) cannot be deployed as services.

Example

# Agent declares required_secrets: ["DISCORD_BOT_TOKEN", "DATABASE_URL"]
# Those are auto-resolved — just add extras if needed
orch service deploy acme/discord-bot \
  --name "prod-discord-bot" \
  --env LOG_LEVEL=info
On success, the CLI returns the service ID, name, state, and a logs command:
✓ Service deployed successfully

  ID:       svc_abc123
  Name:     prod-discord-bot
  Agent:    acme/discord-bot
  State:    provisioning

View logs: orch service logs svc_abc123

How Deployment Works

  1. You publish an agent with runtime.command or loop config (orch publish)
  2. You run orch service deploy org/agent
  3. The platform provisions infrastructure with your configured instance count
  4. A service runner container downloads your agent bundle, installs dependencies (pip or npm), detects the entrypoint, and starts the process
  5. Your service gets a public HTTPS URL (visible in orch service info)
  6. A health endpoint at /health returns 200 when the service is ready
  7. If the process crashes, the platform automatically restarts it
  8. The agent’s required_secrets (plus any --secret extras) are resolved from the workspace vault and injected as environment variables

Managing Services

List Services

# List all services
orch service list

# Filter by state
orch service list --status running

# JSON output (for scripting)
orch service list --json

# Return only specific fields (implies --json)
orch service list --fields service_name,current_state,health_status

# Paginate
orch service list --limit 10 --offset 0
Output shows: Name, Agent, State (color-coded), Health, Restarts (with failure count), and Deployed timestamp. Services that have been auto-paused due to crash loops display a CRASH-LOOP badge.

Service Info

orch service info <service-id>
Shows detailed information including:
  • Service ID, name, agent, and version
  • Current state and health status
  • Restart count and consecutive failure count
  • Instance configuration (min/max)
  • Public service URL
  • Deploy and last restart timestamps
  • Last error message (if any)
  • Alert webhook URL (if configured)
  • Recent events timeline

View Logs

The easiest way to view logs for an always-on service is through orch logs:
# View run history + live service logs together
orch logs my-agent

# View only the live service logs (skip run history)
orch logs my-agent --live
When you filter by agent name, orch logs automatically detects if the agent has an always-on service and shows its live logs alongside the run history. For advanced options (time filtering, higher limits), use orch service logs directly:
# Fetch recent logs (default: 100 lines)
orch service logs <service-id>

# Fetch more lines
orch service logs <service-id> --limit 500

# Fetch logs since a specific time
orch service logs <service-id> --since "2026-02-10T00:00:00Z"
FlagDescriptionDefault
--limit <n>Number of log lines100 (max 500)
--since <timestamp>ISO 8601 timestamp filter
--workspace <slug>Workspace slugCurrent workspace
--jsonOutput as JSON
Logs include timestamps and severity levels. Sensitive values (API keys, tokens, passwords) are automatically redacted.

Restart a Service

orch service restart <service-id>
Restarts the service by deploying a fresh instance. The platform automatically refreshes the signed bundle URL before restarting, which prevents issues with expired download links.

Delete a Service

orch service delete <service-id>
Permanently removes the service and its infrastructure.

HTTP Ingress (Receiving Webhooks)

Every always-on service gets a public HTTPS URL automatically. Use orch service info to find yours:
orch service info <service-id>
# → URL: https://orch-xxxx-my-agent.services.orchagent.io

How It Works

The platform runs an HTTP gateway on port 8080 that:
  • Handles GET /health internally (platform health checks)
  • Proxies all other HTTP requests to your app on port 3000
Your app should bind to port 3000 (or set the ORCHAGENT_HTTP_PORT env var for a different port). The PORT env var is also set to this value for framework compatibility (Express, Uvicorn, etc.).

Example — Telegram Webhook Receiver

from fastapi import FastAPI, Request

app = FastAPI()

@app.post("/webhook/telegram")
async def telegram_webhook(request: Request):
    data = await request.json()
    # Handle Telegram update...
    return {"ok": True}

# Start with: uvicorn app:app --host 0.0.0.0 --port 3000
After deploying, register the webhook URL with Telegram:
# Get your service URL
orch service info <service-id>

# Register with Telegram
curl "https://api.telegram.org/bot<TOKEN>/setWebhook?url=<service-url>/webhook/telegram"

Services That Don’t Need HTTP Ingress

Services that only make outbound connections (Discord bots using WebSocket, pollers, cron-triggered workers) don’t need to bind to any port — the gateway handles health checks automatically.
Your code must not bind to port 8080 — the platform health server uses it. Bind to port 3000 (or your configured ORCHAGENT_HTTP_PORT).

Workspace Secrets

Secrets are automatically resolved from the agent’s required_secrets field. If your agent declares what it needs in orchagent.json, you don’t need any --secret flags at deploy time:
// orchagent.json
{
  "name": "my-bot",
  "required_secrets": ["DISCORD_BOT_TOKEN", "DATABASE_URL"]
}
# Secrets are auto-resolved from the agent — no --secret flags needed
orch service deploy acme/my-bot
At deploy time, the platform:
  1. Reads required_secrets from the agent
  2. Validates that all secrets exist in the workspace vault
  3. Injects them as environment variables into the container
If any declared secrets are missing from the vault, the deploy fails with an actionable error telling you which secrets to add.

The --secret escape hatch

Use --secret to inject additional secrets not declared in required_secrets (e.g., operational tokens, debug credentials):
orch service deploy acme/my-bot --secret MONITORING_TOKEN
The --secret flags are merged with required_secrets — you get both. On version updates and restarts, the platform re-merges to pick up any new required_secrets from the latest version.
Add secrets to your workspace vault in the dashboard under Settings → Secrets, or via orch secrets set SECRET_NAME <value>.

Environment Variables

Pass non-sensitive configuration using the --env flag:
orch service deploy acme/my-worker \
  --env LOG_LEVEL=debug \
  --env REGION=us-east-1 \
  --env BATCH_SIZE=50
Environment variables matching sensitive patterns (secret, token, password, api_key, credential, private_key) are rejected when passed via --env. Use --secret instead to reference workspace secrets securely.

Updating Environment & Secrets

Update environment variables and secrets on a running service without redeploying. Changes trigger an automatic restart to apply the new values.

Environment Variables

# Set or update variables (merges with existing)
orch service env set <service> KEY=VALUE [KEY2=VALUE2 ...]

# Remove variables
orch service env unset <service> KEY [KEY2 ...]

# List current variables
orch service env list <service>
Example:
# Switch from staging to production config
orch service env set my-bot DEPLOY_ENV=production LOG_LEVEL=warn

# Remove a debug variable
orch service env unset my-bot DEBUG_MODE

# Check current values
orch service env list my-bot

Secrets

# Attach workspace secrets (injected as env vars at runtime)
orch service secret add <service> SECRET_NAME [SECRET_NAME2 ...]

# Detach secrets
orch service secret remove <service> SECRET_NAME [SECRET_NAME2 ...]
Secret names must match existing workspace secrets. The secret values are never exposed — only the name is stored on the service.

API

You can also update env/secrets via the PATCH endpoint:
curl -X PATCH https://api.orchagent.io/workspaces/{workspace_id}/services/{service_id} \
  -H "Authorization: Bearer $ORCHAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "env": {"DEPLOY_ENV": "production", "LOG_LEVEL": "warn"},
    "secret_names": ["DISCORD_BOT_TOKEN", "DATABASE_URL"]
  }'
FieldTypeDescription
envobjectFull replacement of all env vars. Pass {} to clear all.
secret_namesstring[]Full replacement of attached secrets. Pass [] to detach all.
The API uses full replacement semantics — whatever you send replaces the entire set. The CLI commands handle merge/unset logic for you. If the service is running, changes trigger an automatic restart (2-5 seconds of downtime). If the service is paused, changes are saved and apply on the next resume.

Crash Loop Detection & Alerts

The platform monitors your service for repeated restart failures and automatically intervenes to prevent runaway restarts.

How It Works

  1. Each time a service restarts and fails again, the consecutive_restart_failures counter increments
  2. When the counter reaches the halfway point of your threshold, a proactive warning alert fires
  3. When the counter reaches the max_restart_failures threshold, the service is auto-paused and a crash loop alert fires
  4. Auto-paused services show a CRASH-LOOP badge in orch service list and orch service info

Configuring Alerts

Set an alert webhook URL and customize the failure threshold via the API:
curl -X PATCH https://api.orchagent.io/workspaces/{workspace_id}/services/{service_id} \
  -H "Authorization: Bearer $ORCHAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "alert_webhook_url": "https://hooks.slack.com/services/T00/B00/xxx",
    "max_restart_failures": 3
  }'
FieldDescriptionDefaultRange
alert_webhook_urlHTTPS URL to receive alert payloadsMust be HTTPS
max_restart_failuresConsecutive failures before auto-pause5150
Alerts fire at two points: a proactive warning at the halfway mark (e.g., 3 out of 5 failures) and a critical alert when the threshold is reached and the service is auto-paused.

Alert Payload

{
  "event": "service.crash_loop",
  "service_id": "...",
  "workspace_id": "...",
  "service_name": "...",
  "restart_count": 10,
  "consecutive_restart_failures": 5,
  "threshold": 5,
  "auto_paused": true,
  "last_error": "connection timeout"
}

Recovering from a Crash Loop

  1. Check the logs: orch service logs <service-id>
  2. Check the events timeline: orch service info <service-id>
  3. Fix the underlying issue (code bug, missing secret, dependency failure)
  4. Restart the service: orch service restart <service-id>
Restarting a service resets the consecutive failure counter.

Entrypoint Detection

When you deploy a service without a --command flag, the runner automatically detects your entrypoint using the following priority:
  1. --command flag — explicit command override
  2. run_command in orchagent.json — define a run_command field in your manifest
  3. Auto-scan — the runner checks for common filenames in order:
Python (checked first):
FileCommand
main.pypython main.py
app.pypython app.py
bot.pypython bot.py
server.pypython server.py
index.pypython index.py
Node.js (checked second):
FileCommand
index.jsnode index.js
main.jsnode main.js
app.jsnode app.js
bot.jsnode bot.js
server.jsnode server.js
If your entrypoint doesn’t match any of these names, use the --command flag or add a run_command field to your orchagent.json manifest:
{
  "name": "my-agent",
  "type": "tool",
  "runtime": {
    "command": "python src/start.py"
  }
}

Permissions

OperationRequired Role
List servicesMember
Get service infoMember
View logsMember
Deploy serviceOwner
Restart serviceOwner
Update config (alerts)Owner
Update env/secretsOwner
Delete serviceOwner

Examples

Discord Bot

# Publish the agent (orchagent.json has required_secrets: ["DISCORD_BOT_TOKEN"])
cd my-discord-bot
orch publish

# Deploy — DISCORD_BOT_TOKEN is auto-resolved from the vault
orch service deploy acme/my-discord-bot \
  --env BOT_PREFIX="!"

Webhook Listener (Telegram, Stripe, etc.)

# Deploy — secrets auto-resolved from vault
orch service deploy acme/webhook-processor

# Find your public URL
orch service info <service-id>
# → URL: https://orch-xxxx-webhook-processor.services.orchagent.io

# Register the URL with your webhook provider
curl "https://api.telegram.org/bot<TOKEN>/setWebhook?url=<service-url>/webhook"
Your HTTP server should bind to port 3000. See HTTP Ingress for details.

Background Worker

# Agent declares required_secrets: ["REDIS_URL"]
orch service deploy acme/queue-worker \
  --command "python worker.py" \
  --env QUEUE_NAME=tasks \
  --env CONCURRENCY=4

Custom Entrypoint with Arguments

orch service deploy acme/my-agent \
  --command "python" \
  --arg "src/main.py" \
  --arg "--verbose" \
  --arg "--port=3000"

Auto-Update on Publish

When you publish a new version of an agent (orch publish), any running services in the same workspace with auto_update enabled are automatically updated to the new version.
Auto-update is workspace-scoped. If you publish from your personal workspace but your service runs in a team workspace, the service won’t be updated. You must publish from the same workspace where the service is deployed.
# Switch to the workspace where your service runs
orch workspace use my-team

# Now publish — services in "my-team" will auto-update
orch publish
If auto-update didn’t work, check:
  1. Are you in the right workspace? Run orch workspace list and orch service list --workspace <slug> to confirm
  2. Is auto_update enabled? Services deployed with --no-auto-update won’t be updated
  3. Does the new version have a code bundle? Agents without runtime.command or a code bundle won’t trigger service updates
To manually update a service to the latest version:
orch service update <service-id>
orch service restart does not update the version — it only restarts the existing code. Use orch service update to pull a new published version.

Troubleshooting

Service stuck in provisioning?
  • Check the events timeline: orch service info <service-id>
  • Verify your agent has a runtime.command or loop config and a published code bundle (prompt type agents cannot be deployed as services)
  • Check the logs for dependency installation failures: orch service logs <service-id>
Service keeps restarting?
  • Check the logs for crash output: orch service logs <service-id>
  • Look at the last error: orch service info <service-id>
  • Ensure all required_secrets from the agent’s orchagent.json exist in your workspace vault
  • Verify your entrypoint process doesn’t exit immediately
  • Your code must not bind to port 8080 — the platform health server uses it. Bind to port 3000 instead.
Service URL not resolving?
  • New services may take up to 2 minutes for DNS to propagate
  • Try a public DNS resolver to confirm: nslookup <hostname> 1.1.1.1
  • If your local machine cached a “not found” response, flush your DNS cache (sudo dscacheutil -flushcache on macOS) or wait ~30 minutes
Webhooks not reaching your app?
  • Confirm your app binds to port 3000 (not 8080): --host 0.0.0.0 --port 3000
  • Check that the webhook provider is using the full URL including the path (e.g. https://<service-url>/webhook/telegram)
  • Test locally: curl https://<service-url>/your-endpoint — you should see your app’s response, not a 404
CRASH-LOOP badge showing?
  • The service has been auto-paused after exceeding the failure threshold
  • Fix the root cause, then restart: orch service restart <service-id>
  • Check the events timeline for the failure pattern
“No entrypoint found” error?
  • Add a main.py or index.js to your project root
  • Or set a run_command in your orchagent.json
  • Or use --command when deploying
Sensitive env var rejected?
  • Variables with names matching secret, token, password, api_key, credential, or private_key must use --secret instead of --env
  • Store the value as a workspace secret first, then reference it by name
Bundle URL expired after a long-running service?
  • Use orch service restart <service-id> — this automatically refreshes the signed bundle URL
Service not updating after orch publish?
  • Auto-update is workspace-scoped. If you published from a different workspace than where the service runs, it won’t auto-update
  • Switch to the correct workspace first: orch workspace use <slug>, then orch publish
  • Or manually update: orch service update <service-id>
  • Note: orch service restart does NOT update the version — use orch service update instead
Deploy fails with 422 after deleting a service?
  • The previous service’s infrastructure may not have fully cleaned up
  • Wait 1-2 minutes and retry
  • If it persists, try a different service name or contact support

Limits

Always-on services are available on Pro, Team, and Enterprise plans. Service compute time counts toward your workspace usage. Instance counts and concurrent service limits depend on your plan tier.