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
| Flag | Aliases | Description |
|---|---|---|
--version | -v | Print 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 | -h | Print 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]
| Arg | Description |
|---|---|
| (none) | Start the wizard at the first step. |
auth | Jump to the auth step. |
model | Jump to the model-selection step. |
personality | Jump to the personality step. |
messaging | Jump to the channel-credentials step. |
memory | Jump to the memory-backend step. |
providers | Jump to the provider-chain step. |
keys | Jump 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]
| Flag | Default | Description |
|---|---|---|
--verbose | off | Print 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 config | Override the resolved skin for this process only. Does not write to disk. |
-q <prompt>, --query <prompt> | off | Single-query mode. Stream one answer and exit. --query=<prompt> is also accepted. |
--continue, -c | off | Resume the most recent CLI session. Scoped to cli:* sessions only — never crosses into gateway sessions. |
--resume <id>, -r <id> | off | Resume 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-hint | off | Suppress the session-resume hint printed on exit. |
--team <name> | off | Override activeContext for this process, routing through a team coordinator. Does not write to disk. |
--dry-run | off | Dry-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>]
| Subcommand | Description |
|---|---|
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>]
| Subcommand | Args | Description |
|---|---|---|
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.
| Flag | Type | Default | Description |
|---|---|---|---|
--json | boolean | off | Print { 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. |
| Output | Exit code | Meaning |
|---|---|---|
running (pid N, heartbeat 4s ago) | 0 | A 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 over | 1 | The lock names a process that is gone. |
stopped | 1 | No lock. |
unhealthy (pid N alive, heartbeat 95s old) | 2 | A 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.
| Subcommand | Args | Effect |
|---|---|---|
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 / flag | Type | Default | Description |
|---|---|---|---|
doctor | subcommand | — | Preflight only: engine, model files, capture device, server reachability, player. Exits without starting anything. |
--url <ws url> | string | derived from webBaseUrl, else http://127.0.0.1:3000 | Satellite lane URL. A bare origin gets /satellite/ws appended; a URL with a path is taken as given. |
--route <id> | string | unpinned — every enabled route may address this host | Pins 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> | string | the enumerated default | Capture device. This build enumerates stdin only. |
--play <command> | string | the first of ffplay, aplay, play found on PATH | Command 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. |
--json | flag | off | doctor 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:
| Code | Meaning |
|---|---|
0 | Preflight clean. |
1 | Hard 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. |
2 | Warning — 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:
| Flag | Required | Description |
|---|---|---|
--name, -n <name> | yes | Human-readable job name. |
--schedule, -s <expr> | yes | 5-field cron expression (e.g. 0 8 * * *). |
--prompt, -p <text> | yes | Prompt the agent runs on each fire. |
--personality <id> | no | Override the default personality for this job. |
--deliver <target> | no | Delivery target (Telegram chat id, Slack channel, etc.). |
--active-hours <HH:MM-HH:MM> | no | Daily 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>]
| Flag | Default | Description |
|---|---|---|
--port <n> | 3000 | HTTP port. |
--bind <addr> | 127.0.0.1 | Bind address. Use 0.0.0.0 to expose to LAN; still token-protected. |
--team <name> | off | Resolve the agent against a team coordinator and mesh. |
--mesh <name> | default | Explicit 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]
| Flag | Default | Description |
|---|---|---|
--json | off | Machine-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
| Subcommand | Description |
|---|---|
status | Show 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> | --all | Approve 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
| Flag | Description |
|---|---|
--eval-output <file> | Run evolution against a specific eval output file and submit candidates to the learning inbox. |
--auto-approve | With --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-pending | List waiting skill candidates from every origin. |
--approve <candidate-id | filename> | Same as apply: approves a pass candidate only. --accept is an alias. |
--approve-all | Same 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
| Subcommand | Description |
|---|---|
list | Candidates 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
| Code | Meaning |
|---|---|
0 | Success, including a replay that ended in a verdict other than pass. |
1 | Missing 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 flag | What 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> --yes | Records 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]]
| Subcommand | Args / flags | Description |
|---|---|---|
list (default) | --json | List 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|both | Print 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|SIGKILL | Signal 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]
| Flag | Type | Default | Description |
|---|---|---|---|
--out <path> | path | <ethosDir>/backups/ethos-backup-<timestamp>-<random>.tar.gz | Where to write the archive. Takes precedence over a positional path. |
--scope <names> | comma-separated list | identity,state | Which 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. |
--bootstrap | boolean | off | Also print the two commands that rebuild this machine somewhere else. |
--json | boolean | off | Emit 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]
| Flag | Type | Default | Description |
|---|---|---|---|
--scope <names> | comma-separated list | every scope the archive holds | Restore only these scopes. Same three values as ethos backup. |
--dry-run | boolean | off | Verify 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. |
--force | boolean | off | Skip the in-use check. Nothing then verifies that another process has these databases open. |
--secrets <source> | path, -, or prompt | none | Refill the vault from a manifest file, from stdin, or by answering one question per missing secret. prompt cannot be combined with --json. |
--json | boolean | off | Emit 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 / flag | Description |
|---|---|
| (none) | Every audit event, filtered by --category when given. |
transitions | Events in category audit.transition. |
decisions | Human 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]
| Flag | Type | Default | Description |
|---|---|---|---|
--cwd <dir> | path | current directory | Directory the project context (AGENTS.md/CLAUDE.md) is measured in. A personality with its own fs_reach.workdir is measured there instead. |
--live | flag | off | Run live scenarios against the configured provider and record per-turn token usage. |
--scenario <id> | string | all | Run one live scenario: hi, one-tool, multi-tool or long-session. |
--turns <n> | integer | 10 | Turns for the long-session scenario. |
--write-baseline | flag | off | Write the results to evals/local/context-baseline.json. |
The static table always prints, with no LLM calls:
| Column | Meaning |
|---|---|
soul ch | SOUL.md size in characters. |
tools | Number of tool schemas the personality's toolset exposes. |
schema ch | Serialized tool-schema size in characters. |
tool_loading_chars | Serialized tools sent on a turn's first step when on-demand tool loading is active. |
project ch | The ## Project Context block a turn sends from the measured directory. |
~tokens | The 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>
| Name | Description |
|---|---|
ethos-gateway | Unit for ethos gateway start. |
ethos-serve | Unit for ethos serve. |
ethos-runall | Unit for ethos run-all. |
Placeholder values are substituted from environment variables at generation time:
| Placeholder | Env var | Default |
|---|---|---|
{{ETHOS_BINARY}} | ETHOS_BINARY | ethos |
{{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]
| Flag | Required | Description |
|---|---|---|
--since <duration> | yes | Time window ending now. Format: Nh (hours), Nd (days), Nm (minutes). Example: 24h, 7d, 30m. A missing or malformed value exits 2. |
--by <dimension> | no | Add a breakdown: day, model, personality, channel, session, tool, or skill. An unknown dimension exits 2. |
--json | no | Emit 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):
| Field | Type | Description |
|---|---|---|
since, until | ISO-8601 string | The window, half-open [since, until). |
totals | object | inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, estimatedCostUsd, messages (rows carrying usage), and cacheHitRate (0–1: cache reads over input + cache reads + cache writes). |
daily | array | One row per UTC day: key (YYYY-MM-DD) plus the same token, cost and messages fields as totals, without cacheHitRate. |
outcomes | object | Turn counts from observability: completed, errored, halted, running. |
by | object, 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]
| Flag | Required | Description |
|---|---|---|
--version <version-or-tag> | no | Install 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-rollback | no | Keep 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.
| Step | What runs | On failure |
|---|---|---|
| Baseline | The running binary's ethos doctor --json. | Stops. Nothing is installed. |
| Backup | ethos backup into the backup directory. The archive path is printed. | Stops. Nothing is installed. |
| Install | npm install -g @ethosagent/cli@<resolved-version>. | Exits with npm's exit code. |
| Health gate | The 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. |
| Rollback | npm 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:
| Trigger | Doctor JSON field |
|---|---|
| The new doctor cannot be started, or prints no JSON report | — |
| The new binary reports a different version than the one installed | version.version |
| A core SDK does not load | sdks[].loadable where required |
sessions.db does not open | db.ok === false |
A database fails PRAGMA integrity_check | storeIntegrity[].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 open | sdks[], 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 code | Meaning |
|---|---|
0 | Upgraded, 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 code | npm 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
| Code | Meaning |
|---|---|
0 | Success. |
1 | Generic error (missing config, command parse error, network failure, unknown subcommand). |
2 | Managed-mode error: ETHOS_MANAGED=1 is set but config is missing, or a usage error (e.g. ethos security with no audit arg). |
3 | ethos 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. |
130 | Interrupted (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
| Variable | Description |
|---|---|
ANTHROPIC_API_KEY | Anthropic provider key (overrides apiKey for Anthropic). |
OPENAI_API_KEY | OpenAI / OpenAI-compat provider key. |
OPENROUTER_API_KEY | OpenRouter key. |
ETHOS_DEBUG | 1 prints hook events, token counts, and prompt diagnostics. |
ETHOS_STATE_DIR | Override the ~/.ethos/ location entirely. Read by ethosDir() in packages/config/src/index.ts. |
ETHOS_VERSION | Override the reported version string (dev runs). |
ETHOS_DEDUP_LEGACY | 1 disables the outbound dedup cache (rollback hatch). |
ETHOS_MANAGED | 1 runs in managed mode — skip interactive setup, exit 2 if config is missing. Used by systemd units and orchestration playbooks. |
ETHOS_BINARY | Override the ethos binary path in ethos systemd-unit output. |
ETHOS_USER | Override the service user in ethos systemd-unit output. |
ETHOS_HOME | Override the home directory in ethos systemd-unit output and EnvironmentFile path. |
ETHOS_ENV_FILE | Override the .env file path for secrets loading (default: ~/.ethos/.env). |
See also
config.yamlreference — every field these subcommands read and write.- Back up and restore an Ethos install —
ethos backupandethos importend to end, including the move to a new machine. - Personality config reference — the per-personality
config.yaml+toolset.yamlthatethos personalityscaffolds. - Slash commands reference — every
/commandavailable insideethos chat. - Glossary — every Ethos term in one definition list, anchored for cross-page links.