Skip to main content
reference

CLI reference

The ethos binary dispatches a single top-level subcommand. Running it with no arguments is equivalent to ethos chat.

Source​

Top-level dispatch lives in apps/ethos/src/index.ts. Each case in the switch is a subcommand; per-command flag parsing lives in apps/ethos/src/commands/.

Synopsis​

ethos [subcommand] [args...] [--version | --help]

If [subcommand] is omitted, ethos runs chat. If ~/.ethos/config.yaml is missing, the run is preceded by ethos setup.

The -q / --query / --query=<text> flag is recognised as a top-level alias for ethos chat -q <text>.

Top-level flags​

FlagAliasesDescription
--version-vPrint the installed CLI version and exit. Combine with --json for structured metadata (name, version, node, platform, arch, supported providers and channels, managed mode status).
--help-hPrint the top-level usage line and exit.
--query <text>-q, --query=<text>One-shot mode. Routes to chat with singleQuery; process exits after the response.

ethos setup​

Interactive first-run wizard. Writes ~/.ethos/config.yaml. Re-running is safe — answers default to the current config.

Synopsis: ethos setup [auth | model | personality | messaging | memory | providers | keys]

ArgDescription
(none)Start the wizard at the first step.
authJump to the auth step.
modelJump to the model-selection step.
personalityJump to the personality step.
messagingJump to the channel-credentials step.
memoryJump to the memory-backend step.
providersJump to the provider-chain step.
keysJump to the key-rotation step.

If the wizard completes with "launch chat", the process continues into ethos chat against the freshly written config.

ethos setup messaging

ethos chat​

Interactive REPL with streaming output, persisted sessions, and slash commands. Sessions are keyed by cli:<basename of cwd>. See session.

Synopsis: ethos chat [--verbose] [--skin <name>] [-q <prompt>] [--continue] [--resume <id>] [--team <name>] [--dry-run]

FlagDefaultDescription
--verboseoffPrint a per-turn timing summary (LLM time, TTFT, tool wall-clock, total, tokens, cost) after every response. Persistent equivalent: verbose: true in config.
--skin <name>from configOverride the resolved skin for this process only. Does not write to disk.
-q <prompt>, --query <prompt>offSingle-query mode. Stream one answer and exit. --query=<prompt> is also accepted.
--continue, -coffResume the most recent CLI session. Scoped to cli:* sessions only — never crosses into gateway sessions.
--resume <id>, -r <id>offResume a specific session by ID or title. Tries exact ID match first, then case-insensitive title match. When multiple sessions match, lists candidates instead of picking one.
--no-resume-hintoffSuppress the session-resume hint printed on exit.
--team <name>offOverride activeContext for this process, routing through a team coordinator. Does not write to disk.
--dry-runoffDry-run mode — tools are planned but not executed. Toggle within a session with /dry-run on|off.
ethos chat --verbose
ethos chat --continue
ethos chat --resume "research session"
ethos chat --team eng --dry-run
ethos -q "summarize the changes since yesterday"

ethos personality​

Manage personalities. With no subcommand, lists every personality (built-in plus user) and marks the default.

Synopsis: ethos personality [list | create [name] [--blank | --from <id>] | show <id> | set <id> | duplicate <src> <dst> | mcp <id> [--attach <name> | --detach <name>] | plugins <id> [--attach <plugin-id> | --detach <plugin-id>] | amendments <subcommand> | birth skip <id>]

SubcommandDescription
list (default)List built-ins and user personalities, marking the default.
create [name] [--blank | --from <id>]Create a user personality: --blank from a starter SOUL.md and toolset, --from <id> as a copy of another personality, and with neither an AI-assisted chat with the personality architect. Every path makes the personality born: it records a birth marker and adds propose_self_amendment to a declared toolset, so its first private CLI or web conversation runs the birth ritual.
show <id>Print the generated character sheet — identity, display (emoji and avatar), routing, memory scope, toolset, MCP servers, plugins, fs_reach, and the prompt size, including the project context of a declared fs_reach.workdir.
set <id>Persist <id> as the default personality in ~/.ethos/config.yaml.
duplicate <src> <dst>Copy <src> into ~/.ethos/personalities/<dst>/ so it becomes user-editable.
mcp <id> [--attach|--detach <name>]Show, attach, or detach MCP servers from ~/.ethos/mcp.json.
plugins <id> [--attach|--detach <id>]Show, attach, or detach installed plugins.
amendments list [--personality <id>] [--all] [--json]Requests a personality filed with propose_self_amendment: a toolset request changes its own toolset.yaml, an identity request its name, description and display.emoji in config.yaml. Lists pending and stale by default; --all lists every status.
amendments show <id> [--json]The review, recomputed from the live files: permission diff (widening rows and [high-risk] marked; an identity request prints none — an identity change grants nothing), the Not compared: line, the diff of the file it changes, flags, rationale, evidence, provenance, history and the review hash.
amendments apply <id>Print the review, ask for the personality id to be typed back, then write exactly the reviewed bytes. Refused without a TTY on stdin and stdout, and when ETHOS_TOOL_PROCESS=1 (a tripwire the host shell tools set, not a boundary). Refused CONFIG_CONFLICT when the file changed since filing. Applying an identity request ends the personality's birth ritual.
amendments decline <id> --reason "<text>"Close a pending or stale request. The reason is required.
amendments rollback <id>Restore the toolset.yaml or config.yaml saved at apply time. Same gate as apply; refused when the file was edited after the apply, so roll back several applies newest first.
birth skip <id>End a personality's birth ritual without changing it: removes its birth marker, so it never asks again. Prints Skipped the birth ritual for <id>. It will not ask again., or <id> has no birth ritual waiting; nothing to skip.
ethos personality show engineer
ethos personality duplicate engineer engineer-paired
ethos personality mcp engineer --attach github
ethos personality amendments show a-mukzpjf2-qiiphi

