ADR-507: The ways commands regroup into operator commands and six groups; names another process calls stay fixed¶
Summary¶
- Decided:
ways --helpshows 14 commands a person types. Settings, targets, the agent, sessions, authoring and tuning are groups (settings,target,agent,session,author,tune). Hook and script plumbing is hidden. A command keeps its current name when something that does not reload with the binary calls it: a running session's hook table, a runningattend, the previous release's updater, or a projected script. Every other caller moves in the same change, with no aliases (option (a) below). - Trades away: old names stop working on the release that ships this. Operator habits (
ways disable <id>,ways lint,ways tune) break, and the release notes are the bridge. Four commands stay at the top level for stability rather than tidiness:context,init,corpusandreconcile. - One-way? Expensive, not one-way. Names can move again, but every move churns callers, skills, ways and agent habits. The fixed names become a contract.
- Probes: Confident: the operator wants
ways --helpto show what a person types, with the hook plumbing out of sight. Not confident: whether retiringways disable <id>andways enable <id>in favour ofways settings set ways.project.<id> falseandunsetis acceptable for a command typed this often. - Inversion: one end is today's flat list of 37, where nothing is renamed and nothing is findable. The other end puts every command under a group, including the hook plumbing, which renames calls made by processes that cannot be updated at the same moment and so needs aliases. This sits between them: groups are sorted by audience, and names that cross a reload boundary stay.
Context¶
ADR-503 §14 left the rest of the command surface to its own record, with old names as "hidden aliases for one release". ADR-506 then ended the round with no legacy compatibility. Under ADR-506 §2, a transition alias may exist only while the round is open, and the PR that adds it names the issue that removes it, which is #717. An alias kept "for one release" would outlive the round, so this record replaces that part of ADR-503 §14.
The commands at e9a5ba8a¶
ways --help lists 37 top-level commands. #730 added hook and removed response-topics-path, so ADR-503's count is unchanged. None is hidden.
| Command | Who types or calls it | Executable call sites outside tests |
|---|---|---|
hook <event> |
Hook adapters only | hooks/ways/require-ways.sh:27, sourced by 11 adapters |
init |
Operator; SessionStart hook | settings.json:154,209 |
corpus |
Operator, author; SessionStart hook; updater; build | settings.json:158; update.rs:315,442; Makefile:125; tools/way-embed/Makefile:91; scripts/fix-way-embed-signature.sh:71; skills ways-update, ways-tests |
reconcile |
Operator (repair); updater; installer | update.rs:319,446; scripts/install.sh:313,375; repair text in check-setup.sh:29,83; skill ways-update |
context |
Agent through skills; attend; keepwarm | meta/start/macro.sh:8; attend/src/sensors/context.rs:42, disclosure.rs:86; sensor-keepwarm/src/lib.rs:240; skills context-status, start, wrap |
show way\|check\|core\|attend |
Agent, told by attend; author | Text in attend/src/sensors/context.rs:182, sensor-processes/src/lib.rs:93-101; skill ways-tests (show check) |
match |
Author; SessionStart probe; build check | check-setup.sh:70,72; Makefile:264; skills ways-tests; commands/ways.md |
suggest |
Author; a macro | meta/knowledge/optimization/macro.sh:34; skill ways-tests |
reflow |
Author; a postcheck | documentation/markdown/reflow/postcheck.sh:62 |
project-slug |
A macro | meta/memory/macro.sh:15 |
sessions-root |
A script | softwaredev/delivery/issues/gh-tasks:163 |
events-log-path |
Scripts outside the binary (none at present) | none |
scan |
Tests only since #730 | tools/ways-cli/tests/session_sim.rs, project_ways.rs |
manifest |
Debugging reconcile |
tests only |
status |
Operator; installer | scripts/install.sh:242; skills ways-tests, ways-update |
update, uninstall |
Operator | Makefile:169 (update) |
settings, projects, agent |
Operator; landed this round | none outside their own crates; ways-agent-core names agent use and agent config in the header it writes to agent.yaml (profile.rs:216-217) and in a setting's help (settings.rs:52) |
config show\|path\|init\|targets\|target |
Operator | skill ways-localize (config path); the settings-tui spike, which #697 deletes |
disable, enable |
Operator | none |
lint |
Author; CI | .github/workflows/build-ways.yml:53; Makefile:263; skills ways-tests; commands/ways.md, project-audit.md |
graph, language |
Author; build check | Makefile:265, Makefile:312; skill ways-localize (language) |
template, tree, siblings, permissions |
Author | skills ways-tests (tree, siblings) |
tune, tune-precision, stats |
Maintainer | skills ways-localize, ways-tests (tune) |
list, introspect, reset |
Operator, agent | skill ways-tests (list, introspect dump) |
rethink |
Operator; #699 retires it | none |
Outside tests and the ways corpus, 22 top-level commands have callers: hooks, the settings.json hook table, scripts, skills, commands, Makefiles, CI and other binaries. Nine run on a hook path: hook, init, corpus, context, match, suggest, reflow, project-slug and sessions-root. The ways corpus also names commands in prose that agents act on: the authoring ways, meta/deployment/deployment.md, the optimization and tuning ways, meta/start and meta/wrap, and the markdown and reflow ways.
When callers and the binary can differ¶
Installed hooks, skills and ways are projected from the same checkout as the binary, so after a successful ways update they agree. They differ in five cases:
- A running Claude Code session keeps the hook table it loaded. On
/clearit runsways initagainst the new binary until the session restarts.ways corpus --if-stale --quietruns only at startup, when the session loads the new hook table, and compaction runs nowayssubcommand. - The previous release's updater runs the update. After installing the new binary, it spawns
corpus --quietandreconcileon it. A failedreconcilestops the update. - A running
attendprocess and the keepwarm sensor spawnways context --jsonfrom code built with the previous release. Attend sensors also tell the agent to runways show attend <signal>. A failed attend refresh keeps the old attend. - In symlink mode, the projected hooks are the pulled checkout. They are live from the pull until the binary refresh finishes, which can take minutes on a source build.
- A failed binary refresh keeps the previous binary under the pulled hooks until the next successful update. The updater says so.
In cases 1 to 3 an old caller reaches a new binary. An alias in the new binary would cover them. In cases 4 and 5 a new caller reaches an old binary, and no alias covers that, because the alias would have to be in the binary that is not yet installed. Only an unchanged name covers every case.
A second target adds no case. Every target ADR-184 records links to the same checkout and calls the same binary, and reconcile converges every enabled target in one run, so the five cases hold for each target at the same moment. A target's hook table names only init and corpus; everything else it runs goes through the projected hook scripts.
Decision¶
- Top level.
ways --helplists these commands, in this order:
| Command | What it is |
|---|---|
status |
Engine health: binary, model, corpus, project |
settings |
Read and change settings; the TUI on a terminal (ADR-503) |
target |
The Claude Code config directories agent-ways is active in (ADR-184) |
agent |
The ways agent: keys, models, the daemon (ADR-502) |
projects |
Claude Code's projects and their session history |
session |
This session and past ones: fired ways, replay, reset |
context |
Context-window usage for a session |
author |
Write and check ways |
tune |
Measure matching against telemetry and locales |
init |
Set up .claude/ways/ in a project |
corpus |
Rebuild the matching corpus |
update |
Update agent-ways |
reconcile |
Repair the projection into ~/.claude |
uninstall |
Remove agent-ways |
-
Fixed names. A command keeps its name and place when something that does not reload with the binary calls it. These are the cases in Context: a running session's hook table, the previous release's updater, a running
attend, and a projected script that fails hard on an unknown command. That fixeshook,init,corpus,reconcile,context,show,project-slugandsessions-root.events-log-pathstays with them, since it exists for scripts. A projected script that degrades quietly on an unknown command does not fix the name it calls:match,suggestandreflowmove, and item 6 says how each of their callers fails. A fixed name is a contract. Changing one needs its own decision and a release in which nothing calls it. -
Hidden commands.
hook,show,scan,manifest,project-slug,sessions-rootandevents-log-pathare hidden. They are absent fromways --helpand from shell completion, and they still run and still answer--help. They keep their top-level names, and there is nointernalgroup: moving a hook-facing command under a group renames it, and case 4 then stops every way from firing between the pull and the binary refresh.ways hook <event>stays the one interface the hook adapters call (ADR-504 §11 and its note of 2026-10-02). Its event names are part of the contract. -
The mapping. Every other caller moves in the same change: hooks, scripts, skills, commands, ways, docs, the Makefiles, CI, the Rust spawns and the tests.
| Old | New |
|---|---|
status |
status |
settings … |
settings … |
config show [--json] [--effective] |
settings list [--json] [--effective] |
config path |
settings list --json, which names each key's file |
config init |
Removed. settings set creates the file on first write; settings emit prints the canonical file |
config targets [--json] |
target list [--json] |
config target plan\|add\|enable\|disable\|remove <dir> |
target plan\|add\|enable\|disable\|remove <dir> |
disable <id> |
settings set ways.project.<id> false |
disable --list [--names-only] |
settings list ways.project |
enable <id> |
settings unset ways.project.<id> |
agent key\|models\|status\|load\|unload |
agent key\|models\|status\|load\|unload |
agent use <profile> [--model <id>] |
settings set gate.engine <profile>, and settings set gate.profiles.<profile>.model <id> |
agent mode <mode> |
settings set gate.mode <mode> |
agent config |
settings list gate --effective |
agent serve |
agent serve, hidden: the client spawns ways-agent serve directly |
projects … |
projects … |
list |
session ways |
introspect list |
session list |
introspect replay |
session replay |
introspect live |
session live |
introspect dump |
session dump |
introspect fires |
session fires |
reset |
session reset |
rethink |
Removed by #699; use session replay, session list and session dump |
context |
context (fixed) |
lint |
author lint |
template |
author template |
match |
author match |
tree |
author tree |
siblings |
author siblings |
suggest |
author suggest |
graph |
author graph |
reflow |
author reflow |
permissions audit |
author permissions |
tune |
tune locale |
tune-precision |
tune precision |
stats |
tune stats |
language |
tune language |
init |
init (fixed) |
corpus |
corpus (fixed) |
update |
update |
reconcile |
reconcile (fixed) |
uninstall |
uninstall |
hook <event> |
hook <event> (fixed, hidden) |
show way\|check\|core\|attend |
show … (fixed, hidden) |
scan … |
scan … (hidden) |
manifest |
manifest (hidden) |
project-slug, sessions-root, events-log-path |
unchanged (fixed, hidden) |
ways-agent's use, mode and config are removed with the old output ADR-506 §1 already retires, so ways agent keeps only the actions of ADR-503 §11.
The same change rewrites the header ways-agent-core writes to a new agent.yaml and the setting help that names agent use. An agent.yaml already written keeps its old header. The header is a comment that nothing reads, so it is left in place rather than rewritten on the user's machine.
-
No aliases (option (a)). No old name is kept, hidden or otherwise. The change migrates every caller in the repository, and the release notes list each old name beside its new one. Option (b), aliases removed by #717, would cover cases 1 to 3 for the part of the round between this change and #717. Item 2 already covers those cases, because every command they call keeps its name. Aliases would then serve only operators typing old names, which is the habit the release notes address. They would also add one more compatibility path for #717 to find and remove.
-
What still breaks, and how it fails. Cases 4 and 5 still reach the moved commands that projected scripts call:
author matchincheck-setup.sh,author suggestin the optimization macro, andauthor reflowin the reflow postcheck. The reflow postcheck already reads exit 2 as no finding. The optimization macro shows zero counts in its table.check-setup.shtreats an unknown-command exit (2) from its probe as no answer, not as a broken engine, because the updater has already reported a stale binary. A test parses every call site against the CLI, so a missed caller failsmake test. -
Help output. ADR-503 §10 holds:
ways --helpprints one line per command. Each line fits 80 columns. ADR numbers and detail go inlong_aboutand appear inways <command> --help. The banner prints only for a barewayson a terminal.ways --help,ways helpand a barewaysin a pipe print help without it. A group run with no verb prints the group's help and exits 2, the usage code of ADR-503 §9.projectskeeps its default,list, andsettingskeeps ADR-503's TUI-or-list default. -
Order with the round. This lands after #699, which removes
rethink, and after #697, which deletes the settings-tui spike that callsways config target plan. #717's check then searches for the old names in this table as well as the ones ADR-506 §1 lists.
This amends ADR-503's Decision at §14: the groups are the ones above, and old names are not kept as aliases.
Consequences¶
Positive¶
ways --helpdrops from 37 commands to 14, and a person reading it sees only what they would type.- Each group's help shows its verbs together, so
ways author --helpis the authoring reference andways session --helpis the introspection one. - The names that hooks, the updater and attend depend on are written down as a contract, not left implicit.
- No alias code exists for #717 to remove.
Negative¶
- Operators and agents lose names they know.
ways lint,ways tune,ways disable <id>andways listfail with a usage error until they learn the new ones. ways tunechanges meaning: it was the locale audit and becomes a group.ways disable <id>becomes a longer command, and a way id inside a dotted key reads less plainly.- Four commands stay at the top level for stability, so the top level is not purely what an operator types:
corpusis mostly the hook and the updater's. - Between a pull and the binary refresh, or after a failed refresh, the moved commands that projected scripts call fail quietly: the reflow postcheck reports nothing, and the optimization table shows zero counts that are wrong.
- An
agent.yamlwritten before the change keeps a comment namingways agent configandways agent use, which no longer exist. - One PR migrates the skills, commands, Makefiles, CI and scripts that name a moved command, the ways corpus prose and the ways-cli tests, which makes it a large review.
Neutral¶
ways-agent's own command line keepskey,models,status,load,unloadand a hiddenserve.- The
Bash(ways:*)permission insettings.jsoncovers every new name. - The hidden commands are listed in the CLI reference doc, since
--helpno longer shows them. - A later decision can remove
scanandmanifest, which only tests and debugging use.
Alternatives Considered¶
- (b) Hidden aliases for the old names, removed by #717. Not chosen. The callers an alias would protect already reach fixed names under item 2. An alias does not help when the binary is older than the scripts. It would add a compatibility path that #717 has to find and remove.
- Aliases for one release, as ADR-503 §14 first said. Rejected by ADR-506 §2: an alias that outlives the round is the compatibility the round removes.
- A hidden
internalgroup for the plumbing. Rejected. It renameshook,project-slugandsessions-root, whose projected callers fail hard on an unknown command, and in symlink mode every way stops firing from the pull until the binary refresh finishes. - Every operator command in a group,
contextundersessionandinit,corpusandreconcileunder aninstallgroup. Rejected. Each is called by a running session's hook table, the previous updater or a runningattend, so moving it needs an alias or breaks those callers. - Keep
disableandenableas permanent top-level verbs over the settings writer. This was a real option: they would be designed verbs, not compatibility. Not chosen, because ADR-503 §11 makes a one-file change a setting and gives settings one front end. It is the second probe. - Leave the surface flat and only shorten the help lines. Rejected. It fixes the width and leaves 37 entries mixing hook plumbing with operator commands, which is the problem ADR-503's basis names.
Note (2026-10-02): the agent.yaml write path is gone¶
Decision item 4 said the change rewrites the header ways-agent-core writes to a new agent.yaml. The implementation removed that header and the UserLayer write path instead, since ways agent use and mode were its only callers. ways settings set gate.… now writes agent.yaml through the settings writer, and a new file has no header. An existing file keeps its old comment, as the Negative consequences say.
Note (2026-10-02): the top-level help may end with a judge footer¶
Issue #751 adds a footer to the help that item 7 governs. The top-level help (a bare ways, ways --help, ways help) may end with a footer of at most two lines, each within 80 columns, saying the relevance judge cannot gate and how to fix it. It prints only when the judge cannot gate, read from stored state with no network call. Per-command help and hooks never print it.
Note (2026-10-02): every screen has a command an agent can run¶
A screen is a view, for a person. An agent authors, refactors and tunes ways through commands, and reads their --json form to decide its next edit. So everything a screen shows comes from a command that runs without a terminal and has a --json form, scoped as the screen is: the session screen's tabs read what ways session replay --json, ways session fires --json, ways agent cost --json, ways tune stats --json and ways tune precision --json print, scoped as the tab is (#738). A screen may add navigation and nothing else. Its data stays with the command. A group that opens a screen when run bare on a terminal (#748) keeps every verb, and keeps item 7's help and exit 2 in a pipe.
Note (2026-10-02): session is the first group that opens its screen bare¶
Since #738, ways session is the first group the note above covers: bare on a terminal (stdin and stdout), it opens the session screen on its sessions tab. Its usage line reads [COMMAND], so the call-site check lists it among the groups a script must still give a verb. target and agent follow in #748; projects keeps list in a pipe.
Note (2026-10-02): target, agent and projects open their screens bare¶
On a terminal, a bare ways target opens the settings screens on their install tab, and a bare ways agent on their gate tab, where the settings and actions those groups change already live (#748). In a pipe target prints its help and exits 2, and agent passes on to ways-agent, which prints its own. target joins session in the call-site check's list of groups a script must still give a verb. A bare ways projects opens the projects screen on a terminal; list, search and show gained --json, the commands its borders name.