Skip to content

crew: route the worker, planner and checker per task (Pareto crew) - #1436

Merged
AbirAbbas merged 933 commits into
devfrom
feat/pareto-crew
Sep 25, 2026
Merged

AbirAbbas merged 933 commits into
devfrom
feat/pareto-crew

Conversation

@santoshkumarradha

@santoshkumarradha santoshkumarradha commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Summary

A task's crew (worker, planner, checker) is now picked for each task by a router (internal/crewroute), instead of being fixed by a preset. By default all three seats are on auto, nothing is pinned, every model a connected provider serves is a candidate, and each task is held to a $5 limit. /crew is now an interactive panel for the few things that persist: pins, allowed models, providers and limits.

dev is merged into the branch (Merge dev into feat/pareto-crew); the PR's net change is taken from its fork point, so dev's own work is kept as dev has it. Three commits follow the merge: a fresh-profile test, the docs, and a timing-test fix. dev's OpenRouter-credits work (#1440) is adapted to the routed crew (see Routing).

User-facing behaviour

  • /crew panel with these rows:

    • the three seats: auto · usually <model>, taken from the recent tasks (likely … before there is any history), or ⌖ <model>[@provider] when pinned;
    • models: the allowed rule (all, open, a price ceiling, or a custom list);
    • providers: one on/off chip per connected provider, plus the free-routes switch in the providers list;
    • the cap row: per task $5 · daily none;
    • today: what crews spent today and how many tasks ran.

    Every change saves at once, ticks its row, and can be undone with z for a few seconds.

  • Shortcuts make the same writes and then open the panel:

    • /crew pin <worker|planner|checker> <model[@provider]>
    • /crew unpin <seat|all>
    • /crew models <rule|+x|-x>
    • /crew cap <$|off>
    • /crew cap task <$>

    A pin outside the allowed models is refused.

  • One-task effort: --best / --cheap. It can be given as the first word of /task, as the hand-off's effort field, or as a codeaf do flag. It moves that task only.

  • codeaf do also takes --pin seat=model[@provider] (repeatable). -json adds class, crew (per seat: model, provider, kind, pinned, est_usd), est_usd, effort, check_model and check_model_source. -yes-spend gets past the daily cap and the plan-price question. It does not lift the per-task limit.

  • Crew line: each task prints one line, updated in place. It shows the class, the seats and the estimate, then the actual cost beside the estimate when the task ends.

  • /redo stronger re-runs the last task one rung stronger. It also records that this class of work in this repo was under-served, so the next such task starts one step higher (at most three steps). Each accepted task takes a step back off.

  • /settings → Providers: the three seat rows are replaced by one seats row that opens the panel. The onboarding screen asks for the daily limit and the chat model only, never a crew.

  • The model you talk to (/model) is unaffected.

Routing (scoring, classes, fallback, limits)

  • Classes: bugfix, complex fix (a bugfix whose report shows reach in two or more ways: multiple files, API/protocol, language rules, long report or several repros, a security defect, existing tests that must keep passing), open-ended, and other. A complex fix moves its worker up one rung. On open-ended and other work, upgrades go to the checker first. A task that can't be read confidently counts as open-ended.

  • Scoring: learned weights (internal/crewroute/prior.json) turn each model's catalog metadata into a quality per seat and class, with a variance:

    • a model that publishes indexes or ratings is scored on those alone;
    • a model that publishes none is scored from context, release date, licence and family, and never above average;
    • price is never read as ability;
    • this install's own task outcomes (accepted, kept, redone, failed) move a model's score in a seat by small, bounded steps.
  • Seat choice: each seat goes to the model where quality minus λ times cost is highest, with λ at the knee of the curve, over the allowed models on connected, enabled providers. Plans and local models cost nothing to route to. Worker and checker must reach an ability floor whenever some allowed model does.

  • Limits:

    • Per task: $5 unless set. Every priced call of the task (seats and helpers) counts against one total. A call that would pass the limit is not made, and the task stops with this task reached its $5 limit · raise it in /crew.
    • Daily crew cap: optional. Crews pace toward it. At the cap a task does not start, and codeaf do refuses unless given -yes-spend.
    • Checker: its own ceiling, 3× its estimate and at least $0.05.
  • Fallback: a seat whose first call is refused moves, inside the task, down this ladder:

    1. the same model on another route;
    2. a model at a similar cost;
    3. the last crew that worked in this repo;
    4. the chat model.

    The line reads running on fallback crew · <seat> <from> → <to> (<cause>). When nothing paid is reachable, free routes are used with free routes in use (may log prompts). With nothing left, the task stops on a single action: add credit on <provider> to continue, reconnect <provider> with /connect, or waiting for the limit to reset.

  • Route health is kept per route:

    • a route that refused a model is left out for a week;
    • a rate-limited route rests until its reset;
    • accounts out of credit and refused keys are avoided by helpers and re-probed by the next task's first seat call.
  • chat: know the OpenRouter balance, start a near-zero account on free models, keep the whole 402 sentence #1440 (low OpenRouter balance): the reflex and small-work rows still take their free defaults. For crew seats, a balance read as low marks OpenRouter as out of credit before any call, so seats go to free routes with the notice instead of spending a refused call. A top-up is picked up at the next launch.