Every amendments failure prints ✗ <CODE>: <cause> and exits 1. Walkthrough: Review a personality's change request.

ethos plugins​

Print a plugin-by-personality attachment matrix. To install or remove plugins, use ethos plugin.

Synopsis: ethos plugins

ethos memory​

View and edit the memory files (the notes an agent keeps between turns). Every verb acts on the backend memory: selects — markdown (default), vault, or vector — in the active personality's scope. See memory.

Synopsis: ethos memory [show | add "<text>" | clear | export [path] | history | pending | restore <slug> | supersede <slug> --by <slug> | retract <slug>]

SubcommandArgsDescription
show (default)[--json]File backends: print MEMORY.md + USER.md. Vector mode: print the 20 most recent chunks.
add"<text>"Append to MEMORY.md. In vector mode, embed and chunk.
clear—Wipe MEMORY.md. In vector mode, prompts for confirmation.
export[path]Vector mode only. Export all chunks to a markdown file. Default: ~/.ethos/memory-export-<ts>.md.
history[--personality <id>] [--key MEMORY.md] [--source <source>] [--since 7d] [--limit 50] [--diff <n>] [--json]Print the provenance history newest-first: who wrote what, when, and the diff. --source is one of tool, capture, consolidation, dream, web-editor, global-entry, restore.
pending[--approve <id> | --reject <id>] [--personality <id>] [--json]List parked candidates from the approve-before-store gate; approve replays one into memory under its original source, reject tombstones its fact so capture never re-proposes it. See memoryApproval.
restore<slug> [--personality <id>]Move an archived section back into the file it came from. Recorded under source restore.
supersede<slug> --by <slug> [--personality <id>]Archive a section as replaced by a newer one.
retract<slug> [--reason <text>] [--personality <id>]Archive a section and tombstone its facts, so nothing re-proposes them.

Under memory: vector every file verb — history, pending's approve, restore, supersede, retract — refuses with NOT_CONFIGURED: that backend keeps entries in memory.db, not in files. Use show / add / export.

ethos memory add "Mitesh prefers terse explanations."

ethos gateway​

Configure and run the multi-platform message gateway — the surface that brings Telegram, Slack, Discord, WhatsApp, and Email senders into the same agent.

Synopsis: ethos gateway [setup | start | status [--json] | spool <replay|discard> <id>]

setup opens the setup wizard at the messaging step (alias for ethos setup messaging). start spins up every platform whose credentials are present in config.yaml and is long-running; exits non-zero if ~/.ethos/config.yaml is absent. If the config has parse errors or a bot binding that resolves to no personality or team, start (and ethos boot) prints them, suggests ethos doctor, and exits 78 (CONFIG_INVALID_EXIT_CODE), which the generated systemd unit does not restart. Source: apps/ethos/src/lib/config-exit.ts. start also exits 78 when every adapter fails because the platform refused its token (adapterStartFailureExitCode in apps/ethos/src/commands/gateway.ts), and exits 1 when every adapter fails for any other reason. ethos boot never exits for adapter failures: it keeps the web UI, cron and serve running and retries the adapters that failed transiently.

start takes the gateway lock, <ethos home>/gateway.lock, before it opens any store: one gateway per Ethos home (ETHOS_STATE_DIR scopes it, so two homes on one machine run two gateways). ethos boot owns channel adapters too and takes the same lock, so a start and a boot exclude each other. A second start or boot against a home whose gateway is alive prints the refusal and exits 3. A lock left by a process that is gone is taken over. Source: packages/wiring/src/gateway-lock.ts.

Another Ethos gateway is already running for /home/me/.ethos (pid 4242). Stop that process, or check it with 'ethos gateway status'. If no such process exists, remove /home/me/.ethos/gateway.lock.

ethos gateway status​

Read the gateway lock and heartbeat without touching either. Source: apps/ethos/src/commands/gateway-status.ts.

FlagTypeDefaultDescription
--jsonbooleanoffPrint { state, pid, heartbeatAgeMs, lockPath, spool, ledger } on one line. spool and ledger are per-status counts, or null when that database does not exist yet.
OutputExit codeMeaning
running (pid N, heartbeat 4s ago)0A live process holds the lock and wrote a heartbeat in the last 30 s.
stale lock (pid N not running) — next start will take it over1The lock names a process that is gone.
stopped1No lock.
unhealthy (pid N alive, heartbeat 95s old)2A live process holds the lock but its heartbeat is older than 30 s.
ethos gateway status --json
{"state":"running","pid":4242,"heartbeatAgeMs":3811,"lockPath":"/home/me/.ethos/gateway.lock","spool":{"received":0,"processing":1,"done":57,"dead":0},"ledger":{"pending":0,"redelivering":0,"delivered":61,"abandoned":0}}

ethos gateway spool​

Act on one dead-lettered inbound message in the inbound spool. ethos doctor lists dead rows with their ids.

SubcommandArgsEffect
replay<id>Dead → owed, attempts reset. A running gateway replays it within 60 s; otherwise its next start does.
discard<id>Dead → done, without a turn.

Exits 0 on success, 1 when the id is unknown, the row is not dead, or no spool exists.

