Overview
Every 5dive VM ships with a single Bash entry point at /usr/local/bin/5dive. It is the canonical way to create, inspect, and tear down agents on the host. The CLI is intentionally narrow — six top-level commands — and emits structured JSON whenever --json is passed, so it is safe to script against from a dashboard, a webhook, or another agent.
Each agent is one Linux user (agent-<name>) in the claude group, one systemd unit (5dive-agent@<name>.service), and one tmux session (agent-<name>). The unit runs the chosen CLI binary inside a tmux restart loop with the agent type's shared credentials injected via EnvironmentFile.
This page is the reference manual. It is written so an LLM can use the CLI without trial-and-error: every flag, every exit code, and every state path is listed below.
Quickstart
Install the CLI on any Linux host (requires sudo):
curl -fsSL https://5dive.com/install.sh | sudo bashFrom a fresh 5dive VM, the shortest path to a running agent paired to Telegram:
# 1. Authenticate the Claude CLI once (covers every claude-typed agent)
sudo 5dive agent auth login claude
# 2. Spawn an agent named "scout" wired to Telegram
sudo 5dive agent create scout \
--type=claude \
--channels=telegram \
--telegram-token=123456:ABC...
# 3. Pair the bot to your chat (DM the bot, then paste the code it replies)
sudo 5dive agent pair scout --code=AB12CD
# 4. Inspect — should print state=running
sudo 5dive agent stats scoutAll four commands exit non-zero on failure with an exit code drawn from the table at the bottom of this page. Add --json to any of them to get a parseable envelope on stdout.
Concepts
Agents
An agent is a long-running tmux session running one of the supported coding CLIs (Claude Code, Codex, Gemini CLI, openclaw, hermes, opencode). It owns its own Linux user, its own home directory, and — when paired — its own bot/app token. Two agents of the same type can coexist on one host.
Accounts (named auth profiles)
By default every agent of a given type shares one set of credentials, stored under /etc/5dive/connectors/<type>.env. To run two agents of the same type against different accounts, create a named account once and bind agents to it: 5dive account add personal, 5dive account login personal --type=claude, then --auth-profile=personal on agent create (or 5dive agent set-account <agent> personal after the fact). See Accounts for the full surface. The lower-level auth set --auth-profile=<name> and auth login --auth-profile=<name>verbs still work and back the dashboard's device-code flow.
Channels
A channel is the inbound message surface the agent listens on. Today: telegram, discord, or none. Channels are only supported by agents whose CLI ships an MCP plugin contract — that is claude, openclaw, and hermes.
Workdir
The tmux session starts in the agent's workdir. Default is /home/claude/projects. Override at create time with --workdir=..., or change later with 5dive agent config <name> set workdir=....
JSON output
Pass --json as a global flag (anywhere on the command line) to switch stdout to a stable envelope. Progress lines (==>) keep going to stderr so the JSON on stdout is always parseable. The exit code matches error.code on failure.
{
"ok": true,
"data": {
"name": "scout",
"type": "claude",
"channels": "telegram",
"workdir": "/home/claude/projects",
"created": true
}
}{
"ok": false,
"error": {
"code": 6,
"class": "auth_required",
"message": "claude is not authenticated (missing) — run: sudo 5dive agent auth login claude"
}
}Branch on error.class for stable program flow — the human-readable message changes, the class does not.
Agent types
5dive agent types lists every supported CLI on the host along with auth state and an installed boolean. Types missing from disk are installed on demand the first time you agent create them.
| Type | Channels | Auth flow | Notes |
|---|---|---|---|
| claude | yes | setup-token / API key | Anthropic Claude Code. Default for new agents. |
| codex | no | OpenAI device flow / API key | OpenAI Codex CLI. |
| gemini | no | Google OAuth / API key | Google Gemini CLI. |
| hermes | yes | OpenAI device flow | Nous Research hermes harness. |
| openclaw | yes | OpenAI device flow | Third-party Claude harness, OpenAI-backed. |
| opencode | no | none / optional API key | opencode.ai. Free models, no signup required. |
Authentication
Auth is decoupled from agents. You authenticate a typeonce (optionally under a named profile) and every agent of that type inherits the credentials via systemd's EnvironmentFile.
Interactive login (TTY)
sudo 5dive agent auth login claudeHands this process off to the upstream CLI's interactive flow. Don't use this from a dashboard; use the device-code variant below instead.
API key
# Direct
sudo 5dive agent auth set claude --api-key=sk-ant-...
# From stdin (recommended — keeps the key out of shell history)
echo "$KEY" | sudo 5dive agent auth set claude --api-key=-Anthropic sk-ant-oat01-* tokens are routed to CLAUDE_CODE_OAUTH_TOKEN; everything else becomes ANTHROPIC_API_KEY.
Non-TTY device-code flow
The dashboard uses this flow because it runs without a PTY. Each call is async — start, poll until you get a URL, show the URL to the user, then submit the callback code the upstream login redirects to.
# 1. Start a session — returns a session id
sudo 5dive agent auth start claude --json
# -> {"ok":true,"data":{"session":"01HXX..","state":"awaiting_url"}}
# 2. Poll until state=awaiting_code; data.url is what the user opens
sudo 5dive agent auth poll 01HXX.. --json
# 3. User pastes the callback code from the redirect URL back to you
sudo 5dive agent auth submit 01HXX.. --code=anthropic#abc123
# 4. (optional) cancel a pending session
sudo 5dive agent auth cancel 01HXX..Status
# Sentinel-only (fast)
sudo 5dive agent auth status
# Live probe — actually calls the API
sudo 5dive agent auth status --probe
# One type only
sudo 5dive agent auth status --type=claude --probeAccounts
An account is a named bundle of credentials that one or more agents can share. Accounts are the user-facing surface over the lower-level auth profile primitive: the same on-disk storage, friendlier verbs.
Use accounts when you want two agents of the same type to authenticate against different sign-ins (e.g. personal + work Anthropic accounts), or when you want a single sign-in to feed many agents — re-authing the account heals every agent bound to it in one shot.
Create and sign in
# 1. Create an empty account
sudo 5dive account add personal
# 2. Sign in interactively (TTY hand-off)
sudo 5dive account login personal --type=claude
# Or, set an API key non-interactively
echo "$KEY" | sudo 5dive agent auth set claude \
--api-key=- --auth-profile=personalNames: lowercase letters, digits, _, and -; must start with a letter; max 32 characters. The literal name defaultis reserved (it's the magic value agent set-account <agent> default uses to clear a binding).
Inspect
sudo 5dive account list # name, types signed in, # agents bound
sudo 5dive account show personal # detail incl. env keys present{
"ok": true,
"data": [
{ "name": "personal", "types": ["claude"], "agents": ["scout","builder"] },
{ "name": "work", "types": ["claude","codex"], "agents": ["work-bot"] }
]
}Bind agents
# At create time
sudo 5dive agent create scout --type=claude --auth-profile=personal
# After the fact (rebinds + restarts the agent to pick up the new env)
sudo 5dive agent set-account scout work
# Clear the binding (agent falls back to the shared default credentials)
sudo 5dive agent set-account scout defaultRename and remove
# Rename — repoints every bound agent's symlink and restarts the units
sudo 5dive account rename personal primary
# Remove — refuses if any agents still bind to it
sudo 5dive account remove primaryaccount remove is a safety net: if any agent has its authProfile set to the account, the command fails with class conflict (exit 5) and prints the agent names. Rebind or delete those agents first.
Account vs `agent auth` (lower-level)
The legacy agent auth set/login/start/poll/submit/cancel verbs still work — they take an --auth-profile=<name>flag and back the dashboard's device-code flow. Prefer account for human-driven flows; reach for agent auth start|poll|submit when you need the non-TTY device-code lifecycle from a programmatic caller.
Agent lifecycle
Create
sudo 5dive agent create <name> --type=<type> \
[--channels=none|telegram|discord] \
[--telegram-token=<bot-token>] \
[--discord-token=<token>] \
[--workdir=<absolute-path>] \
[--auth-profile=<name>] \
[--with-skills=<spec>[,<spec>...]] \
[--no-skills]Names: lowercase letters / digits / hyphens, must start with a letter, max 16 characters. The CLI installs the type binary on demand if it's missing, then refuses to create the agent unless the type is authenticated.
--with-skills preinstalls one or more skills on the new agent. Spec is either a bare id (defaults to 5dive-com/skills) or <owner/repo>:<id>. Multiple specs are comma-separated. When the create call is made by another agent on a claude-typed agent, the flag defaults to --with-skills=5dive-cli so spawned children inherit inter-agent comms knowledge automatically. Use --no-skills to opt out. A failed skill install warns but does not roll back the agent — rerun 5dive agent skill <name> add ... to retry.
List & inspect
sudo 5dive agent list # text or --json
sudo 5dive agent stats <name> # state, restart count, last exit
sudo 5dive agent logs <name> --lines=200 [--follow] [--tmux]--tmux dumps the tmux scrollback (what the user sees in the TUI). Without it, logs come from systemd / journalctl.
Start, stop, restart, remove
sudo 5dive agent start <name>
sudo 5dive agent stop <name>
sudo 5dive agent restart <name>
sudo 5dive agent rm <name> # removes user, unit, tmux session, envReconfigure
sudo 5dive agent config <name> set channels=<none|telegram|discord>
sudo 5dive agent config <name> set workdir=<path> # "default" clears
sudo 5dive agent config <name> set auth-profile=<name> # "default" clears
sudo 5dive agent config <name> set telegram.token=<token>
sudo 5dive agent config <name> set discord.token=<token>
# Shorter alias for the auth-profile case
sudo 5dive agent set-account <agent> <account|default>Send input / attach
# Inject a message into the running tmux session (sends keys + Enter)
sudo 5dive agent send <name> "implement the dashboard skeleton"
# Attach your terminal to the session — Ctrl-b d to detach
sudo 5dive agent <name> tuiWhen called from another agent, send auto-wraps the payload with a [5dive-msg from=<sender> id=<id>] envelope so the receiver knows who pinged it. Use --from=<label> to override and --raw to skip wrapping. See Inter-agent comms for the full protocol and the synchronous agent ask wrapper.
Clone
sudo 5dive agent clone <src> <dst> [--channels=...] \
[--telegram-token=...] [--discord-token=...] [--workdir=...]Clones type, workdir, and auth profile from <src>. Channel + tokens must be passed fresh — two agents cannot share a Telegram bot.
Channels & pairing
A new agent created with --channels=telegram writes the bot token under /etc/5dive/connectors/telegram-<name>.env and starts an MCP plugin process inside the agent. The plugin is locked down by default — it ignores every chat until you pair one.
Pairing
# Option A — give the user a 6-character code, they DM the bot, paste it back
sudo 5dive agent pair <name> # prints/returns code
# Option B — paste the bot's reply directly (dashboard flow)
sudo 5dive agent pair <name> --code=AB12CDAllow-list
The plugin's allow-list lives at ~/.claude/channels/<channel>/access.json inside the agent's home. Pairing appends to allowFrom; rotate the bot token via 5dive agent config <name> set telegram.token=....
Skills
Skills are the skills.sh prompt-bundle format. A skill is a directory with a SKILL.mdat its root. Installing a skill on an agent drops it into the agent type's skills dir (~/.claude/skills/ for claude, ~/.hermes/skills/ for hermes, ~/.agents/skills/ for codex / gemini / opencode, ~/skills/ for openclaw) and makes it loadable on next prompt.
# Install a skill on an agent
sudo 5dive agent skill <name> add \
--source=<owner/repo> \
--skill=<skill-id>
# List installed skills
sudo 5dive agent skill <name> list
# Remove a skill
sudo 5dive agent skill <name> rm <skill-id>Tip: install the 5dive-cli skill from github.com/5dive-com/skills on any agent to teach it the 5dive command surface — agent lifecycle, JSON envelope, recovery on exit codes, and the --json orchestration loop:
sudo 5dive agent skill <name> add \
--source=5dive-com/skills \
--skill=5dive-cliWith it installed, an agent can spawn its own subagents on the same host — see Spawning subagents.
Spawning subagents
Any agent on a 5dive VM can call 5diveto spawn more agents on the same host — that's how recursion works. The agent user (agent-<name>) is in the claude group and has sudo 5dive ... in its sudoers, so:
# From inside one agent's tmux session — spawn a worker for a side task
sudo 5dive agent create worker-1 \
--type=claude \
--workdir=/home/claude/projects/myrepo \
--json
# Send it a task
sudo 5dive agent send worker-1 "audit auth middleware for OWASP A01"
# Watch its output
sudo 5dive agent logs worker-1 --tmux --lines=50
# Tear it down when done
sudo 5dive agent rm worker-1For LLM-driven orchestration, prefer --jsonon every call and branch on error.class rather than parsing the human stderr. The 5dive-cli skill packages this loop as a reusable prompt — install it on the parent agent and ask for “a worker that does X”.
Inter-agent comms
agent send and agent askform a tiny message bus between agents on the same host. There is no separate channel — messages land in the receiver's running CLI as if a human had typed them, but with an envelope that lets the receiver tell who is pinging it.
Auto-attributed sends
When the caller is an agent-* Linux user (i.e. one agent shelling out to talk to another), the CLI auto-detects the sender from $SUDO_USER and wraps the payload as:
[5dive-msg from=<you> id=<8-hex>] <your text>Humans calling sudo 5dive agent send directly are unaffected — only sends from agent users get the envelope. Override with --from=<label> or skip wrapping entirely with --raw.
Replying
A receiver that sees a wrapped message replies the same way it would talk to anything else — by name. The [re=<id>] prefix is convention, not enforced; it lets the sender match replies when juggling several outstanding asks.
# Inside the receiver's session, after seeing
# [5dive-msg from=scout id=ab12cd34] please summarise the audit
sudo 5dive agent send scout "[re=ab12cd34] auth middleware looks clean except ..."Synchronous request/response: agent ask
sudo 5dive agent ask <name> "<prompt>" \
[--from=<sender>] \
[--timeout=120] \
[--idle-secs=5] \
[--poll-secs=2] \
[--json]Sends the wrapped envelope, then watches tmux capture-pane after the marker line until the slice has been quiet for --idle-secs (default 5s) — at which point it returns the reply body. Times out with class timeout (exit 11) if the receiver never goes idle within --timeout.
{
"ok": true,
"data": {
"name": "scout",
"from": "coordinator",
"msg_id": "ab12cd34",
"reply": "auth middleware looks clean except for ..."
}
}Caveats
Idle-by-stability is heuristic. A receiver that streams progress continuously will keep ask awake until --timeout fires. For long agentic work, prompt the receiver for a terse final summary, or use plain send + logs and poll on your own schedule.
The reply is whatever was on screen. It includes any chrome the receiver CLI prints (cursor lines, status hints) — don't expect a clean JSON body unless the prompt explicitly asks for one.
No retries, no delivery confirmation. If the receiver crashed mid-reply you'll get a partial slice or a timeout, nothing in between.
When to use what
Fire-and-forget delegation: agent send, then poll agent logs --tmux at your own convenience. Need an answer before continuing: agent ask. Fan-out across N workers: loop agent send, or run several agent ask calls in parallel via & + wait.
Doctor & health
5dive doctor walks every dependency (tmux/jq/bun/python3/node/npm), every type binary, every live auth probe, registry integrity, and shelld reachability.
# Read-only check
sudo 5dive doctor --json
# Fix what's fixable (apt installs, type installer recipes, registry reseed)
sudo 5dive doctor --repair
# Narrow the scope
sudo 5dive doctor --category=deps # or types | auth | registry | shelldEnvelope is always {ok: true, data: {summary, checks}} with exit 0 — branch on data.summary.errors > 0 in CI.
Exit codes
Both shell exit code and error.code in the JSON envelope. Branch on error.class for human-stable matching.
| Code | Class | Meaning |
|---|---|---|
| 0 | ok | Success |
| 1 | generic | Catch-all / internal error |
| 2 | usage | Unknown flag, missing arg, bad subcommand |
| 3 | validation | Format check failed (name, workdir, token, lines) |
| 4 | not_found | Agent/type/session doesn't exist |
| 5 | conflict | Already exists (name collision) |
| 6 | auth_required | Type not authenticated, bot token missing |
| 7 | not_installed | CLI binary missing, no installer recipe |
| 8 | not_running | tmux session / systemd unit not active |
| 9 | pairing | Pair code not pending or invalid |
| 10 | permission | Must run as root |
| 11 | timeout | Plugin didn't materialize within waitloop |
State & paths
Useful only when debugging — every path below is managed by the CLI. Don't edit by hand.
| /var/lib/5dive/agents.json | Agent registry (versioned). Source of truth for `agent list`. |
| /var/lib/5dive/agents.d/<name>.env | Per-agent systemd EnvironmentFile. |
| /var/lib/5dive/auth-profiles/<name>/ | Named auth profile env files + captured CLI config. |
| /var/lib/5dive/auth-sessions/<id>/ | Live device-code session state. |
| /etc/5dive/connectors/<type>.env | Shared auth env (default profile). |
| /etc/5dive/connectors/telegram-<name>.env | Per-agent bot token. |
| /etc/systemd/system/5dive-agent@.service | Templated systemd unit. One instance per agent. |
| /var/log/5dive/agent-audit.log | NDJSON audit trail of every mutating command. |