infinity-harness 2.8.8 → 2.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/package.json +1 -1
- package/src/core/modelRouter.ts +9 -0
- package/src/daemon/index.ts +4 -4
- package/src/daemon/worker.ts +9 -1
- package/src/loop.ts +19 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ All notable changes to this project are documented here.
|
|
|
4
4
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [2.8.9] — 2026-08-31
|
|
8
|
+
|
|
9
|
+
Locals no longer die on xhigh; DEFINE ladder re-consults.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Locals cut off at 1 token on xhigh.** LM Studio (lm-studio/*) forwarded reasoning_effort xhigh/max as max_completion_tokens:1. Added provider-aware clamp: lm-studio tiers (B/C/D/X when local) never get xhigh/max/high — they are clamped to medium at the router, in daemon/index and in worker createWorker, with a [worker] trace in harness/daemon.log. master (muse-spark) still gets xhigh. Verified: no more length=1 on lfm/qwen/granite.
|
|
14
|
+
|
|
15
|
+
- **DEFINE never re-consulted after RESEARCH spent the ladder.** perLevelEscalation[subtask] kept consultedCount/masterUsed across phase advance (research/r1 → define/d1), so consult/master appeared spent. decideNext now resets perLevelEscalation when retryLevel changes or when tree moves; escalation instruction for feature-criteria now explicitly says to use infinity_plan to add criteria.
|
|
16
|
+
|
|
7
17
|
## [2.8.8] — 2026-08-31
|
|
8
18
|
|
|
9
19
|
Routing is one source; default honestly idle.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infinity-harness",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.9",
|
|
4
4
|
"description": "A pi agent extension that runs a gated build pipeline unattended \u2014 enforces phases, validates with deterministic gates, and keeps working for hours or days without losing the plan.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
package/src/core/modelRouter.ts
CHANGED
|
@@ -13,6 +13,15 @@ import type { TierMap, TierSpec, HarnessConfig, FeatureList, Phase, Difficulty }
|
|
|
13
13
|
export type ThinkingLevel = import("./types.ts").ThinkingLevel;
|
|
14
14
|
export type TierId = import("./types.ts").TierId;
|
|
15
15
|
|
|
16
|
+
export function clampThinkingForProvider(provider: string, level: string | "" | undefined): string | "" {
|
|
17
|
+
if (!level) return (level ?? "") as string | "";
|
|
18
|
+
const pp = String(provider || "").toLowerCase();
|
|
19
|
+
const isLocal = pp === "lm-studio" || pp.startsWith("lm-studio/") || pp === "lmstudio";
|
|
20
|
+
if (!isLocal) return level as string | "";
|
|
21
|
+
if (level === "xhigh" || level === "max" || level === "high") return "medium";
|
|
22
|
+
return level as string | "";
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
const DIFF_TO_TIER: Record<Difficulty, import("./types.ts").TierId> = {
|
|
17
26
|
easy: "B",
|
|
18
27
|
moderate: "C",
|
package/src/daemon/index.ts
CHANGED
|
@@ -26,7 +26,7 @@ import { saveSupervisor, appendActivity, type SupervisorWorker } from "./supervi
|
|
|
26
26
|
import { runPreflight } from "./preflight.ts";
|
|
27
27
|
import { addUsageForTier, isCapExceeded, hasXLeak, xLeakReason, type Tier } from "./budget.ts";
|
|
28
28
|
import { createWorker, promptWorker, type TurnResult } from "./worker.ts";
|
|
29
|
-
import { routeModel, effectiveDifficultyForTask as effectiveDifficulty } from "../core/modelRouter.ts";
|
|
29
|
+
import { routeModel, effectiveDifficultyForTask as effectiveDifficulty, clampThinkingForProvider } from "../core/modelRouter.ts";
|
|
30
30
|
import { dirname } from "node:path";
|
|
31
31
|
import type { Server } from "node:http";
|
|
32
32
|
|
|
@@ -275,7 +275,7 @@ async function runLoop(targetDir: string): Promise<void> {
|
|
|
275
275
|
model: askedModel,
|
|
276
276
|
askedModel,
|
|
277
277
|
servedModel: null,
|
|
278
|
-
thinking: (tierSpec as { thinkingLevel?: string } | null)?.thinkingLevel ?? "medium",
|
|
278
|
+
thinking: clampThinkingForProvider(routed.provider, ((tierSpec as { thinkingLevel?: string } | null)?.thinkingLevel as unknown as string) ?? "medium") || "medium",
|
|
279
279
|
state: "starting",
|
|
280
280
|
doing: "starting",
|
|
281
281
|
startedAt: new Date().toISOString(),
|
|
@@ -298,7 +298,7 @@ async function runLoop(targetDir: string): Promise<void> {
|
|
|
298
298
|
const workerFactory = await import("./worker.ts");
|
|
299
299
|
workerHandle = await workerFactory.createWorker({
|
|
300
300
|
cwd: targetDir,
|
|
301
|
-
modelSpec: { provider: routed.provider, id: routed.id, thinkingLevel: (tierSpec as { thinkingLevel?: string } | null)?.thinkingLevel ?? "medium" },
|
|
301
|
+
modelSpec: { provider: routed.provider, id: routed.id, thinkingLevel: clampThinkingForProvider(routed.provider, ((tierSpec as { thinkingLevel?: string } | null)?.thinkingLevel as unknown as string) ?? "medium") || "medium" },
|
|
302
302
|
askedModel,
|
|
303
303
|
sessionManagerDir: `harness/sessions/${workerLabel.replace(/[^a-z0-9._-]/gi, "-")}`,
|
|
304
304
|
customTools: (await import("./isolation.ts")).harnessToolsForWorker() as unknown[],
|
|
@@ -317,7 +317,7 @@ async function runLoop(targetDir: string): Promise<void> {
|
|
|
317
317
|
if (!workerHandle) {
|
|
318
318
|
workerHandle = await workerFactory.createWorker({
|
|
319
319
|
cwd: targetDir,
|
|
320
|
-
modelSpec: { provider: routed.provider, id: routed.id, thinkingLevel: (tierSpec as { thinkingLevel?: string } | null)?.thinkingLevel ?? "medium" },
|
|
320
|
+
modelSpec: { provider: routed.provider, id: routed.id, thinkingLevel: clampThinkingForProvider(routed.provider, ((tierSpec as { thinkingLevel?: string } | null)?.thinkingLevel as unknown as string) ?? "medium") || "medium" },
|
|
321
321
|
askedModel,
|
|
322
322
|
sessionManagerDir: `harness/sessions/${workerLabel.replace(/[^a-z0-9._-]/gi, "-")}`,
|
|
323
323
|
customTools: (await import("./isolation.ts")).harnessToolsForWorker() as unknown[],
|
package/src/daemon/worker.ts
CHANGED
|
@@ -110,7 +110,15 @@ export async function createWorker(opts: CreateWorkerOpts): Promise<{
|
|
|
110
110
|
? SessionManager.create(opts.cwd, opts.sessionManagerDir)
|
|
111
111
|
: SessionManager.inMemory(opts.cwd);
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
let thinkingLevel = (opts.thinkingLevel ?? opts.modelSpec.thinkingLevel ?? "medium") as unknown as string;
|
|
114
|
+
// A: clamp for lm-studio (xhigh/max not supported, caused max_completion_tokens:1)
|
|
115
|
+
try {
|
|
116
|
+
const p = String(opts.modelSpec.provider || "").toLowerCase();
|
|
117
|
+
const isLocal = p === "lm-studio" || p.startsWith("lm-studio/") || p === "lmstudio";
|
|
118
|
+
if (isLocal && (thinkingLevel === "xhigh" || thinkingLevel === "max" || thinkingLevel === "high")) thinkingLevel = "medium";
|
|
119
|
+
} catch {}
|
|
120
|
+
// B: debug trace of routed model/thinking (readable in harness/daemon.log)
|
|
121
|
+
try { console.log("[worker] model=" + opts.modelSpec.provider + "/" + opts.modelSpec.id + " thinking=" + thinkingLevel + " askedModel=" + opts.askedModel); } catch {}
|
|
114
122
|
|
|
115
123
|
const agentTools = opts.customTools as unknown as import("@earendil-works/pi-coding-agent").ToolDefinition[] | undefined;
|
|
116
124
|
|
package/src/loop.ts
CHANGED
|
@@ -486,11 +486,18 @@ export async function decideNext(options: DecideOptions): Promise<{ decision: Lo
|
|
|
486
486
|
// budgets in rework.json and replan.json still bound the run across
|
|
487
487
|
// stalls, but a rung spent on a problem that resolved should not be
|
|
488
488
|
// missing when a different problem appears.
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
489
|
+
// C: also reset per-level escalation when the active unit changes (define/d1 vs research/r1),
|
|
490
|
+
// otherwise consult/master stays marked spent across unrelated tasks.
|
|
491
|
+
const lastLev = state.escalations.length ? state.escalations[state.escalations.length-1]?.level as string | undefined : undefined;
|
|
492
|
+
if (lastLev && lastLev !== (retryLevel ?? "task")) {
|
|
493
|
+
setEscalationForLevel(state, retryLevel ?? "task", { consultedCount: 0, masterUsed: false, lastUnstuckAt: null, fingerprints: [], tried: [] } as unknown as import("./escalate.ts").EscalationState);
|
|
494
|
+
} else {
|
|
495
|
+
state.escalation = { ...state.escalation, tried: [] };
|
|
496
|
+
const lev = retryLevel
|
|
497
|
+
? escalationStateForLevel(state, retryLevel)
|
|
498
|
+
: null;
|
|
499
|
+
if (lev) setEscalationForLevel(state, retryLevel, { ...lev, tried: [] });
|
|
500
|
+
}
|
|
494
501
|
} else {
|
|
495
502
|
state.noProgressStreak += 1;
|
|
496
503
|
}
|
|
@@ -597,6 +604,13 @@ export async function decideNext(options: DecideOptions): Promise<{ decision: Lo
|
|
|
597
604
|
const task = nextActionableTask(list);
|
|
598
605
|
const focus = task ? `\nCurrent task: ${task.compositeKey} — ${task.description}` : "";
|
|
599
606
|
|
|
607
|
+
// C: feature-criteria guard — when DEFINE stalls on feature-criteria, the plan is wrong, not the model.
|
|
608
|
+
if (escalation && escalation.strategy === "consult" && gate && (gate.failures as string[]).some((f: string) => String(f).toLowerCase().includes("feature-criteria"))) {
|
|
609
|
+
try {
|
|
610
|
+
const msg = "The " + phase.toUpperCase() + " gate is blocked on feature-criteria: every feature needs acceptance criteria. This is a plan gap — use infinity_plan to add criteria, not just a model switch. Escalating to " + (escalation.model ?? "master") + " for help writing them.";
|
|
611
|
+
escalation = { ...escalation, instruction: msg + "\n" + (escalation.instruction ?? "") } as unknown as typeof escalation;
|
|
612
|
+
} catch {}
|
|
613
|
+
}
|
|
600
614
|
// An escalation replaces the standard "fix these" nudge, because repeating
|
|
601
615
|
// that nudge is exactly what the ladder exists to interrupt.
|
|
602
616
|
const head = standingRejection
|