wisp-agents 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,13 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.23.0] - 2026-10-20
9
+
10
+ ### Changed
11
+
12
+ - **Auto's Balanced goal runs frontier models.** When you have a frontier model installed and allowed (Opus, Codex's default, Gemini Argon, Grok 4.x), Balanced now picks it, at the harness's default or medium thinking, instead of Sonnet or Haiku. Best stays on frontier models at high or x-high, so the thinking level is what sets the two apart. Balanced steps down to an everyday model (Sonnet, never Haiku) only while every frontier model is at its limit, so the task still runs, or when you have none; small models' wins no longer pull Balanced to them after 20 outcomes. Cheap is unchanged. For models with the level in their name (Antigravity's `gemini-4-argon-low` / `-medium` / `-high`), the goal now picks the variant: Cheap low, Balanced medium, Best high, and the other variants count as the same model.
13
+ - **Guides on the website for what people search for.** Six new pages under [trywisp.dev/guides](https://trywisp.dev/guides/): [run Claude Code and Codex in parallel](https://trywisp.dev/guides/run-claude-code-and-codex-in-parallel.html), [run multiple Claude Code agents in parallel](https://trywisp.dev/guides/run-multiple-claude-code-agents-in-parallel.html), [run coding agents on multiple machines](https://trywisp.dev/guides/run-coding-agents-on-multiple-machines.html), [multi-agent coding](https://trywisp.dev/guides/multi-agent-coding.html), [Claude Code vs Codex on your own repo](https://trywisp.dev/guides/claude-code-vs-codex.html) and [run coding agents in the background](https://trywisp.dev/guides/run-coding-agents-in-the-background.html). Each answers its question in the first paragraph, shows the manual way with `git worktree` and `ssh`, then how wisp does it, and ends with FAQs (with `FAQPage` structured data). `scripts/guides.mjs` holds them; `scripts/site.mjs` renders them into the sitemap. The landing page's title, description and structured data now say *in parallel* and *on your other machines*, its FAQ answers both questions, and Guides is in the nav and footer of every page. The npm description and keywords match.
14
+
8
15
  ## [0.22.0] - 2026-10-20
9
16
 
10
17
  ### Added
@@ -413,7 +420,8 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
413
420
  - Session queue with follow-ups, answers to agent questions, terminal takeover and mark-as-done.
414
421
  - Older turns are folded, with a floating collapse-all button and back-to-top.
415
422
 
416
- [Unreleased]: https://github.com/M0xoo/wisp/compare/v0.22.0...HEAD
423
+ [Unreleased]: https://github.com/M0xoo/wisp/compare/v0.23.0...HEAD
424
+ [0.23.0]: https://github.com/M0xoo/wisp/compare/v0.22.0...v0.23.0
417
425
  [0.22.0]: https://github.com/M0xoo/wisp/compare/v0.21.0...v0.22.0
418
426
  [0.21.0]: https://github.com/M0xoo/wisp/compare/v0.20.0...v0.21.0
419
427
  [0.20.0]: https://github.com/M0xoo/wisp/compare/v0.19.0...v0.20.0
package/README.md CHANGED
@@ -56,7 +56,7 @@ From there you can **follow up**, **answer its questions**, **take it over** in
56
56
  | 🤝 **Subagents** | Give the lead agent a team on other harnesses (e.g. Claude Code hands tests to Codex ×3). It delegates through a `delegate` tool; their edits land in its worktree. See [Subagents](#subagents). |
57
57
  | ⏪ **Checkpoints & rollback** | Every turn is snapshotted first. Restore to before any turn, revert a single file, or re-run a command block from the presentation. |
58
58
  | ⚖️ **Compare** | Run one task on two harnesses side by side, and keep the better run. |
59
- | 🧾 **Recipes & triggers** | Saved prompts with `{params}` (`wisp run release 0.22.0`), run by hand, on a schedule, when CI fails, or when an issue is labelled. |
59
+ | 🧾 **Recipes & triggers** | Saved prompts with `{params}` (`wisp run release 0.23.0`), run by hand, on a schedule, when CI fails, or when an issue is labelled. |
60
60
  | 📊 **Usage** | Spend, success rate and questions per project and per harness · model. |
61
61
  | 📱 **Phone** | Questions and finished tasks on ntfy or Telegram; answer from there, or start a task with `/new`. |
62
62
  | 🔐 **Permission modes** | Choose **Plan** (read-only), **Edits** (file edits auto-accepted), or **Full auto** (no permission prompts, the default) for each launch. `Shift+Tab` in the prompt cycles modes. In Edits mode, anything the permissions block comes to you: Claude Code waits for **Allow / Always / Deny**, and the other harnesses show an **Allow & retry** card. |
@@ -85,7 +85,7 @@ brew install m0xoo/tap/wisp-agents # Homebrew: CLI and browser mode,
85
85
  wisp "fix the flaky auth test"
86
86
  ```
87
87
 
88
- The Homebrew formula lives in [M0xoo/homebrew-tap](https://github.com/M0xoo/homebrew-tap). `install.sh` honors `WISP_VERSION` to pin a release (`curl -fsSL https://trywisp.dev/install.sh | WISP_VERSION=0.22.0 bash`).
88
+ The Homebrew formula lives in [M0xoo/homebrew-tap](https://github.com/M0xoo/homebrew-tap). `install.sh` honors `WISP_VERSION` to pin a release (`curl -fsSL https://trywisp.dev/install.sh | WISP_VERSION=0.23.0 bash`).
89
89
 
90
90
  From source:
91
91
 
@@ -135,7 +135,7 @@ wisp btw <id> "did you do X?" # ask on the side without interrupting the run
135
135
  wisp stop <id>
136
136
  wisp ship <id> [--draft] # commit, push and open a PR for a worktree task
137
137
  wisp merge <id> # squash-merge its PR, fast-forward your local main
138
- wisp run release 0.22.0 # run a recipe; values fill its {params} (or version=0.22.0)
138
+ wisp run release 0.23.0 # run a recipe; values fill its {params} (or version=0.23.0)
139
139
  wisp recipes # list recipes
140
140
  wisp usage [--days 7] # spend and outcomes
141
141
  wisp mcp # MCP server for other agents and editors
@@ -304,7 +304,7 @@ If the harness has no system-prompt flag, prepend `systemPrompt` to the first pr
304
304
 
305
305
  ## Auto
306
306
 
307
- **Auto** is the default harness: wisp picks the harness, model and thinking level for each task. Its "model" is a goal: **Balanced** (best result per dollar), **Cheap** or **Best**. The goal also sets how hard the agent thinks: Cheap runs at low (or minimal), Balanced at the harness's default or medium, Best at high or x-high. A harness or model without levels runs at its default.
307
+ **Auto** is the default harness: wisp picks the harness, model and thinking level for each task. Its "model" is a goal: **Balanced** (the default), **Cheap** or **Best**. Balanced and Best both run frontier models (Opus, not Sonnet or Haiku) when you have one installed and allowed; what sets them apart is how hard the agent thinks: Balanced runs at the harness's default or medium, Best at high or x-high. Cheap runs at low (or minimal) on whichever model does the job for least. A harness or model without levels runs at its default; for models with the level in their name (Antigravity's `gemini-4-argon-high`) the goal picks the variant the same way.
308
308
 
309
309
  How it picks: every harness · model · thinking level you have installed is an option (`claude:opus:high`). For the kind of task (bug, feature, UI, tests, refactor, docs, release, question) Auto keeps an estimate of how often each option does it well. The estimate starts from a small wisp-bench pilot that ships with wisp (`server/router-priors.json`), then counts your own outcomes: merged PR (best), closed PR, 👍 / 👎, rollback, then how the run ended, less a bit for each follow-up you needed. It draws from each estimate (Thompson sampling), so options with little data now and then get a turn, and takes the best value for the goal after expected cost and time. A launch with `important: true` (API, SDK) gets the best-known option, with no trying. Options that keep failing a kind of task are left out of it. Thinking harder starts out a little more likely to work and costs more, and then your outcomes decide. A model's record at one level counts half at its other levels.
310
310
 
@@ -319,7 +319,7 @@ After the run, Auto checks the work before it lands in your queue:
319
319
  3. **Repair**: new failures go back to the same agent (1 round by default, up to 2).
320
320
  4. **Hand over**: still failing, or the run errored, so the task runs again on a stronger tier (small → everyday → frontier) in its own worktree, next to the first run.
321
321
 
322
- A harness that says it's at its limit (session or usage limit, rate limit, no credit) isn't failing the task. Auto rests it until the limit resets, doesn't count the run against it, and moves the task to the best pick that can run now. Best always runs frontier models only (Opus, not Sonnet or Haiku); it falls back to the strongest you have only when no frontier model is installed. Until Auto has 20 of your own outcomes, Balanced picks only everyday and frontier models. The shipped estimates come from a small pilot, so they count for at most 4 tasks each.
322
+ A harness that says it's at its limit (session or usage limit, rate limit, no credit) isn't failing the task. Auto rests it until the limit resets, doesn't count the run against it, and moves the task to the best pick that can run now. Best always runs frontier models only; it falls back to the strongest you have only when no frontier model is installed. Balanced runs on the strongest tier that can run now, whatever the record of the weaker models: frontier, or everyday (Sonnet, never Haiku) while every frontier model is at its limit or when you have none. The shipped estimates come from a small pilot, so they count for at most 4 tasks each.
323
323
  5. **Best of two** (goal Best, off by default): two picks work side by side; tests decide, then a cheap model reads both diffs, and the better run is marked **Pick**.
324
324
 
325
325
  Each step's outcome feeds the router: a test pass counts more than the agent's "done", needing a repair counts less, and losing a best-of-2 a little less again. Your own follow-up stops whatever Auto was about to do next. A launch on a machine is checked on that machine, where the files are. Settings → Auto turns each step on or off and sets the test command per project.
@@ -354,7 +354,7 @@ Before each turn, wisp snapshots the git working tree (pinned at `refs/wisp/chec
354
354
  Prepare release {version}: update CHANGELOG, README and docs/, bump package.json, tag v{version}.
355
355
  ```
356
356
 
357
- Make one from ⌘K → *Recipes*, or *Save as recipe…* on any card. A recipe may pin a project, harness, model and mode. Run it from ⌘K (*Run: Release*), from the Recipes view, or from a terminal: `wisp run release 0.22.0` fills the fields in order, `wisp run release version=0.22.0` by name.
357
+ Make one from ⌘K → *Recipes*, or *Save as recipe…* on any card. A recipe may pin a project, harness, model and mode. Run it from ⌘K (*Run: Release*), from the Recipes view, or from a terminal: `wisp run release 0.23.0` fills the fields in order, `wisp run release version=0.23.0` by name.
358
358
 
359
359
  **Triggers** launch sessions on their own while wisp is open (⌘K → *Triggers*):
360
360
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "wisp-agents",
3
- "version": "0.22.0",
4
- "description": "Launch coding agents, skip the transcript, read the presentation.",
3
+ "version": "0.23.0",
4
+ "description": "Run Claude Code, Codex, Cursor and other coding agents in parallel, in the background, on any of your machines. Skip the transcript, read the report.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "wisp": "bin/wisp.js",
@@ -31,7 +31,17 @@
31
31
  "keywords": [
32
32
  "claude",
33
33
  "claude-code",
34
+ "codex",
35
+ "openai-codex",
36
+ "cursor",
37
+ "opencode",
34
38
  "coding-agent",
39
+ "ai-agents",
40
+ "multi-agent",
41
+ "parallel",
42
+ "git-worktree",
43
+ "ssh",
44
+ "orchestration",
35
45
  "ai",
36
46
  "agents",
37
47
  "cli",
package/server/router.js CHANGED
@@ -2,8 +2,9 @@
2
2
  //
3
3
  // Every harness × model × thinking level the user has is an "arm" (claude:opus:high; no level is
4
4
  // the harness's default). The goal picks the band of levels: Cheap runs low, Balanced the default
5
- // or medium, Best high. Within the band the levels are learned like models are, and a model's
6
- // record at its other levels counts half. For each kind of task (bug, ui, docs, …) wisp
5
+ // or medium, Best high. Balanced and Best both run frontier models when you have one, so the
6
+ // level is what tells them apart. Within the band the levels are learned like models are, and a
7
+ // model's record at its other levels counts half. For each kind of task (bug, ui, docs, …) wisp
7
8
  // keeps a Beta(wins, losses) estimate per arm, stacked from three sources: the shipped priors
8
9
  // (./router-priors.json, from wisp-bench), the user's history on every kind of task, and the
9
10
  // history on this kind. A launch draws a chance of success from each estimate (Thompson
@@ -27,13 +28,12 @@ const PRIORS = JSON.parse(fs.readFileSync(new URL('./router-priors.json', import
27
28
 
28
29
  export const GOALS = {
29
30
  cheap: { label: 'Cheap', value: 1, hint: 'Lowest cost that still gets it done' },
30
- balanced: { label: 'Balanced', value: 5, hint: 'Best result per dollar' },
31
- best: { label: 'Best', value: 50, hint: 'Frontier models only; cost only breaks ties' },
31
+ balanced: { label: 'Balanced', value: 5, hint: 'Frontier models at default or medium thinking' },
32
+ best: { label: 'Best', value: 50, hint: 'Frontier models at high thinking; cost only breaks ties' },
32
33
  };
33
34
  export const TYPES = ['bug', 'feature', 'ui', 'test', 'refactor', 'docs', 'release', 'question'];
34
35
  const MINUTE_USD = 0.05; // what a minute of waiting costs you, in the utility
35
36
  const PRIOR_CAP = 4; // shipped priors count as at most this many tasks, so your own soon outweigh them
36
- const COLD_START = 20; // until Auto has this many of your outcomes, Balanced and Best stay on stronger tiers
37
37
  const ALL_WEIGHT = 0.5; // your history on other kinds of task counts half
38
38
  const HALF_LIFE_MS = 30 * 864e5; // models change: older outcomes fade
39
39
  const NEW_ARM_SHARE = 0.25; // how often a new arm on probation may take an exploring slot
@@ -80,6 +80,17 @@ export const levelsFor = (efforts = [], goal = 'balanced') => {
80
80
  const have = (BANDS[goal] || BANDS.balanced).filter((e) => e === '' || efforts.includes(e));
81
81
  return have.length ? have : [''];
82
82
  };
83
+ // Some harnesses bake the level into the model id (Antigravity's gemini-4-argon-high, no separate
84
+ // selector): there the goal picks among a model's variants the same way.
85
+ const SUFFIX = /-(minimal|low|medium|high|xhigh|max)$/;
86
+ const NAMED = { cheap: ['minimal', 'low'], balanced: ['medium'], best: ['high', 'xhigh', 'max'] };
87
+ const stemOf = (k) => baseOf(k).replace(SUFFIX, '');
88
+ function byNamedLevel(cands, goal) {
89
+ const want = NAMED[goal] || NAMED.balanced;
90
+ const named = (c) => !c.efforts.length && c.model.match(SUFFIX)?.[1];
91
+ const fits = new Set(cands.filter((c) => want.includes(named(c))).map((c) => stemOf(c.key)));
92
+ return cands.filter((c) => !named(c) || want.includes(named(c)) || !fits.has(stemOf(c.key)));
93
+ }
83
94
  const effortLabel = (e) => ({ xhigh: 'X-High' })[e] || e.charAt(0).toUpperCase() + e.slice(1);
84
95
 
85
96
  export const armKey = (agent, model = '', effort = '') => `${agent}:${model || ''}${effort ? `:${effort}` : ''}`;
@@ -99,10 +110,12 @@ export const baseOf = (k) => {
99
110
  };
100
111
  // Versions stripped: claude-sonnet-5-5 and claude-sonnet-5-6 are one family, and a new version
101
112
  // starts from what the family already showed.
102
- const family = (k) => k.replace(/[-.]?\d+([.-]\d+)*/g, '');
113
+ // A level in the id is the same model too: gemini-4-argon-high starts from what -medium showed.
114
+ const family = (k) => k.replace(SUFFIX, '').replace(/[-.]?\d+([.-]\d+)*/g, '');
103
115
 
104
116
  // How strong a model is, roughly: 1 small/fast/free, 2 everyday, 3 frontier. Best runs on tier 3
105
- // only, hand-overs step up a tier, and until Auto has your own outcomes Balanced stays on tier 2+.
117
+ // only, Balanced on the strongest tier that can run (frontier when you have one), and hand-overs
118
+ // step up a tier.
106
119
  // The shipped priors come from a small pilot that flattered cheap models; tiers keep them honest.
107
120
  const FRONTIER_DEFAULTS = new Set(['codex:']); // a harness whose default model is its frontier one
108
121
  export function tierOf(key) {
@@ -259,34 +272,38 @@ export class Router {
259
272
  // Pick an arm. agents: listAgents() of the place it runs. goal: cheap | balanced | best, which
260
273
  // also sets the band of thinking levels (levelsFor).
261
274
  // important: no exploring. exclude: arm keys already taken (compare, hand-overs): every level of
262
- // that harness · model is out. needDelegate: the lead
263
- // of a team must be a harness that can delegate. rng: for tests.
275
+ // that harness · model is out, and every variant of a model with its level in its id.
276
+ // needDelegate: the lead of a team must be a harness that can delegate. rng: for tests.
264
277
  // scouted: { type, size } from the scout, which beats the keyword guess.
265
278
  // minTier: only arms at least that strong (a hand-over steps up). Arms cooling down after a usage
266
279
  // or rate limit are skipped while any other arm can run.
267
280
  // Best runs frontier models only: if any is installed and allowed, Best never steps down a tier,
268
281
  // even when they're all taken or at their limit (null then). With none installed, the strongest.
282
+ // Balanced runs frontier models too, whatever its record of the others (Sonnet or Haiku winning
283
+ // a few tasks doesn't make them the face of Auto), and steps down a tier only while every
284
+ // frontier model is taken or at its limit, so the task still runs.
269
285
  choose({ prompt = '', attachments = 0, agents = [], goal = 'balanced', important = false, explore = true, exclude = [], needDelegate = false, allowed = null, scouted = null, minTier = 0, rng = Math.random } = {}) {
270
286
  if (!this.synced) this.sync(agents);
287
+ if (!GOALS[goal]) goal = 'balanced';
271
288
  const guess = classify(prompt, { attachments });
272
289
  const type = scouted?.type || guess.type;
273
290
  const size = scouted?.size || guess.size;
274
- const V = (GOALS[goal] || GOALS.balanced).value;
275
- const out = new Set(exclude.map(baseOf));
291
+ const V = GOALS[goal].value;
292
+ const out = new Set(exclude.map(stemOf));
276
293
  const usable = candidates(agents, new Set(Object.keys(this.state.arms))).filter((c) => (!needDelegate || c.delegates) && (!allowed || allowed.includes(c.agent)));
277
294
  const frontierOnly = goal === 'best' && usable.some((c) => tierOf(c.key) >= 3);
278
- let arms = usable
279
- .filter((c) => !out.has(c.key) && (!frontierOnly || tierOf(c.key) >= 3))
295
+ let arms = byNamedLevel(usable.filter((c) => !out.has(stemOf(c.key))), goal)
296
+ .filter((c) => !frontierOnly || tierOf(c.key) >= 3)
280
297
  .flatMap((c) => levelsFor(c.efforts, goal).map((effort) => ({ ...c, key: armKey(c.agent, c.model, effort), effort, label: `${c.label}${effort ? ` · ${effortLabel(effort)}` : ''}`, base: c.key })))
281
298
  .map((c) => ({ ...c, est: this.estimate(type, c.key), state: this.state.arms[c.base]?.state || 'new' }));
282
299
  if (!arms.length) return null;
283
300
  const free = arms.filter((x) => !this.cooling(x.key));
284
301
  if (!free.length) return null; // everything allowed is at its limit: nothing to route to now
285
302
  arms = free;
286
- // Cold start: until there are enough of your own outcomes, lean to quality (the shipped priors
287
- // are thin). Cheap is left alone: that goal asked for cheap.
288
- const cold = this.learned() < COLD_START;
289
- const floorTier = Math.max(minTier, goal === 'best' ? 3 : cold && goal !== 'cheap' ? 2 : 0);
303
+ // Balanced and Best run on the strongest tier that can run now, whatever the record of the
304
+ // weaker ones. Cheap is left alone: that goal asked for cheap.
305
+ const top = Math.max(...arms.map((x) => tierOf(x.key)));
306
+ const floorTier = Math.max(minTier, goal === 'cheap' ? 0 : top);
290
307
  for (let t = floorTier; t > 0; t--) {
291
308
  const strong = arms.filter((x) => tierOf(x.key) >= t);
292
309
  if (strong.length) {
@@ -323,7 +340,7 @@ export class Router {
323
340
  key: pick.key,
324
341
  label: pick.label,
325
342
  route: {
326
- goal: GOALS[goal] ? goal : 'balanced',
343
+ goal,
327
344
  type,
328
345
  size,
329
346
  key: pick.key,
@@ -336,7 +353,6 @@ export class Router {
336
353
  explored: byMean[0].key !== pick.key,
337
354
  trying: pick.state === 'new',
338
355
  tier: tierOf(pick.key),
339
- ...(cold && goal !== 'cheap' && goal !== 'best' && { coldStart: true }),
340
356
  propensity: round(hits / 200),
341
357
  alternatives: byMean.filter((x) => x.key !== pick.key).slice(0, 3).map((x) => ({ key: x.key, label: x.label, chance: round(x.est.mean), usd: round(x.est.usd) })),
342
358
  at: this.now(),
@@ -453,7 +469,6 @@ export class Router {
453
469
  return {
454
470
  priors: { version: PRIORS.version, source: PRIORS.source },
455
471
  learned: routes.filter((r) => r.r !== null).length,
456
- coldStart: { needed: COLD_START, have: this.learned() },
457
472
  routed: routes.length,
458
473
  arms,
459
474
  recent: routes.sort((p, q) => q.at - p.at).slice(0, 30).map((r) => ({ id: r.id, key: r.key, label: r.label, type: r.type, goal: r.goal, chance: r.chance, r: r.r, explored: r.explored, trying: r.trying, at: r.at, signals: r.signals })),
@@ -25,7 +25,7 @@ export async function openRouter({ host } = {}) {
25
25
  if (!r || !node.isConnected) return;
26
26
  const arms = r.arms.filter((a) => a.state !== 'gone' || a.n);
27
27
  $('.ro-body', node).innerHTML = `
28
- <p class="hint">For each task Auto estimates every harness · model · thinking level’s chance of doing it well, from wisp-bench (${esc(r.priors.version)}) and then more and more from your own tasks: merged PRs, 👍 / 👎, rollbacks, follow-ups and how the run ended. It picks the best value for your goal (<b>${esc(AUTO_GOALS.find((g) => g.id === r.goal)?.label || '')}</b>), which also sets how hard it thinks: Cheap low, Balanced default or medium, Best high. Best runs frontier models only (Opus, not Sonnet or Haiku). Now and then it tries an alternative so the estimates stay honest.${r.coldStart && r.coldStart.have < r.coldStart.needed ? ` Until it has ${r.coldStart.needed} of your own outcomes (${r.coldStart.have} so far), Balanced uses everyday and frontier models only.` : ''}</p>
28
+ <p class="hint">For each task Auto estimates every harness · model · thinking level’s chance of doing it well, from wisp-bench (${esc(r.priors.version)}) and then more and more from your own tasks: merged PRs, 👍 / 👎, rollbacks, follow-ups and how the run ended. It picks the best value for your goal (<b>${esc(AUTO_GOALS.find((g) => g.id === r.goal)?.label || '')}</b>), which also sets how hard it thinks: Cheap low, Balanced default or medium, Best high. Balanced and Best run frontier models (Opus, not Sonnet or Haiku) when you have one; Balanced steps down to an everyday model only while every frontier one is at its limit. Now and then it tries an alternative so the estimates stay honest.</p>
29
29
  <h4>Harness · model · thinking</h4>
30
30
  <table class="us-table ro-table"><thead><tr><th></th><th>Works</th><th>Your tasks</th><th>~Cost</th><th>~Time</th><th></th></tr></thead><tbody>${arms
31
31
  .map((a) => {
@@ -21,9 +21,9 @@ export const agentName = (id) => S.agents.find((a) => a.id === id)?.name || (id
21
21
  // level per task. Its "models" are the goal it optimises for, which also sets the level band, so
22
22
  // there are no thinking levels to pick here.
23
23
  export const AUTO_GOALS = [
24
- { id: 'balanced', label: 'Balanced', hint: 'Best result per dollar · default or medium thinking' },
24
+ { id: 'balanced', label: 'Balanced', hint: 'Frontier models · default or medium thinking' },
25
25
  { id: 'cheap', label: 'Cheap', hint: 'Lowest cost that still gets it done · low thinking' },
26
- { id: 'best', label: 'Best', hint: 'Best result; cost only breaks ties · high thinking' },
26
+ { id: 'best', label: 'Best', hint: 'Frontier models · high thinking; cost only breaks ties' },
27
27
  ];
28
28
  export const AUTO = { id: 'auto', name: 'Auto', available: true, delegates: true, models: AUTO_GOALS, efforts: [], hint: 'wisp picks the harness, model and thinking level from what worked before' };
29
29