Skip to content
AgentDock

Runners

Runner reference

CLI commands, runner.json keys, state files and logs.

Everything about agentdock-runner in one place: its commands and exit codes, every key of runner.json, the files it keeps, its environment variables and logs, the OTLP receiver, and the commands the server can send it. The facts here are from runner 0.1.0.

Commands

Synopsis
Usage: agentdock-runner <command>
 
Commands:
  pair --server <url> --code <XXXX-XXXX> [--no-detect]
  run
  status
  profiles [--detect [--write]]
  install-service
  version
CommandWhat it does
pair --server <api-origin> --code XXXX-XXXXExchanges a one-time pairing code for a runner id and token and writes them to runner.json. If the config has no profiles yet, it also detects them and prints each with its authentication state. Pairing again replaces the previous pairing; existing profiles are kept.
pair ... --no-detectPairs without detecting profiles.
runThe daemon, in the foreground. Refuses to start when the machine is not paired. Warns when no profiles are configured.
statusPrints the config path, whether it is paired and as which id, the server, the number of profiles, the spool state, and whether the server answers. Exits 1 when the server is unreachable.
profilesPrints the configured profiles as JSON, each with authenticated.
profiles --detectPrints the profiles detection would propose. Changes nothing.
profiles --detect --writeReplaces the configured profiles with the proposal. --write without --detect is a usage error.
install-serviceWrites a systemd user unit (Linux) or a launchd agent (macOS) and prints the command that starts it.
version (also --version, -v)Prints the version and the target, for example 0.1.0 darwin-arm64.
help (also --help, -h)Prints the usage above.

--server is the API origin: http(s)://host[:port][/path], without credentials, a query or a fragment. The runner posts to <server>/runners/pair and connects to <server>/runner (ws:// for http://, wss:// for https://). Behind the reference proxy that is https://<host>/api.

There is one more subcommand, exec-run <runDir>, which a skill run's tmux session executes. It is internal; do not call it yourself.

Exit codes

CodeMeaningService behaviour
0Success, or a clean stopnot restarted
1Failure: crash, unreachable server, invalid config, pairing error, lock heldsystemd restarts it after 5 s; launchd and Homebrew after their throttle
2Wrong arguments
78The server closed the connection for good: protocol mismatch (4400), revoked or invalid token (4401), or replaced by a newer pairing (4409)not restarted: RestartPreventExitStatus=78 on systemd; a /bin/sh wrapper turns 78 into 0 for launchd and brew services

While running, the runner reconnects with exponential backoff from 1 to 60 seconds on any other disconnect.

runner.json

The config lives at $XDG_CONFIG_HOME/agentdock/runner.json, by default ~/.config/agentdock/runner.json. The runner writes it atomically with mode 0600. It is read when the daemon starts, so restart the runner after you edit it. An invalid file is refused with the names of the bad fields (never their values, since one of them is the token).

~/.config/agentdock/runner.json
{
  "server": "https://agentdock.example.com/api",
  "runnerId": "...",
  "token": "...",
  "profiles": [
    { "id": "claude-default", "runtime": "claude", "env": {}, "args": [] }
  ],
  "projects": [],
  "disabledCommands": ["terminal.attach"],
  "otlp": { "enabled": true, "http": 4318, "codexExperimental": false },
  "fleet": { "pollSeconds": 15, "prPollSeconds": 60, "eventsPollSeconds": 5 },
  "queue": { "pollSeconds": 60 },
  "sessions": { "enabled": true, "ingestSince": "2026-10-13T09:00:00.000Z" },
  "skills": { "maxConcurrentRuns": 2, "maxTimeoutSec": 21600 }
}
KeyDefaultMeaning
servernoneAPI origin, written by pair.
runnerIdnoneWritten by pair.
tokennoneWritten by pair. Redacted from logs. Treat the file as a secret.
profiles[]Runtime profiles: id, runtime, optional binary, env, args. See Runtime profiles.
projects[]The runner's cached watch list (id, root). The server manages it when projects are connected; do not edit it.
disabledCommands[]Server commands this machine refuses. See Disabling commands.
otlpnullThe OTLP receiver. null means on, on 127.0.0.1:4318.
otlp.enabledtrueStart the receiver.
otlp.http4318Port. The host is always 127.0.0.1.
otlp.grpcnullReserved. gRPC is not served.
otlp.codexExperimentalfalseAlso map codex.* telemetry records, whose attribute names are not verified yet.
fleet.pollSeconds15Fleet collectors' poll interval (5 to 3600).
fleet.prPollSeconds60Pull request and checks poll interval through gh (15 to 3600).
fleet.eventsPollSeconds5Poll fallback next to file watching for Code Sentinel's events.jsonl (1 to 3600).
queue.pollSeconds60How often the issues collector polls GitHub for a project's open issues (15 to 3600).
sessions.enabledtrueRead agent transcripts and send session metadata. false turns session ingest off.
sessions.ingestSinceset by pairTranscripts last modified before this time are skipped unless an admin runs a backfill.
skills.maxConcurrentRuns2Skill runs at once (1 to 32). The rest wait as queued, oldest first.
skills.maxTimeoutSec21600A skill run's timeout above this is cut to it (60 to 21600).

Files

State lives under $XDG_STATE_HOME/agentdock/, by default ~/.local/state/agentdock/.

