@femtomc/mu-orchestrator 26.2.19 → 26.2.21
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/dist/dag_runner.d.ts.map +1 -1
- package/dist/dag_runner.js +37 -23
- package/dist/mu_roles.d.ts +2 -1
- package/dist/mu_roles.d.ts.map +1 -1
- package/dist/mu_roles.js +20 -8
- package/package.json +4 -4
package/dist/dag_runner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dag_runner.d.ts","sourceRoot":"","sources":["../src/dag_runner.ts"],"names":[],"mappings":"AAGA,OAAO,EAEN,KAAK,QAAQ,EAKb,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,uBAAuB,CAAC;AAGjF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD,MAAM,MAAM,SAAS,GAAG;IACvB,MAAM,EAAE,YAAY,GAAG,oBAAoB,GAAG,qBAAqB,GAAG,OAAO,CAAC;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC5B,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,qBAAqB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,yBAAyB,KAAK,IAAI,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC9B,KAAK,CAAC,EAAE,cAAc,CAAC;CACvB,CAAC;
|
|
1
|
+
{"version":3,"file":"dag_runner.d.ts","sourceRoot":"","sources":["../src/dag_runner.ts"],"names":[],"mappings":"AAGA,OAAO,EAEN,KAAK,QAAQ,EAKb,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,uBAAuB,CAAC;AAGjF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD,MAAM,MAAM,SAAS,GAAG;IACvB,MAAM,EAAE,YAAY,GAAG,oBAAoB,GAAG,qBAAqB,GAAG,OAAO,CAAC;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC5B,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,qBAAqB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,yBAAyB,KAAK,IAAI,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC9B,KAAK,CAAC,EAAE,cAAc,CAAC;CACvB,CAAC;AAkBF,qBAAa,SAAS;;gBAYpB,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE;QAAE,OAAO,CAAC,EAAE,aAAa,CAAC;QAAC,MAAM,CAAC,EAAE,QAAQ,CAAC;QAAC,cAAc,CAAC,EAAE,cAAc,CAAA;KAAO;IAiLrF,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAW,EAAE,IAAI,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,CAAC;CAiNjG"}
|
package/dist/dag_runner.js
CHANGED
|
@@ -2,20 +2,12 @@ import { mkdir } from "node:fs/promises";
|
|
|
2
2
|
import { join, relative } from "node:path";
|
|
3
3
|
import { currentRunId, fsEventLogFromRepoRoot, getStorePaths, newRunId, runContext, } from "@femtomc/mu-core/node";
|
|
4
4
|
import { resolveModelConfig } from "./model_resolution.js";
|
|
5
|
-
import {
|
|
5
|
+
import { roleFromTags, systemPromptForRole } from "./mu_roles.js";
|
|
6
6
|
import { PiSdkBackend } from "./pi_sdk_backend.js";
|
|
7
7
|
function roundTo(n, digits) {
|
|
8
8
|
const f = 10 ** digits;
|
|
9
9
|
return Math.round(n * f) / f;
|
|
10
10
|
}
|
|
11
|
-
function specRoleFromExecutionSpec(execution_spec) {
|
|
12
|
-
const role = execution_spec?.role;
|
|
13
|
-
if (typeof role !== "string") {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
const trimmed = role.trim();
|
|
17
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
18
|
-
}
|
|
19
11
|
function relPath(repoRoot, path) {
|
|
20
12
|
try {
|
|
21
13
|
const rel = relative(repoRoot, path);
|
|
@@ -33,6 +25,7 @@ export class DagRunner {
|
|
|
33
25
|
#backend;
|
|
34
26
|
#modelOverrides;
|
|
35
27
|
#reorchestrateOutcomes = new Set(["failure", "needs_work"]);
|
|
28
|
+
#attempts = new Map();
|
|
36
29
|
constructor(store, forum, repoRoot, opts = {}) {
|
|
37
30
|
this.#store = store;
|
|
38
31
|
this.#forum = forum;
|
|
@@ -41,11 +34,10 @@ export class DagRunner {
|
|
|
41
34
|
this.#backend = opts.backend ?? new PiSdkBackend();
|
|
42
35
|
this.#modelOverrides = opts.modelOverrides ?? {};
|
|
43
36
|
}
|
|
44
|
-
async #resolveConfig(
|
|
45
|
-
void issue;
|
|
37
|
+
async #resolveConfig() {
|
|
46
38
|
return resolveModelConfig(this.#modelOverrides);
|
|
47
39
|
}
|
|
48
|
-
async #renderUserPrompt(issue, rootId, step) {
|
|
40
|
+
async #renderUserPrompt(issue, rootId, step, attempt = 1) {
|
|
49
41
|
let rendered = issue.title ?? "";
|
|
50
42
|
if (issue.body) {
|
|
51
43
|
rendered += `\n\n${issue.body}`;
|
|
@@ -55,12 +47,15 @@ export class DagRunner {
|
|
|
55
47
|
if (runId) {
|
|
56
48
|
rendered += `Run: ${runId}\n`;
|
|
57
49
|
}
|
|
50
|
+
if (attempt > 1) {
|
|
51
|
+
rendered += `\nAttempt: ${attempt} (previous attempt failed — check \`mu forum read issue:${issue.id}\` for context)\n`;
|
|
52
|
+
}
|
|
58
53
|
return rendered;
|
|
59
54
|
}
|
|
60
55
|
async #executeBackend(issue, cfg, rootId, step, opts = {}) {
|
|
61
|
-
const role =
|
|
56
|
+
const role = roleFromTags(issue.tags);
|
|
62
57
|
const logSuffix = opts.logSuffix ?? "";
|
|
63
|
-
const rendered = await this.#renderUserPrompt(issue, rootId, step);
|
|
58
|
+
const rendered = await this.#renderUserPrompt(issue, rootId, step, opts.attempt ?? 1);
|
|
64
59
|
const systemPrompt = systemPromptForRole(role);
|
|
65
60
|
const { logsDir } = getStorePaths(this.#repoRoot);
|
|
66
61
|
await mkdir(logsDir, { recursive: true });
|
|
@@ -116,7 +111,7 @@ export class DagRunner {
|
|
|
116
111
|
const reopened = await this.#store.update(issueId, {
|
|
117
112
|
status: "open",
|
|
118
113
|
outcome: null,
|
|
119
|
-
|
|
114
|
+
tags: [...before.tags.filter((t) => !t.startsWith("role:")), "role:orchestrator"],
|
|
120
115
|
});
|
|
121
116
|
await this.#events.emit("dag.unstick.reopen", {
|
|
122
117
|
source: "dag_runner",
|
|
@@ -152,6 +147,9 @@ export class DagRunner {
|
|
|
152
147
|
continue;
|
|
153
148
|
if (row.status !== "closed")
|
|
154
149
|
continue;
|
|
150
|
+
// Circuit breaker: skip issues that have exhausted their attempts.
|
|
151
|
+
if ((this.#attempts.get(row.id) ?? 0) >= 3)
|
|
152
|
+
continue;
|
|
155
153
|
const outcome = row.outcome;
|
|
156
154
|
if (outcome && this.#reorchestrateOutcomes.has(outcome)) {
|
|
157
155
|
if (hasOpenChildren(row.id))
|
|
@@ -215,9 +213,8 @@ export class DagRunner {
|
|
|
215
213
|
...rootIssue,
|
|
216
214
|
title: `Repair stuck DAG: ${rootIssue.title}`,
|
|
217
215
|
body: `${(rootIssue.body || "").trim()}\n\n## Runner Diagnostics\n\n${diag}`.trim(),
|
|
218
|
-
execution_spec: null,
|
|
219
216
|
};
|
|
220
|
-
const cfg = await this.#resolveConfig(
|
|
217
|
+
const cfg = await this.#resolveConfig();
|
|
221
218
|
const logSuffix = "unstick";
|
|
222
219
|
const onBackendLine = hooks?.onBackendLine;
|
|
223
220
|
const { exitCode, elapsedS } = await this.#executeBackend(repairIssue, cfg, rootId, step, {
|
|
@@ -243,8 +240,7 @@ export class DagRunner {
|
|
|
243
240
|
}
|
|
244
241
|
const issue = candidates[0];
|
|
245
242
|
const issueId = issue.id;
|
|
246
|
-
|
|
247
|
-
const role = parseMuRole(specRoleFromExecutionSpec(issue.execution_spec));
|
|
243
|
+
const role = roleFromTags(issue.tags);
|
|
248
244
|
await this.#events.emit("dag.step.start", {
|
|
249
245
|
source: "dag_runner",
|
|
250
246
|
issueId,
|
|
@@ -260,12 +256,16 @@ export class DagRunner {
|
|
|
260
256
|
payload: { root_id: rootId, step },
|
|
261
257
|
});
|
|
262
258
|
await this.#store.claim(issueId);
|
|
259
|
+
// Track attempt count for circuit breaker.
|
|
260
|
+
const attempt = (this.#attempts.get(issueId) ?? 0) + 1;
|
|
261
|
+
this.#attempts.set(issueId, attempt);
|
|
263
262
|
// 4. Route + 5. Render + 6. Execute.
|
|
264
|
-
const cfg = await this.#resolveConfig(
|
|
265
|
-
const logSuffix = "";
|
|
263
|
+
const cfg = await this.#resolveConfig();
|
|
264
|
+
const logSuffix = attempt > 1 ? `attempt-${attempt}` : "";
|
|
266
265
|
const onBackendLine = hooks?.onBackendLine;
|
|
267
266
|
const { exitCode, elapsedS } = await this.#executeBackend(issue, cfg, rootId, step, {
|
|
268
267
|
logSuffix,
|
|
268
|
+
attempt,
|
|
269
269
|
onLine: onBackendLine
|
|
270
270
|
? (line) => onBackendLine({ rootId, step, issueId, logSuffix, line })
|
|
271
271
|
: undefined,
|
|
@@ -277,6 +277,11 @@ export class DagRunner {
|
|
|
277
277
|
return final;
|
|
278
278
|
}
|
|
279
279
|
if (updated.status !== "closed") {
|
|
280
|
+
await this.#events.emit("dag.step.force_close", {
|
|
281
|
+
source: "dag_runner",
|
|
282
|
+
issueId,
|
|
283
|
+
payload: { root_id: rootId, step, role, attempt, reason: "agent_did_not_close" },
|
|
284
|
+
});
|
|
280
285
|
updated = await this.#store.close(issueId, "failure");
|
|
281
286
|
}
|
|
282
287
|
// 8. Log to forum.
|
|
@@ -309,9 +314,18 @@ export class DagRunner {
|
|
|
309
314
|
outcome: updated.outcome,
|
|
310
315
|
},
|
|
311
316
|
});
|
|
312
|
-
// 9. Re-orchestrate on failure / needs_work.
|
|
317
|
+
// 9. Re-orchestrate on failure / needs_work (circuit breaker: max 3 attempts).
|
|
313
318
|
if (updated.outcome && this.#reorchestrateOutcomes.has(updated.outcome)) {
|
|
314
|
-
|
|
319
|
+
if (attempt < 3) {
|
|
320
|
+
await this.#reopenForOrchestration(issueId, { reason: `outcome=${updated.outcome}`, step });
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
await this.#events.emit("dag.circuit_breaker", {
|
|
324
|
+
source: "dag_runner",
|
|
325
|
+
issueId,
|
|
326
|
+
payload: { root_id: rootId, step, attempt, outcome: updated.outcome },
|
|
327
|
+
});
|
|
328
|
+
}
|
|
315
329
|
}
|
|
316
330
|
}
|
|
317
331
|
final = { status: "max_steps_exhausted", steps: maxSteps, error: "" };
|
package/dist/mu_roles.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type MuRole = "orchestrator" | "worker";
|
|
2
|
-
|
|
2
|
+
/** Determine role from tags. Defaults to orchestrator if no role tag present. */
|
|
3
|
+
export declare function roleFromTags(tags: readonly string[]): MuRole;
|
|
3
4
|
export declare function systemPromptForRole(role: MuRole): string;
|
|
4
5
|
//# sourceMappingURL=mu_roles.d.ts.map
|
package/dist/mu_roles.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mu_roles.d.ts","sourceRoot":"","sources":["../src/mu_roles.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG,cAAc,GAAG,QAAQ,CAAC;AAE/C,wBAAgB,
|
|
1
|
+
{"version":3,"file":"mu_roles.d.ts","sourceRoot":"","sources":["../src/mu_roles.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG,cAAc,GAAG,QAAQ,CAAC;AAE/C,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAM5D;AA2DD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAuExD"}
|
package/dist/mu_roles.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/** Determine role from tags. Defaults to orchestrator if no role tag present. */
|
|
2
|
+
export function roleFromTags(tags) {
|
|
3
|
+
for (const tag of tags) {
|
|
4
|
+
if (tag === "role:worker")
|
|
5
|
+
return "worker";
|
|
6
|
+
if (tag === "role:orchestrator")
|
|
7
|
+
return "orchestrator";
|
|
4
8
|
}
|
|
5
|
-
|
|
6
|
-
if (trimmed === "orchestrator" || trimmed === "worker") {
|
|
7
|
-
return trimmed;
|
|
8
|
-
}
|
|
9
|
-
throw new Error(`unsupported execution_spec.role=${JSON.stringify(trimmed)} (only "orchestrator" and "worker" are supported)`);
|
|
9
|
+
return "orchestrator";
|
|
10
10
|
}
|
|
11
11
|
/* ------------------------------------------------------------------ */
|
|
12
12
|
/* mu CLI reference */
|
|
@@ -90,6 +90,18 @@ export function systemPromptForRole(role) {
|
|
|
90
90
|
"- Assign executable leaves to role=worker.",
|
|
91
91
|
"- If a child requires further decomposition, assign role=orchestrator.",
|
|
92
92
|
"",
|
|
93
|
+
"## Review Pattern",
|
|
94
|
+
"",
|
|
95
|
+
"For work that benefits from verification, create a review issue:",
|
|
96
|
+
"1. Create worker issues for implementation.",
|
|
97
|
+
"2. Create a review issue (`--role orchestrator`) blocked by the workers.",
|
|
98
|
+
"3. The review runs after workers complete — verify, test, or expand further.",
|
|
99
|
+
"",
|
|
100
|
+
"Example:",
|
|
101
|
+
' mu issues create "Implement X" --parent <id> --role worker',
|
|
102
|
+
' mu issues create "Review X" --parent <id> --role orchestrator',
|
|
103
|
+
" mu issues dep <worker-id> blocks <review-id>",
|
|
104
|
+
"",
|
|
93
105
|
MU_CLI_REFERENCE,
|
|
94
106
|
].join("\n");
|
|
95
107
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@femtomc/mu-orchestrator",
|
|
3
|
-
"version": "26.2.
|
|
3
|
+
"version": "26.2.21",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
"dist/**"
|
|
15
15
|
],
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@femtomc/mu-core": "26.2.
|
|
18
|
-
"@femtomc/mu-forum": "26.2.
|
|
19
|
-
"@femtomc/mu-issue": "26.2.
|
|
17
|
+
"@femtomc/mu-core": "26.2.21",
|
|
18
|
+
"@femtomc/mu-forum": "26.2.21",
|
|
19
|
+
"@femtomc/mu-issue": "26.2.21",
|
|
20
20
|
"@mariozechner/pi-agent-core": "^0.52.12",
|
|
21
21
|
"@mariozechner/pi-coding-agent": "^0.52.12",
|
|
22
22
|
"@mariozechner/pi-ai": "^0.52.12"
|