forge-delegate 1.0.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/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # forge-delegate
2
+
3
+ MCP server that delegates coding work from your primary agent (**Claude Code, Codex, or opencode**)
4
+ to other — usually cheaper or free — AI models, so your main agent's context stays small and your
5
+ token bill stays low. Direct API calls for quick questions; full agentic delegation via
6
+ [opencode](https://opencode.ai) for real work.
7
+
8
+ Your main agent (the expensive one) keeps architecture, critical logic, and hard decisions.
9
+ The cheap model grinds through the high-volume, low-risk work (boilerplate, repetitive edits,
10
+ tests, lint/type fixes) and reports back.
11
+
12
+ ```
13
+ ┌──────────────────┐ MCP (stdio) ┌──────────────────┐ opencode run ┌──────────────────┐
14
+ │ Claude / Codex / │ ──────────────► │ forge-delegate │ ───────────────► │ opencode + cheap │
15
+ │ opencode (primary)│ │ (this server) │ --model cheap │ model │
16
+ └──────────────────┘ ◄────────────── └──────────────────┘ ◄─────────────── └──────────────────┘
17
+ result: text + session + token usage
18
+ ```
19
+
20
+ ## Quick start
21
+
22
+ **Web configurator** — pick model/targets/scope and get a ready-to-paste command
23
+ (https://muhamadbarzani.github.io/forge-delegate/). The model picker is backed by the live
24
+ [models.dev](https://models.dev) catalog — searchable, with free/cheap filters and real prices,
25
+ so you're picking from actual available models rather than guessing.
26
+
27
+ **npx (recommended — works everywhere, no bash needed):**
28
+
29
+ ```bash
30
+ npx -y forge-delegate setup --model "opencode/mimo-v2.5-free" --targets "claude,codex,opencode"
31
+ ```
32
+
33
+ That's it — npx pulls the latest package and `setup` registers the MCP server with the agents
34
+ you chose and sets your default model. Works on Windows (cmd/PowerShell), macOS, and Linux —
35
+ no bash needed, and always runs the latest version.
36
+
37
+ Run `setup` **without flags** and it walks you through the choices interactively (defaults in
38
+ `[brackets]`, Enter accepts); pass any of the flags to skip that prompt. In an automated/non-terminal
39
+ run it just uses sensible defaults.
40
+
41
+ > GitHub Pages: enable it with *Settings → Pages → Deploy from a branch → `main` / `/docs`*.
42
+ > The configurator lives at `docs/index.html`. `install.sh`'s `FORGE_DELEGATE_REPO` default
43
+ > (and the raw URL inside it) matters only if you self-host the fallback installer.
44
+ >
45
+ > Working on a checkout yourself? `node cli.mjs setup` does the same thing.
46
+
47
+ `setup` will:
48
+ 1. Register the MCP server with each agent you chose (`--targets claude,codex,opencode`).
49
+ 2. Set your default model (`--model`) so calls can omit `model`.
50
+
51
+ Routing and cost-policy guidance is injected by the MCP server itself into the agent's context
52
+ via MCP `instructions` — no memory-file (`CLAUDE.md`/`AGENTS.md`) editing needed.
53
+
54
+ Then restart your agent and confirm the server is connected: `/mcp` in Claude Code,
55
+ `codex mcp`, or `opencode`.
56
+
57
+ ### What you need
58
+
59
+ - **Node.js ≥ 18** and **opencode** installed (the installer can fetch opencode for you).
60
+ - **Free `opencode/*` models** (e.g. `opencode/mimo-v2.5-free`) need no auth — zero setup.
61
+ - **Any other provider** (deepseek, openai, google, ollama, …) must be logged into **your**
62
+ opencode first — that's a step *you* do in your own opencode, not something forge-delegate
63
+ handles: `opencode auth login <provider>`. forge-delegate reuses your opencode's credentials
64
+ and never stores API keys. (Ollama needs no key, just the Ollama app running.)
65
+
66
+ ### Windows
67
+
68
+ Use the `npx` route — no bash needed (works in cmd/PowerShell). Drop `codex` from `--targets`
69
+ (Codex CLI isn't natively supported on Windows; use `--targets "claude,opencode"`).
70
+
71
+ ### Scope
72
+
73
+ | `--scope` | Claude Code | Codex | opencode |
74
+ |---|---|---|---|
75
+ | `user` (default) | `~/.claude.json` — all projects | `~/.codex/config.toml` — all projects | `~/.config/opencode/opencode.json` — all projects |
76
+ | `project` | `.mcp.json` in the project (needs one-time approval) | `.codex/config.toml` in the project (loads for trusted projects only) | `opencode.json` in the project |
77
+
78
+ ### Try it free — zero signup
79
+
80
+ opencode ships free no-auth models under `opencode/`:
81
+
82
+ ```bash
83
+ npx -y forge-delegate setup --model "opencode/mimo-v2.5-free" --targets "claude,codex"
84
+ ```
85
+
86
+ Other free options: `opencode/hy3-free`, `opencode/nemotron-3.5-lightning-free`. Free tiers are
87
+ rate-limited; switch anytime with `forge-delegate config set --model <m>`.
88
+
89
+ ## Tools
90
+
91
+ | Tool | What it does | Context cost |
92
+ |---|---|---|
93
+ | `ask_model` | One-shot question to any model (direct API, no file access) | Small |
94
+ | `delegate_task` | Send a task + optional files to a model agent running via opencode; returns result + session id | Medium |
95
+ | `delegate_agent` | Model works **autonomously** in the **background** — returns a job id instantly; poll `check_delegation` | Tiny |
96
+ | `check_delegation` | Poll a background job (or list all with no id) | Tiny |
97
+ | `list_models` | List local/keyless, direct-API, and agentic models | Small |
98
+ | `get_delegate_config` | Read current configuration (default model, directory, autoApprove, profiles) | Tiny |
99
+ | `set_delegate_config` | Change configuration at runtime — no reinstall | Tiny |
100
+
101
+ `delegate_agent` is the token-saver: the external model gets its own tool loop executed by
102
+ opencode locally. File contents never touch your main agent's context. The delegation runs as a
103
+ detached background process, so the MCP call returns instantly.
104
+
105
+ ## When to delegate
106
+
107
+ | ✅ Delegate — high volume, low risk | ⛔ Keep on the primary agent |
108
+ |---|---|
109
+ | Tests for well-specified behavior | Architecture, system design, choosing abstractions |
110
+ | Boilerplate & scaffolding (CRUD, DTOs, fixtures, mocks) | Security-sensitive code (auth, crypto, secrets, permissions) |
111
+ | Mechanical edits across many files (renames, prop propagation, imports) | Concurrency, performance-critical paths, subtle correctness |
112
+ | Lint / formatting / type-error fixes | Ambiguous / underspecified requirements needing judgment |
113
+ | Docstrings, comments, README / changelog sections | Public API / interface design, breaking changes |
114
+ | Straightforward data transforms / migrations with a clear spec | Debugging unknown root causes |
115
+ | Obvious glue code / format conversions | Anything costly or hard to detect if wrong |
116
+
117
+ Full guide: [docs/DELEGATION.md](docs/DELEGATION.md)
118
+
119
+ ## Configuration
120
+
121
+ Stored in `~/.forge-delegate/config.json`, read fresh on every call — change it anytime without
122
+ reinstalling. Four ways:
123
+
124
+ 1. **From your agent** via the MCP tools `get_delegate_config` / `set_delegate_config`.
125
+ 2. **Terminal CLI**: `forge-delegate config get`, `forge-delegate config set --model <m> --timeout <s> --dir <path> --autoApprove false`, `forge-delegate config path`.
126
+ 3. **`setup` flags**: `--model`, `--targets`, `--scope`, `--project-dir`.
127
+ 4. **Edit the file** directly.
128
+
129
+ | Field | Description | Default |
130
+ |---|---|---|
131
+ | `defaultModel` | provider/model used when a call omits `model` | unset |
132
+ | `defaultAgent` | opencode agent used for delegations | unset |
133
+ | `defaultVariant` | default reasoning-effort variant | unset |
134
+ | `autoApprove` | auto-approve permissions so headless delegations don't block | `true` |
135
+ | `timeoutMs` | per-delegation timeout | `900000` (env `FORGE_OPENCODE_TIMEOUT`) |
136
+ | `defaultDirectory` | default working directory for delegations | unset (server cwd) |
137
+ | `profiles` | named presets `{ name: { model, agent, variant, autoApprove, directory } }` | `{}` |
138
+
139
+ Every delegation tool also accepts per-call `model`, `directory`, `agent`, `autoApprove`, and
140
+ `profile` overrides. Per-call values beat profile values beat config defaults.
141
+
142
+ ## Models
143
+
144
+ - **opencode provider** — free no-auth models (`opencode/mimo-v2.5-free`, ...) plus
145
+ anything you have logged in via `opencode auth login <provider>`.
146
+ - **Direct API from opencode logins** — `provider/model` for OpenAI-compatible providers.
147
+ - **Custom providers** — OpenAI-compatible providers defined in `opencode.json`
148
+ (`@ai-sdk/openai-compatible` + `baseURL`), callable as `provider/model`.
149
+ - **Ollama (local)** — `ollama:llama3`, `local:qwen2.5-coder`, no key needed.
150
+
151
+ ## Safety notes
152
+
153
+ - `autoApprove: true` runs delegations non-interactively with tool permissions granted — point
154
+ them at repositories you trust and review the diffs. Set `autoApprove: false` to require
155
+ manual approval.
156
+ - This server never stores API keys — provider auth lives in opencode's own auth store.
157
+ - Usage is logged to `~/.forge-delegate/usage.jsonl`; see `forge-delegate stats`.
158
+
159
+ ## CLI
160
+
161
+ ```text
162
+ forge-delegate setup Register with Claude Code / Codex / opencode
163
+ forge-delegate serve Start the MCP server (used internally)
164
+ forge-delegate config get | set | path
165
+ forge-delegate status Show opencode logins and usage summary
166
+ forge-delegate stats Show detailed usage statistics
167
+ ```
168
+
169
+ ## Publishing (npm)
170
+
171
+ The primary install is `npx -y forge-delegate setup`, so the package must be on npm. Checklist:
172
+
173
+ 1. `npm run prepublishOnly` — syntax-checks all entry files (runs automatically on publish).
174
+ 2. `npm pack --dry-run` — confirm the tarball contains only `cli.mjs`, `server.mjs`, `lib.mjs`,
175
+ `install.sh`, `docs/`, `README.md` (via the `files` field), not `node_modules`.
176
+ 3. Bump `version` in `package.json` (and the root `version` in `package-lock.json`).
177
+ 4. `npm login && npm publish` — the `bin` field makes `npx forge-delegate …` work.
178
+
179
+ Also make sure the GitHub repo exists at `MuhamadBarzani/forge-delegate` with Pages enabled on
180
+ `/docs` (configurator) — the `raw.githubusercontent.com` install URL and the configurator's
181
+ hardcoded URL point there.
182
+
183
+ ## License
184
+
185
+ MIT
package/cli.mjs ADDED
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env node
2
+ import { createInterface } from "node:readline/promises";
3
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
4
+ import { join, dirname } from "node:path";
5
+ import { homedir } from "node:os";
6
+ import { fileURLToPath } from "node:url";
7
+ import { execSync } from "node:child_process";
8
+ import { loadConfig, saveConfig, configPath } from "./lib.mjs";
9
+
10
+ const here = dirname(fileURLToPath(import.meta.url));
11
+
12
+ function parseArgs(argv) {
13
+ const args = {};
14
+ for (let i = 0; i < argv.length; i++) {
15
+ const a = argv[i];
16
+ if (!a.startsWith("--")) continue;
17
+ const eq = a.indexOf("=");
18
+ const key = eq >= 0 ? a.slice(2, eq) : a.slice(2);
19
+ if (eq >= 0) args[key] = a.slice(eq + 1);
20
+ else if (i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
21
+ args[key] = argv[i + 1];
22
+ i++;
23
+ } else args[key] = true;
24
+ }
25
+ return args;
26
+ }
27
+
28
+ function launchCommand() {
29
+ const isPublished = here.includes(join("node_modules", "forge-delegate")) || here.includes("/.npm/_npx/");
30
+ return isPublished ? "npx -y forge-delegate serve" : `node ${join(here, "cli.mjs")} serve`;
31
+ }
32
+
33
+ function launchArray() {
34
+ const isPublished = here.includes(join("node_modules", "forge-delegate")) || here.includes("/.npm/_npx/");
35
+ return isPublished ? ["npx", "-y", "forge-delegate", "serve"] : [process.execPath, join(here, "cli.mjs"), "serve"];
36
+ }
37
+
38
+ function registerClaude(launch, scope) {
39
+ try {
40
+ execSync(`claude mcp remove --scope ${scope} forge-delegate`, { stdio: "ignore" });
41
+ } catch {}
42
+ try {
43
+ execSync(`claude mcp remove forge-delegate`, { stdio: "ignore" });
44
+ } catch {}
45
+ execSync(`claude mcp add --scope ${scope} forge-delegate -- ${launch}`, { stdio: "inherit" });
46
+ console.log(` ✓ Claude Code (scope=${scope})`);
47
+ }
48
+
49
+ function codexConfigPath(scope, projectDir) {
50
+ return scope === "user" ? join(homedir(), ".codex", "config.toml") : join(projectDir, ".codex", "config.toml");
51
+ }
52
+
53
+ function registerCodex(launchArr, scope, projectDir) {
54
+ const path = codexConfigPath(scope, projectDir);
55
+ mkdirSync(dirname(path), { recursive: true });
56
+ let toml = "";
57
+ try {
58
+ toml = readFileSync(path, "utf8");
59
+ } catch {}
60
+ const section = `[mcp_servers.forge-delegate]`;
61
+ const block = `${section}\ncommand = ${JSON.stringify(launchArr[0])}\nargs = ${JSON.stringify(launchArr.slice(1))}\n`;
62
+ const escaped = section.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
63
+ if (toml.includes(section)) {
64
+ toml = toml.replace(new RegExp(`${escaped}[\\s\\S]*?(?=\\n\\[|$)`), block.trimEnd());
65
+ } else {
66
+ toml += (toml.endsWith("\n") || toml === "" ? "" : "\n") + "\n" + block;
67
+ }
68
+ writeFileSync(path, toml);
69
+ console.log(` ✓ Codex (${path})`);
70
+ }
71
+
72
+ function opencodeConfigPath(scope, projectDir) {
73
+ return scope === "user" ? join(homedir(), ".config", "opencode", "opencode.json") : join(projectDir, "opencode.json");
74
+ }
75
+
76
+ function registerOpencode(launchArr, scope, projectDir) {
77
+ const path = opencodeConfigPath(scope, projectDir);
78
+ mkdirSync(dirname(path), { recursive: true });
79
+ let cfg = {};
80
+ try {
81
+ cfg = JSON.parse(readFileSync(path, "utf8"));
82
+ } catch {}
83
+ cfg.mcp = cfg.mcp ?? {};
84
+ cfg.mcp["forge-delegate"] = { type: "local", command: launchArr, enabled: true };
85
+ writeFileSync(path, JSON.stringify(cfg, null, 2) + "\n");
86
+ console.log(` ✓ opencode (${path})`);
87
+ }
88
+
89
+ async function setup() {
90
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
91
+ const ask = (q) => rl.question(q);
92
+ const flags = parseArgs(process.argv.slice(3));
93
+ const current = loadConfig();
94
+
95
+ // Flags win; anything omitted is asked interactively (Enter accepts the [default]).
96
+ // In a non-terminal (piped/automated) run, prompts are skipped and defaults are used.
97
+ const interactive = !!process.stdin.isTTY;
98
+ let model = flags.model;
99
+ let targets = flags.targets;
100
+ let scope = flags.scope;
101
+ let projectDir = flags["project-dir"];
102
+
103
+ console.log("\n=== forge-delegate setup ===\n");
104
+ if (interactive) console.log("Press Enter to accept the suggested [default], or type a value.\n");
105
+
106
+ if (!model) {
107
+ const def = current.defaultModel ?? "opencode/mimo-v2.5-free";
108
+ model = interactive ? ((await ask(`Default model [${def}]: `)) ?? "").trim() || def : def;
109
+ }
110
+ if (!targets) targets = "claude,codex,opencode";
111
+ if (!scope) scope = "user";
112
+ if (scope === "project" && !projectDir) projectDir = process.cwd();
113
+ projectDir = projectDir ?? process.cwd();
114
+ targets = targets.split(",").map((s) => s.trim().toLowerCase());
115
+
116
+ const launch = launchCommand();
117
+ const launchArr = launchArray();
118
+
119
+ // 1. Set the default model first so it's saved even if registration hits an issue
120
+ if (model && model !== current.defaultModel) {
121
+ saveConfig({ defaultModel: model });
122
+ console.log(` ✓ default model set: ${model}`);
123
+ }
124
+
125
+ // 2. Register with chosen hosts (claude / codex / opencode)
126
+ for (const target of targets) {
127
+ try {
128
+ if (target === "claude") registerClaude(launch, scope);
129
+ else if (target === "codex") registerCodex(launchArr, scope, projectDir);
130
+ else if (target === "opencode") registerOpencode(launchArr, scope, projectDir);
131
+ else console.log(` ✗ unknown target '${target}' (use claude,codex,opencode)`);
132
+ } catch (e) {
133
+ console.log(` ✗ ${target}: ${e.message}`);
134
+ }
135
+ }
136
+
137
+ // 3. Check opencode logins
138
+ let authCount = 0;
139
+ try {
140
+ const authPath = join(homedir(), ".local/share/opencode/auth.json");
141
+ authCount = Object.keys(JSON.parse(readFileSync(authPath, "utf8"))).length;
142
+ } catch {}
143
+ if (authCount > 0) {
144
+ console.log(`Found ${authCount} opencode provider login(s) — ready to use.`);
145
+ } else {
146
+ console.log("\nNo opencode logins found. To enable models, run:");
147
+ console.log(" opencode auth login <provider> (e.g. opencode auth login deepseek)");
148
+ if (interactive) await ask("Press Enter when done (or Ctrl+C to skip): ");
149
+ }
150
+
151
+ console.log(`
152
+ Done! Restart your agent, then run /mcp (Claude Code) or \`codex mcp\` / \`opencode\` to confirm "forge-delegate" is connected.
153
+ Use list_models inside the agent to see everything available.
154
+ `);
155
+ rl.close();
156
+ }
157
+
158
+ function configCmd(argv) {
159
+ const sub = argv[0];
160
+ if (sub === "path") {
161
+ console.log(configPath());
162
+ return;
163
+ }
164
+ const flags = parseArgs(argv);
165
+ if (sub === "set") {
166
+ const patch = {};
167
+ if (flags.model) patch.defaultModel = flags.model;
168
+ if (flags.agent) patch.defaultAgent = flags.agent;
169
+ if (flags.variant) patch.defaultVariant = flags.variant;
170
+ if (flags.timeout) patch.timeoutMs = Number(flags.timeout) * 1000;
171
+ if (flags.dir) patch.defaultDirectory = flags.dir;
172
+ if (flags["default-dir"]) patch.defaultDirectory = flags["default-dir"];
173
+ if (flags.autoApprove !== undefined) patch.autoApprove = flags.autoApprove === "true" || flags.autoApprove === "1" || flags.autoApprove === true;
174
+ saveConfig(patch);
175
+ console.log(`updated ${configPath()}`);
176
+ }
177
+ const cfg = loadConfig();
178
+ console.log(JSON.stringify(cfg, null, 2));
179
+ }
180
+
181
+ async function status() {
182
+ let providers = [];
183
+ try {
184
+ const authPath = join(homedir(), ".local/share/opencode/auth.json");
185
+ const { listAuthDirectProviders } = await import("./lib.mjs");
186
+ const auth = JSON.parse(readFileSync(authPath, "utf8"));
187
+ providers = Object.keys(auth);
188
+ } catch {}
189
+ console.log(`\nopencode logins: ${providers.length ? providers.join(", ") : "none (run 'opencode auth login <provider>')"}\n`);
190
+ try {
191
+ const usagePath = join(homedir(), ".forge-delegate", "usage.jsonl");
192
+ if (existsSync(usagePath)) {
193
+ const lines = readFileSync(usagePath, "utf8").trim().split("\n").length;
194
+ console.log(`recorded delegations: ${lines} (see 'forge-delegate stats')`);
195
+ }
196
+ } catch {}
197
+ }
198
+
199
+ function stats() {
200
+ const usagePath = join(homedir(), ".forge-delegate", "usage.jsonl");
201
+ if (!existsSync(usagePath)) {
202
+ console.log("no usage recorded yet");
203
+ return;
204
+ }
205
+ const byKey = new Map();
206
+ for (const line of readFileSync(usagePath, "utf8").split("\n")) {
207
+ if (!line.trim()) continue;
208
+ try {
209
+ const u = JSON.parse(line);
210
+ const key = `${u.tool} · ${u.model}`;
211
+ const s = byKey.get(key) ?? { calls: 0, ok: 0, ms: 0, tokens: 0 };
212
+ s.calls++;
213
+ if (u.ok) s.ok++;
214
+ s.ms += u.ms ?? 0;
215
+ s.tokens += u.tokens ?? 0;
216
+ byKey.set(key, s);
217
+ } catch {}
218
+ }
219
+ console.log("\nforge-delegate usage\n");
220
+ console.log("calls ok% avg(s) tokens tool · model");
221
+ for (const [key, s] of [...byKey.entries()].sort((a, b) => b[1].calls - a[1].calls)) {
222
+ console.log(
223
+ String(s.calls).padStart(5),
224
+ Math.round((s.ok / s.calls) * 100).toString().padStart(4) + "%",
225
+ (s.ms / s.calls / 1000).toFixed(1).padStart(7),
226
+ s.tokens ? String(s.tokens).padStart(8) : " —",
227
+ " ", key
228
+ );
229
+ }
230
+ console.log(`\nlog: ${usagePath}\n`);
231
+ }
232
+
233
+ function help() {
234
+ console.log(`
235
+ forge-delegate — delegate coding work from Claude Code/Codex/opencode to other AI models
236
+
237
+ Usage:
238
+ forge-delegate setup [--model provider/model] [--targets claude,codex,opencode]
239
+ [--scope user|project] [--project-dir <path>]
240
+ Interactive setup; flags optional (run bare to be prompted)
241
+ forge-delegate serve Start the MCP server (used internally by agents)
242
+ forge-delegate config get Show current configuration
243
+ forge-delegate config set --model <m> [--agent <a>] [--variant <v>]
244
+ [--timeout <s>] [--dir <path>] [--autoApprove true|false]
245
+ forge-delegate config path Print config file path
246
+ forge-delegate status Show opencode logins and usage summary
247
+ forge-delegate stats Show detailed usage statistics
248
+
249
+ Models come from your opencode installation:
250
+ opencode auth login <provider> add a provider
251
+ opencode models list available models
252
+ `);
253
+ }
254
+
255
+ const cmd = process.argv[2] ?? "help";
256
+ if (cmd === "setup") await setup();
257
+ else if (cmd === "serve") await import("./server.mjs");
258
+ else if (cmd === "config") configCmd(process.argv.slice(3));
259
+ else if (cmd === "status") await status();
260
+ else if (cmd === "stats") stats();
261
+ else help();
@@ -0,0 +1,61 @@
1
+ # Delegation guide
2
+
3
+ forge-delegate exists so your primary (expensive) agent stays focused on the decisions that
4
+ need its judgment, while a cheap model grinds through the mechanical majority of coding work.
5
+
6
+ ## When does the primary agent delegate?
7
+
8
+ Installing the tools doesn't force an agent to use them — it decides, guided by the
9
+ `instructions` that forge-delegate injects into its context over MCP. Those instructions
10
+ contain the cost policy, the delegate-vs-keep rules below, and the routing rules for which
11
+ tool to call. Edit them by editing the `INSTRUCTIONS` string in `server.mjs`.
12
+
13
+ ## What to delegate vs. keep
14
+
15
+ | ✅ Delegate — high volume, low risk | ⛔ Keep on the primary agent |
16
+ |---|---|
17
+ | Tests for well-specified behavior | Architecture, system design, choosing abstractions |
18
+ | Boilerplate & scaffolding (CRUD, DTOs, fixtures, mocks) | Security-sensitive code (auth, crypto, secrets, permissions) |
19
+ | Mechanical edits across many files (renames, prop propagation, imports) | Concurrency, performance-critical paths, subtle correctness |
20
+ | Lint / formatting / type-error fixes | Ambiguous / underspecified requirements needing judgment |
21
+ | Docstrings, comments, README / changelog sections | Public API / interface design, breaking changes |
22
+ | Straightforward data transforms / migrations with a clear spec | Debugging unknown root causes |
23
+ | Obvious glue code / format conversions | Anything costly or hard to detect if wrong |
24
+
25
+ **Rule of thumb:** high volume + low risk → delegate. Low volume + high risk → keep it.
26
+ Unsure → keep it.
27
+
28
+ ## Which tool?
29
+
30
+ | Need | Tool |
31
+ |---|---|
32
+ | Quick one-shot question, no file access needed | `ask_model` |
33
+ | A self-contained task with concrete code output | `delegate_task` |
34
+ | Autonomous multi-file work, don't want to block | `delegate_agent` + `check_delegation` |
35
+ | See what's available | `list_models` |
36
+
37
+ ## Cost policy (non-negotiable)
38
+
39
+ The user pays per delegated call on a budget plan. **Always pick cheap or free models.**
40
+ Never pick top-tier/premium/frontier models — not even "just this once" or for hard tasks.
41
+ If a task seems too hard for a cheap model, split it into smaller delegated steps instead of
42
+ upgrading the model.
43
+
44
+ ## Preconditions for a good delegation
45
+
46
+ 1. **Self-contained prompt.** State the goal, constraints, and acceptance criteria. Don't
47
+ assume the cheap model saw the surrounding conversation.
48
+ 2. **Right working directory.** Pass `directory:` (or set `defaultDirectory`) — don't rely on
49
+ the server's inherited cwd.
50
+ 3. **Bound the blast radius.** Auto-approve is on by default, so point delegations at
51
+ repositories you trust and review the diffs afterward.
52
+ 4. **Follow up cheaply.** Keep the returned `session` id and pass it as `continue_session` for
53
+ follow-ups — the agent keeps its context instead of re-exploring.
54
+
55
+ ## Tuning
56
+
57
+ - Change the default model / directory / auto-approve anytime: `set_delegate_config`, or
58
+ `forge-delegate config set`.
59
+ - Save named presets in `profiles` (e.g. `tests`, `quick`) and reference them per call with
60
+ `profile: "name"`.
61
+ - Raise/lower the per-delegation timeout with `timeoutMs` (env default `FORGE_OPENCODE_TIMEOUT`).