claude-overnight 1.25.39 → 1.25.42
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/README.md +3 -3
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/index.js +36 -7
- package/dist/providers.js +5 -0
- package/dist/run.js +2 -25
- package/dist/settings.js +4 -4
- package/dist/steering.js +22 -3
- package/dist/swarm.js +27 -24
- package/docs/PROXIED_FAST_MODEL_RESEARCH.md +403 -0
- package/package.json +2 -2
- package/plugins/claude-overnight/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-overnight/skills/claude-overnight/SKILL.md +2 -2
- package/plugins/claude-overnight/skills/coach/SKILL.md +21 -19
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Parallel Claude agents in isolated git worktrees. Set a usage cap so your intera
|
|
|
4
4
|
|
|
5
5
|
Hand it an objective and a session budget, walk away, review the diff when the run ends. Every agent runs in its own worktree on its own branch — a misbehaving agent can't trash your working tree. Unmerged branches are preserved for manual review, never discarded.
|
|
6
6
|
|
|
7
|
-
Built on the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) — every session runs on the SDK's agent harness. Three roles, each picked independently: **planner** (thinks, steers, reviews), **worker** (runs the tasks), and an optional **fast**
|
|
7
|
+
Built on the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) — every session runs on the SDK's agent harness. Three roles, each picked independently: **planner** (thinks, steers, reviews), **main worker** (runs the tasks), and an optional **fast worker** (a cheaper/faster second worker for well-scoped tasks, verified by the next wave's workers). Pair any planner (Opus, Sonnet) with any worker — Anthropic, Cursor, Qwen, OpenRouter, or any Anthropic-compatible endpoint.
|
|
8
8
|
|
|
9
9
|
## Run on Qwen 3.6 Plus
|
|
10
10
|
|
|
@@ -333,7 +333,7 @@ claude-overnight "fix auth bug in src/auth.ts" "add tests for user model"
|
|
|
333
333
|
|
|
334
334
|
## Custom providers (Qwen, OpenRouter, any Anthropic-compatible endpoint)
|
|
335
335
|
|
|
336
|
-
Planner, worker, and optional fast
|
|
336
|
+
Planner, main worker, and optional fast worker are each picked separately -- pair Opus-on-Anthropic for the planner/thinker with a cheaper model on another provider for the bulk of work. The fast worker is a real worker (same tools, same env), just on a cheaper/faster model — steering routes well-scoped tasks to it by default.
|
|
337
337
|
|
|
338
338
|
From the interactive picker, choose `Other…` on the planner, worker, or fast step:
|
|
339
339
|
|
|
@@ -353,7 +353,7 @@ From the interactive picker, choose `Other…` on the planner, worker, or fast s
|
|
|
353
353
|
|
|
354
354
|
Saved providers live user-level at `~/.claude/claude-overnight/providers.json` (mode 0600) and show up automatically in every repo. No per-project config.
|
|
355
355
|
|
|
356
|
-
**How routing works.** Each `query()` gets its own env override (`ANTHROPIC_BASE_URL` + `ANTHROPIC_AUTH_TOKEN`) -- planner queries use the planner provider, worker queries use the worker provider, fast queries use the fast provider. No global shell env, no proxy daemon, no `process.env` pollution between calls.
|
|
356
|
+
**How routing works.** Each `query()` gets its own env override (`ANTHROPIC_BASE_URL` + `ANTHROPIC_AUTH_TOKEN`) -- planner queries use the planner provider, main-worker queries use the worker provider, fast-worker queries use the fast provider. No global shell env, no proxy daemon, no `process.env` pollution between calls.
|
|
357
357
|
|
|
358
358
|
**Pre-flight.** Before the swarm starts, each custom provider is pinged with a 1-turn auth check. Bad keys fail fast with `✗ worker preflight failed: ...` instead of N scattered mid-run errors.
|
|
359
359
|
|
package/dist/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "1.25.
|
|
1
|
+
export declare const VERSION = "1.25.42";
|
package/dist/_version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated by build — do not edit manually.
|
|
2
|
-
export const VERSION = "1.25.
|
|
2
|
+
export const VERSION = "1.25.42";
|
package/dist/index.js
CHANGED
|
@@ -156,7 +156,7 @@ async function main() {
|
|
|
156
156
|
--budget=N Target number of agent runs ${chalk.dim("(default: 10)")}
|
|
157
157
|
--concurrency=N Max parallel agents ${chalk.dim("(default: 5)")}
|
|
158
158
|
--model=NAME Worker model override ${chalk.dim("(interactive mode picks planner + worker separately -- supports 'Other…' for Qwen / OpenRouter / etc.)")}
|
|
159
|
-
--fast-model=NAME Fast model for quick tasks ${chalk.dim("(optional -- checked by
|
|
159
|
+
--fast-model=NAME Fast worker model for quick tasks ${chalk.dim("(optional -- checked by next wave's workers)")}
|
|
160
160
|
--usage-cap=N Stop at N% utilization ${chalk.dim("(e.g. 90 to save 10% for other work)")}
|
|
161
161
|
--allow-extra-usage Allow extra/overage usage ${chalk.dim("(default: stop when plan limits hit)")}
|
|
162
162
|
--extra-usage-budget=N Max $ for extra usage ${chalk.dim("(implies --allow-extra-usage)")}
|
|
@@ -843,20 +843,36 @@ async function main() {
|
|
|
843
843
|
* preflight now also runs a write-capability probe (see probeCursorWriteCapability) that
|
|
844
844
|
* asks cursor to Bash a marker file — so the total budget must cover auth ping + write turn. */
|
|
845
845
|
const preflightMs = (p) => isCursorProxyProvider(p) ? 90_000 : 20_000;
|
|
846
|
-
|
|
846
|
+
// Cursor's composer-2 pipeline intermittently stalls for 100s+ on a write-tool turn
|
|
847
|
+
// even though the tool succeeded (proxy logs it as "SLOW response"). A single retry
|
|
848
|
+
// almost always clears it — so we retry once on timeout-style failures for cursor
|
|
849
|
+
// proxy providers before giving up.
|
|
850
|
+
const isTimeoutError = (err) => /^timeout after /.test(err) || /: timeout after /.test(err);
|
|
851
|
+
const runPreflight = async (role, p) => {
|
|
847
852
|
statuses.set(role, "connecting…");
|
|
848
853
|
renderStatus();
|
|
849
|
-
|
|
854
|
+
let result = await preflightProvider(p, cwd, preflightMs(p), {
|
|
850
855
|
onProgress: (msg) => { statuses.set(role, msg); renderStatus(); },
|
|
851
856
|
});
|
|
857
|
+
if (!result.ok && isCursorProxyProvider(p) && isTimeoutError(result.error)) {
|
|
858
|
+
statuses.set(role, "retrying after timeout…");
|
|
859
|
+
renderStatus();
|
|
860
|
+
result = await preflightProvider(p, cwd, preflightMs(p), {
|
|
861
|
+
onProgress: (msg) => { statuses.set(role, `retry: ${msg}`); renderStatus(); },
|
|
862
|
+
});
|
|
863
|
+
}
|
|
852
864
|
statuses.delete(role);
|
|
853
865
|
renderStatus();
|
|
854
866
|
return { role, provider: p, result };
|
|
855
|
-
}
|
|
867
|
+
};
|
|
868
|
+
const results = await Promise.all(pending.map(([role, p]) => runPreflight(role, p)));
|
|
856
869
|
clearStatusLine();
|
|
870
|
+
let fastDegraded = false;
|
|
857
871
|
for (const { role, provider, result } of results) {
|
|
858
872
|
if (!result.ok) {
|
|
859
|
-
|
|
873
|
+
const degradable = role === "fast";
|
|
874
|
+
const prefix = degradable ? chalk.yellow(` ⚠ ${role} preflight failed`) : chalk.red(` ✗ ${role} preflight failed`);
|
|
875
|
+
console.error(`${prefix}: ${chalk.dim(result.error)}`);
|
|
860
876
|
if (isCursorProxyProvider(provider)) {
|
|
861
877
|
const tail = readCursorProxyLogTail(25);
|
|
862
878
|
if (tail) {
|
|
@@ -865,16 +881,29 @@ async function main() {
|
|
|
865
881
|
console.error(chalk.dim(` ${line}`));
|
|
866
882
|
}
|
|
867
883
|
const cmd = bundledComposerProxyShellCommand();
|
|
868
|
-
|
|
884
|
+
const proxyUrl = provider.baseURL || PROXY_DEFAULT_URL;
|
|
885
|
+
console.error(chalk.yellow(` The proxy at ${proxyUrl} may have crashed or timed out (e.g. keychain/UI). Retry, or start the bundled proxy: ${cmd ?? "npm install in the claude-overnight package, then re-run"}`));
|
|
869
886
|
}
|
|
870
|
-
else {
|
|
887
|
+
else if (!degradable) {
|
|
871
888
|
console.error(chalk.red(` Fix the provider at ~/.claude/claude-overnight/providers.json and retry.`));
|
|
872
889
|
}
|
|
890
|
+
if (degradable) {
|
|
891
|
+
console.error(chalk.yellow(` Continuing without the fast worker — fast-eligible tasks will run on the main worker model instead.`));
|
|
892
|
+
console.error("");
|
|
893
|
+
fastDegraded = true;
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
873
896
|
console.error("");
|
|
874
897
|
process.exit(1);
|
|
875
898
|
}
|
|
876
899
|
console.log(` ${chalk.green(`✓ ${role} ready`)} ${chalk.dim(`· ${provider.displayName} · ${provider.model}`)}`);
|
|
877
900
|
}
|
|
901
|
+
if (fastDegraded) {
|
|
902
|
+
fastModel = undefined;
|
|
903
|
+
fastProvider = undefined;
|
|
904
|
+
const rebuilt = buildEnvResolver({ plannerModel, plannerProvider, workerModel, workerProvider, fastModel, fastProvider });
|
|
905
|
+
setPlannerEnvResolver(rebuilt);
|
|
906
|
+
}
|
|
878
907
|
}
|
|
879
908
|
if (nonInteractive) {
|
|
880
909
|
const capStr = usageCap != null ? ` cap=${Math.round(usageCap * 100)}%` : "";
|
package/dist/providers.js
CHANGED
|
@@ -1011,6 +1011,11 @@ async function startProxyProcess(baseUrl, url, port) {
|
|
|
1011
1011
|
// cursor-composer chat-only mode fakes HOME to a temp dir; on macOS the agent still waits on
|
|
1012
1012
|
// Keychain (~30s) for `cursor-user` despite CURSOR_API_KEY. Use the real workspace profile.
|
|
1013
1013
|
CURSOR_BRIDGE_CHAT_ONLY_WORKSPACE: "false",
|
|
1014
|
+
// Broad base so per-request `X-Cursor-Workspace` headers (set from each
|
|
1015
|
+
// agent's cwd in swarm.ts) validate under the proxy's `resolveWorkspace`
|
|
1016
|
+
// check. Without this, proxied agents in worktrees all resolve to the
|
|
1017
|
+
// proxy's startup cwd.
|
|
1018
|
+
CURSOR_BRIDGE_WORKSPACE: "/",
|
|
1014
1019
|
};
|
|
1015
1020
|
if (sysNode && agentJs) {
|
|
1016
1021
|
proxyEnv.CURSOR_AGENT_NODE = sysNode;
|
package/dist/run.js
CHANGED
|
@@ -979,34 +979,11 @@ export async function executeRun(cfg) {
|
|
|
979
979
|
}
|
|
980
980
|
function reviewPrompt(scope, objective) {
|
|
981
981
|
const scopeLine = scope === "wave"
|
|
982
|
-
? "
|
|
982
|
+
? "Review and simplify all changes from the most recent wave."
|
|
983
983
|
: `You are the final quality gate before this autonomous run completes.\n\nThe objective was: ${objective || "improve the codebase"}`;
|
|
984
|
-
const diffCmd = scope === "wave"
|
|
985
|
-
? "Run `git diff` to see what changed."
|
|
986
|
-
: "Run `git diff main` (or `git diff HEAD` if on the same branch) to see ALL changes made during this run.";
|
|
987
|
-
const checks = scope === "wave"
|
|
988
|
-
? `1. **Missed reuse**: Did any agent write something that already exists elsewhere? Find existing utilities and suggest replacements.
|
|
989
|
-
2. **Quality issues**: Redundant state, copy-paste variations, leaky abstractions, stringly-typed code where enums exist, unnecessary JSX nesting, comments that narrate what the code does.
|
|
990
|
-
3. **Efficiency problems**: Redundant computations, sequential operations that could be parallel, hot-path bloat, recurring no-op updates, TOCTOU patterns, memory leaks.
|
|
991
|
-
4. **Merge conflicts or inconsistencies**: Changes that work against each other or break existing patterns.`
|
|
992
|
-
: `1. **Architecture coherence**: Do the changes form a coherent whole, or are they a patchwork of independent edits that don't fit together?
|
|
993
|
-
2. **Missed reuse**: Any new code that duplicates existing functionality?
|
|
994
|
-
3. **Quality**: Redundant state, copy-paste variations, leaky abstractions, stringly-typed code, unnecessary nesting, narrative comments.
|
|
995
|
-
4. **Efficiency**: N+1 patterns, redundant computations, hot-path bloat, missing cleanup, unbounded data structures.
|
|
996
|
-
5. **Consistency**: Do all changes follow the project's existing patterns, conventions, and design system?
|
|
997
|
-
6. **Build and test**: Run the build and any existing tests. Fix any breakage.`;
|
|
998
|
-
const close = scope === "wave"
|
|
999
|
-
? "Fix issues directly. Delete and simplify rather than add. If the code is already clean, skip."
|
|
1000
|
-
: "Fix issues directly. Delete and simplify. If the codebase is clean and the build passes, say so.";
|
|
1001
984
|
return `${scopeLine}
|
|
1002
985
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
${checks}
|
|
1006
|
-
|
|
1007
|
-
${close}
|
|
1008
|
-
|
|
1009
|
-
No need to explain your changes -- just fix them.`;
|
|
986
|
+
Invoke the \`simplify\` skill to review changed code for reuse, quality, and efficiency, then fix any issues found.`;
|
|
1010
987
|
}
|
|
1011
988
|
async function runReview(opts, scope, objective, onSwarm) {
|
|
1012
989
|
const swarm = new Swarm({
|
package/dist/settings.js
CHANGED
|
@@ -25,12 +25,12 @@ export async function editRunSettings(options) {
|
|
|
25
25
|
s.workerModel = workerPick.model;
|
|
26
26
|
s.workerProviderId = workerPick.providerId;
|
|
27
27
|
const suggestFast = !!(options.defaults?.fastModel);
|
|
28
|
-
const fastChoice = await select(`${chalk.cyan("③")} Fast model ${chalk.dim("(optional -- Haiku/Qwen for
|
|
29
|
-
{ name: "Skip", value: "skip", hint: "
|
|
30
|
-
{ name: "Pick a fast
|
|
28
|
+
const fastChoice = await select(`${chalk.cyan("③")} Fast worker model ${chalk.dim("(optional -- Haiku/Qwen for well-scoped tasks, checked by next wave's workers)")}:`, [
|
|
29
|
+
{ name: "Skip", value: "skip", hint: "single-worker mode (main worker handles everything)" },
|
|
30
|
+
{ name: "Pick a fast worker", value: "pick", hint: "Haiku, Qwen, or any provider -- a cheaper, faster second worker" },
|
|
31
31
|
], suggestFast ? 1 : 0);
|
|
32
32
|
if (fastChoice === "pick") {
|
|
33
|
-
const fastPick = await pickModel(`${chalk.cyan("③b")} Fast model:`, models, options.defaults?.fastModel ?? s.fastModel);
|
|
33
|
+
const fastPick = await pickModel(`${chalk.cyan("③b")} Fast worker model:`, models, options.defaults?.fastModel ?? s.fastModel);
|
|
34
34
|
s.fastModel = fastPick.model;
|
|
35
35
|
s.fastProviderId = fastPick.providerId;
|
|
36
36
|
}
|
package/dist/steering.js
CHANGED
|
@@ -89,6 +89,9 @@ You have full creative freedom. Design the wave that will have the highest impac
|
|
|
89
89
|
**Polish** -- Agents focus purely on feel: loading states, error messages, micro-interactions, empty states, responsiveness. Not features -- the texture that makes users trust the product.
|
|
90
90
|
Example: 2 agents, one on happy paths, one on error/edge states
|
|
91
91
|
|
|
92
|
+
**Simplify** -- Invoke the 'simplify' skill. It reviews changed code and spawns parallel sub-agents for thorough review.
|
|
93
|
+
Example: 1 agent per wave with task type "review", let the skill handle the rest
|
|
94
|
+
|
|
92
95
|
You can combine these. A wave can have 3 execute agents + 1 verification agent. Or 2 divergent explorers. Whatever the situation calls for.
|
|
93
96
|
|
|
94
97
|
For non-execute tasks (critique, verify, user-test, synthesize), tell agents to write their output to files in the run directory so findings persist for future waves. Use paths like: .claude-overnight/latest/reflections/wave-N-{topic}.md or .claude-overnight/latest/verifications/wave-N-{topic}.md.
|
|
@@ -104,15 +107,31 @@ Respond with ONLY a JSON object (no markdown fences):
|
|
|
104
107
|
"estimatedSessionsRemaining": 15,
|
|
105
108
|
"tasks": [
|
|
106
109
|
{"prompt": "task instruction...", "model": "worker", "postcondition": "test -f src/new-file.ts"},
|
|
107
|
-
{"prompt": "quick icon fix, verified by
|
|
110
|
+
{"prompt": "quick icon fix, verified by next wave's workers...", "model": "fast"},
|
|
108
111
|
{"prompt": "verify the app end-to-end...", "model": "worker", "noWorktree": true}
|
|
109
112
|
]
|
|
110
113
|
}
|
|
111
114
|
|
|
112
115
|
"estimatedSessionsRemaining" is REQUIRED. Your best honest estimate of how many MORE agent sessions (beyond the wave you just composed above) are needed to reach 'amazing' -- include follow-up fixes, polish, verification, and anything else you'd want before shipping. Be realistic, not optimistic. Use 0 only if truly done.
|
|
113
116
|
|
|
114
|
-
The "model" field on each task
|
|
115
|
-
|
|
117
|
+
The "model" field on each task — you have **two kinds of workers**, both first-class. Pick the right one per task:
|
|
118
|
+
|
|
119
|
+
**Fast worker — "fast" (${fastModel ?? "not set"})** is the default workhorse for well-scoped, mechanical tasks. It's a real worker, same tools, same environment — just a cheaper, faster model. The next wave's workers (fast or main) will catch and fix any issues. Route here by default when any of these apply:
|
|
120
|
+
- Single-file edits, refactors, renames
|
|
121
|
+
- Surgical multi-line changes with a clear spec (add a param, wrap a call, tweak a prompt line)
|
|
122
|
+
- Read/research: scan files, summarize findings
|
|
123
|
+
- Build checks, postcondition verification
|
|
124
|
+
- E2E test runs with concrete steps
|
|
125
|
+
- Simple critiques, polish tweaks
|
|
126
|
+
- Running existing scripts/tests and capturing output
|
|
127
|
+
- Docs / markdown updates
|
|
128
|
+
- Stdlib-only utility scripts with a crisp spec
|
|
129
|
+
|
|
130
|
+
**Main worker — "worker" (${workerModel})** is for tasks that genuinely need deeper reasoning: multi-file features, complex logic, architectural changes, ambiguous specs, anything where a mis-step costs more than a wave to recover from.
|
|
131
|
+
|
|
132
|
+
When in doubt, pick "fast". Both are workers; the wave loop iterates. Over-using "worker" is a real cost — aim to route the clear majority of well-scoped tasks to the fast worker whenever a fast worker is configured.
|
|
133
|
+
|
|
134
|
+
Set "noWorktree": true for verify/user-test tasks -- they need the real project directory with env files, dependencies, and local config.
|
|
116
135
|
|
|
117
136
|
OPTIONAL "postcondition": a single shell one-liner that exits 0 when the task is truly done. The framework runs it after merge; if it fails, the agent's "no-op" claim is rejected and the task is retried with the failure output as context. Use it whenever the task has a concrete, machine-checkable outcome. Examples: \`test -f src/tracking/watchlist-poller.ts && grep -q "runWatchlistPoll" src/tracking/watchlist-poller.ts\`, \`grep -q "watchlistPollerTask" src/scraper/scheduler.ts\`, \`pnpm run build\`, \`diff -q src/public/index.html frontend/dist/index.html\`. Keep it cheap (sub-second, no network). Omit for exploratory/research tasks where there is no crisp check.
|
|
118
137
|
|
package/dist/swarm.js
CHANGED
|
@@ -6,31 +6,32 @@ import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
|
6
6
|
import { NudgeError, RATE_LIMIT_WINDOW_SHORT, extractToolTarget, sumUsageTokens } from "./types.js";
|
|
7
7
|
import { gitExec, autoCommit, mergeAllBranches, warnDirtyTree, cleanStaleWorktrees, writeSwarmLog } from "./merge.js";
|
|
8
8
|
import { ensureCursorProxyRunning, PROXY_DEFAULT_URL } from "./providers.js";
|
|
9
|
+
/**
|
|
10
|
+
* Proxied Cursor models ignore SDK `cwd` and use their own workspace
|
|
11
|
+
* resolution. Inject `X-Cursor-Workspace` via ANTHROPIC_CUSTOM_HEADERS so the
|
|
12
|
+
* proxy's per-request workspace override points at this agent's cwd.
|
|
13
|
+
* Requires the proxy to run with `CURSOR_BRIDGE_WORKSPACE=/` (or a parent of
|
|
14
|
+
* all worktree paths) so the header value passes the safety check.
|
|
15
|
+
*/
|
|
16
|
+
function withCursorWorkspaceHeader(env, cwd) {
|
|
17
|
+
if (!env)
|
|
18
|
+
return undefined;
|
|
19
|
+
if (env.ANTHROPIC_BASE_URL !== PROXY_DEFAULT_URL)
|
|
20
|
+
return env;
|
|
21
|
+
const hdr = `X-Cursor-Workspace: ${cwd}`;
|
|
22
|
+
const existing = env.ANTHROPIC_CUSTOM_HEADERS?.trim();
|
|
23
|
+
return {
|
|
24
|
+
...env,
|
|
25
|
+
ANTHROPIC_CUSTOM_HEADERS: existing
|
|
26
|
+
? `${existing}\n${hdr}`
|
|
27
|
+
: hdr,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
9
30
|
import { getModelCapability } from "./models.js";
|
|
10
31
|
import { createTurn, beginTurn, endTurn, updateTurn } from "./turns.js";
|
|
11
|
-
const SIMPLIFY_PROMPT = `You just finished your task.
|
|
12
|
-
|
|
13
|
-
Run \`git diff\` to see what you changed, then fix any issues:
|
|
14
|
-
|
|
15
|
-
1. **Reuse**: Search the codebase -- did you write something that already exists? Use existing utilities, helpers, patterns instead. Hand-rolled string manipulation, manual path handling, custom env checks, ad-hoc type guards -- all candidates for existing utilities.
|
|
16
|
-
|
|
17
|
-
2. **Quality**:
|
|
18
|
-
- Redundant state: cached values that could be derived, observers that could be direct calls
|
|
19
|
-
- Copy-paste with slight variation: near-duplicate blocks that should be unified
|
|
20
|
-
- Leaky abstractions: exposing internals or breaking existing abstraction boundaries
|
|
21
|
-
- Stringly-typed code: raw strings where enums/unions already exist
|
|
22
|
-
- Unnecessary JSX nesting: wrappers that add no layout value
|
|
23
|
-
- Comments narrating WHAT the code does -- delete them; keep only non-obvious WHY
|
|
24
|
-
|
|
25
|
-
3. **Efficiency**:
|
|
26
|
-
- Redundant computations, repeated file reads, duplicate API calls
|
|
27
|
-
- Sequential operations that could be parallel
|
|
28
|
-
- Hot-path bloat: new blocking work in startup or per-request paths
|
|
29
|
-
- Recurring no-op updates: state/store updates inside polling loops that fire unconditionally -- add change-detection guard
|
|
30
|
-
- Unnecessary existence checks before operating (TOCTOU anti-pattern)
|
|
31
|
-
- Memory: unbounded data structures, missing cleanup, event listener leaks
|
|
32
|
+
const SIMPLIFY_PROMPT = `You just finished your task. Review and simplify your changes.
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
Invoke the \`simplify\` skill to review your changes for reuse, quality, and efficiency, then fix any issues found.`;
|
|
34
35
|
export class Swarm {
|
|
35
36
|
agents = [];
|
|
36
37
|
logs = [];
|
|
@@ -561,7 +562,7 @@ export class Swarm {
|
|
|
561
562
|
? `You are working in an isolated git worktree. Focus only on this task. Do NOT commit your changes -- the framework handles that.\n\n${preamble}${task.prompt}${postBlock}`
|
|
562
563
|
: `${preamble}${task.prompt}${postBlock}`;
|
|
563
564
|
const effectiveModel = task.model || this.config.model;
|
|
564
|
-
const envOverride = this.config.envForModel?.(effectiveModel);
|
|
565
|
+
const envOverride = withCursorWorkspaceHeader(this.config.envForModel?.(effectiveModel), agentCwd);
|
|
565
566
|
const agentQuery = query({
|
|
566
567
|
prompt: agentPrompt,
|
|
567
568
|
options: {
|
|
@@ -786,7 +787,9 @@ Respond with JSON: {"keep": true/false, "reason": "brief explanation"}`;
|
|
|
786
787
|
allowDangerouslySkipPermissions: true,
|
|
787
788
|
maxTurns: 1,
|
|
788
789
|
persistSession: false,
|
|
789
|
-
...(envFor?.(evalModel) && {
|
|
790
|
+
...(envFor?.(evalModel) && {
|
|
791
|
+
env: withCursorWorkspaceHeader(envFor(evalModel), this.config.cwd),
|
|
792
|
+
}),
|
|
790
793
|
},
|
|
791
794
|
});
|
|
792
795
|
this.activeQueries.add(eq);
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# Proxied fast-model research — Skills, tool_use, workspace, and cursor-native translation
|
|
2
|
+
|
|
3
|
+
Session date: 2026-04-18. Status: **research notes, no code changes yet.** Picks up where `CURSOR_PROXY_MACOS_DISCOVERY.md` left off.
|
|
4
|
+
|
|
5
|
+
Goal: understand what happens when a proxied Cursor model (composer-2-fast via cursor-composer-in-claude) is dispatched through the Agent SDK's `query()` — specifically whether Anthropic skills and tool-use introspection work, and what would be needed to make proxied fast models feel "just like another endpoint" (qwen-style).
|
|
6
|
+
|
|
7
|
+
## TL;DR findings
|
|
8
|
+
|
|
9
|
+
1. **Proxied fast models cannot invoke the Skill tool.** Not a phrasing issue — cursor-agent has its own hardcoded tool loop and treats SDK-provided tools (Skill, Task, sub-Agent, etc.) as text context only.
|
|
10
|
+
2. **Zero `tool_use` content blocks surface to the SDK.** cursor-agent emits rich `tool_call` events in its `stream-json` output, but the proxy's `cli-stream-parser.ts` only parses `type:"assistant"` blocks with nested `part.type==="tool_use"`. It drops every `tool_call` event on the floor. ~30 LOC fix.
|
|
11
|
+
3. **SDK `cwd` option is ignored** by cursor-agent. Needs per-request `X-Cursor-Workspace` header (already supported by the proxy) + `CURSOR_BRIDGE_WORKSPACE=/` (or broad enough base) for worktree isolation with proxied agents.
|
|
12
|
+
4. **Proxy version floor is 0.9.4.** v0.9.2 forced `--mode ask` (read-only); fixed in 0.9.3 but 0.9.3 was never published. `npm install cursor-composer-in-claude@0.9.4` gets agent-mode default.
|
|
13
|
+
5. **The cloud endpoint is `https://agentn.global.api5.cursor.sh/agent.v1.AgentService/Run`** — HTTP/2 + protobuf, not JSON. It's an *agent* endpoint, not a pure model endpoint.
|
|
14
|
+
6. **Cursor-native rules work perfectly as skill equivalents.** `.cursor/rules/*.mdc` files with frontmatter are discovered, read, and followed by cursor-agent verbatim — including slash-command invocation like `/simplify`.
|
|
15
|
+
|
|
16
|
+
## Baseline: what works vs doesn't
|
|
17
|
+
|
|
18
|
+
| | Haiku 4.5 direct | composer-2-fast via proxy (0.9.4) |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `/simplify` Skill invocation | ✅ 12 tool calls, follows skill recipe (3 parallel review agents) | ❌ model says "Skill tool isn't wired up in this session" |
|
|
21
|
+
| File actually simplified | ✅ | ✅ (done inline via cursor-agent's internal tools) |
|
|
22
|
+
| `tool_use` blocks surface to SDK | ✅ Read, Edit, Bash, Agent visible | ❌ zero — everything is invisible |
|
|
23
|
+
| `cwd: <path>` option | ✅ respected | ❌ cursor-agent uses its own workspace resolution |
|
|
24
|
+
| Cost | $0.21 | $0.068 (≈3× cheaper) |
|
|
25
|
+
| Duration | 41s | 24–43s |
|
|
26
|
+
|
|
27
|
+
## How I tested
|
|
28
|
+
|
|
29
|
+
All probes in `/tmp/simplify-probe/` (scratch dir, not committed). Created a trivial messy TypeScript file:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
export function add(a: number, b: number): number {
|
|
33
|
+
const result: number = a + b;
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Then spawned `query()` from `@anthropic-ai/claude-agent-sdk` with different model/env combinations, each time asking it to simplify the file.
|
|
39
|
+
|
|
40
|
+
### 1. Haiku 4.5 direct (baseline)
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
const agent = query({
|
|
44
|
+
prompt: "Please run /simplify on messy.ts in the current directory.",
|
|
45
|
+
options: { cwd: "/tmp/simplify-probe", model: "claude-haiku-4-5-20251001", permissionMode: "bypassPermissions" },
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- **Result:** invoked `Skill({skill:"simplify", args:"messy.ts"})` on turn 1, then launched 3 parallel `general-purpose` subagents (reuse/quality/efficiency), then edited.
|
|
50
|
+
- **Tool calls surfaced:** Skill, Read (×2), Bash (×3), Agent (×3), Edit (×1).
|
|
51
|
+
- **Cost / time:** $0.21 / 41s.
|
|
52
|
+
|
|
53
|
+
### 2. composer-2-fast via cursor-composer-in-claude (v0.9.2 — broken)
|
|
54
|
+
|
|
55
|
+
Symptoms that led us to debug:
|
|
56
|
+
- Text reply: *"### Ask mode — I can't run `/simplify` or change messy.ts from here. That needs Agent mode."*
|
|
57
|
+
- Zero tool calls.
|
|
58
|
+
- File not modified.
|
|
59
|
+
- Looked for the file in the proxy's startup cwd, not the SDK's.
|
|
60
|
+
|
|
61
|
+
Root cause (from `cursor-composer-in-claude/CHANGELOG.md` 0.9.3):
|
|
62
|
+
|
|
63
|
+
> `--mode agent` is now the default — Previously the proxy always appended `--mode <plan|ask>` to every cursor-agent invocation. Current cursor-agent treats both as strictly read-only (Write/Bash calls are silently dropped, exit 0 with empty stdout).
|
|
64
|
+
|
|
65
|
+
Fix: `npm install cursor-composer-in-claude@0.9.4`. The package.json already pins `^0.9.4` but our `node_modules` had stale 0.9.2.
|
|
66
|
+
|
|
67
|
+
### 3. composer-2-fast via v0.9.4 (now agent-mode default)
|
|
68
|
+
|
|
69
|
+
Model now does real work but:
|
|
70
|
+
- Edits `src/__tests__/simplify-target.ts` in the claude-overnight repo instead of `/tmp/simplify-probe/messy.ts`, because it resolves cwd from the proxy's startup dir, not the SDK's `cwd: "/tmp/simplify-probe"` option. **Real bug for claude-overnight worktree isolation.**
|
|
71
|
+
- Still zero `tool_use` blocks surfaced. File changes happen through cursor-agent's internal Write tool and don't bubble up.
|
|
72
|
+
|
|
73
|
+
### 4. composer-2-fast with workspace header (the fix)
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
const env = envFor(p);
|
|
77
|
+
env.ANTHROPIC_CUSTOM_HEADERS = "X-Cursor-Workspace: /tmp/simplify-probe";
|
|
78
|
+
// and start proxy with CURSOR_BRIDGE_WORKSPACE=/
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
- Agent SDK honors `ANTHROPIC_CUSTOM_HEADERS` env var (newline-separated `Key: Value` pairs — confirmed in `cli.js` string `ANTHROPIC_CUSTOM_HEADERS`).
|
|
82
|
+
- Proxy's `resolveWorkspace()` in `workspace.ts:50` reads `x-cursor-workspace` header; validates that the requested path is under `config.workspace` (the proxy's base). Setting base to `/` (or a broad parent) lets arbitrary worktree paths validate.
|
|
83
|
+
- Three prompt variants (`/simplify`, "use the simplify skill", concrete instructions) all simplified correctly now. Still 0 tool_use blocks.
|
|
84
|
+
|
|
85
|
+
### 5. Forcing the Skill tool explicitly (confirmation test)
|
|
86
|
+
|
|
87
|
+
Prompt: *"You have a tool named Skill. Invoke it now with parameters {skill: \"simplify\", args: \"messy.ts\"}. Do not do any work yourself — your only job is to emit that one Skill tool call."*
|
|
88
|
+
|
|
89
|
+
Response: *"I don't have a `Skill` tool in this Cursor session, so I can't emit that call here."*
|
|
90
|
+
|
|
91
|
+
Confirmed: the model is correctly reporting that the Skill tool isn't actually callable from its vantage point. Not a prompting issue.
|
|
92
|
+
|
|
93
|
+
## Why tool_use doesn't surface
|
|
94
|
+
|
|
95
|
+
Ran cursor-agent directly, bypassing the proxy:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
CI=true CURSOR_SKIP_KEYCHAIN=1 CURSOR_API_KEY="..." \
|
|
99
|
+
/opt/homebrew/bin/node /Users/francesco/.local/share/cursor-agent/versions/2026.04.17-479fd04/index.js \
|
|
100
|
+
-p --output-format stream-json --stream-partial-output \
|
|
101
|
+
--trust --workspace /tmp/simplify-probe --model composer-2-fast \
|
|
102
|
+
"read messy.ts then edit it to remove the intermediate result variable"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Cursor-agent emits rich `tool_call` events** (not `tool_use`):
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{"type":"tool_call","subtype":"started","call_id":"tool_…","tool_call":{"readToolCall":{"args":{"path":"/tmp/simplify-probe/messy.ts"}}}}
|
|
109
|
+
{"type":"tool_call","subtype":"completed","call_id":"tool_…","tool_call":{"readToolCall":{"args":{…},"result":{"success":{"content":"…","totalLines":5,"fileSize":103,"path":"…","readRange":{"startLine":1,"endLine":5}}}}}}
|
|
110
|
+
{"type":"tool_call","subtype":"started","tool_call":{"editToolCall":{"args":{"path":"/tmp/simplify-probe/messy.ts","streamContent":"export function add(a: number, b: number): number {\n return a + b;\n}"}}}}
|
|
111
|
+
{"type":"tool_call","subtype":"completed","tool_call":{"editToolCall":{"args":{…},"result":{"success":{"linesAdded":1,"linesRemoved":2,"diffString":"--- a//tmp/simplify-probe/messy.ts\n+++ …"}}}}}
|
|
112
|
+
{"type":"tool_call","subtype":"started","tool_call":{"readLintsToolCall":{"args":{"paths":["/tmp/simplify-probe/messy.ts"]}}}}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Tool taxonomy observed (there are more — this is just what I triggered):
|
|
116
|
+
|
|
117
|
+
| cursor-agent event | Mapping to Anthropic standard |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `readToolCall` | `Read` |
|
|
120
|
+
| `editToolCall` | `Edit` (also `Write` when streamContent is full file) |
|
|
121
|
+
| `readLintsToolCall` | (no direct equivalent — could be "LSP diagnostics") |
|
|
122
|
+
| `globToolCall` | `Glob` |
|
|
123
|
+
| `grepToolCall` | `Grep` |
|
|
124
|
+
| `shellToolCall` | `Bash` |
|
|
125
|
+
| `taskToolCall` | `Task` / `Agent` (parallel sub-agents — confirmed working) |
|
|
126
|
+
| `webFetchToolCall` | `WebFetch` |
|
|
127
|
+
| `webSearchToolCall` | `WebSearch` |
|
|
128
|
+
|
|
129
|
+
The proxy's `cli-stream-parser.ts` only handles:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
if (obj.type === "assistant" && obj.message?.content) {
|
|
133
|
+
for (const part of obj.message.content) {
|
|
134
|
+
if (part.type === "text") …
|
|
135
|
+
else if (part.type === "thinking") …
|
|
136
|
+
else if (part.type === "tool_use" && part.id && part.name) …
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (obj.type === "result" && obj.subtype === "success") { done = true; onDone(); }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**It never matches `obj.type === "tool_call"`.** That's the bug. The `anthropic-sse-writer.ts` at line 59–82 already has a full `kind: "tool_use"` → SSE `content_block_start` path. We just don't feed it.
|
|
143
|
+
|
|
144
|
+
Fix sketch (~30 LOC in `cli-stream-parser.ts`):
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
if (obj.type === "tool_call" && obj.subtype === "started") {
|
|
148
|
+
const [kind, body] = Object.entries(obj.tool_call)[0]; // e.g. ["readToolCall", {args, ...}]
|
|
149
|
+
const name = mapToolName(kind); // readToolCall → Read
|
|
150
|
+
const input = translateArgs(kind, body.args); // keep args shape the Anthropic SDK expects
|
|
151
|
+
onEvent({ kind: "tool_use", id: obj.call_id, name, input });
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
(May also need to buffer tool results and forward them as `tool_result` content blocks in the next turn, depending on how the Agent SDK wants to correlate them.)
|
|
156
|
+
|
|
157
|
+
## The cloud endpoint — what Cursor actually talks to
|
|
158
|
+
|
|
159
|
+
Instrumented cursor-agent with a `NODE_OPTIONS=--require` preload (`/tmp/simplify-probe/fetch-logger.cjs`) that hooks `global.fetch`, `http.request`, `https.request`, and `http2.connect`. Only http2 captured the real chat traffic — cursor-agent uses undici under the hood, but the chat RPC goes through node:http2.
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
HTTP/2 POST https://agentn.global.api5.cursor.sh/agent.v1.AgentService/Run
|
|
163
|
+
Authorization: Bearer <JWT>
|
|
164
|
+
Content-Type: (protobuf, inferred — body is binary)
|
|
165
|
+
Body: 153 KB for a "what is 2+2" prompt (!!)
|
|
166
|
+
Response: streaming, ~9 KB+ rolling
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Plus many auxiliary JSON HTTP/1.1 calls to `https://api2.cursor.sh/aiserver.v1.*Service/*`:
|
|
170
|
+
- `AnalyticsService/BootstrapStatsig`
|
|
171
|
+
- `DashboardService/GetMe`, `GetTeamAdminSettings…`, `GetTeamHooks`, `GetManagedSkills`
|
|
172
|
+
- `ServerConfigService/GetServerConfig`
|
|
173
|
+
- `AiService/GetUsableModels`, `GetDefaultModelForCli`
|
|
174
|
+
- `AnalyticsService/SubmitLogs`, `TrackEvents`
|
|
175
|
+
- `DashboardService/GetCliDownloadUrl`
|
|
176
|
+
- `/v1/traces` (OTEL)
|
|
177
|
+
|
|
178
|
+
The chat endpoint `agent.v1.AgentService/Run` is revealing: **it's an agent-loop RPC, not a model-completion endpoint**. It expects the client to hold conversational state, execute tools locally, and feed tool results back for the next step. The 153 KB initial payload carries the whole context (prompt + tool defs + workspace hints + history).
|
|
179
|
+
|
|
180
|
+
So composer-2-fast's *only* public interface is the agent loop. There's no bare "generate text from this prompt" endpoint to call qwen-style.
|
|
181
|
+
|
|
182
|
+
## Full path A: bypass cursor-agent (the qwen dream) — not recommended
|
|
183
|
+
|
|
184
|
+
What it would take:
|
|
185
|
+
1. Extract `agent.v1.*` proto schema from `cursor-agent-svc.js` (contains hundreds of message type definitions — looks doable but tedious).
|
|
186
|
+
2. Implement protobuf codec for request + streaming response.
|
|
187
|
+
3. Handle JWT refresh (observed short-lived tokens ~1h expiry).
|
|
188
|
+
4. Translate Anthropic tool_use ↔ cursor tool_call format bidirectionally.
|
|
189
|
+
5. Handle all the auxiliary RPCs (`BootstrapStatsig`, `GetUsableModels`, etc.) that cursor-agent fires on startup.
|
|
190
|
+
6. Maintain against Cursor's API churn indefinitely.
|
|
191
|
+
|
|
192
|
+
**Weeks of work, permanent maintenance tax, can break any time.** Probably also violates Cursor's TOS.
|
|
193
|
+
|
|
194
|
+
Also: even if we do this, SDK-provided tools like Skill wouldn't automatically "just work" — we'd need to map them to cursor's native tool concepts anyway, which we can do without the protobuf spike.
|
|
195
|
+
|
|
196
|
+
## Full path B+C: fix the parser + expose cursor tools as Anthropic names (recommended)
|
|
197
|
+
|
|
198
|
+
Scope:
|
|
199
|
+
|
|
200
|
+
1. **`cli-stream-parser.ts` — translate `tool_call` events to `tool_use` events.** ~30 LOC. Gives the SDK full tool visibility: progress UI, budget tracking, nudge-on-silence, logs.
|
|
201
|
+
2. **Tool-name mapping** (tiny table in the proxy): `readToolCall → Read`, `editToolCall → Edit`, `globToolCall → Glob`, `runTerminalToolCall → Bash`, etc.
|
|
202
|
+
3. **Rewrite `toolsToSystemText`**: drop SDK-provided tools that cursor-agent can't honor (Skill, Task, sub-Agent) from the system text. Advertise only the cursor-native tools that actually execute, under Anthropic-standard names.
|
|
203
|
+
|
|
204
|
+
After this, the SDK sees: `assistant → tool_use(Read) → tool_result → tool_use(Edit) → …` exactly like a direct Anthropic session.
|
|
205
|
+
|
|
206
|
+
## Path D — **skill translation via `.cursor/rules/*.mdc`** (the killer unlock)
|
|
207
|
+
|
|
208
|
+
cursor-agent supports `.cursor/rules/<name>.mdc` files natively (confirmed: `cursor-agent rule` subcommand, `generate-rule`, rules auto-discovered). Shape:
|
|
209
|
+
|
|
210
|
+
```markdown
|
|
211
|
+
---
|
|
212
|
+
description: Short description for the model to decide when to apply
|
|
213
|
+
alwaysApply: false
|
|
214
|
+
# globs: optional
|
|
215
|
+
---
|
|
216
|
+
# Rule body
|
|
217
|
+
|
|
218
|
+
Instructions the agent follows…
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Proof that cursor-agent resolves them autonomously** — wrote `/tmp/skilltest/.cursor/rules/simplify.mdc` with a description matching Anthropic's simplify skill, then ran:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
cursor-agent -p --workspace /tmp/skilltest --model composer-2-fast "/simplify messy.ts"
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
First emitted tool call:
|
|
228
|
+
|
|
229
|
+
```json
|
|
230
|
+
{"tool_call":{"readToolCall":{"args":{"path":"/tmp/skilltest/.cursor/rules/simplify.mdc"}}}}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Cursor-agent autonomously discovered, read, and followed the rule.** File was simplified according to the rule body. Full tool stream: read rule → glob for target → read target → edit → lint.
|
|
234
|
+
|
|
235
|
+
### Translation map
|
|
236
|
+
|
|
237
|
+
| Anthropic | Cursor |
|
|
238
|
+
|---|---|
|
|
239
|
+
| `SKILL.md` frontmatter `name`, `description`, `type` | `.mdc` frontmatter `description`, `alwaysApply`, `globs` |
|
|
240
|
+
| Skill body | Rule body |
|
|
241
|
+
| Skill lives in plugin/user dir | Rule lives in `.cursor/rules/` or `~/.cursor/rules/` |
|
|
242
|
+
| Slash invocation `/simplify` | Slash invocation `/simplify` (identical UX — model resolves from description) |
|
|
243
|
+
| Model-selected based on task | Model-selected based on task (identical) |
|
|
244
|
+
| MCP tools | `.cursor/mcp.json` MCP tools (universal MCP protocol — no translation) |
|
|
245
|
+
| `CLAUDE.md` | `.cursor/rules/_always.mdc` with `alwaysApply: true` |
|
|
246
|
+
|
|
247
|
+
### Proxy behavior after adding skill translation
|
|
248
|
+
|
|
249
|
+
Per request:
|
|
250
|
+
1. Receive Anthropic `/v1/messages` with tools + system + user prompt.
|
|
251
|
+
2. Extract skill metadata (names + descriptions). Full bodies either:
|
|
252
|
+
- (a) bundled in the proxy for well-known Anthropic skills, OR
|
|
253
|
+
- (b) sent by claude-overnight as custom headers / system-prompt extra blocks, OR
|
|
254
|
+
- (c) the Agent SDK exposes them via a mechanism TBD.
|
|
255
|
+
3. Materialize each advertised skill as `.cursor/rules/<name>.mdc` in the workspace (or per-request temp dir if `chatOnlyWorkspace`).
|
|
256
|
+
4. Strip Skill/Task/sub-Agent from `toolsToSystemText` (they're unneeded now — skills live on disk as rules).
|
|
257
|
+
5. Run cursor-agent.
|
|
258
|
+
6. `tool_call` → `tool_use` translation streams back (from B).
|
|
259
|
+
|
|
260
|
+
**Result:** from the SDK's view, proxied fast models now honor skills. From cursor-agent's view, it's a normal Cursor session.
|
|
261
|
+
|
|
262
|
+
### Caveats
|
|
263
|
+
|
|
264
|
+
- **Skill bodies need to travel** — simplest path: bundle the common ones (simplify, security-review, etc.) with the proxy. Less clean but works day one.
|
|
265
|
+
- **Rule-file writes need per-request workspace isolation** — tie-in with the `X-Cursor-Workspace` fix. Don't stomp on parallel agents.
|
|
266
|
+
- **`alwaysApply: false`** rules are model-selected based on description — works well in practice (test confirmed composer-2-fast picked up the rule on `/simplify`). For stronger guarantees use `alwaysApply: true` or matching `globs`.
|
|
267
|
+
- **Sub-skill chains** (skill A invokes skill B) — Cursor rules can reference other rules (`@ruleName`). Needs a naming convention.
|
|
268
|
+
- **Parallel sub-agents DO work.** Earlier version of this doc claimed cursor-agent was single-agent — that was wrong. cursor-agent ships a first-class `TaskToolCall` (proto `agent.v1.TaskToolCallArgsProto`, fields `description`/`prompt`/`model`/`subagent_type`/`resume`/`readonly`/`run_in_background`/`attachments` — identical shape to Anthropic's Task tool). Runtime creates `kind: "subagent"` sessions with their own `agentId`, and the UI explicitly groups parallel `taskToolCall`s. See "Parallel sub-agents — confirmed" below for the empirical test. `/simplify`'s 3-reviewer fan-out replicates directly.
|
|
269
|
+
|
|
270
|
+
## Parallel sub-agents — confirmed (2026-04-18)
|
|
271
|
+
|
|
272
|
+
Empirical test that cursor-agent runs sub-agents concurrently, not sequentially.
|
|
273
|
+
|
|
274
|
+
Setup: `/tmp/subagent-probe/` with `messy.ts` and `.cursor/rules/fanout.mdc`. Rule body instructs the model to spawn three Task sub-agents in a single turn (count lines / count exports / find inline candidates).
|
|
275
|
+
|
|
276
|
+
Invocation:
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
CI=true CURSOR_SKIP_KEYCHAIN=1 CURSOR_API_KEY=… \
|
|
280
|
+
/opt/homebrew/bin/node /Users/francesco/.local/share/cursor-agent/versions/2026.04.17-479fd04/index.js \
|
|
281
|
+
-p --output-format stream-json --trust \
|
|
282
|
+
--workspace /tmp/subagent-probe --model composer-2-fast \
|
|
283
|
+
"/fanout messy.ts"
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Observed in `stream-json`:
|
|
287
|
+
|
|
288
|
+
```
|
|
289
|
+
other started: readToolCall # rule discovery
|
|
290
|
+
other started: readToolCall # target file
|
|
291
|
+
task started id=tool_171e… desc=Count lines in messy.ts
|
|
292
|
+
task started id=tool_d2ab… desc=Count exports in messy.ts
|
|
293
|
+
task started id=tool_da0d… desc=Inline candidates in messy.ts
|
|
294
|
+
task completed id=tool_d2ab…
|
|
295
|
+
task completed id=tool_da0d…
|
|
296
|
+
task completed id=tool_171e…
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Three `taskToolCall`s dispatched in the same assistant turn. **Start order (171e, d2ab, da0d) differs from completion order (d2ab, da0d, 171e) — proves concurrent execution.** Each sub-agent got its own `agentId` and ran its own internal tools independently (one used `shellToolCall` for `wc -l`, the others used `readToolCall`).
|
|
300
|
+
|
|
301
|
+
Task call payload shape (what the SDK must encode when surfacing):
|
|
302
|
+
|
|
303
|
+
```json
|
|
304
|
+
{
|
|
305
|
+
"taskToolCall": {
|
|
306
|
+
"args": {
|
|
307
|
+
"description": "Count lines in messy.ts",
|
|
308
|
+
"prompt": "Read the file at absolute path /tmp/subagent-probe/messy.ts. Report ONLY the total number of lines…",
|
|
309
|
+
"subagentType": {"unspecified": {}},
|
|
310
|
+
"model": "composer-2-fast",
|
|
311
|
+
"agentId": "0b2fd6e9-9e3f-406a-92b6-8c87072303be",
|
|
312
|
+
"attachments": [],
|
|
313
|
+
"mode": "TASK_MODE_UNSPECIFIED",
|
|
314
|
+
"respondingToMessageIds": []
|
|
315
|
+
},
|
|
316
|
+
"result": {"success": {"conversationSteps": [ /* nested tool calls executed by the subagent */ ]}}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Totals: 12.1s, 9.8k input / 826 output tokens for the full fan-out including parent aggregation.
|
|
322
|
+
|
|
323
|
+
**Implications for Path B/D:**
|
|
324
|
+
|
|
325
|
+
1. `cli-stream-parser.ts` tool-name table must include `taskToolCall → Task` (or `Agent`, whichever name the SDK expects for the parent-visible sub-agent tool).
|
|
326
|
+
2. Subagent inner events live inside `result.success.conversationSteps`. Decide whether to flatten them into the outer event stream (so the SDK sees `tool_use(Task) → tool_use(Read) inside → tool_result(Task)` as a nested tree) or collapse them into just the outer Task tool_use/tool_result pair. The latter is simpler and matches Anthropic's Task-tool UX, where sub-agent internals are opaque to the caller.
|
|
327
|
+
3. `subagent_type` can be left unspecified; cursor-agent accepts it. `model` defaults to the parent's model (inherited), which is the right default.
|
|
328
|
+
|
|
329
|
+
Raw stream preserved at `/tmp/subagent-probe/run.jsonl` for later inspection.
|
|
330
|
+
|
|
331
|
+
## Per-workspace isolation — the adjacent bug
|
|
332
|
+
|
|
333
|
+
Independent of skills, claude-overnight currently has a real correctness issue for proxied agents in worktrees:
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
// src/swarm.ts:578 — current spawn
|
|
337
|
+
const agentQuery = query({
|
|
338
|
+
prompt: agentPrompt,
|
|
339
|
+
options: {
|
|
340
|
+
cwd: agentCwd, model: effectiveModel, permissionMode: perm,
|
|
341
|
+
allowedTools: this.config.allowedTools,
|
|
342
|
+
…
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
For proxied agents, `cwd: agentCwd` has no effect. Two agents in separate worktrees would both execute in the proxy's startup cwd. Fix:
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
const env = this.config.envForModel?.(effectiveModel);
|
|
351
|
+
if (env && isCursorProxiedModel(effectiveModel)) {
|
|
352
|
+
env.ANTHROPIC_CUSTOM_HEADERS = `X-Cursor-Workspace: ${agentCwd}`;
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Plus ensure the proxy is started with `CURSOR_BRIDGE_WORKSPACE=/` (or a common parent of all worktree dirs).
|
|
357
|
+
|
|
358
|
+
This is a separate fix that should land regardless of the skill-translation work.
|
|
359
|
+
|
|
360
|
+
## Code locations for reference
|
|
361
|
+
|
|
362
|
+
### `cursor-composer-in-claude` (sibling repo, Francesco's fork at ../cursor-composer-in-claude)
|
|
363
|
+
|
|
364
|
+
- `src/lib/agent-cmd-args.ts` — builds `--mode` / `--workspace` / `--model` flags. 0.9.3 made `agent` default.
|
|
365
|
+
- `src/lib/env.ts:276–281` — `CURSOR_BRIDGE_MODE` parsing (`plan` | `ask` | `agent`).
|
|
366
|
+
- `src/lib/env.ts:256–258` — `workspace` config (defaults to proxy's `process.cwd()`).
|
|
367
|
+
- `src/lib/workspace.ts:50–106` — `resolveWorkspace()`: reads `x-cursor-workspace` header, validates path is under base.
|
|
368
|
+
- `src/lib/handlers/anthropic-messages.ts:147–159` — per-request header-based workspace resolution.
|
|
369
|
+
- `src/lib/openai.ts:58–87` — `toolsToSystemText()`: how SDK tool defs get serialized to system-prompt text (this is where to rewrite when exposing cursor tools under Anthropic names).
|
|
370
|
+
- `src/lib/cli-stream-parser.ts:41–75` — the parser that needs the `tool_call` case added.
|
|
371
|
+
- `src/lib/anthropic-sse-writer.ts:59–82` — already-wired SSE emitter for `tool_use` events.
|
|
372
|
+
|
|
373
|
+
### `claude-overnight`
|
|
374
|
+
|
|
375
|
+
- `src/providers.ts:160–215` — `envFor()`: where per-model env (including proxy auth + bridge settings) is built. Add `X-Cursor-Workspace` injection here, driven by the agent's `cwd`.
|
|
376
|
+
- `src/swarm.ts:563–584` — agent spawn. `env` is already passed via `envForModel(effectiveModel)`; just needs per-agent cwd propagation.
|
|
377
|
+
|
|
378
|
+
### Agent SDK (`@anthropic-ai/claude-agent-sdk`)
|
|
379
|
+
|
|
380
|
+
- `cli.js` — honors `ANTHROPIC_CUSTOM_HEADERS` env var (newline-separated `Key: Value`), string confirmed present.
|
|
381
|
+
- `sdk.d.ts:700–710` — `headers` field on McpHttpServerConfig (not the right one for our use — the env var is the right path).
|
|
382
|
+
|
|
383
|
+
### Cursor
|
|
384
|
+
|
|
385
|
+
- `https://agentn.global.api5.cursor.sh/agent.v1.AgentService/Run` — the chat RPC (HTTP/2 + protobuf).
|
|
386
|
+
- `https://api2.cursor.sh/aiserver.v1.*Service/*` — auxiliary REST/JSON endpoints.
|
|
387
|
+
- Proto schema lives in `/Users/francesco/.local/share/cursor-agent/versions/<ver>/cursor-agent-svc.js` (bundled, minified) — contains hundreds of `aiserver.v1.*` / `agent.v1.*` message type definitions.
|
|
388
|
+
|
|
389
|
+
## Quick artifacts for picking this up later
|
|
390
|
+
|
|
391
|
+
- Scratch test dir: `/tmp/simplify-probe/` — has all probe scripts (probe.mjs, probe-proxy.mjs, probe-proxy-v2.mjs, probe-proxy-v3.mjs, probe-skill-direct.mjs, fetch-logger.cjs).
|
|
392
|
+
- Cursor-rule test dir: `/tmp/skilltest/` — has the `.cursor/rules/simplify.mdc` demo.
|
|
393
|
+
- Proxy logs: `/Users/francesco/.cursor-api-proxy/proxy.out.log` and `sessions.log`.
|
|
394
|
+
- Cursor-agent CLI: `/Users/francesco/.local/bin/cursor-agent` (avoid — segfaults with bundled Node on macOS); use `/opt/homebrew/bin/node <cursor-agent-install>/index.js` instead.
|
|
395
|
+
|
|
396
|
+
## Recommended next steps (in order)
|
|
397
|
+
|
|
398
|
+
1. **Land the `X-Cursor-Workspace` fix in claude-overnight** — independent, fixes a real worktree-isolation bug. Small patch in `providers.ts:envFor()` + start proxy with `CURSOR_BRIDGE_WORKSPACE=/`.
|
|
399
|
+
2. **Patch the proxy's `cli-stream-parser.ts`** to translate `tool_call` → `tool_use`. ~30 LOC. Gives full tool visibility in claude-overnight's UI/logs for proxied agents.
|
|
400
|
+
3. **Update `toolsToSystemText`** to drop non-executable SDK tools (Skill/Task/sub-Agent) for proxied sessions and list cursor-native tools under Anthropic names.
|
|
401
|
+
4. **Bundle skill → rule translation** in the proxy. Start with `/simplify`, `/review`, `/security-review`, `/init`. Materialize into workspace on request. Confirm end-to-end.
|
|
402
|
+
5. **Update steering/planner prompts** to give concrete operational briefs instead of skill invocations (works for both direct and proxied models — concrete is the common denominator).
|
|
403
|
+
6. **Optional/far future:** Path A (bypass cursor-agent entirely) only if the ceiling of B+C+skill-translation turns out to be too low — which seems unlikely given the experiments so far.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-overnight",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.42",
|
|
4
4
|
"description": "Parallel Claude agents in git worktrees with a usage cap that reserves headroom for your interactive Claude Code. Crash-safe resume. Provider-agnostic model catalog (Anthropic, Cursor, OpenAI, Gemini, DeepSeek, Llama, Qwen) with capability-based task scoping.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@anthropic-ai/claude-agent-sdk": "^0.2.92",
|
|
19
19
|
"chalk": "^5.4.1",
|
|
20
|
-
"cursor-composer-in-claude": "^0.
|
|
20
|
+
"cursor-composer-in-claude": "^0.10.0",
|
|
21
21
|
"jsonwebtoken": "^9.0.2"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-overnight",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.42",
|
|
4
4
|
"description": "Claude Code skill for understanding, installing, and inspecting claude-overnight runs -- parallel Claude agents in git worktrees with thinking waves, multi-wave steering, and crash-safe resume. Supports Cursor API Proxy, Qwen, OpenRouter.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Francesco Fornace"
|
|
@@ -11,7 +11,7 @@ description: >
|
|
|
11
11
|
|
|
12
12
|
# What it is
|
|
13
13
|
|
|
14
|
-
`claude-overnight` is a CLI (npm: `claude-overnight`, bin: `claude-overnight`) that takes an objective + budget and launches many Claude agent sessions in parallel, each in an isolated git worktree. It's a local multi-session orchestrator built on top of the Claude Agent SDK -- not itself an agent harness, but a layer that plans, dispatches, and steers many sessions that run on the SDK's harness. Three roles are picked independently: **planner** (thinks, steers, reviews), **worker** (runs the tasks), and an optional **fast**
|
|
14
|
+
`claude-overnight` is a CLI (npm: `claude-overnight`, bin: `claude-overnight`) that takes an objective + budget and launches many Claude agent sessions in parallel, each in an isolated git worktree. It's a local multi-session orchestrator built on top of the Claude Agent SDK -- not itself an agent harness, but a layer that plans, dispatches, and steers many sessions that run on the SDK's harness. Three roles are picked independently: **planner** (thinks, steers, reviews), **main worker** (runs the tasks), and an optional **fast worker** (a cheaper/faster second worker for well-scoped tasks, verified by the next wave's workers). A "thinking wave" of architect sessions explores the codebase, an orchestrator synthesizes concrete tasks, worker waves run them in parallel, and steering decides between more work, reflection, or declaring done. Rate limits, crashes, and usage caps are all resumable -- nothing is lost.
|
|
15
15
|
|
|
16
16
|
**Three-layer review system** runs on every wave:
|
|
17
17
|
1. **Per-agent self-review** -- after each agent finishes, the same session continues via SDK session resume (continue mechanism) with a follow-up prompt to review and simplify its own `git diff`. The agent's full context stays warm -- no initial context bloat.
|
|
@@ -55,7 +55,7 @@ Every run lives at `<repo>/.claude-overnight/runs/<ISO-timestamp>/`:
|
|
|
55
55
|
|
|
56
56
|
| File / dir | What it tells you |
|
|
57
57
|
|----------------------|-----------------------------------------------------------------------------------|
|
|
58
|
-
| `run.json` | Machine state: objective, planner/worker/fast models, budget, cost, waves done, branches, done flag. |
|
|
58
|
+
| `run.json` | Machine state: objective, planner/main-worker/fast-worker models, budget, cost, waves done, branches, done flag. |
|
|
59
59
|
| `status.md` | **Living project snapshot**, rewritten by steering every wave. First line = short status. |
|
|
60
60
|
| `goal.md` | Evolving "north star" -- what the run currently thinks "amazing" means. |
|
|
61
61
|
| `themes.md` | The thinking-wave research angles picked for this objective (human-readable). |
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
name: claude-overnight-coach
|
|
3
3
|
description: >
|
|
4
4
|
Setup coach for claude-overnight. Turns a raw user objective into a ready
|
|
5
|
-
objective plus recommended run settings (budget, concurrency, planner/
|
|
6
|
-
models, flex, usage cap, permission mode)
|
|
5
|
+
objective plus recommended run settings (budget, concurrency, planner /
|
|
6
|
+
main-worker / optional fast-worker models, flex, usage cap, permission mode)
|
|
7
|
+
and an actionable preflight
|
|
7
8
|
checklist. Invoked once, before the interactive pickers, to catch prompt-shape
|
|
8
9
|
failures (vague, overambitious, multi-goal, unverifiable) and environmental
|
|
9
10
|
failures (missing keys, dirty tree, missing .env) while they're still cheap
|
|
@@ -69,8 +70,8 @@ Rules:
|
|
|
69
70
|
- `improvedObjective` preserves the user's voice and domain vocabulary. It MUST include a `Done:` line, a `Critical:` line (or `Critical: none` when nothing is off-limits), and a `Verify by:` line.
|
|
70
71
|
- `recommended.budget` is an integer ≥ 1. `concurrency` is an integer in [1, 12]. `usageCap` is either `null` (unlimited) or a float in (0, 1].
|
|
71
72
|
- `recommended.permissionMode` is `"auto" | "bypassPermissions" | "default"`.
|
|
72
|
-
- `fastModel` is `null` unless adding one is clearly warranted for this scope + budget AND a cheap fast model is reachable from the available providers.
|
|
73
|
-
- `recommended.plannerModel` / `workerModel` / `fastModel` MUST be model IDs that the user can actually reach given the providers listed in the input. Stock Anthropic IDs (e.g. `claude-opus-4-7`, `claude-sonnet-4-6`, `claude-haiku-4-5`) are only valid when "Anthropic direct: available" appears in the input.
|
|
73
|
+
- `fastModel` (the fast-worker model) is `null` unless adding one is clearly warranted for this scope + budget AND a cheap fast-worker model is reachable from the available providers.
|
|
74
|
+
- `recommended.plannerModel` (planner) / `workerModel` (main worker) / `fastModel` (fast worker) MUST be model IDs that the user can actually reach given the providers listed in the input. Stock Anthropic IDs (e.g. `claude-opus-4-7`, `claude-sonnet-4-6`, `claude-haiku-4-5`) are only valid when "Anthropic direct: available" appears in the input.
|
|
74
75
|
- `checklist` `remediation` is an informational label — the host does NOT auto-act on it. Set it to the slug that best describes the issue, or `"none"` for purely advisory items.
|
|
75
76
|
- `questions` is reserved for a future clarification loop; return `[]` for now.
|
|
76
77
|
|
|
@@ -95,36 +96,37 @@ Rows: scope. Each cell is a starting point — adjust by one step when repo fact
|
|
|
95
96
|
|
|
96
97
|
| scope | tight ≤ 10 | standard 11–25 | wide 26–60 | saturated > 60 |
|
|
97
98
|
| ------------------------ | -------------------------------------------- | --------------------------------------------- | --------------------------------------------- | ----------------------------------------------- |
|
|
98
|
-
| bugfix | conc=2, flex=false, fast=null, cap=0.75 | conc=3, flex=true, fast=null, cap=0.75 | conc=4, flex=true, fast=
|
|
99
|
-
| feature-add | conc=2, flex=true, fast=null, cap=0.75 | conc=4, flex=true, fast=null, cap=0.75 | conc=6, flex=true, fast=
|
|
100
|
-
| refactor | conc=2, flex=false, fast=null, cap=0.75 | conc=4, flex=false, fast=null, cap=0.75 | conc=6, flex=true, fast=null, cap=0.9
|
|
101
|
-
| audit-and-fix | conc=3, flex=true, fast=
|
|
102
|
-
| migration | conc=2, flex=true, fast=null, cap=0.75 | conc=4, flex=true, fast=null, cap=0.9 | conc=6, flex=true, fast=null, cap=0.9
|
|
103
|
-
| research-and-implement | conc=2, flex=true, fast=null, cap=0.75 | conc=3, flex=true, fast=null, cap=0.75 | conc=4, flex=true, fast=null, cap=0.9
|
|
104
|
-
| polish-and-verify | conc=3, flex=false, fast=
|
|
99
|
+
| bugfix | conc=2, flex=false, fast=null, cap=0.75 | conc=3, flex=true, fast=null, cap=0.75 | conc=4, flex=true, fast=true, cap=0.9 | conc=5, flex=true, fast=true, cap=null |
|
|
100
|
+
| feature-add | conc=2, flex=true, fast=null, cap=0.75 | conc=4, flex=true, fast=null, cap=0.75 | conc=6, flex=true, fast=true, cap=0.9 | conc=8, flex=true, fast=true, cap=null |
|
|
101
|
+
| refactor | conc=2, flex=false, fast=null, cap=0.75 | conc=4, flex=false, fast=null, cap=0.75 | conc=6, flex=true, fast=null, cap=0.9 | conc=8, flex=true, fast=true, cap=null |
|
|
102
|
+
| audit-and-fix | conc=3, flex=true, fast=true, cap=0.75 | conc=5, flex=true, fast=true, cap=0.9 | conc=8, flex=true, fast=true, cap=0.9 | conc=10, flex=true, fast=true, cap=null |
|
|
103
|
+
| migration | conc=2, flex=true, fast=null, cap=0.75 | conc=4, flex=true, fast=null, cap=0.9 | conc=6, flex=true, fast=null, cap=0.9 | conc=8, flex=true, fast=null, cap=null |
|
|
104
|
+
| research-and-implement | conc=2, flex=true, fast=null, cap=0.75 | conc=3, flex=true, fast=null, cap=0.75 | conc=4, flex=true, fast=null, cap=0.9 | conc=5, flex=true, fast=true, cap=null |
|
|
105
|
+
| polish-and-verify | conc=3, flex=false, fast=true, cap=0.75 | conc=5, flex=false, fast=true, cap=0.75 | conc=8, flex=true, fast=true, cap=0.9 | conc=10, flex=true, fast=true, cap=null |
|
|
105
106
|
|
|
106
107
|
`conc` ⇒ `recommended.concurrency` (clamp to ≤ budget).
|
|
107
108
|
`flex` ⇒ `recommended.flex`.
|
|
108
|
-
`fast=
|
|
109
|
+
`fast=true` ⇒ recommend a fast-worker model **if the user has one configured and reachable** from their available providers. The fast worker is a real worker (same tools, same env) on a cheaper/faster model — steering routes well-scoped tasks to it by default. Pick whatever the cheapest fast-worker model is among their providers (e.g. `claude-haiku-4-5`, `composer-2-fast`, `qwen3` variants). If none is reachable, set `null`.
|
|
110
|
+
`fast=null` ⇒ do not recommend a fast worker (scope too complex or no suitable fast-worker model available).
|
|
109
111
|
`cap=null` ⇒ unlimited (`recommended.usageCap = null`).
|
|
110
112
|
|
|
111
|
-
## Planner / worker model selection
|
|
113
|
+
## Planner / main-worker / fast-worker model selection
|
|
112
114
|
|
|
113
|
-
Pick the strongest reachable model for the planner; pick a cheap-but-capable reachable model for the worker.
|
|
115
|
+
Pick the strongest reachable model for the planner; pick a cheap-but-capable reachable model for the main worker; optionally add a cheaper/faster second model as the fast worker.
|
|
114
116
|
|
|
115
117
|
Decision order (stop at the first row whose providers are present):
|
|
116
118
|
|
|
117
119
|
1. **Anthropic direct available**
|
|
118
120
|
- planner: `claude-opus-4-7` (or its `-thinking-high` variant when scope is `audit-and-fix` / `research-and-implement` / `migration`).
|
|
119
|
-
- worker: `claude-sonnet-4-6` for normal work; `claude-opus-4-7` for `wide`/`saturated` migrations or research.
|
|
120
|
-
- fastModel:
|
|
121
|
+
- main worker: `claude-sonnet-4-6` for normal work; `claude-opus-4-7` for `wide`/`saturated` migrations or research.
|
|
122
|
+
- fast worker (`fastModel`): recommend the cheapest fast-worker model available among the user's reachable providers when the matrix says `fast=true`.
|
|
121
123
|
2. **Custom Anthropic-compatible provider with a strong model** (e.g. `qwen3.6-plus`, `qwen3-coder-plus`)
|
|
122
124
|
- planner: the strongest such model the user has.
|
|
123
|
-
- worker: same model, or a cheaper sibling if the user has one.
|
|
125
|
+
- main worker: same model, or a cheaper sibling if the user has one.
|
|
124
126
|
3. **Cursor proxy is the only reachable provider**
|
|
125
127
|
- planner: `claude-opus-4-7` via Cursor (only if the proxy exposes it).
|
|
126
|
-
- worker: `claude-sonnet-4-6` via Cursor, or `composer-2` for the cheapest path.
|
|
127
|
-
- fastModel: `composer-2-fast` when the matrix says `fast=
|
|
128
|
+
- main worker: `claude-sonnet-4-6` via Cursor, or `composer-2` for the cheapest path.
|
|
129
|
+
- fast worker (`fastModel`): recommend a Cursor fast-worker model (e.g. `composer-2-fast`) when the matrix says `fast=true`.
|
|
128
130
|
4. **No reachable provider** — leave `plannerModel` and `workerModel` as `claude-sonnet-4-6` and emit a `blocking` checklist item titled "No reachable provider".
|
|
129
131
|
|
|
130
132
|
Never recommend Cursor models when the input does not list a `cursor proxy` provider, and never recommend stock Anthropic IDs when the input does not say "Anthropic direct: available". `fastModel` MUST be `null` rather than guessed.
|