~/.local/state/agentdock/
spool/                 events not yet acknowledged by the server
spool/runner.lock      one daemon per machine
offsets.json           how far each agent transcript was read
events-offsets.json    byte offset and inode of each project's events.jsonl
runs/<runId>/          one directory per skill run
  run.json             what to launch, as an argv
  state.json           the run's state
  stream.jsonl         the session's output stream
  stderr.log
  exit.json
  patch.diff           the full patch (the server keeps at most 128 KiB)

Events are delivered at least once: they sit in the spool until the server acknowledges them, so a runner that loses its connection catches up when it returns.

Service files:

WhatWhere
systemd user unit (Linux)$XDG_CONFIG_HOME/systemd/user/agentdock-runner.service
launchd agent (macOS)~/Library/LaunchAgents/com.agentdock.runner.plist

Environment variables

VariableEffect
AGENTDOCK_LOGLog level: debug, info (default), warn or error.
HOMEBase for every default path.
XDG_CONFIG_HOMEWhere agentdock/runner.json and the systemd unit go. Used only when it is an absolute path.
XDG_STATE_HOMEWhere the state directory goes. Used only when it is an absolute path.
CODEX_HOMEThe Codex config directory that detection puts into codex-default.

Logs

Logs are JSON lines on stderr, with the token redacted.

How it runsWhere the log goes
Foreground (run)the terminal
systemd user unitthe journal: journalctl --user -u agentdock-runner -f
launchd agent~/Library/Logs/agentdock-runner.log
brew services$(brew --prefix)/var/log/agentdock-runner.log

Set AGENTDOCK_LOG=debug in the service's environment for more detail.

OTLP receiver

The runner listens for OpenTelemetry logs on 127.0.0.1:4318, so a Claude Code session with telemetry on reports each LLM request as it happens, before its transcript is read. Without it, tokens and cost still arrive from transcripts, just later.

  • OTLP over HTTP only, on POST /v1/logs, as application/x-protobuf or application/json, optionally gzip. /v1/metrics and /v1/traces are accepted and discarded.
  • Connections from anything but loopback are refused, and so are bodies over 4 MiB.
  • Only claude_code.api_request records are read. Each becomes one LLM request with model, request id, token counts, duration and the runtime's own reported cost. Prompts, tool input and output, responses, email and account or organization ids are never sent on, even with OTEL_LOG_USER_PROMPTS=1.
  • If the port is taken, the error is logged and the runner keeps running without the receiver.

Skill runs that AgentDock launches turn telemetry on by themselves. For any other Claude Code session on the machine:

Terminal
CLAUDE_CODE_ENABLE_TELEMETRY=1 \
OTEL_LOGS_EXPORTER=otlp \
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318 \
claude

You can add OTEL_RESOURCE_ATTRIBUTES=agentdock.project=<id>,agentdock.slot=<slot>,agentdock.issue=<n>,agentdock.run=<run> to attribute requests directly. All four are optional; without them a request is matched to its session by session.id.

Command allowlist

The server can only ask a runner to do what is in this list. Each command has a schema for its arguments, a minimum role the requesting user must have on the project, and a timeout. There is no command that runs a shell string. Paths are confined to registered project roots and their .wt-<repo>-* sibling worktrees.

CommandMinimum roleTimeoutWhat it does
runner.pingviewer30 sLiveness check
runner.describeviewer30 sHost facts and capabilities
project.inspectadmin45 sRead a repository before connecting it
project.refreshoperator45 sRe-read a connected project
session.backfilladmin10 minRe-read older transcripts
orchestrator.startoperator30 sStart the orchestrator in tmux
orchestrator.stopoperator10 sKill the orchestrator's session only
orchestrator.statusviewer5 sPresence and pane state
slot.stopoperator10 sKill a slot's tmux session, keep its worktree
slot.messageoperator10 sWrite .orchestrator-msg.md and nudge the worker
issue.createoperator45 sCreate a GitHub issue with gh
issues.refreshoperator45 sPoll the queue now
collector.polladmin30 sPoll collectors now (GitHub App push)
pr.inspectviewer30 sPull request details for approvals
pr.approveoperator10 sWrite an approval signal file
pr.requestChangesoperator10 sWrite a request-changes signal file
pr.voidApprovaloperator10 sWithdraw a decision
terminal.attachadmin10 sOpen a PTY on a tmux session
skill.searchoperator15 sSearch the skills.sh catalog
skill.inspectoperator2 minClone and hash a skill for preview
skill.installoperator3 minInstall a skill (profile installs need admin)
skill.uninstalladmin10 sRemove an installed skill
skill.listviewer60 sInstalled skills
skill.runoperator30 sStart a headless skill run
skill.canceloperator15 sCancel a skill run

The timeout is how long the server waits for the command to answer, not how long the work may take: skill.run answers once the run is started.

Disabling commands

disabledCommands lets the owner of a machine refuse commands no matter what the server asks. A disabled command answers with the error disabled. For example, to forbid terminal attach and skill runs on this machine:

runner.json (excerpt)
{
  "disabledCommands": ["terminal.attach", "skill.run", "skill.install"]
}

Restart the runner after the change. Disabling terminal.attach also turns off the runner's terminal capability, so the UI stops offering Attach for it.

Next steps