ethos gateway spool discard 6f1c2a3e-0b7d-4c55-9a51-1f0e8d2b7c44
Discarded 6f1c2a3e-0b7d-4c55-9a51-1f0e8d2b7c44.

ethos listen​

Run a wake satellite — a process that owns a microphone and streams captured speech to the web API's satellite lane at GET /satellite/ws. Long-running in the foreground.

Synopsis: ethos listen [doctor] [--url <ws url>] [--route <id>] [--device <id>] [--play <command>] [--json]

Arg / flagTypeDefaultDescription
doctorsubcommand—Preflight only: engine, model files, capture device, server reachability, player. Exits without starting anything.
--url <ws url>stringderived from webBaseUrl, else http://127.0.0.1:3000Satellite lane URL. A bare origin gets /satellite/ws appended; a URL with a path is taken as given.
--route <id>stringunpinned — every enabled route may address this hostPins the microphone to one route: only that phrase may address it, and another agent's phrase is discarded rather than answered. Resolved against the table the server pushes on connect — a voice.wake.routes id, or a synthesized auto:<personality-id> one. A pin that names no enabled route is a hard failure; the flag never exempts the host from needing a phrase.
--device <id>stringthe enumerated defaultCapture device. This build enumerates stdin only.
--play <command>stringthe first of ffplay, aplay, play found on PATHCommand the reply's audio is written into, on that process's stdin. {rate} and {channels} are substituted, and the template runs through sh -c, so quoting and pipelines survive. Taken at face value — a supplied command is never second-guessed by a probe. With no flag and none of the three on PATH, the host registers playback: false, the server skips synthesis for it, and replies arrive as text. There is deliberately no config key: which command drives a sound card is a per-machine fact, like the capture pipe.
--jsonflagoffdoctor only. One JSON object on stdout — probes, configuredRoutes and requestedRoute (never a resolved route: this command does not connect), node id, lane URL, and exit — then exits.

Capture is a pipe — the daemon reads raw signed 16-bit little-endian mono PCM at 16 kHz from stdin, and there is no microphone binding. Playout is a pipe for the same reason.

ffmpeg -nostats -loglevel error -f avfoundation -i :0 -ar 16000 -ac 1 -f s16le - | ethos listen # macOS
arecord -q -f S16_LE -r 16000 -c 1 -t raw | ethos listen # Linux

The mic is open and the server decides who was addressed. Nothing here matches a phrase against sound: every captured utterance is transcribed server-side, and a turn runs only when the transcript opens with a wake phrase — which picks the personality and is stripped from the text — or follows one within voice.wake.idleTimeout. Anything else is transcribed and discarded. The room is therefore transcribed even when no agent answers. --json reports engine.daemonMode: "open-mic" and engine.phraseMatch: false, so a script cannot infer an acoustic gate from the engine name. Acoustic wake needs the sherpa engine and its models — see voice.wake.engine and Run a wake satellite.

Exit codes:

CodeMeaning
0Preflight clean.
1Hard failure — this host will never hear you until it is fixed: no config, a bad --url or --device, a configured engine that will not load, or missing models for a sherpa host.
2Warning — true right now and possibly not in a minute: nothing piped to stdin yet, or the server not answering yet. The daemon still refuses to start with no pipe, no usable engine, or a --route pin the server's table does not hold. An empty pushed table is a warning, not a refusal — a Settings save reaches the daemon without a restart.

ffplay is probed first because it is the only one of the three that can open an encoded reply — aplay and play take raw samples only, and an opus or mp3 playout to either is refused rather than played as noise. Raw PCM plays at 24 kHz unless the lane declares a rate, which no first-party provider does today: TtsProvider.synthesize reports no sample rate. A reply that sounds fast or slow is that assumption, and --play with an explicit -ar is the override.

The microphone is suppressed for the whole reply, so the agent cannot wake itself. playback_done — the frame re-arming it — is sent when the player process exits, not when the words are known; a player that never exits trips a 90-second watchdog that re-arms anyway and says so.

State on disk: the node's stable id in ~/.ethos/listen-node-id, and a heartbeat written every 10 seconds to ~/.ethos/listen-health.json (the gateway-health.json shape plus captureState), removed on clean shutdown. make listen and make listen-doctor wrap both commands from a clone.

ethos cron​

Schedule prompts to fire on cron expressions. list (default) shows every job with schedule, active hours, last run, next run. create schedules a new job (flags below). update <id> changes one. pause <id> / resume <id> / delete <id> manage existing jobs. run <id> fires immediately, even outside the job's active hours.

Synopsis: ethos cron [list | create | update <id> | pause <id> | resume <id> | delete <id> | run <id>]

Flags for create:

FlagRequiredDescription
--name, -n <name>yesHuman-readable job name.
--schedule, -s <expr>yes5-field cron expression (e.g. 0 8 * * *).
--prompt, -p <text>yesPrompt the agent runs on each fire.
--personality <id>noOverride the default personality for this job.
--deliver <target>noDelivery target (Telegram chat id, Slack channel, etc.).
--active-hours <HH:MM-HH:MM>noDaily window, on the host's clock, in which scheduled runs may happen. A start later than the end crosses midnight (22:00-06:00); start and end must differ. An occurrence outside it is skipped before any model call and is not counted as a run. Refused on a one-shot schedule.
ethos cron create -n "Daily standup" -s "0 8 * * 1-5" -p "What's on my calendar today?"
ethos cron create -n "Check-in" -s "0 */3 * * *" -p "Check in." --active-hours 09:00-21:00
ethos cron update check-in --active-hours off