Removed

  • The crew presets (frugal / balanced / max), the model-family row (models.crew.source) and the pick row (models.crew.pick: table / catalog / learn).
  • internal/crewpick and config/auto.go.
  • The built-in default models for crew seats. No seat falls back to a model this build chose.
  • The crew step in onboarding, and the crew, model family and picked from settings rows.
  • The check seat inheriting the planner's model.
  • dev's low-balance "free crew" table for the three crew seats (see Routing).

Migration

On the first launch, a profile carrying preset, pick or auto rows is migrated once and told so in one line. Preset, pick and auto rows become auto. Any model id written on a seat stays pinned (kept your pins: …). The open family becomes the allowed rule open. A profile with none of these rows is left untouched. Remote protocol moves from 17 to 18 (Task.Start carries effort, and Task.RedoStronger is new), and an older engine is refused at the handshake.

Tests

make check (with OPENROUTER_API_KEY unset, see #1489) at e5ffb1666: passed — vet, fmt-check, the full test suite (136 packages ok, 0 failures), test-packed-manual, size and build-cross. manual-gates, test-laws and changelog-check also pass.

New tests:

  • a fresh profile (empty HOME and CODEAF_HOME) starts with auto on all three seats, no pins, rule all, a $5 per-task limit and no daily cap;
  • no crew seat falls back to a built-in model;
  • a low OpenRouter balance routes the crew to free pools with the notice, and a healthy balance does not.

How to try it

make build
codeaf                      # /crew opens the panel
/crew pin checker moonshotai/kimi-k3
/crew unpin checker
/crew cap task 10
/task --cheap fix: <a small bug>
/redo stronger
codeaf do --best --json "fix: …"
codeaf do --pin checker=moonshotai/kimi-k3@openrouter "…"

🤖 Generated with Claude Code

…operties, with the verdict basis recorded (#1220)

The worker declares its checks, and a holds verdict rests on a recorded, auditable, zero-exit run of every declared command. The worker records each command's own exit status, an absent status reads as unknown and earns nothing, and a record opens with a line that marks it as one that records exits, so a declared check that was never observed is refused, including when the record is missing or empty. A record written before exits were recorded still holds by reading and names the checks it did not observe. A does-not-hold verdict is ungated, a task with no declaration gets a reading verdict, and the verdict basis is persisted. One quote-aware reader of "is this one command" lives in internal/approval and is shared by the proposal door, the checker's runner gate and the store.
…her's keys (#1248)

A profile write now takes a cross-process lock on a stable config.json.lock
for the whole read, copy and rename, inside the in-process write mutex. The
lock rides internal/filelock, so it holds on every platform the repo builds
for. A write waits at most two seconds for another process and then fails
with a plain timeout error. The lock file is never unlinked: a crashed holder
is released by the operating system closing its file, so there is no stale
lock to reclaim.

Tests run two real processes forced to contend and show both keys survive,
a bounded timeout, and recovery after a holder exits without unlocking. Those
forced tests are unix-only; their clocks are sanity bounds, not load bounds.
…ump (#1249)

Both tests dumped the whole environment through the capped output collector
and read variables out of it. On a machine with a large ambient environment
the dump is cut before the TMUX_TMPDIR line, so the test read it absent while
the job shell had it, and the two checks for stripped variables passed without
proving anything. The shell now prints the three variables on one short line
with an explicit word for unset, through the same login shell road the product
uses, and the test fails if that line is missing from the captured output.
Test only; the product was already correct on every platform.
…llows on a beat (#1251)

The run's summary is a read nobody pressed for, and its refresh can wait on a
model for seconds. It was asked through the one ordered line that carries a
person's gestures to the engine, so a press on a run's row, or a stop, waited
behind it: 2.4 to 10.1 seconds on a real screen, 0.09 to 0.18 after. The
summary now goes beside the line. What may leave the line is decided by
property: the ask was not a gesture and nothing a person does next depends on
the engine having seen it first. A law lists every door that stands in the
line and fails when one is added without a reason.

An open page on a task that can still move was re-read on every paint tick
once the last read answered, 509 wire reads in ninety seconds for one page. It
now follows on the rail's own beat, counted from the last read for any reason,
and a page on an ended or held task is never read again. The follow stays in
the line because its fold replaces the page and must not overtake a note.
A run under the task belt was started on a context nothing could cut, and no
cancel was kept. Its rows wear task numbers, but a stop by number went only to
the task graph, which has never held a run's rows. Measured on the real binary
in hosted mode: the stop card answered that there was no such task, the run's
own page answered with the store's sentence about who owns the root, ctrl+c and
/quit closed the window, and the run carried on to its own landing every time.

The id a surface already sends is now resolved to whoever owns the row, so an
older window stops a run through a newer engine. A stop of a run ends the run's
task and everything open under it in one store write, then cuts the run's
context so workers and their calls end at once, then commits what was made on
the run's own branch and gives the copy back. Nothing goes into the person's
folder, no model turn is bought, and the person is told where the work is and
how to bring it in or drop it. A hand-off that joined a run is stopped alone.
A stopped run buys no further summary.

On the surface: x on the run's own page raises the same stop card, x typed
while that page is still loading reaches it too, the page no longer offers a
hold the store would refuse, and ctrl+c is read above both the loading page
and the open page. A law lists every place that publishes a running row and
fails until each has a test that a stop on that row ends its owner.

What ctrl+c, /quit and a closed window do to a live run is unchanged here.
FINDINGS.md at the repository root is a working note from the cell behind the
unread config key notice. It is not product documentation and nothing reads
it. A cell's findings belong in its run folder, outside the repository.
The test printed the whole PATH through the capped output collector and read
its first entry. On a machine with a long PATH the capture can be cut, and a
cut capture could read as a pass or as a false failure. The shell now prints
the first PATH entry on one short marker line, and the test fails when that
line is missing from the capture. Test only.
)

make check built and tested only the host platform, so a symbol defined for
one platform family passed the gate and would have broken another platform's
build; only a nightly would have caught it. A new build-cross step reads the
shipped platform list from the release workflow, builds ./... for each with
the shared build cache, names the target that failed, and prints its own
duration: about 11 seconds on a warm box, at most about 111 on a cold one.
…es (#1256)

The standing ticker was a package-level goroutine started once and never
stopped, so it could write under the home's v3/standing folder after Close
returned; one of the temp directory cleanup failures in the full check named
that folder. The ticker now belongs to the process: Close stops it and waits
for its loop, and a second process in the same binary starts its own.

Quitting never waits out background work: the ticker holds one context that
the stop cancels, and a pass derives its 120 second ceiling from it, so a pass
in flight ends at once when the process closes. A cancelled pass leaves no
half-written file, because standing writes are temp then rename or single
short appends and a pass checks its context between steps.

Tests: no write under v3/standing after Close on the real close path, a second
process starts its own ticker, and Close returns promptly with a pass held in
flight, ordered by channels.
The side list read a run's rows from the engine inside the frame: on the first
reading, when one of the window's own rows moved, and when the beat came due.
Over a connection that read is a call to another process, so a slow link froze
the frame for as long as the read took. Measured on Spark with 250 ms injected
on the read, real remote client and engine: 254 to 258 ms per frame on trunk,
0.1 to 0.7 ms after, with no read inside the frame.

The rows are now asked for from the update loop, held on the surface per
conversation, and the frame draws what is held; a verb that lands while a read
is out is answered by one more read. The law that lists every door still on
the loop could not see a door reached through the plan reader; it now can, so
every plan door is under it. The hosted drive script opens the side list with
its own key when the copied profile starts with it hidden.
…ran (#1257)

A run's page listed every step a worker ran, including the run's own
bookkeeping (recording its plan) and the change into the run's own copy, so
the work a person wanted to read was buried. The session now says, for each
part of a recorded command, whether it is the run's bookkeeping or a change
into the run's copy, and the surface leaves those parts out.

The recorded command is never retyped. internal/approval's one reader of a
command line now also reports each part's byte span, and the surface cuts the
left-out spans from the line as it ran, so spacing, operators and closing
brackets stay as typed; approval verdicts read the same trimmed segments as
before. A part is marked only in sequence, never inside a substitution or a
group; a pipeline headed by a record command is left out whole; a run's copy
is recognised under the task belt and after it is given back; the display
facts cross the wire, so a hosted conversation sees the same page. Rows keep
their recorded numbers.
…wn (#1258)

After a person stopped a run, the side column said stopped while the run's own
page said incomplete, the word for an ending nobody judged, and the page
offered its verbs again. Measured on the real binary in hosted mode: the
stopped run's page read incomplete twelve times and stopped never; after, it
reads stopped everywhere and incomplete nowhere.

Whether a task was stopped by a person is now established once, in the
session, from the store's own record of the ending, and crosses the wire as
row data; every place that draws a state word reads that one fact, so the
page, the side column and the rail cannot disagree. A part a stop took down
reads stopped too when it was cancelled in the same instant as a stopped
ancestor, which the store's cascade guarantees with one timestamp. A stopped
task counts as ended, so its page no longer offers verbs the store would
refuse.
…exits (#1259)

A usage ledger writer was started on first use and never stopped, so it could
outlive the process close that started it; under repeated runs of one test
binary the leaked writers piled up until the run timed out. The process now
owns its writers: CloseUsage detaches every writer and closes its queue under
the same lock rows are enqueued through, the writer drains what it holds so
the last row is on disk, and every exit door that used to flush now closes,
including the engine host and the fallback that serves a conversation on a
pipe.

Closing keeps the bargain the flush already made: one ceiling covers the whole
call, so a writer stalled on a disk that is not answering is left behind
rather than holding the exit open. A flush hands its markers to the writers
under the same lock, so it can never send on a queue a close has just closed.
A usage writer holds the path it was started with, so it is not one of the
writers that land files in a later test's home.
…e door a person uses (#1263)

The test pushed its mid-turn revision straight into the agent's steering
queue and needed a forced lock ordering to see the second ending. It now
submits the revision the way a surface does and drains that revision's own
stream, so it asks for exactly what the product promises: a direction typed
while a turn runs earns its own ending on a normal completion. Test only.
… plain line (#1260)

A task's page drew every harness-made answer as a numbered step, so a
correction addressed to the worker and an action a door refused both read as
work that ran. The engine now records two facts where the event is known: the
call did not run, and whether it was an action the worker attempted and a door
refused. A correction about the form of a reply stays in the record and draws
nothing. A refused action draws as one dim line in the step's place, led by
the word the permissions page already uses, with what was tried and no number,
and the rows around it keep the numbers the record gave them. The surface
never reads the answer's words, the facts cross the wire so a hosted
conversation sees the same page, and a record written before the fields
existed draws as it did.
santoshkumarradha and others added 8 commits September 25, 2026 01:37
The paper's LaTeX source, bibliography, generated tables (tab-*.tex),
figure PDFs (fig-*.pdf) and the Makefile that built them are removed.
docs/design/model-pool keeps pareto-crewing.pdf, RUNBOOK.md and
data/seed-cells.csv, which internal/pool/index/cmd/seedgen reads.

Nothing else in the tree referenced the removed sources; only the
removed files referenced each other.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…5 limit

Replace the shipped per-model table and hand-set scoring in
internal/crewroute with a learned prior read from catalog metadata.

crewroute:
- prior.json now holds fitted weights only: a joint Gaussian over ability
  and catalog features (log prices, log context, release date, open
  weights, the three published indexes, arena Elo), family offsets, a
  per-(class, seat) linear link from ability to quality, per-seat token
  shapes and cost scales. No per-model rows ship.
- quality() conditions on the fields a row has and returns a mean and a
  standard deviation; missing fields widen the variance. credible() needs a
  finite-variance score (evidence ratio) and an upper bound over the floor.
- Cost comes from catalog prices x seat shape x class scale x the install
  factor. costFloor is the cheapest credible priced model; unpriced models
  are weighed at a priced model of equal ability.
- Remove Snapshot/Measured/IsMeasured, seatWeight, unseenMargin,
  unseenShrink, the measured tie-break and the price ceiling. Every catalog
  model is a candidate.
- Request.Learned applies per-install offsets; Request.TaskCap keeps the
  pick under the per-task limit (estimate x install factor), --best
  included. Note reads "held under the $X task limit".
- Model gains ArenaElo and Released; ability memoised per model.

router/config:
- CrewRecord.Learned and CrewLog.Quality: accepted, kept, redo and failed
  outcomes move a per-(class, seat, model) offset by a bounded step
  (rate 0.1, bound 1 quality point), recorded on the decision row.
- catalog reads `created`; crewModelOf sets Released from it or the slug
  date. Rows with indexes added are re-scored on the next refresh.
- Per-task spend limit models.crew.task_cap (default $5): pre-call guard
  on every task call, stop line "this task reached its $5 limit · raise it
  in /crew", /crew cap row "per task $5 · daily $X", `/crew cap task <$>`,
  `codeaf do` holds it and -yes-spend does not lift it.

tui3: Spending tab's per task row reads the /crew limit. Crew panel
fixtures carry release dates and Elo; snapshots follow the new picks.

Tests: replace the table-reproduction and effort-evidence tests with
behaviour tests (stronger-indexed similar-priced model wins a seat, no
metadata is not picked unless pinned, install evidence moves but does not
freeze a score, bounded outcome learning, --best under the task limit,
decision under 2 ms, weights under 2 MB). Manual and changelog updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ach on one-paragraph asks

crewroute ability:
- A row that publishes any index (AA intelligence/coding/agentic, arena
  Elo) is conditioned on those indexes only; price, context, date, licence
  and family no longer move it. A row with none is conditioned on context,
  date and licence plus the family offset, and its mean is capped at the
  population mean with the excess added to the variance. Price is not an
  ability feature. prior.json v2 carries index_features/base_features.
- Seats score the solve rate at theta - 1 sd (riskKappa); quality is no
  longer clamped to [0, 10], so ordering is preserved.
- The checker needs mean u >= u_floor whenever any candidate reaches it
  (seatCredible); Gaps uses u + sd against the open-ended u_ref.
- Link refitted with seat pooling within a class (support slopes shrunk
  toward the worker's), so no seat has a zero slope; --best buys the top
  model in every seat under the task cap.

classify:
- Reach signals read the title when a task has no body.
- New reach signal: security fixes (traversal, injection, bypass, ...);
  "N repros" counts; keepPassing allows words between "existing" and
  "tests".
- Title failure words add wrongly/rejected/instead of; a rename or version
  bump reads as a simple fix.

Tests: dominance property test (no dearer and >= on every shared index
never loses, 400 random pairs plus the v4.1-flash/glm-5.3-flash case), fix
checker not the cheapest thin model, checker floor, --best strongest per
seat, index-less row capped at the population mean, one-paragraph reach
(T03/T11 texts), defect words and renames. Knee test now asserts
open-ended buys a stronger checker at prices a fix does not. Config and
tui3 expectations follow the new picks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…and cheap tolerance

- checkerFirst: on open-ended and other work at λ > 0, when the planner and
  checker slopes are within one SD of each other, the stronger of the two
  support picks sits the checker seat and the weaker the planner. Pins are
  never moved; a model that cannot sit the other seat stays. --best (λ = 0)
  is unchanged.
- seatCredible: the worker, like the checker, needs mean u >= u_floor
  whenever any candidate reaches it (abilityFloor). Fallback ladders are
  exempt, so a seat that cannot start still moves to a similar cost.
- --cheap: strongerWithin trades the worker pick for the strongest eligible
  model costing at most 1.5x (cheapTolerance). The knee comparison for the
  effort note runs without it.

Tests: the checker is never weaker than the planner at the knee on
open-ended and other work, and a pinned planner stays; a cheap worker is
credible (glm-5.3-flash over a coding-only model) and the tolerance buys a
stronger worker at 1.2x cost but not at 2.4x. Manual and changelog updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
dev now carries santos/dev2 (#1410) and #1388, #1426, #1440 and others.
The PR's net change is taken from its fork point bdb08cf; the earlier
dev2 merge and its revert are left out, so dev's skills (#1396) and
worker step-boundary work stay as dev has them.

Conflicts: config/crew.go, config/seats.go and tui3/crew.go take the
routed-crew versions; /crew keeps dev's notice event. do.go keeps both
the kept-record line and the crew outcome log. task_run_belt.go keeps
both the machine-hold rail and the crew completer. home.md keeps dev's
rows without the preset forms and adds the crew-panel row. truth_test.go
keeps dev's new facts and counts the crew as three seats. back_test.go
stays deleted as on dev. CHANGELOG.md keeps dev's released history.

#1440 on the routed crew: reflex and small work keep their free defaults
on a balance read as low; the three crew seats see the OpenRouter
account as out of credit before any call (not re-probed per task) and
route to free pools with the free-routes notice. Tests follow.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With HOME and CODEAF_HOME empty, all three seats are routed, nothing is
pinned, the allowed rule is all, a task is held to $5 and there is no
daily crew cap. With nothing routable, no crew seat falls back to a
built-in model and resolving a crew is an error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Names the four kinds of work (bugfix, complex fix, open-ended, other),
the candidates (every model a connected provider serves) and learned
weights; draws the panel's cap row as "per task $5 · daily none" and
says the seats' "usually" reads the last eight tasks; adds a route
health section, the /crew cap task row, the per-task limit on codeaf do
(-yes-spend does not lift it), and the crew's limits in LIMITS.md.
openrouter-credits.md says how a low balance reaches the routed crew.
The changelog entry names the classes, the #1440 change and the redo
decay as the code has it. Onboarding comments no longer mention a crew
step.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The two-millisecond budget was checked against one batch's mean, which
on a loaded shared machine also counts scheduler preemption. It now
takes the fastest of five batches of forty.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@santoshkumarradha

Copy link
Copy Markdown
Member Author

@AbirAbbas this is up to date with dev (merged in, no rebase or force-push; head e5ffb1666), and make check is green at that head. Ready for review.

AbirAbbas and others added 7 commits September 25, 2026 12:00
A seat pinned to `z-ai/glm-5.3:high` (the owner's own planner row) was
routed as `z-ai/glm-5.3`: resolveCrewPin looked the whole id up in the
catalog, found only the model's lineage row, and replaced the send with
that row's id, so every task asked the planner at no level while the
crew line drew `glm-5.3:high → glm-5.3` as though it had moved. The same
id given as --plan-model kept its level. The level is now split off for
the catalog and route lookups and put back on whatever send they answer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…grates

Before crews were routed every tier row could say `auto`, and the reflex
and small-work rows resolved it to their own default. The migration only
cleared `auto` from the three seat rows, and the new reader handed the
other two rows' `auto` to the provider as a literal model id on every
turn's reflex call, session names, digests and the safety gate. `auto`
there now reads the row's default until the migration removes it; an
emptied row ("follow the conversation") is left alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
At the crew's daily cap `codeaf do` refuses with "… or pass -yes-spend",
and with the flag the door let the run start, but the guard its calls
are held to still carried the same cap, so the first worker call was
refused and the run ended incomplete with no calls made. The flag now
lifts the crew's daily cap from that guard as well as the day's limit;
the per-task limit still holds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
At the daily cap the chat refused a task with "… or ask for this task with
--cheap" and `codeaf do` with "… run with --cheap, or pass -yes-spend", but
the router refuses every effort at the cap, --cheap included, so the offer
sent people round in a circle. A dollar cap is a cap: the refusals now name
raising or turning off /crew cap, -yes-spend on `do`, and waiting until
midnight on this machine's clock, when the day's spend starts again. The
manual pages and docs/HEADLESS.md say the same.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The emptiness law, on the crew's own money: `/crew cap` said
`$0.000 spent today`, the panel's day line said `today $0.000 · 1 task`,
and a `codeaf do` run that made no call ended on `$0.000 (est $0.013)`,
with `est $0.000` for a crew nothing could price. A zero or unknown
spend or estimate now takes its segment and separator with it
(`none`, `today 1 task`, `est $0.013`); money that was spent is said as
before. TestCrewCap now seeds a real spend so it still checks the spend
said beside the cap.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Four passages still described the tab before the crew was routed: separate
worker and checker select rows, a planner text box, "the five model rows"
to set, and role groups ordered under five rows with `follows planner
above`. The tab draws reflex and small work, then one `seats` row that
opens /crew; a role riding a seat says `follows the planner seat (/crew)`;
and a seat, with its thinking level, is pinned on /crew or with /crew pin.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
One conflict, internal/manual/chat/getting-started.md: the crew branch
dropped "three" from the first-run questions (the crew step is gone) and
#1429 made the places opt+1…opt+8; both are kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@AbirAbbas AbirAbbas mentioned this pull request Sep 25, 2026
AbirAbbas and others added 4 commits September 25, 2026 13:27
Two conflicts. getting-started.md: the crew branch's "the questions" (no
crew step) beside #1494's opt+1…opt+9. tui3/app.go's wheel: the crew
panel still takes the wheel while it is up, ahead of #1494's nav, whose
comment is kept as #1494 wrote it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@AbirAbbas

Copy link
Copy Markdown
Collaborator

Reviewed this and checked it by hand in the real binary (deepseek-v4-flash and glm-5.3-flash only, under a cent).

These hold:

  • Migration: fixtures cover the presets, the family, pick, auto rows and hand-written ids. A second start is silent. A copy of a real, long-lived profile printed no line, stayed byte-identical and kept its pins.
  • /crew: the panel works, and so do pin and unpin, the refusal of a pin outside the allowed models, /crew models and /crew cap.
  • Crew line: it shows at start and on landing, with not right? /redo stronger.
  • Tasks and do: /task --best/--cheap work, as do the new do -json fields. The checker no longer takes the planner's model.
  • Protocol 18: a mismatch is refused in both directions.

I'm pushing six commits on top of e5ffb1666, then plain merges of dev (34598f1bf over #1429 and #1485, 195cf13b7 over #1494, 3e44d5f2f over #1511) and of your README link 805aba004, with no rebase. The only conflicts were in getting-started.md, where your crew wording is kept beside the new opt+1…opt+9 lines, and in tui3/app.go, where the crew panel still takes the mouse wheel ahead of #1494's top-line handling. The fixes:

  • e0ed1f5b6 A pin's thinking level was dropped. z-ai/glm-5.3:high reached the provider as z-ai/glm-5.3; resolveCrewPin stripped it, and --pin lost it too.
  • ff4cde11a An auto left in the reflex or small-work row was sent to the provider as a model id. That happened on every turn's reflex call, session names and the safety gate. The row now reads its default, and the migration clears auto there.
  • 1030df446 codeaf do -yes-spend at the daily cap started the run, but its first call was refused at the same cap. The flag now lifts the crew's daily cap. The per-task limit still holds.
  • b2995d989 The cap refusal offered --cheap, which is refused at the cap like any other effort. The offer is gone. Each refusal now names only what works: /crew cap, -yes-spend on do, or waiting until midnight.
  • f96b33249 Emptiness law. $0.000 spent today, today $0.000 · 1 task and a zero actual or estimate on the crew line are no longer drawn.
  • c3d3f63ae Manual: the four passages that still described separate worker/checker/planner rows on the Providers tab now describe the one seats row.

Left for you, not blocking:

  • There's no checker ceiling when another seat shares the checker's model, which is the default fresh crew for a narrow fix. The manual doesn't say so.
  • exec and plan run have no spend guard, so the $5 per-task limit doesn't apply there.
  • A model the catalog can't price skips the cap check before the call.
  • Downgrade: a model@provider pin lives in the tier row, and an older binary sends it as the model id.
  • The paper's sources are deleted (the .tex, figures.py, data/measured-crews.csv, the Makefile), so the kept PDF can't be rebuilt.
  • The headless crew line uses a literal 📌.
  • $5, 3× and $0.05 are copied into the manual with no test tying them to the constants.
  • The PR body is stale in two places: the panel reads auto · likely/usually, not auto · now; and one accepted task removes a redo step, not five.
  • Behaviour change for a fresh profile: it now gets glm-5.3-flash on all three seats for a fix (est $0.013), and --best puts claude-fable-5.1 on all three (est $3.34).
  • Free pools that may log prompts: a mid-task 402 now also falls back to up to three of them, in addition to chat: know the OpenRouter balance, start a near-zero account on free models, keep the whole 402 sentence #1440's low-balance start. The person sees free routes in use (may log prompts) only afterwards. That's consistent with chat: know the OpenRouter balance, start a near-zero account on free models, keep the whole 402 sentence #1440's policy, but flagging it for Abir.

@AbirAbbas AbirAbbas left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified by hand in the real binary (migration, /crew, routed tasks, do -json, protocol 18) and on top of dev at 49496ec; CI green on 3e44d5f. Merging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants