From b3b34443f39f57bc2282722e0f956d12ad100ca0 Mon Sep 17 00:00:00 2001 From: santoshkumarradha Date: Sun, 27 Sep 2026 13:39:21 -0400 Subject: [PATCH 1/4] docs(cli): complete task and chat budget help synopses --- cmd/codeaf/main.go | 6 +++++- internal/manual/chat/running-from-the-terminal.md | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cmd/codeaf/main.go b/cmd/codeaf/main.go index 16f3dd417d..43925616d8 100644 --- a/cmd/codeaf/main.go +++ b/cmd/codeaf/main.go @@ -476,7 +476,7 @@ const ( // source of truth, two places it is read. var handWorkFooter = ` the three differ by how much thinking happens first: do plans and may split the job, exec does not plan, run follows a plan somebody saved. None takes - --yolo, and What do and run can still refuse is a plan whose price crosses + --yolo. do and run may still refuse a plan whose price crosses your limit — --yes-spend answers that in advance. All three end the same way, and why is in --json's stop field: ` + foldedExitLadder(2, helpWidth) + ` @@ -490,14 +490,18 @@ Talk to it — a surface you sit in front of codeaf chat [--model slug] [--reasoning level] [--session path] [--yolo] [--host host[:path]] [--at name[:path]] [--once "text"] [--no-compact] [--one-model] [--no-host] [--debug] + [--max-cost dollars] [--max-hours hours] --no-host runs the conversation in this process rather than on this workspace's session host; --debug keeps the whole record of the run + --max-cost and --max-hours bound unattended work and require --yolo codeaf resume pick an earlier conversation by name and open it — /resume inside the chat Hand it work — nobody is watching, the answer is on stdout codeaf do "" [--db path] [--keep] [--dir dir] [--timeout 15m] [--json] [--yes-spend] [--model slug] [--plan-model slug] + [--check-model slug] [--slots N] [--best|--cheap] + [--pin seat=model[@provider]] [--context-fill 60] [--completion-reserve 65536] [--debug] do one task and exit — the same living agent the chat runs, unwatched codeaf exec [""] [--dir dir] [--system text] [--max-turns N] diff --git a/internal/manual/chat/running-from-the-terminal.md b/internal/manual/chat/running-from-the-terminal.md index d4964a96b0..0b86918a07 100644 --- a/internal/manual/chat/running-from-the-terminal.md +++ b/internal/manual/chat/running-from-the-terminal.md @@ -267,6 +267,10 @@ reference somebody consults and it used to be more than half of what `--help` pr Every verb also answers ` --help` with its own line and its flags, and `codeaf plan --help` answers with all four of its subcommands. +The top-level synopsis includes `do` worker limits and model choices (`--slots`, +`--best`, `--cheap`, `--pin`, and `--check-model`). It also names chat +`--max-cost` and `--max-hours`; those unattended-work limits require `--yolo`. + ## What $? means after a headless one-shot — the codes it leaves with **One table, and `codeaf do`, `codeaf exec` and `codeaf run` all leave on it.** From dc3375dcd6f719b132a8afbe3c073a5eccd38730 Mon Sep 17 00:00:00 2001 From: santoshkumarradha Date: Sun, 27 Sep 2026 12:46:51 -0400 Subject: [PATCH 2/4] test: preserve machine gate settings when pinning run models --- cmd/codeaf/do_engine_test.go | 47 ++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/cmd/codeaf/do_engine_test.go b/cmd/codeaf/do_engine_test.go index 90df20d0dc..f3384da34d 100644 --- a/cmd/codeaf/do_engine_test.go +++ b/cmd/codeaf/do_engine_test.go @@ -149,6 +149,23 @@ func beltRunEnv(t *testing.T) string { return home } +// beltModelPins preserves the fixture's resource settings while changing the +// seats a test intends to exercise. Replacing the profile wholesale would +// silently re-enable the real machine gate on a busy test host (#1525). +func beltModelPins(t *testing.T, profileDir string, pins map[string]string) { + t.Helper() + settings := config.NewSettings(config.SettingsOptions{ProfileDir: profileDir}) + for key, value := range pins { + row, ok := settings.Row(key) + if !ok { + t.Fatalf("missing setting %s", key) + } + if err := row.Apply(value); err != nil { + t.Fatal(err) + } + } +} + // beltPlandbDoor is the real plandb CLI behind the resolver's override, built // once for the package. THE LOOP ENDS IN THE STORE: a worker's task is done // when `plandb done` marks it so and no other way, so a scripted worker that @@ -376,16 +393,10 @@ func TestDoOnTheRunEngineSeatsEveryLaunchOnTheDoorsModels(t *testing.T) { if err := os.MkdirAll(profileDir, 0o700); err != nil { t.Fatal(err) } - rows, err := json.Marshal(map[string]string{ + beltModelPins(t, profileDir, map[string]string{ config.KeyTierWorkerModel: "vendor/profile-worker", config.KeyTierMastermindModel: "vendor/profile-thinking", }) - if err != nil { - t.Fatal(err) - } - if err := os.WriteFile(config.BudgetConfigPath(profileDir), rows, 0o600); err != nil { - t.Fatal(err) - } workspace := beltRepoWorkspace(t) const ( @@ -433,7 +444,7 @@ func TestDoOnTheRunEngineSeatsEveryLaunchOnTheDoorsModels(t *testing.T) { } var stdout, stderr strings.Builder - err = doErrand(doRequest{ + err := doErrand(doRequest{ task: "write out.txt and say what you did", workspace: workspace, asJSON: true, timeout: 60 * time.Second, slots: bound(1), model: workModel, planModel: planModel, checkModel: planModel, stdout: &stdout, stderr: &stderr, newBeltCompleter: newBelt, @@ -668,18 +679,12 @@ func TestDoOnTheRunEngineSeatsACheckOnTheCheckModel(t *testing.T) { if err := os.MkdirAll(profileDir, 0o700); err != nil { t.Fatal(err) } - rows, err := json.Marshal(map[string]string{ + beltModelPins(t, profileDir, map[string]string{ config.KeyTierLowModel: "vendor/profile-small", config.KeyTierWorkerModel: "vendor/profile-worker", config.KeyTierHighModel: "vendor/profile-careful", config.KeyTierMastermindModel: "vendor/profile-thinking", }) - if err != nil { - t.Fatal(err) - } - if err := os.WriteFile(config.BudgetConfigPath(profileDir), rows, 0o600); err != nil { - t.Fatal(err) - } workspace := beltRepoWorkspace(t) const ( @@ -722,7 +727,7 @@ func TestDoOnTheRunEngineSeatsACheckOnTheCheckModel(t *testing.T) { } var stdout, stderr strings.Builder - err = doErrand(doRequest{ + err := doErrand(doRequest{ task: "write out.txt and say what you did", workspace: workspace, asJSON: true, timeout: 60 * time.Second, slots: bound(1), model: workModel, planModel: planModel, checkModel: checkModel, @@ -767,17 +772,11 @@ func TestDoOnTheRunEngineSeatsAnUnpinnedCheckOnTheCrewsChecker(t *testing.T) { if err := os.MkdirAll(profileDir, 0o700); err != nil { t.Fatal(err) } - rows, err := json.Marshal(map[string]string{ + beltModelPins(t, profileDir, map[string]string{ config.KeyTierWorkerModel: "vendor/profile-worker", config.KeyTierHighModel: "vendor/profile-careful", config.KeyTierMastermindModel: "vendor/profile-thinking", }) - if err != nil { - t.Fatal(err) - } - if err := os.WriteFile(config.BudgetConfigPath(profileDir), rows, 0o600); err != nil { - t.Fatal(err) - } workspace := beltRepoWorkspace(t) // The same one-seat shape as the two tests above. @@ -813,7 +812,7 @@ func TestDoOnTheRunEngineSeatsAnUnpinnedCheckOnTheCrewsChecker(t *testing.T) { } var stdout, stderr strings.Builder - err = doErrand(doRequest{ + err := doErrand(doRequest{ task: "write out.txt and say what you did", workspace: workspace, asJSON: true, timeout: 60 * time.Second, slots: bound(1), stdout: &stdout, stderr: &stderr, newBeltCompleter: newBelt, From 5357fbe340c3d446eeb8340f5c330f4d4719a3fc Mon Sep 17 00:00:00 2001 From: santoshkumarradha Date: Sun, 27 Sep 2026 13:40:10 -0400 Subject: [PATCH 3/4] docs: record CLI help correction in PR 1626 --- .../unreleased/1626-cli-help-completeness.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 docs/changes/unreleased/1626-cli-help-completeness.md diff --git a/docs/changes/unreleased/1626-cli-help-completeness.md b/docs/changes/unreleased/1626-cli-help-completeness.md new file mode 100644 index 0000000000..4375e1afe5 --- /dev/null +++ b/docs/changes/unreleased/1626-cli-help-completeness.md @@ -0,0 +1,13 @@ +--- +kind: fixed +title: CLI help names task model choices and unattended chat budgets +pr: 1626 +surface: [docs] +invalidates: + - "The top-level help omitted do's worker limit and crew-selection flags. Its shared synopsis now lists --slots, --best, --cheap, --pin and --check-model." + - "Chat budget flags were only discoverable in detailed help and the environment table. The summary now names --max-cost and --max-hours and their --yolo requirement." +--- + +The shared headless-work explanation also uses a complete sentence for the +plan-price refusal. This changes help text, not command behavior or engine +workspace policy. Part of #1558. From 72c8ff1a4f5cfd7a547d96ea4e6e9374e5db444b Mon Sep 17 00:00:00 2001 From: santoshkumarradha Date: Sun, 27 Sep 2026 13:45:41 -0400 Subject: [PATCH 4/4] docs(cli): fit complete help within existing page limits --- cmd/codeaf/main.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/cmd/codeaf/main.go b/cmd/codeaf/main.go index 43925616d8..3645434aec 100644 --- a/cmd/codeaf/main.go +++ b/cmd/codeaf/main.go @@ -474,11 +474,9 @@ const ( // per-command pages — which is where somebody writing a script actually looks, // and where taking it out of the group's lines had silently removed it. One // source of truth, two places it is read. -var handWorkFooter = ` the three differ by how much thinking happens first: do plans and may split - the job, exec does not plan, run follows a plan somebody saved. None takes - --yolo. do and run may still refuse a plan whose price crosses - your limit — --yes-spend answers that in advance. All three end the same - way, and why is in --json's stop field: +var handWorkFooter = ` do plans and may split the job, exec skips planning, run follows a saved plan. + None takes --yolo. do and run may refuse a plan over your spending limit; + --yes-spend answers in advance. The reason is in --json's stop field: ` + foldedExitLadder(2, helpWidth) + ` CODEAF_EXIT_CODES=legacy restores exec's old 2/3/4/5/6 for one release` @@ -491,8 +489,7 @@ Talk to it — a surface you sit in front of [--host host[:path]] [--at name[:path]] [--once "text"] [--no-compact] [--one-model] [--no-host] [--debug] [--max-cost dollars] [--max-hours hours] - --no-host runs the conversation in this process rather than on this - workspace's session host; --debug keeps the whole record of the run + --no-host stays in this process; --debug keeps the full run record --max-cost and --max-hours bound unattended work and require --yolo codeaf resume pick an earlier conversation by name and open it — /resume inside the chat @@ -511,8 +508,7 @@ Hand it work — nobody is watching, the answer is on stdout run one worker for one pass, with no planning at all codeaf run --input [--dir dir] [--model slug] [--journal path] [--json] - run one saved program: typed input in, its typed output on stdout. A - question it was not told how to answer stops it rather than being guessed + run a saved program: typed input and output; unanswered questions stop it ` + handWorkFooter + ` Look at what happened — read-only, no key, nothing spent