update <id> takes --name, --schedule, --prompt, --script <file>, --precheck <file> and --active-hours <HH:MM-HH:MM|off>; at least one is required. off clears the window. Walkthrough: Schedule tasks with cron.

ethos acp​

Run the Agent Control Protocol server. JSON-RPC over stdin/stdout. Long-running; typically spawned by an orchestrator.

Synopsis: ethos acp

ethos serve​

Start the Ethos web UI and HTTP API on a local port. Long-running.

Synopsis: ethos serve [--port <n>] [--bind <addr>] [--team <name>] [--mesh <name>]

FlagDefaultDescription
--port <n>3000HTTP port.
--bind <addr>127.0.0.1Bind address. Use 0.0.0.0 to expose to LAN; still token-protected.
--team <name>offResolve the agent against a team coordinator and mesh.
--mesh <name>defaultExplicit mesh name when --team is not set.

Prints a sign-in URL of the form http://localhost:3000/auth/exchange?t=<token>. On an unclaimed instance, opening it sets the ethos_auth cookie and signs the browser in. On a claimed instance (username and password set at /welcome), the same URL opens /welcome/reset with the token prefilled instead of granting a session. With ETHOS_WEB_TOKEN set in the environment (at least 24 characters — a shorter value stops the boot), the URL is not printed at all.

ethos status​

Single-pane health summary — config, adapters, personalities, error log. Exits non-zero if critical issues are found.

Synopsis: ethos status [--json]

FlagDefaultDescription
--jsonoffMachine-readable JSON output with version, config, adapter status, personality count, and error log summary.
ethos status
ethos status --json

ethos batch​

Run a JSONL of prompts in parallel against the configured agent. Outputs JSONL. Required positional: <tasks.jsonl> — one task per line as {"id": "...", "prompt": "..."}. Optional: --concurrency (default 3), --output (default <input>.output.jsonl), --checkpoint (default <input>.checkpoint.json) — re-running with the same checkpoint skips already-completed tasks.

Synopsis: ethos batch <tasks.jsonl> [--concurrency <n>] [--output <out>] [--checkpoint <cp>]

ethos eval​

Score the agent's output against expected answers. Required: --expected <file> (JSONL keyed by id). Optional: --scorer (exact / contains / regex / llm, default contains), --concurrency (default 3), --output (default <input>.eval.jsonl), --evolve to chain skill evolution after scoring, --auto-approve (with --evolve) to replay each evolved candidate inside the command so one that passes can promote under the auto-promotion rules. Without --auto-approve, candidates wait in the learning inbox.

Synopsis: ethos eval run <tasks.jsonl> --expected <expected.jsonl> [--scorer <name>] [--concurrency <n>] [--output <out>] [--evolve [--auto-approve]]

ethos evolve​

Generate candidate skill files from session history or a prior eval run and submit them to the learning inbox for the default personality. Nothing this command drafts goes live without a pass replay or a human. Supports both a newer subcommand style and the original flag-based style.

Synopsis (subcommand style):

ethos evolve status
ethos evolve run [--quiet]
ethos evolve apply <candidate-id | filename> | --all
ethos evolve prune [--older-than <days>] [--yes]
ethos evolve archive [--older-than <days>]

Synopsis (legacy flag style — backwards-compatible):

ethos evolve --eval-output <file> [--auto-approve]
ethos evolve --list-pending
ethos evolve --approve <candidate-id | filename> | --approve-all
ethos evolve --reject <candidate-id | filename>

Subcommands​

