Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions docs/design/c18-cli-grammar.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ The table shows each verb's targets and the flags this design renames or adds. E
| `get <kind> [name…] [--unfinished]` | every kind | `list`, `list --unfinished`, `events`, `explain` (no code) |
| `describe <target>` | every kind | `status`, `step show`, `explain <code>`, `inspect <artifact>` |
| `explain <kind>[.field…]` | every kind | — (new: field docs from the kind's schema) |
| `wait <target> --for=<condition> [--timeout]` | run, step | — (new; replaces polling `status`) |
| `wait <target> --for=<condition> [--timeout <duration>]` | run, step | — (new; replaces polling `status`) |
| `start workflow/<name> \| <path>` | workflow, artifact | `start` |
| `execute workflow/<name> \| <path>` | workflow, artifact | `run` (L5) |
| `continue [run/<id>] [--wait]` | run | `continue`, `worker --run --until-parked` |
Expand All @@ -68,7 +68,7 @@ The table shows each verb's targets and the flags this design renames or adds. E
| `init` | — | `init` |
| `version` | — | `capabilities` |

`get` prints a table, one row per item, with columns from the kind's entry (L3); `-o wide` adds the low-priority columns. `describe` prints everything a person needs about one item: for a run, its status, position, the arrow into it, what it needs, the outstanding invocation, the commands that answer it, and whether a worker is alive. It is the one call that answers "what is this run doing and what do I do?" (`describe run/X -o json` gives the same as an object, which kubectl lacks.) `get events --run X` replaces `events`, as `get` with a parent (L6). `get runs --unfinished` keeps today's `list --unfinished`: only unfinished runs, newest first, filtered in the store rather than by the caller. `get runs` keeps `list`'s tree in text output: a child run is indented under its parent, in sibling order, with the parent node named. `start` and `execute` take the same targets `start` and `run` take today: `workflow/<name>` for a workflow in the project folder, or a path to a workflow folder, its `workflow.dot` or a built artifact. As today, an existing path wins: a target that names an existing file or folder is that path, and only otherwise is it a workflow name in the project folder. `workflow/<name>` always means the name. When two folders share a workflow name, which a lookup by name refuses with `workflow.ambiguous`, `get workflows -o name` prints each of them by its path, which every workflow verb takes.
`get` prints a table, one row per item, with columns from the kind's entry (L3); `-o wide` adds the low-priority columns. `describe` prints everything a person needs about one item: for a run, its status, position, the arrow into it, what it needs, the outstanding invocation, the commands that answer it, and whether a worker is alive. It is the one call that answers "what is this run doing and what do I do?" (`describe run/X -o json` gives the same as an object, which kubectl lacks.) `describe` changes nothing and shows nothing stale (#268). At a held async step whose action has a probe, it runs the probe. On a `dead` answer it records nothing. The attempt is still held: its token still reports, and no effect rule has moved the run. So `describe` shows the attempt as held, next to the probe's `dead` answer and when it was checked, and says what the expiry will do once a verb that writes records it. Those verbs are `serve`'s sweep, `claim` and `continue`, as today. Its commands are only those that apply now, such as `continue run/X`, which expires the attempt and goes on, and never one that applies only after the expiry, such as `resolve` on an `unsafe` step. Through 0.4 the `status` alias keeps `--no-probe` as in 0.2, a read of the store that runs no probe (L9). `wait` reads the same live view. `continue run/X` works the run until it parks, as `worker --run --until-parked` does: it runs watches, and exits 6 at a held step with no watch, or one whose watch says the holder waits for a person. At a step another worker runs, it waits for that worker's result instead of returning exit 0, so it replaces `worker --run --until-parked` exactly (#268). `get events --run X` replaces `events`, as `get` with a parent (L6). `get runs --unfinished` keeps today's `list --unfinished`: only unfinished runs, newest first, filtered in the store rather than by the caller. `get runs` keeps `list`'s tree in text output: a child run is indented under its parent, in sibling order, with the parent node named. `start` and `execute` take the same targets `start` and `run` take today: `workflow/<name>` for a workflow in the project folder, or a path to a workflow folder, its `workflow.dot` or a built artifact. As today, an existing path wins: a target that names an existing file or folder is that path, and only otherwise is it a workflow name in the project folder. `workflow/<name>` always means the name. When two folders share a workflow name, which a lookup by name refuses with `workflow.ambiguous`, `get workflows -o name` prints each of them by its path, which every workflow verb takes.

Why settled: these are kubectl's read verbs (`get`, `describe`, `explain`, `wait`) and Temporal's run verbs (`start`, `execute`, `signal`, `cancel`), with spore's own `resolve`, `continue`, `claim` and the step-worker verbs kept as they are. `serve` is the usual name for a long-running process that does work for others; `worker` stays the kind it serves as (`get workers`). `version` follows `kubectl version`. `version -o json` keeps the whole handshake `capabilities` gives today, `actionKinds`, `asyncKinds` and `resultChannels` included, so a caller such as `ml-workflow` checks the host in one call before it starts anything. Action kinds (`command`, `external`, `native`) are host capabilities, not kinds a verb targets, so they stay in that handshake and are never rows of `get kinds`.

Expand Down Expand Up @@ -140,10 +140,11 @@ Why settled: kubectl's lesson is that CRDs (new kinds as data) carry most of the
**Decision.**
- **Flags.** `--data` is every JSON payload (`signal`, `resolve`, `complete`), `--outcome` every outcome, `--key` every repeat-safety key (`start`, `signal`, `resolve`, `cancel`), `--fail`/`--error` both become `--error`, `--run` every run scope. Worker tuning flags (`--lease-ms`, `--heartbeat-ms`, `--poll-ms`, `--grace-ms`, `--stop-grace-ms`, `--max-watches`) belong to `serve`, `execute` and `continue` only; `--db` stays global; `--native-kernel` becomes the `RUNSPORE_NATIVE_KERNEL` variable. Every flag and argument has help text and every verb has examples.
- **Output.** `-o text|wide|json|jsonl|name`; `--json` stays as `-o json`. `--fields a,b` selects fields in JSON. `-o jsonl` streams one object per line and ends with `{"type":"done"}`; it is the form of `wait` and `continue --wait`. Results go to stdout, progress and warnings to stderr.
- **Durations.** `wait --timeout` takes a number with a unit (`500ms`, `30s`, `5m`, `1h`, or a compound such as `1m30s`), as kubectl does. A bare number is refused with exit 2 and an error that shows the fix: `--timeout 30 has no unit; give one, e.g. 30s or 5m (units: ms, s, m, h)`. Leaving out `--timeout` waits at most 30s, and `0s` checks once and exits. No value waits forever (#268).
- **Errors.** Under `-o json` every error is JSON, including usage errors, and names the argument. Each error has a stable code, the current state, `retryable`, and the exact next commands. Writes print `kind/name verbed` and the next command.
- **Exit codes.** The 0.2 table stays, plus `7` cancelled (c08), `8` timeout (`wait`), and `9` not found from 0.5. Until then anything not found, a run, a workflow or a code, stays exit 2 as in 0.2, since `start`, `validate` and other 0.2 command names are unchanged and L9 keeps them working.
- **Dry run.** `--dry-run` on `signal`, `resolve`, `cancel` and `release` prints what the command would change and the resulting status, writing nothing: the event `signal`, `resolve` and `cancel` would record, and for `release`, which records no event, the quarantine it would clear.
- **One source.** `--help`, `help -o json`, shell completion and the CLI reference in `spec/cli.md` come from the registry and the clap tree.
- **One source.** `--help`, `help -o json`, shell completion and the CLI reference come from the registry and the clap tree. The reference is its own generated file, `spec/cli-reference.md`, with each verb's synopsis, flags, kinds and examples. CI fails when the committed file is out of date. `spec/cli.md` stays hand-written, keeps the prose contracts and links to the reference (#268).

Why settled: gh (`--json fields`), kubectl (`-o`, `wait --for`), terraform and cargo (JSON streams with a final event), Restate's 2026 CLI revamp (typed exit codes, next steps, dry run), Cloudflare's `cf` (generated from one schema) and Arcjet's guidance all converge on these. They fix the 0.2 gaps listed in §Why.

Expand All @@ -161,7 +162,7 @@ Tickets in build order, each one release of the CLI that works end to end:
2. **The rest of the core kinds.** `step`, `signal`, `event`, `invocation`, `workflow`, `artifact`, `worker`, `quarantine`, `code`; `explain`; `get all`; `-o name` round trips. About 1 day.
3. **Action verbs and aliases.** `start`, `execute`, `continue`, `signal`, `resolve`, `release`, step-worker verbs, `serve`, `validate`, `build`, `cat`, `version`; the unified flags; every 0.2 command as an alias. About 1 day.
4. **`wait`, `cancel`, `-o jsonl`, dry run, exit codes 7–9, JSON usage errors.** About 1 day; `cancel` is #74.
5. **Generated reference and completion.** `help -o json`, completion scripts, `spec/cli.md` generated. Half a day.
5. **Generated reference and completion.** `help -o json`, completion scripts, `spec/cli-reference.md` generated, with a CI check that it is current. Half a day.

The `ml-workflow` plugin and `work/build-0-2/` move to the new forms with ticket 3. Plugin loading is M6 and not part of this list.

Expand Down
Loading