Commands
Echo ships exactly three slash commands. They are thin front-ends: each one
relays to a deterministic script under skills/echo-core/scripts/ and, for
/echo:setup and /echo:tick, owns the handful of steps that genuinely need
the session (an interview, or holding the Discord MCP tool). Nothing about the
backlog mechanics lives in the command prose.
| Command | Runs | Cadence |
|---|---|---|
/echo:setup | Once per project, from that repo’s root | One-time onboarding (safe to re-run — reconfigure) |
/echo:tick | The hub, over every enabled project | Every cron firing (e.g. every 30 min) |
/echo:status | The hub, read-only | Any time you want a fleet snapshot |
/echo:setup
Section titled “/echo:setup”What it does. Onboards the current repo onto Echo: inspects the repo,
interviews you one question at a time, provisions (or adopts) a GitHub Projects
v2 board, writes .claude/echo/config.json, and registers the project in the
Echo registry. Ends by running tick.sh --dry-run as a final gate so you can
see the project is dispatchable before you walk away.
When to run it. Once, from the root of the repo you want Echo to drive. Run
it again any time you want to reconfigure — an existing .claude/echo/config.json
puts the command into reconfigure mode, where every write backs up the prior
file with a timestamp first, so nothing is silently clobbered. Provisioning is
create-if-missing, so re-running is always safe.
Phases. The command walks a fixed sequence, each phase shelling out to a
script under skills/echo-core/scripts/setup/:
- Inspect —
inspect.sh --jsoncold-detects the repo slug, owner, and sensible proposals. - Interview — the one genuinely conversational phase: repo, board
(create/adopt), status columns, priorities/category map, supporting repos,
deploy verification (
none/command/k8s-argocd), gated paths, LESSONS location, Discord channel, worker caps, and the (default-off) validator. - Preflight —
preflight.shprints an error list (not a first-failure throw); onboarding stops until a re-run exits 0. - Provision —
provision_board.sh --dry-runfirst, then the same command for real on your confirmation. - Write + register —
write_config.sh(timestamped backup) thenregister_project.sh(replaces, never appends, an existing entry). Posts a one-time Discord confirmation to the configured channel. - Bootstrap CLAUDE.md — offered only if Phase 1 recommended it.
- Final gate —
tick.sh --dry-run --project <name>.
Arguments. None. Run it from the project root ($(pwd) is the project).
Example (interview excerpt + final gate).
This looks like acme/widgets — is that right? (or paste owner/repo)> [Enter]Is `acme` an org or a user account? (org/user) [org]> [Enter]Create a new GitHub Project for this, or use an existing one's number? [create]> createTitle for the new project? [widgets]> Widgets Backlog...[DRY-RUN] would: create project "Widgets Backlog" under org acme[DRY-RUN] would: add single-select field "Status" with 8 optionsConfirm? (y/n) > yPROJECT_NUMBER=42✅ Echo connected to widgets — escalations will arrive here.
$ tick.sh --dry-run --project widgetswidgets: dispatch (0 Ready, 0/2 slots)tick: sweep complete/echo:tick
Section titled “/echo:tick”What it does. One hub sweep across every enabled registered project.
For each project it runs the global gates once, then the per-project fast-paths
(worker cap, free slots, a Ready column), and hands any dispatchable project to
dispatch_one.sh. After the sweep it does two Discord chores that need the
session’s MCP tool: it drains each project’s escalation outbox (posting
Needs-Human items to Discord) and routes inbound operator replies (a
quote-reply to an escalation re-queues that item from Needs Human back to
Ready with the reply text as guidance).
When to run it. On a timer — this is the heartbeat. A cron firing calls
/echo:tick (e.g. every 30 minutes); a worker also chain-triggers a sweep the
moment it frees a slot, so freed capacity or new Ready work elsewhere gets
picked up without waiting for the next cron tick.
Arguments. Pass-through to tick.sh:
| Argument | Effect |
|---|---|
--dry-run | Report what each project would do; dispatch nothing. |
--project <name> | Restrict the sweep to a single registered project by name. |
Important: the tick is never handed to a subagent. The sweep and its
dispatch pipeline are deterministic scripts; the one true LLM-judgment step
(pre-validate) already shells out to claude from inside the pipeline.
Example output. One status line per enabled project, then a terminal line:
widgets: dispatch (3 Ready, 0/2 slots)demo: no Ready itemssensors: slots full (2/2)tick: sweep completeIf a global gate blocks the whole sweep (registry unreadable, GraphQL budget
low), you get that message instead of per-project lines. Drained escalations
surface as Discord posts; routed replies surface as routed: lines in the
summary.
/echo:status
Section titled “/echo:status”What it does. Prints one row per registered project — a read-only fleet
snapshot with no dispatch side effects. It runs status.sh, which does at
most one board read per enabled project (a single unfiltered
list_backlog.sh --json --limit 1000, grouped client-side by that project’s
own configured status names — never three separate per-status reads).
When to run it. Any time you want to see the fleet at a glance — how many items are Ready / In progress / Needs Human, how many worker slots are live, and whether anything is queued in a project’s Discord outbox.
Arguments. None.
Row shape.
<name> enabled=<bool> board=<...> workers=<alive>/<slots> outbox=<n> last=<mtime|—>| Field | Meaning |
|---|---|
| name | Registry entry name ((unnamed) if none). |
| enabled | The registry enabled flag. |
| board | ready:N,inprogress:N,needshuman:N from one board read; disabled if the project is off (board read skipped entirely — no GitHub call); ? if the board or the project’s config couldn’t be read. |
| workers | Live worker count over configured slots (same pid-glob + kill -0 liveness check the tick uses); ? in either position means it couldn’t be resolved. |
| outbox | Files queued in this project’s Discord outbox (0 if the directory doesn’t exist yet). |
| last | mtime of the newest run directory under the project’s agent-runs dir, or — if it has never run. |
A single broken or corrupt project degrades its own row toward ?
placeholders but never stops the sweep — every other project still gets a row.
Only a genuinely global failure (the registry itself unreadable) aborts the
script (exit 3); in that case it suggests /echo:setup.
Example output.
widgets enabled=true board=ready:3,inprogress:1,needshuman:0 workers=1/2 outbox=0 last=2026-07-16T14:02:11Zdemo enabled=false board=disabled workers=0/2 outbox=0 last=—sensors enabled=true board=? workers=?/2 outbox=1 last=2026-07-15T09:20:00Z