Skip to main content
Agents running in Docker containers have access to shell commands for persisting environment variables, signaling the scheduler, calling other agents, and coordinating with resource locks. These commands are installed at /tmp/bin/ and taught to agents via a preamble injected before SKILL.md.

Environment Commands

setenv

Persist an environment variable across bash commands. Each bash command the agent runs starts in a fresh shell, so variables set with export are lost between commands. setenv makes them stick.
setenv <NAME> <value>
First bash command — set variables:
setenv REPO "acme/app"
setenv ISSUE_NUMBER 42
Later bash command — variables are still available:
gh issue view $ISSUE_NUMBER --repo $REPO

How it works

setenv writes each variable to /tmp/env.sh, which is automatically sourced at the start of every bash command. The variable is also exported immediately in the current shell, so it’s available right away in the same command.

Signal Commands

Signal commands write signal files that the scheduler reads after the session ends.

al-rerun

Request an immediate rerun to drain remaining backlog. Without this, the scheduler treats the run as complete and waits for the next scheduled tick.
al-rerun
  • Only applies to scheduled runs. Webhook-triggered and agent-called runs do not re-run.
  • Reruns continue until the agent completes without calling al-rerun, hits an error, or reaches the maxReruns limit (default: 10).

al-status "<text>"

Update the status text shown in the TUI and web dashboard.
al-status "reviewing PR #42"
al-status "found 3 issues to work on"

al-return "<value>"

Return a value to the calling agent. Used when this agent was invoked via al-subagent.
al-return "PR looks good. Approved with minor suggestions."
al-return '{"approved": true, "comments": 2}'
The calling agent receives this value when it calls al-subagent-wait.

al-exit [code]

Terminate the agent with an exit code indicating an unrecoverable error. Defaults to exit code 15.
al-exit          # exit code 15
al-exit 1        # exit code 1

Call Commands

Agent-to-agent calls allow agents to delegate work and collect results. These commands require the gateway (GATEWAY_URL must be set).

al-subagent <agent>

Call another agent. Pass context via stdin. Returns a JSON response with a callId.
echo "Review PR #42 on acme/app" | al-subagent reviewer
Response:
{"ok": true, "callId": "abc123"}
Errors:
{"ok": false, "error": "self-call not allowed"}
{"ok": false, "error": "queue full"}

al-subagent-check <callId>

Non-blocking status check on a call. Never blocks.
al-subagent-check abc123
Response:
{"status": "pending"}
{"status": "running"}
{"status": "completed", "returnValue": "PR approved."}
{"status": "error", "error": "timeout"}

al-subagent-wait <callId> [...] [--timeout N]

Wait for one or more calls to complete. Polls every 5 seconds. Default timeout: 900 seconds.
al-subagent-wait abc123 --timeout 600
al-subagent-wait abc123 def456 --timeout 300
Response:
{
  "abc123": {"status": "completed", "returnValue": "PR approved."},
  "def456": {"status": "completed", "returnValue": "Tests pass."}
}

Complete call example

# Fire multiple calls
REVIEW_ID=$(echo "Review PR #42 on acme/app" | al-subagent reviewer | jq -r .callId)
TEST_ID=$(echo "Run full test suite for acme/app" | al-subagent tester | jq -r .callId)

# ... do other work ...

# Collect results
RESULTS=$(al-subagent-wait "$REVIEW_ID" "$TEST_ID" --timeout 600)
echo "$RESULTS" | jq ".\"$REVIEW_ID\".returnValue"
echo "$RESULTS" | jq ".\"$TEST_ID\".returnValue"

Call rules

  • An agent cannot call itself (self-calls are rejected)
  • If all runners for the target agent are busy, the call is queued (up to workQueueSize, default: 100)
  • Call chains are allowed (A calls B, B calls C) up to maxCallDepth (default: 3)
  • Called runs do not re-run — they respond to the single call
  • The called agent receives a <skill-subagent> block with the caller name and context
  • To return a value, the called agent uses al-return

Lock Commands

Resource locks prevent multiple agent instances from working on the same resource. Lock keys use URI format (e.g. github://acme/app/issues/42).

rlock

Acquire an exclusive lock on a resource.
rlock "github://acme/app/issues/42"
Success:
{"ok": true}
Already held:
{"ok": false, "holder": "dev-abc123", "heldSince": "2025-01-15T10:30:00Z"}
Deadlock detected:
{"ok": false, "reason": "possible deadlock detected", "cycle": ["dev-abc", "github://acme/app/pr/10", "dev-def", "deploy://api-prod"]}

runlock

Release a lock. Only the holder can release.
runlock "github://acme/app/issues/42"
Success:
{"ok": true}
Not holder:
{"ok": false, "reason": "not the lock holder"}

rlock-heartbeat

Reset the TTL on a held lock. Use during long-running work to prevent the lock from expiring.
rlock-heartbeat "github://acme/app/issues/42"
Success:
{"ok": true, "expiresAt": "2025-01-15T11:00:00Z"}

Example in SKILL.md

Reference lock commands directly in your SKILL.md workflow:
## Workflow

1. List open issues labeled "agent" in repos from `<skill-config>`
2. For each issue:
   - rlock "github://owner/repo/issues/123"
   - If the lock fails, skip this issue — another instance is handling it
   - Clone the repo, create a branch, implement the fix
   - Open a PR and link it to the issue
   - runlock "github://owner/repo/issues/123"
3. If you completed work and there may be more issues, run `al-rerun`
The preamble teaches the agent the lock commands, their responses, and the URI key format.

Lock authentication

Each container gets a unique per-run secret. Lock requests are authenticated with this secret, so only the container that acquired a lock can release or heartbeat it. There is no way for one agent instance to release another’s lock.

Auto-release on exit

When a container exits — whether it finishes successfully, hits an error, or times out — all of its locks are released automatically by the scheduler. See Resource Locks for a complete description of the locking system.