Skip to content

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.

CommandRunsCadence
/echo:setupOnce per project, from that repo’s rootOne-time onboarding (safe to re-run — reconfigure)
/echo:tickThe hub, over every enabled projectEvery cron firing (e.g. every 30 min)
/echo:statusThe hub, read-onlyAny time you want a fleet snapshot

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/:

  1. Inspectinspect.sh --json cold-detects the repo slug, owner, and sensible proposals.
  2. 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.
  3. Preflightpreflight.sh prints an error list (not a first-failure throw); onboarding stops until a re-run exits 0.
  4. Provisionprovision_board.sh --dry-run first, then the same command for real on your confirmation.
  5. Write + registerwrite_config.sh (timestamped backup) then register_project.sh (replaces, never appends, an existing entry). Posts a one-time Discord confirmation to the configured channel.
  6. Bootstrap CLAUDE.md — offered only if Phase 1 recommended it.
  7. Final gatetick.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]
> create
Title 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 options
Confirm? (y/n) > y
PROJECT_NUMBER=42
✅ Echo connected to widgets — escalations will arrive here.
$ tick.sh --dry-run --project widgets
widgets: dispatch (0 Ready, 0/2 slots)
tick: sweep complete

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:

ArgumentEffect
--dry-runReport 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 items
sensors: slots full (2/2)
tick: sweep complete

If 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.

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|—>
FieldMeaning
nameRegistry entry name ((unnamed) if none).
enabledThe registry enabled flag.
boardready: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.
workersLive worker count over configured slots (same pid-glob + kill -0 liveness check the tick uses); ? in either position means it couldn’t be resolved.
outboxFiles queued in this project’s Discord outbox (0 if the directory doesn’t exist yet).
lastmtime 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:11Z
demo enabled=false board=disabled workers=0/2 outbox=0 last=—
sensors enabled=true board=? workers=?/2 outbox=1 last=2026-07-15T09:20:00Z