SubcommandDescription
statusShow the last evolver run from ~/.ethos/evolver-history.jsonl and every skill candidate waiting in the learning inbox, with its id, destination filename, personality, origin and verdict. For Expression candidates too, use ethos learning list.
run [--quiet]Export recent sessions (last 7 days from sessions.db), run the evolver against them, and submit candidates to the learning inbox. --quiet suppresses progress output — the cron-safe equivalent of the legacy --eval-output path.
apply <candidate-id | filename> | --allApprove a waiting skill candidate through the learning inbox, named by id or by its destination filename. Approves only a candidate whose replay verdict is pass; any other needs ethos learning approve <id> --override "<reason>". --all tries every waiting skill candidate. A filename that matches more than one candidate is refused.
prune [--older-than <days>] [--yes]Reject waiting skill candidates submitted more than <days> ago (default 7) through the learning inbox. Nothing is deleted: each stays on record as rejected and writes a learning.reject row. Asks for confirmation unless --yes.
archive [--older-than <days>]Move live skill files (~/.ethos/skills/*.md) whose modification time is older than <days> (default 30) into ~/.ethos/skills/.archive/<YYYY-MM-DD>/, with a manifest.json. Measured by file mtime, not by use.

Legacy flags​

FlagDescription
--eval-output <file>Run evolution against a specific eval output file and submit candidates to the learning inbox.
--auto-approveWith --eval-output, replay each new candidate inside the command. A candidate promotes only on a pass verdict, to a personality-scoped destination, when the personality sets no explicit review. The flag stands in for the global autoApprove knob for this run.
--list-pendingList waiting skill candidates from every origin.
--approve <candidate-id | filename>Same as apply: approves a pass candidate only. --accept is an alias.
--approve-allSame as apply --all. --accept-all is an alias.
--reject <candidate-id | filename>Reject a waiting skill candidate. It stays on disk with status rejected.

ethos learning​

Review, replay, approve, reject and roll back learning candidates: every skill and Expression change a learning path drafted. Decisions go through LearningInbox (extensions/learning-inbox/src/inbox.ts), the same class the web dashboard's Learning page and Living Soul section decide through. For what a replay measures and when a candidate promotes itself, see Why does a learned change need a replay before it goes live?.

Synopsis:

ethos learning list [--personality <id>] [--all]
ethos learning show <id>
ethos learning replay <id>
ethos learning approve <id> [--override "<reason>"]
ethos learning reject <id> [--reason "<text>"]
ethos learning rollback <id> [--reason "<text>"]

Subcommands​

SubcommandDescription
listCandidates waiting for a decision (pending_replay, pending_review), newest first. --personality <id> filters to one personality. --all lists every status.
show <id>Evidence, proposed content, the newest replay scorecard with its limitations, rollback availability for a promoted candidate, and the candidate's timeline from audit.jsonl.
replay <id>Replay a waiting candidate now: a dry run of every selected case on real models, which costs money. May auto-promote under the resolver. Refused when learningReplay.enabled is false.
approve <id>Promote a waiting candidate. A verdict other than pass, including never replayed, is refused unless --override "<reason>" is given; the reason is recorded in audit.jsonl. Writes a learning.approve or learning.override row to ethos audit decisions.
reject <id>Mark a waiting, invalid or stale candidate rejected. --reason is optional. Writes learning.reject.
rollback <id>Undo a promotion. A skill gets its prior bytes back, or a created file is removed; refused when the live file was edited since promotion. An Expression reverts only when it is the personality's latest promoted Expression candidate. Writes learning.rollback.

Examples​

ethos learning list --personality researcher
ethos learning approve c-mf3k2x-a1b2c3 --override "replay incomplete: new personality, no case pool yet"
ethos learning rollback c-mf3k2x-a1b2c3 --reason "tone drifted in real chats"

Exit codes​

CodeMeaning
0Success, including a replay that ended in a verdict other than pass.
1Missing id, unknown subcommand, unknown candidate, or a refusal (override_required, stale, invalid, live_edited, not_latest, replay_unavailable).

ethos plugin​

Install, list, and remove individual plugins (npm packages or local paths), and manage the capability grant each install records. For a personality-attachment overview, use ethos plugins.

Synopsis: ethos plugin [list | install <path-or-pkg> [--personality <id>] [--yes] | remove <id> | grants [--json] | revoke <id> | credentials <id>]

A plugin runs inside the ethos process with your files, your environment, and your API keys. Installing one is equivalent to running arbitrary code as your user. The pre-install safety scan is a static read of the source: advisory, not a sandbox. install shows what the plugin declares, takes your consent, and writes a capability grant before any code lands.

Subcommand or flagWhat it does
install <pkg>Downloads to a temp dir, scans, shows the declared capabilities, prompts, then installs. Red scan findings block the install outright.
install <pkg> --yesRecords the grant unattended. Without a TTY (or under ETHOS_MANAGED=1) and without --yes, the install is refused rather than auto-granted.
grants [--json]Lists each recorded grant: source, declared capabilities, consent mode, and the scan findings as they stood at install.
revoke <id>Withdraws a grant. The loader refuses to import that plugin from the next load, and will not auto-install it from a personality lockfile. It cannot undo what an already-loaded plugin did.

ethos skills​

Install ClawHub-compatible skills (markdown files with frontmatter). list (default) shows installed skills. install <slug> installs from ClawHub — slug formats: owner/repo, github:owner/repo, github:owner/repo/path. update [<slug>] updates one or all. remove <slug> uninstalls.

Synopsis: ethos skills [list | install <slug> | update [<slug>] | remove <slug>]

ethos keys​

Manage the API-key rotation pool at ~/.ethos/keys.json (chmod 600). list (default) prints masked keys with labels and priorities. add <key> [--label <name>] [--priority <n>] appends; priority defaults to 50 and higher values are chosen first. remove <index> deletes the 1-based entry.

Synopsis: ethos keys [list | add <api-key> [--label <name>] [--priority <n>] | remove <index>]

ethos set​

Set or inspect the active context that chat and serve route through. ethos set with no args prints the current context. ethos set personality <id> routes to a single personality; ethos set team <name> routes to a team coordinator (start it separately with ethos team start <name>).

Synopsis: ethos set [personality <id> | team <name>]

ethos team​

Manage team manifests and the supervisor lifecycle.

Synopsis: ethos team [list | create <name> | <name> add <personality> | <name> remove <personality> | start <name> | stop <name> | status <name> | logs <name> [--member <id>]]

ethos mesh​

Inspect mesh-level runtime state (live member counts, per-mesh registries).

Synopsis: ethos mesh [list | status <name> | create <name> | destroy <name>]

ethos process​

Inspect and stop background processes started by the process_* tool family inside ethos chat. The subcommands drive the same registry helpers the tools use, so list / logs / stop produce the same output and lifecycle behaviour the agent sees — letting you watch or kill a process from a separate shell.

Synopsis: ethos process [list [--json] | logs <id> [--lines N] [--stream stdout|stderr|both] | stop <id> [--signal SIGTERM|SIGKILL]]

SubcommandArgs / flagsDescription
list (default)--jsonList every tracked process with PID, status, and duration. Runs a liveness check (marks dead running entries as orphan) and reaps stale terminal entries. --json emits the raw array instead of a table.
logs <id>--lines N, --stream stdout|stderr|bothPrint the last N lines (default 200) of a process's logs. --stream selects which log; default both interleaves stdout then stderr.
stop <id>--signal SIGTERM|SIGKILLSignal a running process. Default SIGTERM waits up to 5 s for a graceful exit, then escalates to SIGKILL.
ethos process list
ethos process logs 6f6c1e2a-... --lines 50 --stream stderr
ethos process stop 6f6c1e2a-...

logs and stop exit 1 for an unknown id (PROCESS_NOT_FOUND). The process_* tool family it mirrors — process_start, process_list, process_logs, process_stop, process_wait — is documented in extensions/tools-process/README.md; it is gated by a personality's toolset.yaml like every other tool.

ethos logs​

Inspect and bundle local diagnostics from ~/.ethos/logs/.

Synopsis: ethos logs [list | summary | note | bundle [--out <path>] [--lines <n>] | tail [--lines <n>] [--interval-ms <n>]]

ethos mcp​

Expose Ethos to MCP-compatible clients (Claude Desktop, Cursor, OpenCode, Continue, Zed) over stdio.

Synopsis: ethos mcp [serve | install --client <name> | init | doctor | inspect]

Supported --client values: claude-desktop, cursor, opencode, continue, zed.

ethos backup​

Snapshot ~/.ethos/ into a gzipped tar archive. With no path, writes <ethosDir>/backups/ethos-backup-<timestamp>-<random>.tar.gz. A path argument (or --out) names the archive file; its parent directory is created if missing.

Synopsis: ethos backup [<path>] [--out <path>] [--scope identity,state,telemetry] [--bootstrap] [--json]

FlagTypeDefaultDescription
--out <path>path<ethosDir>/backups/ethos-backup-<timestamp>-<random>.tar.gzWhere to write the archive. Takes precedence over a positional path.
--scope <names>comma-separated listidentity,stateWhich scopes to archive. identity — config.yaml, mcp.json, MEMORY.md, USER.md, cron/jobs.json, personalities/. state — the durable databases (sessions.db, cards.db, memory.db, board.db, jobs.db, calls.db, goals.db, dashboards.db, pairing.db, a2a/tasks.db) plus skills/, teams/, users/, digests/, cron/output/ and the plugin pins. telemetry — observability.db, opt-in.
--bootstrapbooleanoffAlso print the two commands that rebuild this machine somewhere else.
--jsonbooleanoffEmit one JSON document instead of the human report.

Secrets are never archived: secrets/, keys.json, web-token and MCP OAuth token files are excluded, and the archive instead carries a secrets.manifest.yaml naming what a restore must refill. Delivery, dedup and notify-queue databases, cache/, logs/, processes/, plugins/node_modules and previous backups are excluded too.

A state archive contains conversation history. Treat it as sensitive as the machine it came from.

Exits 1 when another backup holds the backups/.lock sentinel, or when --scope names something outside identity / state / telemetry.

ethos import​

Restore an ethos backup archive into ~/.ethos/. The whole archive is streamed and hashed against its manifest before anything is written; a truncated or tampered archive is refused with nothing changed.

Synopsis: ethos import <archive> [--scope <names>] [--dry-run] [--force] [--secrets <file | - | prompt>] [--json]

FlagTypeDefaultDescription
--scope <names>comma-separated listevery scope the archive holdsRestore only these scopes. Same three values as ethos backup.
--dry-runbooleanoffVerify the archive and report what would be restored. Writes nothing — including the exclusive database locks, so it cannot tell you whether a real restore would be refused as in-use.
--forcebooleanoffSkip the in-use check. Nothing then verifies that another process has these databases open.
--secrets <source>path, -, or promptnoneRefill the vault from a manifest file, from stdin, or by answering one question per missing secret. prompt cannot be combined with --json.
--jsonbooleanoffEmit one JSON document instead of the human report.

Before replacing any database, the restore takes an exclusive lock on the live file. Anything holding it — ethos chat, serve, gateway, the desktop app — makes the restore refuse with IMPORT_BLOCKED rather than write over a store in use. An identity-only restore touches no database and so needs no lock, but config.yaml and mcp.json are read at boot: the report says to restart.

Files the restore replaces are moved to .pre-restore/<timestamp>/ rather than deleted.

Exits 1 on a corrupt archive, a locked database, an archive written by a newer format (IMPORT_NEWER_SCHEMA), or a --secrets payload that fails to inject. When secrets injection is the only failure, the restore itself has still completed — the report says which half landed.

There is no --merge. Use --scope to restore part of an archive.

ethos trace​

Inspect persisted traces from the observability store.

Synopsis: ethos trace [list | show <id> | export <id> [--out <path>]]

ethos audit​

Inspect the audit events in ~/.ethos/observability.db, newest first. Source: apps/ethos/src/commands/audit.ts.

Synopsis: ethos audit [transitions | decisions] [--since <duration>] [--category <pattern>] [--limit <n>]

Subcommand / flagDescription
(none)Every audit event, filtered by --category when given.
transitionsEvents in category audit.transition.
decisionsHuman and safety decisions: categories audit.approval, audit.block and audit.watcher, merged. Learning decisions appear here with codes learning.approve, learning.override, learning.reject and learning.rollback, beside outbox decisions (outbox.approve, outbox.reject and the rest). A candidate that promoted itself after a pass replay appears with code learning.auto_promote, attributed to the system.
--since <duration>Only events newer than the duration: an integer plus ms, s, m, h or d (30m, 7d). Any other shape is ignored and no time filter applies.
--category <pattern>Without a subcommand, an exact category. With one, a case-insensitive substring filter on the category.
--limit <n>Maximum events shown. Default 50.
ethos audit decisions --since 7d

ethos security audit​

Run the local security audit — permission diffs, exposed secrets, untrusted skill warnings. --fix auto-remediates the safe subset; --json emits machine-readable findings; --deep runs the slower, network-touching checks. Exits 2 on usage error (e.g. ethos security with no audit arg).

Synopsis: ethos security audit [--fix] [--json] [--deep]

ethos errors​

Inspect ~/.ethos/logs/errors.jsonl.

Synopsis: ethos errors [list | show <id> | clear]

ethos perf​

Per-turn performance summaries from the local trace store.

Synopsis: ethos perf [summary | turn <id>]

ethos bench​

Measure the per-turn context cost of every personality. context is the only subcommand.

Synopsis: ethos bench context [--cwd <dir>] [--live] [--scenario <id>] [--turns <n>] [--write-baseline]

FlagTypeDefaultDescription
--cwd <dir>pathcurrent directoryDirectory the project context (AGENTS.md/CLAUDE.md) is measured in. A personality with its own fs_reach.workdir is measured there instead.
--liveflagoffRun live scenarios against the configured provider and record per-turn token usage.
--scenario <id>stringallRun one live scenario: hi, one-tool, multi-tool or long-session.
--turns <n>integer10Turns for the long-session scenario.
--write-baselineflagoffWrite the results to evals/local/context-baseline.json.

The static table always prints, with no LLM calls:

ColumnMeaning
soul chSOUL.md size in characters.
toolsNumber of tool schemas the personality's toolset exposes.
schema chSerialized tool-schema size in characters.
tool_loading_charsSerialized tools sent on a turn's first step when on-demand tool loading is active.
project chThe ## Project Context block a turn sends from the measured directory.
~tokensThe static prefix at chars/4: SOUL, tool schemas, injection-defense prelude and project context. The same arithmetic as the small-window check (measureStaticFloor).
ethos bench context
ethos bench context --cwd ~/code/my-repo
ethos bench context --live --scenario hi

Exits 1 on an unknown flag or scenario. Source: apps/ethos/src/commands/bench.ts.

ethos tail​

Live-tail every known Ethos log file in one stream.

Synopsis: ethos tail [--lines <n>] [--interval-ms <n>]

ethos retention​

Inspect and run retention sweeps for observability data.

Synopsis: ethos retention [show | run [--dry-run]]

ethos data​

Inspect local data stores (sessions, traces, blobs).

Synopsis: ethos data [stats | sessions | traces]

ethos support​

Generate a support bundle for issue triage.

Synopsis: ethos support [bundle [--out <path>]]

ethos archive​

Manage long-term archive of sessions and memory.

Synopsis: ethos archive [list | create | restore <id>]

ethos systemd-unit​

Generate a systemd service unit file for a given Ethos command. Writes to stdout — pipe to a file or inspect before installing.

Synopsis: ethos systemd-unit <name>

NameDescription
ethos-gatewayUnit for ethos gateway start.
ethos-serveUnit for ethos serve.
ethos-runallUnit for ethos run-all.

Placeholder values are substituted from environment variables at generation time:

PlaceholderEnv varDefault
{{ETHOS_BINARY}}ETHOS_BINARYethos
{{ETHOS_USER}}ETHOS_USER$USER (current user)
{{ETHOS_HOME}}ETHOS_HOME$HOME

Generated units set ETHOS_MANAGED=1 and load ~/.ethos/.env via EnvironmentFile. Values containing newlines are rejected.

ethos systemd-unit ethos-gateway > ~/.config/systemd/user/ethos-gateway.service
systemctl --user daemon-reload
systemctl --user enable --now ethos-gateway

ethos usage​

Aggregate token usage and estimated cost over a time window. Tokens and cost come from ~/.ethos/sessions.db; turn outcomes and the tool/skill breakdowns come from ~/.ethos/observability.db (zeros when it is absent).

Synopsis: ethos usage --since <duration> [--by <dimension>] [--json]

FlagRequiredDescription
--since <duration>yesTime window ending now. Format: Nh (hours), Nd (days), Nm (minutes). Example: 24h, 7d, 30m. A missing or malformed value exits 2.
--by <dimension>noAdd a breakdown: day, model, personality, channel, session, tool, or skill. An unknown dimension exits 2.
--jsonnoEmit one JSON object instead of a human-readable summary.

Cost is the sum of the stored message rows in the window: LLM usage on assistant rows plus any cost a tool reported (cost_usd) on its tool-result row. A forked session's copied history carries no usage, so a fork does not count its source's spend twice.

--json fields (UsageResult in apps/ethos/src/commands/usage.ts):

FieldTypeDescription
since, untilISO-8601 stringThe window, half-open [since, until).
totalsobjectinputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, estimatedCostUsd, messages (rows carrying usage), and cacheHitRate (0–1: cache reads over input + cache reads + cache writes).
dailyarrayOne row per UTC day: key (YYYY-MM-DD) plus the same token, cost and messages fields as totals, without cacheHitRate.
outcomesobjectTurn counts from observability: completed, errored, halted, running.
byobject, only with --by{ dimension, rows }. For day/model/personality/channel/session, rows have the daily row shape keyed by that dimension. For tool: { tool, calls, errors }. For skill: { skill, invoked, exposed }.
ethos usage --since 7d
ethos usage --since 24h --by model --json

The web dashboard shows the same totals under Activity → Usage. Both fold the rows with summarizeUsageRows in extensions/session-sqlite/src/index.ts. Turn outcomes and --by tool|skill are CLI-only.

ethos claw​

Migrate an OpenClaw install (~/.claw/) into Ethos (~/.ethos/). Idempotent.

Synopsis: ethos claw migrate [--dry-run] [--preset all|user-data] [--overwrite] [--yes]

--dry-run prints the plan without writing. --preset all (default) migrates everything; user-data skips personalities. --overwrite replaces existing Ethos files on conflict. --yes / -y skips the confirmation prompt.

ethos doctor​

Diagnostic — checks config, provider auth, session-store integrity, key permissions. It opens every database without migrating it, so a newer version's ethos doctor leaves the databases readable by the older one. ethos gateway status follows the same rule.

Synopsis: ethos doctor [--json]

ethos upgrade​

Install the newest published @ethosagent/cli (or the version --version names) and keep it only if it passes ethos doctor. A source clone gets the git pull / pnpm install / pnpm build steps instead, and nothing is installed.

Synopsis: ethos upgrade [--version <version-or-tag>] [--no-rollback]

FlagRequiredDescription
--version <version-or-tag>noInstall this exact version (0.7.3) or dist-tag (next) instead of latest. The registry resolves it to one exact version, which the health gate checks for. A range (^0.7) is refused before the registry is contacted.
--no-rollbacknoKeep the new version even if its health check fails. The failed checks and the way back are still printed.

The registry is npm_config_registry when set, otherwise https://registry.npmjs.org. An npm-global upgrade runs these steps in order. Source: apps/ethos/src/commands/upgrade.ts.

StepWhat runsOn failure
BaselineThe running binary's ethos doctor --json.Stops. Nothing is installed.
Backupethos backup into the backup directory. The archive path is printed.Stops. Nothing is installed.
Installnpm install -g @ethosagent/cli@<resolved-version>.Exits with npm's exit code.
Health gateThe new binary's ethos doctor --json, run from $(npm root -g)/@ethosagent/cli with the current Node, never from PATH. On success, prints What changed: with the changelog entry for the new version.Rolls back, unless --no-rollback.
Rollbacknpm install -g @ethosagent/cli@<previous>, then the old binary's doctor, which must match the baseline.Prints the archive path and the ethos import <archive> command.

The health gate rolls back on any of these:

TriggerDoctor JSON field
The new doctor cannot be started, or prints no JSON report—
The new binary reports a different version than the one installedversion.version
A core SDK does not loadsdks[].loadable where required
sessions.db does not opendb.ok === false
A database fails PRAGMA integrity_checkstoreIntegrity[].status === 'failed'
A check that passed in the baseline now fails: a configured channel SDK, a secret, AWS Secrets Manager, secrets-directory permissions, a rejected channel token, call-capture dependencies, or the inbound spool failing to opensdks[], secrets[], awsSecrets, secretsDir, channels[].reason === 'rejected', callCapture, inboundSpool.status

These are printed and never roll back: a check that already failed in the baseline, a stale gateway or call-capture heartbeat, an unreachable or rate-limited channel, and owed inbound-spool messages for a bot no longer configured.

ethos upgrade never restarts a gateway and never restores a backup. If a gateway is running, restart it yourself to run the new version, for example systemctl --user restart ethos-gateway. Roll back before that restart: a gateway on the new version may migrate its databases, and the older version then refuses to open them.

Exit codeMeaning
0Upgraded, already on the target version, or a source clone.
1--version missing its value or not a version or dist-tag, registry unreachable or the version unpublished, baseline or backup failed, the new version failed its health gate (rolled back, or kept with --no-rollback), or the rollback failed.
npm's exit codenpm install -g of the new version failed. Nothing was changed.
ethos upgrade
ethos upgrade --no-rollback
ethos upgrade --version 0.7.3
npm_config_registry=http://localhost:4873 ethos upgrade

Exit codes​

CodeMeaning
0Success.
1Generic error (missing config, command parse error, network failure, unknown subcommand).
2Managed-mode error: ETHOS_MANAGED=1 is set but config is missing, or a usage error (e.g. ethos security with no audit arg).
3ethos gateway start or ethos boot refused: another gateway holds this Ethos home's lock. ethos run-all and the desktop app treat it as "already running", not as a crash.
130Interrupted (Ctrl+C / SIGINT).

Errors land on stderr formatted by formatError(toEthosError(err)) and are appended to ~/.ethos/logs/errors.jsonl. Set ETHOS_DEBUG=1 for verbose tracing.

Environment variables​

VariableDescription
ANTHROPIC_API_KEYAnthropic provider key (overrides apiKey for Anthropic).
OPENAI_API_KEYOpenAI / OpenAI-compat provider key.
OPENROUTER_API_KEYOpenRouter key.
ETHOS_DEBUG1 prints hook events, token counts, and prompt diagnostics.
ETHOS_STATE_DIROverride the ~/.ethos/ location entirely. Read by ethosDir() in packages/config/src/index.ts.
ETHOS_VERSIONOverride the reported version string (dev runs).
ETHOS_DEDUP_LEGACY1 disables the outbound dedup cache (rollback hatch).
ETHOS_MANAGED1 runs in managed mode — skip interactive setup, exit 2 if config is missing. Used by systemd units and orchestration playbooks.
ETHOS_BINARYOverride the ethos binary path in ethos systemd-unit output.
ETHOS_USEROverride the service user in ethos systemd-unit output.
ETHOS_HOMEOverride the home directory in ethos systemd-unit output and EnvironmentFile path.
ETHOS_ENV_FILEOverride the .env file path for secrets loading (default: ~/.ethos/.env).

See also​