forge-delegate 1.0.4 → 1.0.7
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 +11 -0
- package/cli.mjs +30 -9
- package/install.sh +5 -1
- package/lib.mjs +1 -14
- package/package.json +2 -1
- package/server.mjs +76 -19
- package/skills/delegate-work/SKILL.md +84 -0
package/README.md
CHANGED
|
@@ -116,6 +116,17 @@ inline; long ones are auto-backgrounded by Claude Code (past ~2 min) or run deta
|
|
|
116
116
|
|
|
117
117
|
Full guide: [docs/DELEGATION.md](docs/DELEGATION.md)
|
|
118
118
|
|
|
119
|
+
### The `delegate-work` skill
|
|
120
|
+
|
|
121
|
+
Setup also installs a Claude Code skill (`~/.claude/skills/delegate-work/`, or
|
|
122
|
+
`<project>/.claude/skills/` under `--scope project`). Skip it with `--no-skill`.
|
|
123
|
+
|
|
124
|
+
It exists because MCP server `instructions` are injected once at session start and
|
|
125
|
+
get buried as the context fills — by turn 30 they lose to whatever is in front of the
|
|
126
|
+
model. A skill description is re-matched against every user turn, so the delegation
|
|
127
|
+
triggers ("write tests for", "rename X everywhere", "fix the lint errors") stay live
|
|
128
|
+
for the whole session instead of decaying.
|
|
129
|
+
|
|
119
130
|
## Configuration
|
|
120
131
|
|
|
121
132
|
Stored in `~/.forge-delegate/config.json`, read fresh on every call — change it anytime without
|
package/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createInterface } from "node:readline/promises";
|
|
3
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from "node:fs";
|
|
4
4
|
import { join, dirname } from "node:path";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -35,15 +35,33 @@ function launchArray() {
|
|
|
35
35
|
return isPublished ? ["npx", "-y", "forge-delegate", "serve"] : [process.execPath, join(here, "cli.mjs"), "serve"];
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
// The MCP server's `instructions` are injected once at session start and decay as the
|
|
39
|
+
// context fills. A skill is re-matched against every user turn, so the delegation
|
|
40
|
+
// triggers stay live for the whole session.
|
|
41
|
+
function installClaudeSkill(scope, projectDir) {
|
|
42
|
+
const src = join(here, "skills", "delegate-work", "SKILL.md");
|
|
43
|
+
if (!existsSync(src)) return;
|
|
44
|
+
const root = scope === "user" ? join(homedir(), ".claude") : join(projectDir, ".claude");
|
|
45
|
+
const dest = join(root, "skills", "delegate-work", "SKILL.md");
|
|
46
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
47
|
+
copyFileSync(src, dest);
|
|
48
|
+
console.log(` \u2713 delegate-work skill (${dest})`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// `claude mcp` writes .mcp.json relative to its own cwd, so a project-scope install
|
|
52
|
+
// must run from projectDir — otherwise it lands in whatever directory setup was
|
|
53
|
+
// invoked from, and installClaudeSkill (which does honour projectDir) disagrees
|
|
54
|
+
// with it.
|
|
55
|
+
function registerClaude(launch, scope, projectDir) {
|
|
56
|
+
const opts = scope === "project" ? { cwd: projectDir } : {};
|
|
39
57
|
try {
|
|
40
|
-
execSync(`claude mcp remove --scope ${scope} forge-delegate`, { stdio: "ignore" });
|
|
58
|
+
execSync(`claude mcp remove --scope ${scope} forge-delegate`, { ...opts, stdio: "ignore" });
|
|
41
59
|
} catch {}
|
|
42
60
|
try {
|
|
43
|
-
execSync(`claude mcp remove forge-delegate`, { stdio: "ignore" });
|
|
61
|
+
execSync(`claude mcp remove forge-delegate`, { ...opts, stdio: "ignore" });
|
|
44
62
|
} catch {}
|
|
45
|
-
execSync(`claude mcp add --scope ${scope} forge-delegate -- ${launch}`, { stdio: "inherit" });
|
|
46
|
-
console.log(` ✓ Claude Code (scope=${scope})`);
|
|
63
|
+
execSync(`claude mcp add --scope ${scope} forge-delegate -- ${launch}`, { ...opts, stdio: "inherit" });
|
|
64
|
+
console.log(` ✓ Claude Code (scope=${scope}${scope === "project" ? `, ${projectDir}` : ""})`);
|
|
47
65
|
}
|
|
48
66
|
|
|
49
67
|
function codexConfigPath(scope, projectDir) {
|
|
@@ -125,7 +143,10 @@ async function setup() {
|
|
|
125
143
|
// 2. Register with chosen hosts (claude / codex / opencode)
|
|
126
144
|
for (const target of targets) {
|
|
127
145
|
try {
|
|
128
|
-
if (target === "claude")
|
|
146
|
+
if (target === "claude") {
|
|
147
|
+
registerClaude(launch, scope, projectDir);
|
|
148
|
+
if (!flags["no-skill"]) installClaudeSkill(scope, projectDir);
|
|
149
|
+
}
|
|
129
150
|
else if (target === "codex") registerCodex(launchArr, scope, projectDir);
|
|
130
151
|
else if (target === "opencode") registerOpencode(launchArr, scope, projectDir);
|
|
131
152
|
else console.log(` ✗ unknown target '${target}' (use claude,codex,opencode)`);
|
|
@@ -169,7 +190,6 @@ function configCmd(argv) {
|
|
|
169
190
|
if (flags.variant) patch.defaultVariant = flags.variant;
|
|
170
191
|
if (flags.timeout) patch.timeoutMs = Number(flags.timeout) * 1000;
|
|
171
192
|
if (flags.dir) patch.defaultDirectory = flags.dir;
|
|
172
|
-
if (flags["default-dir"]) patch.defaultDirectory = flags["default-dir"];
|
|
173
193
|
if (flags.autoApprove !== undefined) patch.autoApprove = flags.autoApprove === "true" || flags.autoApprove === "1" || flags.autoApprove === true;
|
|
174
194
|
saveConfig(patch);
|
|
175
195
|
console.log(`updated ${configPath()}`);
|
|
@@ -236,8 +256,9 @@ forge-delegate — delegate coding work from Claude Code/Codex/opencode to other
|
|
|
236
256
|
|
|
237
257
|
Usage:
|
|
238
258
|
forge-delegate setup [--model provider/model] [--targets claude,codex,opencode]
|
|
239
|
-
[--scope user|project] [--project-dir <path>]
|
|
259
|
+
[--scope user|project] [--project-dir <path>] [--no-skill]
|
|
240
260
|
Interactive setup; flags optional (run bare to be prompted)
|
|
261
|
+
--no-skill skips installing the delegate-work skill
|
|
241
262
|
forge-delegate serve Start the MCP server (used internally by agents)
|
|
242
263
|
forge-delegate config get Show current configuration
|
|
243
264
|
forge-delegate config set --model <m> [--agent <a>] [--variant <v>]
|
package/install.sh
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
# --targets a,b,c hosts to register with: claude,codex,opencode (default all)
|
|
13
13
|
# --scope user|project registration scope (default user)
|
|
14
14
|
# --project-dir <path> project for --scope project (default current dir)
|
|
15
|
+
# --no-skill skip installing the delegate-work skill for Claude Code
|
|
15
16
|
# --no-opencode skip auto-installing opencode if missing
|
|
16
17
|
# --no-npx skip the npx route, force clone/checkout
|
|
17
18
|
# --help
|
|
@@ -23,6 +24,7 @@ SCOPE="user"
|
|
|
23
24
|
PROJECT_DIR=""
|
|
24
25
|
NO_OPENCODE=0
|
|
25
26
|
NO_NPX=0
|
|
27
|
+
NO_SKILL=0
|
|
26
28
|
|
|
27
29
|
while [[ $# -gt 0 ]]; do
|
|
28
30
|
case "$1" in
|
|
@@ -34,10 +36,11 @@ while [[ $# -gt 0 ]]; do
|
|
|
34
36
|
--scope=*) SCOPE="${1#*=}"; shift ;;
|
|
35
37
|
--project-dir) PROJECT_DIR="${2:-}"; shift 2 ;;
|
|
36
38
|
--project-dir=*) PROJECT_DIR="${1#*=}"; shift ;;
|
|
39
|
+
--no-skill) NO_SKILL=1; shift ;;
|
|
37
40
|
--no-opencode) NO_OPENCODE=1; shift ;;
|
|
38
41
|
--no-npx) NO_NPX=1; shift ;;
|
|
39
42
|
--help|-h)
|
|
40
|
-
sed -n '3,
|
|
43
|
+
sed -n '3,19p' "$0"
|
|
41
44
|
exit 0 ;;
|
|
42
45
|
*) echo "unknown option: $1"; exit 1 ;;
|
|
43
46
|
esac
|
|
@@ -68,6 +71,7 @@ fi
|
|
|
68
71
|
ARGS=(--targets "$TARGETS" --scope "$SCOPE")
|
|
69
72
|
if [[ -n "$MODEL" ]]; then ARGS+=(--model "$MODEL"); fi
|
|
70
73
|
if [[ "$SCOPE" == "project" ]]; then ARGS+=(--project-dir "$PROJECT_DIR"); fi
|
|
74
|
+
if [[ "$NO_SKILL" -eq 1 ]]; then ARGS+=(--no-skill); fi
|
|
71
75
|
|
|
72
76
|
# 3. Preferred route: published npm package (works on Windows, always latest)
|
|
73
77
|
if [[ "$NO_NPX" -eq 0 ]] && command -v npx >/dev/null 2>&1; then
|
package/lib.mjs
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { promisify } from "node:util";
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
3
2
|
import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
3
|
import { join } from "node:path";
|
|
5
4
|
import { homedir } from "node:os";
|
|
6
5
|
|
|
7
|
-
const execFileAsync = promisify(execFile);
|
|
8
6
|
export const OPENCODE_TIMEOUT = Number(process.env.FORGE_OPENCODE_TIMEOUT ?? 900_000);
|
|
9
7
|
const CONFIG_DIR = join(homedir(), ".forge-delegate");
|
|
10
8
|
const AUTH_PATH = join(homedir(), ".local/share/opencode/auth.json");
|
|
@@ -344,14 +342,3 @@ export async function runOne(model, prompt) {
|
|
|
344
342
|
throw new Error(`unknown model '${model}' — see list_models`);
|
|
345
343
|
}
|
|
346
344
|
|
|
347
|
-
export async function gitDiff() { try {
|
|
348
|
-
const { stdout } = await execFileAsync("git", ["diff", "HEAD"], {
|
|
349
|
-
cwd: process.cwd(),
|
|
350
|
-
maxBuffer: 5 * 1024 * 1024,
|
|
351
|
-
});
|
|
352
|
-
return stdout.trim();
|
|
353
|
-
} catch {
|
|
354
|
-
return null;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forge-delegate",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "MCP server that relays coding work to other AI models (DeepSeek, Gemini, OpenAI, Ollama) so Claude Code saves tokens",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"server.mjs",
|
|
17
17
|
"lib.mjs",
|
|
18
18
|
"install.sh",
|
|
19
|
+
"skills/",
|
|
19
20
|
"docs/",
|
|
20
21
|
"README.md"
|
|
21
22
|
],
|
package/server.mjs
CHANGED
|
@@ -4,13 +4,13 @@ import { z } from "zod";
|
|
|
4
4
|
import { execFile, spawn } from "node:child_process";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { mkdirSync, writeFileSync, readFileSync, existsSync, openSync, readdirSync } from "node:fs";
|
|
7
|
-
import { join } from "node:path";
|
|
7
|
+
import { join, dirname } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
8
9
|
import { homedir } from "node:os";
|
|
9
10
|
import {
|
|
10
11
|
logUsage,
|
|
11
12
|
opencodeRun,
|
|
12
13
|
runOne,
|
|
13
|
-
gitDiff,
|
|
14
14
|
opencodeArgs,
|
|
15
15
|
parseEvents,
|
|
16
16
|
pickVariant,
|
|
@@ -24,25 +24,63 @@ import {
|
|
|
24
24
|
|
|
25
25
|
const execFileAsync = promisify(execFile);
|
|
26
26
|
const RUNS_DIR = join(homedir(), ".forge-delegate", "runs");
|
|
27
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const VERSION = JSON.parse(readFileSync(join(here, "package.json"), "utf8")).version;
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
// The server auto-updates (npx re-resolves latest on each spawn) but the installed
|
|
31
|
+
// skill is a file on disk that only `setup` rewrites — so a published skill change
|
|
32
|
+
// goes unnoticed. Compare the two and say so, rather than running stale in silence.
|
|
33
|
+
function skillStatus() {
|
|
34
|
+
const src = join(here, "skills", "delegate-work", "SKILL.md");
|
|
35
|
+
if (!existsSync(src)) return null;
|
|
36
|
+
const dests = [
|
|
37
|
+
join(homedir(), ".claude", "skills", "delegate-work", "SKILL.md"),
|
|
38
|
+
join(process.cwd(), ".claude", "skills", "delegate-work", "SKILL.md"),
|
|
39
|
+
];
|
|
40
|
+
const installed = dests.filter(existsSync);
|
|
41
|
+
if (!installed.length) return { state: "missing", detail: "not installed" };
|
|
42
|
+
const want = readFileSync(src, "utf8");
|
|
43
|
+
const stale = installed.filter((d) => readFileSync(d, "utf8") !== want);
|
|
44
|
+
return stale.length
|
|
45
|
+
? { state: "stale", detail: `out of date: ${stale.join(", ")}` }
|
|
46
|
+
: { state: "ok", detail: "up to date" };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const SKILL_STATUS = skillStatus();
|
|
50
|
+
if (SKILL_STATUS && SKILL_STATUS.state !== "ok") {
|
|
51
|
+
// stderr only — stdout is the JSON-RPC channel.
|
|
52
|
+
process.stderr.write(
|
|
53
|
+
`forge-delegate: delegate-work skill ${SKILL_STATUS.detail}. Run: npx -y forge-delegate setup\n`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
29
56
|
|
|
30
|
-
|
|
31
|
-
|
|
57
|
+
const INSTRUCTIONS = `forge-delegate hands coding work to cheap external models running as full agents. They read the project and write the code in their own loop, so the work never passes through your context. Spend your context on judgment; delegate the typing.
|
|
58
|
+
|
|
59
|
+
DELEGATE BY DEFAULT — do not hand-write these yourself:
|
|
60
|
+
- Tests for code that already exists.
|
|
61
|
+
- Mechanical refactors: renames, signature changes, import rewrites, API migrations.
|
|
62
|
+
- Lint, type, and build errors — paste the error output and delegate the fix.
|
|
63
|
+
- Boilerplate: CRUD endpoints, config and schema files, adapters, fixtures, scaffolding.
|
|
64
|
+
- Ports and translations between languages, frameworks, or file formats.
|
|
65
|
+
- The same edit repeated across 3 or more files.
|
|
66
|
+
- Reading a large file or directory to answer one narrow question — delegate the reading, keep your context clean.
|
|
67
|
+
- The survey half of an audit ("does this follow X standards", "is this secure", "where is the tech debt", "how does X work"). Delegate the gathering, keep the verdict: ask for a structured digest with a file path behind every claim, then judge it yourself. A claim with no path is not a finding.
|
|
68
|
+
|
|
69
|
+
KEEP FOR YOURSELF: architecture, debugging an unknown root cause, security-sensitive code, anything where being wrong is expensive, and the verdict on whatever comes back. Delegation does not transfer responsibility for the result.
|
|
32
70
|
|
|
33
71
|
Routing:
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
- Parallel or
|
|
37
|
-
- Follow-
|
|
38
|
-
- Omit model/directory
|
|
39
|
-
- Never paste
|
|
40
|
-
|
|
41
|
-
|
|
72
|
+
- One-shot question, no file access needed -> ask_model.
|
|
73
|
+
- Anything touching files -> delegate (inline; Claude Code auto-backgrounds calls past 2 minutes).
|
|
74
|
+
- Parallel fan-out or long jobs -> delegate with background:true, then poll check_delegation.
|
|
75
|
+
- Follow-up in the same area -> pass the previous session id as continue_session.
|
|
76
|
+
- Omit model/directory/agent: the configured defaults are already correct (get_delegate_config to see them, set_delegate_config to change them — never reinstall).
|
|
77
|
+
- Never paste file contents into a task. Name the paths; the agent reads them itself.
|
|
78
|
+
|
|
79
|
+
Cost: the configured default model is already a cheap or free tier — just use it. Do not override \`model\` upward to a premium or flagship tier; if a task looks too big for the default, split it into smaller delegations instead.
|
|
42
80
|
`;
|
|
43
81
|
|
|
44
82
|
const server = new McpServer(
|
|
45
|
-
{ name: "forge-delegate", version:
|
|
83
|
+
{ name: "forge-delegate", version: VERSION },
|
|
46
84
|
{ instructions: INSTRUCTIONS }
|
|
47
85
|
);
|
|
48
86
|
|
|
@@ -79,7 +117,22 @@ async function startBackgroundJob(model, task, opts = {}) {
|
|
|
79
117
|
stdio: ["ignore", out, out],
|
|
80
118
|
});
|
|
81
119
|
child.unref();
|
|
82
|
-
|
|
120
|
+
const started = Date.now();
|
|
121
|
+
const deadline = opts.timeoutMs ? started + opts.timeoutMs : null;
|
|
122
|
+
if (deadline) {
|
|
123
|
+
setTimeout(() => {
|
|
124
|
+
try {
|
|
125
|
+
process.kill(-child.pid, "SIGKILL");
|
|
126
|
+
} catch {}
|
|
127
|
+
try {
|
|
128
|
+
process.kill(child.pid, "SIGKILL");
|
|
129
|
+
} catch {}
|
|
130
|
+
}, opts.timeoutMs).unref();
|
|
131
|
+
}
|
|
132
|
+
writeFileSync(
|
|
133
|
+
join(RUNS_DIR, `${id}.json`),
|
|
134
|
+
JSON.stringify({ id, pid: child.pid, model, directory: cwd, started, deadline, logPath })
|
|
135
|
+
);
|
|
83
136
|
return id;
|
|
84
137
|
}
|
|
85
138
|
|
|
@@ -120,7 +173,7 @@ server.tool(
|
|
|
120
173
|
|
|
121
174
|
server.tool(
|
|
122
175
|
"ask_model",
|
|
123
|
-
"
|
|
176
|
+
"USE THIS INSTEAD OF ANSWERING FROM YOUR OWN REASONING when the question is self-contained and cheap to verify: a syntax or API detail, a regex, a short snippet, a second opinion on a design, a summary of text you already have. Sends a one-shot prompt to an external model and returns its raw response. Fast direct API call; the model has NO file access and CANNOT take actions — for anything touching files use delegate instead. Omit model to use the configured default (already a cheap tier).",
|
|
124
177
|
{
|
|
125
178
|
model: z.string().optional().describe("provider/model, e.g. deepseek/deepseek-v4-flash, ollama:llama3 (defaults to config defaultModel)"),
|
|
126
179
|
prompt: z.string(),
|
|
@@ -137,7 +190,7 @@ server.tool(
|
|
|
137
190
|
|
|
138
191
|
server.tool(
|
|
139
192
|
"delegate",
|
|
140
|
-
"
|
|
193
|
+
"USE THIS INSTEAD OF WRITING THE CODE YOURSELF when the task is: tests for code that already exists; a mechanical refactor (rename, signature change, import rewrite, API migration); fixing lint/type/build errors; boilerplate or scaffolding (CRUD endpoints, config, schemas, adapters, fixtures); a port between languages or frameworks; the same edit repeated across 3 or more files; reading a large file/directory to answer one narrow question; or the survey half of an audit (delegate the gathering, keep the verdict — demand a file path behind every claim). Hands the task to another AI model running as a full agent via local opencode: it reads your project, explores, and writes concrete code in its own tool loop — none of which enters your context. Keep architecture, unknown-root-cause debugging, and security-sensitive code for yourself, and always review what comes back. Runs INLINE by default (result returned when done; Claude Code auto-backgrounds calls past 2 minutes). Pass background:true to run detached and get a job id instantly, then poll check_delegation — use that for parallel fan-out or hosts that block on long calls. Pass continue_session (returned by a previous delegation) to keep the same agent session instead of re-exploring. Never paste file contents into task: name the paths and let the agent read them. model/directory/agent/autoApprove default from config — omit them.",
|
|
141
194
|
{
|
|
142
195
|
model: z.string().optional().describe("provider/model format, e.g. opencode/mimo-v2.5-free (defaults to config defaultModel)"),
|
|
143
196
|
task: z.string(),
|
|
@@ -159,6 +212,7 @@ server.tool(
|
|
|
159
212
|
directory: r.directory,
|
|
160
213
|
agent: r.agent,
|
|
161
214
|
autoApprove: r.autoApprove,
|
|
215
|
+
timeoutMs: r.timeoutMs,
|
|
162
216
|
});
|
|
163
217
|
return `Started background job ${id}. Poll with check_delegation(id: "${id}").`;
|
|
164
218
|
});
|
|
@@ -202,7 +256,9 @@ server.tool(
|
|
|
202
256
|
const parsed = parseEvents(log);
|
|
203
257
|
const tail = parsed.text || "(agent still thinking, no text output yet)";
|
|
204
258
|
const footer = parsed.sessionId ? ` · session: ${parsed.sessionId}` : "";
|
|
205
|
-
|
|
259
|
+
let status = running ? "RUNNING" : "DONE";
|
|
260
|
+
if (meta.deadline && Date.now() > meta.deadline) status = "TIMED OUT";
|
|
261
|
+
return `[${status}] ${id} (${meta.model}, ${Math.round((Date.now() - meta.started) / 1000)}s)${footer}\n\n${tail.slice(-8000)}`;
|
|
206
262
|
})
|
|
207
263
|
);
|
|
208
264
|
|
|
@@ -215,7 +271,8 @@ server.tool(
|
|
|
215
271
|
const { profiles, ...rest } = cfg;
|
|
216
272
|
const lines = Object.entries(rest).map(([k, v]) => ` ${k}: ${v ?? "(unset)"}`);
|
|
217
273
|
const prof = Object.entries(profiles).map(([k, v]) => ` ${k}: ${JSON.stringify(v)}`).join("\n") || " (none)";
|
|
218
|
-
|
|
274
|
+
const skill = SKILL_STATUS ? `\nskill (delegate-work): ${SKILL_STATUS.detail}${SKILL_STATUS.state === "ok" ? "" : " — run: npx -y forge-delegate setup"}` : "";
|
|
275
|
+
return { content: [{ type: "text", text: `config: ${configPath()}\n${lines.join("\n")}\nprofiles:\n${prof}${skill}` }] };
|
|
219
276
|
}
|
|
220
277
|
);
|
|
221
278
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: delegate-work
|
|
3
|
+
description: Hand a coding task to a cheap external model via forge-delegate instead of writing it yourself. Load BEFORE starting work that is high-volume and low-judgment - writing tests for code that already exists, mechanical refactors (renames, signature changes, import rewrites, API migrations), fixing lint/type/build errors, boilerplate and scaffolding (CRUD endpoints, config files, schemas, adapters, fixtures), porting code between languages or frameworks, the same edit repeated across 3 or more files, or reading a large file or directory to answer one narrow question. ALSO load for audit and survey questions - "does my codebase follow X", "audit", "review the architecture", "is this idiomatic", "where is the tech debt", "is this secure", "explore the codebase", "how does X work", "find where" - where the reading is wide but the judgment is narrow: delegate the survey, keep the verdict. Triggers on - "write tests for", "add tests", "rename X to Y everywhere", "refactor", "migrate", "port this to", "fix the lint errors", "fix the type errors", "scaffold", "boilerplate", "do the same for the other files".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Delegating work to a cheap model
|
|
7
|
+
|
|
8
|
+
`forge-delegate` runs another AI model as a full agent over this project. It reads
|
|
9
|
+
files, explores, and writes code in its own tool loop. None of that passes through
|
|
10
|
+
your context — you get back a summary and a diff on disk.
|
|
11
|
+
|
|
12
|
+
The point is not to save the user money on your tokens. It is that your context is
|
|
13
|
+
the scarce resource in a long session, and mechanical work burns it fast.
|
|
14
|
+
|
|
15
|
+
## Decide
|
|
16
|
+
|
|
17
|
+
Delegate when the task is **high-volume and low-judgment**: the shape of the correct
|
|
18
|
+
answer is already known, and the work is typing it out across files.
|
|
19
|
+
|
|
20
|
+
| Delegate | Keep |
|
|
21
|
+
|---|---|
|
|
22
|
+
| Tests for code that already exists | Deciding what behaviour to test |
|
|
23
|
+
| Rename / signature change / import rewrite across files | Designing the new interface |
|
|
24
|
+
| Fixing lint, type, or build errors | Debugging an unknown root cause |
|
|
25
|
+
| CRUD endpoints, config, schemas, fixtures, adapters | Architecture and data modelling |
|
|
26
|
+
| Porting between languages or frameworks | Security-sensitive code |
|
|
27
|
+
| The same edit repeated across 3+ files | The first instance, so the pattern is right |
|
|
28
|
+
| Reading a big file to answer one question | Anything where being wrong is expensive |
|
|
29
|
+
|
|
30
|
+
Delegating does not transfer responsibility. Review what comes back before you
|
|
31
|
+
report it as done.
|
|
32
|
+
|
|
33
|
+
## Delegate the survey, keep the verdict
|
|
34
|
+
|
|
35
|
+
An audit looks like judgment work, so it reads as "keep" — but only the *verdict* is
|
|
36
|
+
judgment. The input gathering is mechanical, wide, and the single most expensive
|
|
37
|
+
thing you can do to your own context. Split them:
|
|
38
|
+
|
|
39
|
+
1. **Delegate the survey.** Ask for a structured digest, not an opinion:
|
|
40
|
+
"Report which framework this repo uses. List every routing and data-fetching
|
|
41
|
+
entry point: path, 3-line summary. Paths only, no assessment."
|
|
42
|
+
2. **Keep the verdict.** Judge the digest yourself. You are now reading a page
|
|
43
|
+
instead of a repo, and the expensive-if-wrong part never left your hands.
|
|
44
|
+
|
|
45
|
+
This covers "does this follow X standards", "is this secure", "where is the tech
|
|
46
|
+
debt", "how does X work", "explore the codebase" — broad read, narrow judgment.
|
|
47
|
+
|
|
48
|
+
**Demand paths as evidence.** A survey's worth depends on the cheap model reporting
|
|
49
|
+
*absence* honestly, and "there is no Next.js here" is exactly the answer a weak model
|
|
50
|
+
is least likely to give — it will produce a plausible audit of a framework you do not
|
|
51
|
+
use rather than contradict the question's premise. Require a file path for every
|
|
52
|
+
claim. A claim with no path attached is not a finding; spot-check two of them before
|
|
53
|
+
you trust the rest.
|
|
54
|
+
|
|
55
|
+
## Do it
|
|
56
|
+
|
|
57
|
+
1. Do the judgment part yourself first — decide the interface, write the first
|
|
58
|
+
instance, name the files.
|
|
59
|
+
2. Call `delegate` with a task that names paths. **Never paste file contents into
|
|
60
|
+
the task**; the agent reads them itself. That is the whole point.
|
|
61
|
+
3. Omit `model`, `directory`, and `agent` — the configured defaults are already a
|
|
62
|
+
cheap or free tier. Do not raise `model` to a premium tier; if the task is too
|
|
63
|
+
big for the default, split it into smaller delegations.
|
|
64
|
+
4. Review the result. Run the tests or the build yourself.
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
delegate(task: "Write unit tests for the exported functions in src/parser.ts.
|
|
68
|
+
Follow the style of tests/lexer.test.ts. Cover the error paths.")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Parallel or long-running work: `background: true` returns a job id immediately,
|
|
72
|
+
then poll `check_delegation(id)`. Follow-up in the same area: pass the previous
|
|
73
|
+
`session: <id>` back as `continue_session` so the agent skips re-exploring.
|
|
74
|
+
|
|
75
|
+
For a question that needs no file access at all — a syntax detail, a regex, a
|
|
76
|
+
second opinion — use `ask_model` instead; it is a direct API call with no agent loop.
|
|
77
|
+
|
|
78
|
+
## Don't
|
|
79
|
+
|
|
80
|
+
- Don't delegate a task you cannot describe precisely. Vague task, vague diff.
|
|
81
|
+
- Don't delegate and walk away. Unreviewed delegated code is worse than no code.
|
|
82
|
+
- Don't paste large files into the task text.
|
|
83
|
+
- Don't accept a survey finding with no file path behind it.
|
|
84
|
+
- Don't override `model` upward because a task "seems hard" — split it instead.
|