@mjasnikovs/pi-task 0.38.21 → 0.38.23
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/config/reasoning.d.ts +19 -2
- package/dist/config/reasoning.js +304 -6
- package/dist/config/register.d.ts +27 -7
- package/dist/config/register.js +28 -2
- package/dist/task/phases.d.ts +16 -0
- package/dist/task/phases.js +14 -6
- package/dist/workers/pi-worker-core.d.ts +22 -2
- package/dist/workers/pi-worker-core.js +32 -6
- package/package.json +1 -1
|
@@ -31,7 +31,16 @@ import type { PiTaskConfig } from './config.js';
|
|
|
31
31
|
* thinking, while two that reach the model by different code paths (`refine` via
|
|
32
32
|
* runPhaseChild, `compress-label` via the same) want the same.
|
|
33
33
|
*
|
|
34
|
-
* - `research` the
|
|
34
|
+
* - `research` the ad-hoc `pi-worker` subagent tool, and the FALLBACK the four
|
|
35
|
+
* research workers use when their own cell is unset
|
|
36
|
+
* - `research:files` / `research:apis` / `research:context` / `research:tooling`
|
|
37
|
+
* one cell per research worker. They are FOUR CELLS, not one,
|
|
38
|
+
* because the four wander differently: over mx5-n 2026-08-27
|
|
39
|
+
* every one of the 40.7 wasted research minutes was a restart
|
|
40
|
+
* in `tooling` or `context`, and `files` and `apis` never
|
|
41
|
+
* restarted once. A single `research` cell cannot be set to
|
|
42
|
+
* pay for thinking where it is needed without also paying for
|
|
43
|
+
* it where it measurably is not.
|
|
35
44
|
* - `phase` refine, verify-tooling, grill, compose, critique, compress-label
|
|
36
45
|
* - `planning` /task-auto's clarify / decompose / extract children
|
|
37
46
|
* - `plan` /task-plan's question and answer children
|
|
@@ -39,7 +48,7 @@ import type { PiTaskConfig } from './config.js';
|
|
|
39
48
|
* - `extraction` the --no-tools focused docs/fetch extractors
|
|
40
49
|
* - `implementation` the host-session turn that writes the code (not a child)
|
|
41
50
|
*/
|
|
42
|
-
export type ReasoningGroup = 'research' | 'phase' | 'planning' | 'plan' | 'gate' | 'extraction' | 'implementation';
|
|
51
|
+
export type ReasoningGroup = 'research' | 'research:files' | 'research:apis' | 'research:context' | 'research:tooling' | 'phase' | 'planning' | 'plan' | 'gate' | 'extraction' | 'implementation';
|
|
43
52
|
export declare const REASONING_GROUPS: readonly ReasoningGroup[];
|
|
44
53
|
/**
|
|
45
54
|
* The four profiles offered by /task-config.
|
|
@@ -132,6 +141,14 @@ export declare const DEFAULT_REASONING_TABLE: Readonly<Record<ReasoningGroup, Gr
|
|
|
132
141
|
* would see "custom" in the file and the default table in behaviour.
|
|
133
142
|
*/
|
|
134
143
|
export declare function sanitizeReasoningMode(value: unknown): ReasoningMode;
|
|
144
|
+
/**
|
|
145
|
+
* The group each research worker reads its level from, keyed by the section
|
|
146
|
+
* heading its output is assembled under (task/phases.ts `workerSpecs`).
|
|
147
|
+
*
|
|
148
|
+
* Exported so the wiring is one table rather than four string literals spread
|
|
149
|
+
* through the phase code, and so a test can assert every value is a real group.
|
|
150
|
+
*/
|
|
151
|
+
export declare const RESEARCH_WORKER_GROUPS: Readonly<Record<string, ReasoningGroup>>;
|
|
135
152
|
/**
|
|
136
153
|
* Always returns a COMPLETE record, never a partial one.
|
|
137
154
|
*
|
package/dist/config/reasoning.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export const REASONING_GROUPS = [
|
|
2
2
|
'research',
|
|
3
|
+
'research:files',
|
|
4
|
+
'research:apis',
|
|
5
|
+
'research:context',
|
|
6
|
+
'research:tooling',
|
|
3
7
|
'phase',
|
|
4
8
|
'planning',
|
|
5
9
|
'plan',
|
|
@@ -148,7 +152,214 @@ export const DEFAULT_REASONING_TABLE = {
|
|
|
148
152
|
// so the `off` arm decodes on sampling tuned for the `on` arm. That is the
|
|
149
153
|
// regime this machine really runs pi-task in, so the result is
|
|
150
154
|
// ecologically valid — it is NOT a clean comparison.
|
|
151
|
-
|
|
155
|
+
//
|
|
156
|
+
// OVERRIDDEN TO `medium` BY USER DECISION, 2026-08-27. THIS IS NOT A NEW
|
|
157
|
+
// MEASUREMENT — the trials above are unchanged and still say the arms tied.
|
|
158
|
+
// What changed is the prior. Rung 3 says "no reason found to pay for
|
|
159
|
+
// thinking"; it does not say thinking is worthless, and at n=12 an absent
|
|
160
|
+
// effect and an undetected one look identical.
|
|
161
|
+
//
|
|
162
|
+
// WHAT THE SAME LEDGER SAYS ABOUT THE CLOCK, re-read for this decision:
|
|
163
|
+
// medium is the FASTER arm on the mean, 62.9s vs 105.6s over the 12 paired
|
|
164
|
+
// stimuli (median 51.7s vs 87.7s), and it is faster on 6 of 12 by 1-265s
|
|
165
|
+
// while slower on 6 by 0-43s. The mean gap is entirely `off`'s TAIL: off's
|
|
166
|
+
// two slowest trials are 289s and 367s, medium's slowest is 131s. So the
|
|
167
|
+
// typical trial is a coin flip worth under a minute and the difference the
|
|
168
|
+
// arms actually have is in the tail.
|
|
169
|
+
//
|
|
170
|
+
// AND THE TAIL IS THE PATHOLOGY THIS CHANGE IS AIMED AT. mx5-n 2026-08-27:
|
|
171
|
+
// 40.7 of the research phase's 81.4 wall-clock minutes were thrown away on
|
|
172
|
+
// restarts, all of them in `worker:tooling` and `worker:context` — `files`
|
|
173
|
+
// and `apis` never restarted once. The worst was 550 tool calls over 20
|
|
174
|
+
// distinct files in 20 minutes for 25s of useful work.
|
|
175
|
+
//
|
|
176
|
+
// THE HONEST LIMIT OF THIS DECISION: no axis in
|
|
177
|
+
// scripts/live-reasoning-group-ab.ts scores tool-call count or repetition —
|
|
178
|
+
// the Trial record has no such field and RunWorkerResult exposes no count —
|
|
179
|
+
// so the ledger CANNOT say whether thinking prevents the wander. It was
|
|
180
|
+
// never asked. And the ledger's stimuli are the FILES worker, not the two
|
|
181
|
+
// that looped. Anyone flipping this back should note that the evidence for
|
|
182
|
+
// `off` is equally silent on the question.
|
|
183
|
+
//
|
|
184
|
+
// The structural fix for the wander shipped separately and does not depend
|
|
185
|
+
// on this cell: StallDetector is now wired into runWorker (it was in phase
|
|
186
|
+
// children only), so a rotation is killed ~8 calls into its second lap
|
|
187
|
+
// rather than at the 20-minute ceiling.
|
|
188
|
+
research: 'medium',
|
|
189
|
+
// ── THE FOUR RESEARCH WORKERS, SPLIT OUT OF `research` 2026-08-28 ──
|
|
190
|
+
//
|
|
191
|
+
// WHY THE SPLIT EXISTS, from `research`'s own evidence. The override to
|
|
192
|
+
// `medium` above was aimed at ONE pathology — the restart tail — and that
|
|
193
|
+
// tail is not spread evenly. mx5-n 2026-08-27: 40.7 of the research phase's
|
|
194
|
+
// 81.4 wall-clock minutes were lost to restarts, ALL of them in
|
|
195
|
+
// `worker:tooling` and `worker:context`; `worker:files` and `worker:apis`
|
|
196
|
+
// never restarted once. With one cell, paying for thinking in the two that
|
|
197
|
+
// wander means paying for it in the two that do not.
|
|
198
|
+
//
|
|
199
|
+
// ── research:files — MEASURED, `off`. WRITTEN 2026-08-28 ──
|
|
200
|
+
//
|
|
201
|
+
// A/B 2026-08-26, Qwen3.8-27B-NVFP4-MTP-VERY-HIGH.gguf (llama.cpp
|
|
202
|
+
// b10620-0f3b51e03), scripts/live-reasoning-group-ab.ts, n=12/arm over 12
|
|
203
|
+
// distinct mx5 tasks. off 10/12 vs medium 11/12 (p=1.0000); clock PAIRED
|
|
204
|
+
// over the 10 usable pairs p=0.7090, mean off 79.4s vs medium 59.3s.
|
|
205
|
+
// off 95% CI [0.55, 0.95]. RUNG 3 — a DECISION, not a finding.
|
|
206
|
+
//
|
|
207
|
+
// THIS IS NOT A NEW RUN. It is ledger-research.jsonl, rescored under the
|
|
208
|
+
// group's new name:
|
|
209
|
+
//
|
|
210
|
+
// AB_CORPUS=/home/edgars/hub/ab-grouplab/mx5-copy bun run \
|
|
211
|
+
// scripts/rescore-reasoning-ledger.ts \
|
|
212
|
+
// /home/edgars/hub/ab-grouplab/ledger-research.jsonl research:files --from-text
|
|
213
|
+
//
|
|
214
|
+
// That ledger was ALWAYS a FILES worker — `child: 'worker:files'`, prompt
|
|
215
|
+
// `RESEARCH_FILES_PROMPT`, run in each task's own before-tree — so the split
|
|
216
|
+
// did not strand it, it named it. The axis is the CONJUNCTION: every path
|
|
217
|
+
// named is real AND every pre-existing file the task edited is named. The
|
|
218
|
+
// full derivation, the stimulus screen, the parser fix and the confound sit
|
|
219
|
+
// on the `research` cell above; they are that ledger's, and this is that
|
|
220
|
+
// ledger.
|
|
221
|
+
//
|
|
222
|
+
// WHY THIS CELL DOES NOT FOLLOW `research`'s OVERRIDE. That override was
|
|
223
|
+
// aimed at the restart tail, and the tail is not here: `worker:files` never
|
|
224
|
+
// restarted once. The reason for paying does not apply to this worker, and
|
|
225
|
+
// its own ledger says the arms tied.
|
|
226
|
+
'research:files': 'off',
|
|
227
|
+
// NOT MEASURED. DECIDED BY PRIOR: a worker with no trial of its own keeps
|
|
228
|
+
// whatever `research` was decided to run at. No trial in this repo has ever
|
|
229
|
+
// run apis, context or tooling as its own arm, so a cell here that differed
|
|
230
|
+
// from `research` would be intuition wearing the authority of a default.
|
|
231
|
+
//
|
|
232
|
+
// AND AN AXIS IS THE HARD PART, not the GPU. Two candidates were screened
|
|
233
|
+
// offline and DIED there — scripts/research-worker-axes-step0.ts, no model,
|
|
234
|
+
// no GPU. For APIS: "every dotted symbol named is present in the tree"
|
|
235
|
+
// scores the recorded answers 28/28 tasks and 81/81 items. SATURATED, and
|
|
236
|
+
// loose with it — the check greps the symbol's LAST SEGMENT, so
|
|
237
|
+
// `Hono.c.json` passes on the word `json`. Tightening it has nothing to
|
|
238
|
+
// bite on, because APIS names are model-composed pseudo-symbols
|
|
239
|
+
// (`Hono.c.var`, `UUID regex`); that is the same wall
|
|
240
|
+
// [[apis-contract-stage3-refuted]] hit from the other side.
|
|
241
|
+
// TO REPLACE THIS WITH A MEASUREMENT, the axis must be an EXECUTION or a
|
|
242
|
+
// production adjudication, the way gate's and planning's are — not a
|
|
243
|
+
// property of the text.
|
|
244
|
+
//
|
|
245
|
+
// A COST RUN WAS DONE ANYWAY, 2026-08-28, and it found nothing to act on.
|
|
246
|
+
// n=20/arm on the same corpus and trees, production's tools, extensions,
|
|
247
|
+
// search hint and the task's own recorded FILES map: off 19/20 answered vs
|
|
248
|
+
// medium 20/20, and the PAIRED clock over 19 stimuli is off 127.2s vs
|
|
249
|
+
// medium 144.4s, p=0.5262. So thinking is neither better nor cheaper here
|
|
250
|
+
// by anything this run can see, and there is no cost argument for moving
|
|
251
|
+
// the cell either way. Ledger: ledger-research-apis.jsonl.
|
|
252
|
+
// THAT RUN CANNOT WRITE THIS CELL. Its axis is TERMINATION — "did it answer
|
|
253
|
+
// at all" — so its `off [rung 3]` line is the standing prior speaking with
|
|
254
|
+
// no quality reading behind it. See README-research-cost-runs.txt.
|
|
255
|
+
// ITS BUILD IS b10665-ca3d5a3e1, not the b10620-0f3b51e03 every other cell
|
|
256
|
+
// was measured on: llama.cpp was rebuilt mid-session. Internally paired, so
|
|
257
|
+
// off-vs-medium is fair within it; its SECONDS are not comparable to the
|
|
258
|
+
// files or tooling ledgers.
|
|
259
|
+
'research:apis': 'medium',
|
|
260
|
+
// NOT MEASURED. DECIDED BY PRIOR — see the block above `research:apis`.
|
|
261
|
+
// Its candidate axis died offline too: "every backticked project path in a
|
|
262
|
+
// CONTEXT bullet exists in the tree" scores the recorded answers 6/53 tasks
|
|
263
|
+
// and 213/391 items. THE CHECK LOSES, on exactly the residue the phase
|
|
264
|
+
// path-axis audit catalogued — npm specifiers (`@hono/zod-validator`),
|
|
265
|
+
// import paths (`hono/cookie`, `../server/auth`), bare filenames
|
|
266
|
+
// (`schema.ts`) and `src/` prefix elision.
|
|
267
|
+
// TO REPLACE THIS WITH A MEASUREMENT, note that this worker is one of the
|
|
268
|
+
// two the restart tail lives in, so the honest axis is the wander itself —
|
|
269
|
+
// and no Trial field records a tool-call count today.
|
|
270
|
+
//
|
|
271
|
+
// A COST RUN WAS DONE ANYWAY, 2026-08-28, n=20/arm, production's `read,grep`
|
|
272
|
+
// and no extensions. It found no clock difference — medians 81.2s vs 80.8s,
|
|
273
|
+
// paired p=0.8594 over 16 stimuli — but it did see the wander directly:
|
|
274
|
+
//
|
|
275
|
+
// died in a loop off 3/20 vs medium 0/20 p=0.2308
|
|
276
|
+
// emitted bullets off 17/20 vs medium 20/20 p=0.2308
|
|
277
|
+
//
|
|
278
|
+
// Neither reaches significance at n=20, and the second is TERMINATION, not
|
|
279
|
+
// quality. But this is the first time the restart tail has appeared INSIDE
|
|
280
|
+
// a measured run rather than in a production log, and it appeared only in
|
|
281
|
+
// the `off` arm. off's higher MEAN (97.8s vs 69.3s) is those three deaths,
|
|
282
|
+
// not slower work. THIS RUN CANNOT WRITE THIS CELL either: an underpowered
|
|
283
|
+
// termination signal is not a measurement, and its `off [rung 3]` verdict
|
|
284
|
+
// line is the prior, not a reading. THE CELL IS UNCHANGED. Ledger: ledger-research-context.jsonl,
|
|
285
|
+
// README-research-cost-runs.txt, build b10665-ca3d5a3e1.
|
|
286
|
+
//
|
|
287
|
+
// INSTRUMENT NOTE, because that run ABSTAINED first with 40 good answers
|
|
288
|
+
// already stored. It scored CONTEXT with `hasAnswerContent` — the FILES
|
|
289
|
+
// `name<gap>description` shape — and this worker emits a BULLET list of
|
|
290
|
+
// prose sentences, which `isEntryLine` rejects for ending in a full stop.
|
|
291
|
+
// Both arms read 0/20. Fixed by `contextEmittedBullets` and the stored
|
|
292
|
+
// trials rescored with no GPU. Fourth instance of
|
|
293
|
+
// [[ab-scorer-must-match-the-real-prompt]] in one harness.
|
|
294
|
+
// AND THE REPLACEMENT WAS WRONG BY ONE, found in review the same day: it
|
|
295
|
+
// asked for >=2 bullets where production's `classifyContextSilence` calls
|
|
296
|
+
// >=1 PRODUCTIVE, so a one-bullet answer production accepts read UNUSABLE
|
|
297
|
+
// in both arms. `countBullets` — production's own function, called not
|
|
298
|
+
// restated — moved 37/40 stored trials and the emitted-bullets line above
|
|
299
|
+
// from off 16/20 to off 17/20. The verdict, the cell and the clock are
|
|
300
|
+
// unchanged: still an underpowered termination signal, still a prior.
|
|
301
|
+
'research:context': 'medium',
|
|
302
|
+
// ── research:tooling — MEASURED, `medium`. RUNG 1. WRITTEN 2026-08-28 ──
|
|
303
|
+
//
|
|
304
|
+
// A/B 2026-08-28, Qwen3.8-27B-NVFP4-MTP-VERY-HIGH.gguf (llama.cpp
|
|
305
|
+
// b10620-0f3b51e03), scripts/live-reasoning-group-ab.ts, n=20/arm over 20
|
|
306
|
+
// distinct mx5 tasks, one rep each so the clock can pair.
|
|
307
|
+
//
|
|
308
|
+
// every command runs off 13/20 vs medium 20/20 p=0.0083
|
|
309
|
+
// wall clock (paired) off 49.5s vs medium 15.9s p=0.0400
|
|
310
|
+
// off 95% CI [0.43, 0.82]. Ledger: ledger-research-tooling.jsonl.
|
|
311
|
+
//
|
|
312
|
+
// RUNG 1 — quality decided it, and this is only the SECOND cell in the table
|
|
313
|
+
// a quality axis has ever decided. THE FIRST WAS extraction, which went the
|
|
314
|
+
// other way. Note there is no trade here: medium is both better AND 3.1x
|
|
315
|
+
// faster, so the sampler caveat below cannot be what produced it.
|
|
316
|
+
//
|
|
317
|
+
// THE AXIS: every command the checker can adjudicate must RESOLVE in the
|
|
318
|
+
// tree the worker inspected (scripts/reasoning-ab-tooling-truth.ts). Not a
|
|
319
|
+
// property of the text — the same kind of truth as gate's executed VERIFY,
|
|
320
|
+
// which is why it lives where four `phase` text axes died. `unknown` is a
|
|
321
|
+
// first-class answer and is scored neither way: three of the four command
|
|
322
|
+
// shapes can only return `real` or `unknown`, because an undeclared binary
|
|
323
|
+
// may still be on PATH and `bun test` resolves through the runtime.
|
|
324
|
+
//
|
|
325
|
+
// OFF'S FAILURE IS ONE CLASS, AUDITED ROW BY ROW. All seven name a
|
|
326
|
+
// dev-server command against `src/server/index.ts` — a file that exists in
|
|
327
|
+
// NEITHER the before-tree nor the after-tree of any of those seven tasks;
|
|
328
|
+
// it first appears around TASK_0057. The worker's own prompt says "If a
|
|
329
|
+
// tool isn't present in the repo, omit it — don't invent." Medium omits it.
|
|
330
|
+
//
|
|
331
|
+
// THE CHECKER WAS WRONG THREE TIMES FIRST, and all three are pinned in
|
|
332
|
+
// reasoning-ab-tooling-truth.test.ts:
|
|
333
|
+
// 1. `bun run <file.ts>` is a file invocation, not a script lookup.
|
|
334
|
+
// Caught offline; four of twelve reported failures were this bug.
|
|
335
|
+
// 2. `bun run <installed-bin>` falls back to `node_modules/.bin`.
|
|
336
|
+
// VERIFIED BY EXECUTION: `bun run tsc --version` → `Version 6.0.3`,
|
|
337
|
+
// exit 0; `bun run dev` → `error: Script not found "dev"`, exit 1.
|
|
338
|
+
// Caught LIVE at trial 19. It cost two MEDIUM trials, and BOTH were
|
|
339
|
+
// recovered by rescoring the stored text with no GPU — which is the
|
|
340
|
+
// whole reason the ledger stores `output`.
|
|
341
|
+
// 3. `-f` is a compose file only when a compose token precedes it, and
|
|
342
|
+
// `<runner> run <arg>` must be read for EVERY segment of a compound
|
|
343
|
+
// line, not the first. Found in review: `curl -f <url>` and
|
|
344
|
+
// `git clean -f -d` scored as invented paths (strict), while
|
|
345
|
+
// `bun run lint && bun run dev` scored `real` on `lint` alone and let
|
|
346
|
+
// `dev` — the exact class this cell turns on — through (LOOSE, and the
|
|
347
|
+
// harder half to spot). Caught after the run; rescoring the stored text
|
|
348
|
+
// moved 2/40 trials and left every number on this cell IDENTICAL.
|
|
349
|
+
// No fix moved the offline ceiling (32/45 tasks, 102/118 commands, before
|
|
350
|
+
// and after all three), so none loosened the check against known-good work.
|
|
351
|
+
//
|
|
352
|
+
// WHAT THIS CELL DOES NOT SAY. Medium sits at 20/20, so the axis has no
|
|
353
|
+
// headroom ABOVE it: a future run could not show medium getting worse. And
|
|
354
|
+
// the axis does not score the restart tail this worker is known for — no
|
|
355
|
+
// Trial field records a tool-call count — so "medium is better here" is
|
|
356
|
+
// about the ANSWER, not about the wander.
|
|
357
|
+
//
|
|
358
|
+
// REPRODUCE: `AB_SPECS=research:tooling:20 /abrun/run-group.sh`, then
|
|
359
|
+
// AB_CORPUS=.../mx5-copy bun run scripts/rescore-reasoning-ledger.ts \
|
|
360
|
+
// /home/edgars/hub/ab-grouplab/ledger-research-tooling.jsonl \
|
|
361
|
+
// research:tooling --from-text
|
|
362
|
+
'research:tooling': 'medium',
|
|
152
363
|
// NOT MEASURED. DECIDED BY PRIOR, 2026-08-27 — the same prior that carries
|
|
153
364
|
// every rung-3 cell in this table: thinking that buys nothing measurable is
|
|
154
365
|
// not worth its tokens. `off`.
|
|
@@ -453,6 +664,42 @@ export const DEFAULT_REASONING_TABLE = {
|
|
|
453
664
|
// carry the cell. The value is unchanged; only its standing improved, from
|
|
454
665
|
// a stated prior to a measured win. Rescored from the original ledger on 2026-08-25 when the harness
|
|
455
666
|
// moved to a forced two-way verdict; the trials are unchanged.
|
|
667
|
+
//
|
|
668
|
+
// OVERRIDE WITHDRAWN 2026-08-28; THE CELL IS `off` AGAIN, ON ITS OWN
|
|
669
|
+
// MEASUREMENT. Between 2026-08-27 and 2026-08-28 this cell read `medium` by
|
|
670
|
+
// user decision, overriding the rung-2 result above rather than a prior. The
|
|
671
|
+
// decision's stated reason was an implementation turn observed wandering in
|
|
672
|
+
// the mx5-n run, and the cell recorded honestly that the run's .pi-tasks logs
|
|
673
|
+
// had been destroyed before anyone could count the loop.
|
|
674
|
+
//
|
|
675
|
+
// A SECOND mx5-n RUN KEPT ITS LOGS, and they refute the reason. The run
|
|
676
|
+
// executed 10 tasks in 9h22 with this cell at `medium` — thinking ON for
|
|
677
|
+
// every implementation turn — and TASK_0004 wandered anyway: 167 assistant
|
|
678
|
+
// turns, 192 tool calls, 45 files and 2,355 lines committed against a
|
|
679
|
+
// one-file spec, and its own deliverable (src/server/migrate.ts) never
|
|
680
|
+
// written; it arrived four tasks later. Two single responses in that turn
|
|
681
|
+
// were 18,297 and 23,568 output tokens. Thinking did not prevent the
|
|
682
|
+
// rotation, so the lever the override was reaching for is not this one.
|
|
683
|
+
// Measured over the run's 15 implementation sessions: 441,399 output tokens,
|
|
684
|
+
// of which 59.3% by character was thinking (1,314,790 thinking chars vs
|
|
685
|
+
// 902,034 text chars).
|
|
686
|
+
//
|
|
687
|
+
// WHAT IS STRUCTURALLY TRUE, and still true, and checked: the implementation
|
|
688
|
+
// turn runs in the USER'S OWN SESSION (see task/implementation-thinking.ts
|
|
689
|
+
// and task/implementation-turn.ts), not as a child. It therefore has NO
|
|
690
|
+
// LoopDetector and NO StallDetector — only the per-tool-call command
|
|
691
|
+
// watchdog and superviseImplementation's MAX_COMPACTION_RESUMES, which the
|
|
692
|
+
// runaway above came nowhere near (it took 2 compactions of an allowed 20).
|
|
693
|
+
// So `implementation` remains the one group in this table where a rotation
|
|
694
|
+
// has no guard at all. That is an argument for GIVING it a guard, not for
|
|
695
|
+
// paying 1.8x in thinking that has now been observed not to guard it.
|
|
696
|
+
//
|
|
697
|
+
// THE CELL IS STILL RUNG 2 AND STILL THE WIDEST CI IN THE TABLE ([0.39,
|
|
698
|
+
// 0.78] at 12/20), so it is carried by the clock, not by quality. Nothing
|
|
699
|
+
// measured since has withdrawn the paired-clock win. To put it on more
|
|
700
|
+
// evidence, re-run scripts/live-implementation-thinking-ab.ts at a larger n;
|
|
701
|
+
// the ledger is /home/edgars/hub/ab-implab/impl-ledger.jsonl, 40 rows,
|
|
702
|
+
// b10618 — the odd build out, so a re-run on b10620 is not a replicate.
|
|
456
703
|
implementation: 'off'
|
|
457
704
|
};
|
|
458
705
|
/**
|
|
@@ -463,6 +710,29 @@ export const DEFAULT_REASONING_TABLE = {
|
|
|
463
710
|
export function sanitizeReasoningMode(value) {
|
|
464
711
|
return REASONING_MODES.includes(value) ? value : 'default';
|
|
465
712
|
}
|
|
713
|
+
/**
|
|
714
|
+
* The group each research worker reads its level from, keyed by the section
|
|
715
|
+
* heading its output is assembled under (task/phases.ts `workerSpecs`).
|
|
716
|
+
*
|
|
717
|
+
* Exported so the wiring is one table rather than four string literals spread
|
|
718
|
+
* through the phase code, and so a test can assert every value is a real group.
|
|
719
|
+
*/
|
|
720
|
+
export const RESEARCH_WORKER_GROUPS = {
|
|
721
|
+
FILES: 'research:files',
|
|
722
|
+
APIS: 'research:apis',
|
|
723
|
+
CONTEXT: 'research:context',
|
|
724
|
+
TOOLING: 'research:tooling'
|
|
725
|
+
};
|
|
726
|
+
/**
|
|
727
|
+
* For a `research:*` group, the group a stored config falls back to when its own
|
|
728
|
+
* key is missing. Every other group maps to `undefined`.
|
|
729
|
+
*/
|
|
730
|
+
const RESEARCH_SUBGROUP_PARENT = {
|
|
731
|
+
'research:files': 'research',
|
|
732
|
+
'research:apis': 'research',
|
|
733
|
+
'research:context': 'research',
|
|
734
|
+
'research:tooling': 'research'
|
|
735
|
+
};
|
|
466
736
|
/**
|
|
467
737
|
* Always returns a COMPLETE record, never a partial one.
|
|
468
738
|
*
|
|
@@ -475,13 +745,33 @@ export function sanitizeReasoningLevels(value) {
|
|
|
475
745
|
const stored = typeof value === 'object' && value !== null && !Array.isArray(value) ?
|
|
476
746
|
value
|
|
477
747
|
: {};
|
|
748
|
+
const valid = (v) => REASONING_SETTINGS.includes(v);
|
|
478
749
|
const out = {};
|
|
479
750
|
for (const group of REASONING_GROUPS) {
|
|
480
751
|
const stored_ = stored[group];
|
|
752
|
+
if (valid(stored_)) {
|
|
753
|
+
out[group] = stored_;
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
// A `research:*` key absent from the file falls back to the user's own
|
|
757
|
+
// `research`, NOT to the default table. This is the migration path: a
|
|
758
|
+
// config written before the split carries one `research` level and
|
|
759
|
+
// nothing else, and filling the four sub-cells from the table would
|
|
760
|
+
// silently overrule a choice the user had already made — the four
|
|
761
|
+
// workers are exactly the children that `research` used to set.
|
|
762
|
+
//
|
|
763
|
+
// IT CANNOT TELL A CHOICE FROM A SEED, and that is accepted. Leaving
|
|
764
|
+
// `default`/`on`/`off` for ANY reason makes `applyReasoningLevel` seed
|
|
765
|
+
// every group from `resolveReasoning`, so a user who only ever nudged
|
|
766
|
+
// `gate` still has a `research` on disk they never picked. Inheriting
|
|
767
|
+
// it pins the four workers to the old default and the measured
|
|
768
|
+
// `research:files: 'off'` never reaches them. That is what CUSTOM MODE
|
|
769
|
+
// MEANS: a frozen table, not a subscription to later measurements — the
|
|
770
|
+
// same is true of every other group in the file. `default` mode is
|
|
771
|
+
// where a new reading takes effect, and it takes effect there at once.
|
|
772
|
+
const parent = RESEARCH_SUBGROUP_PARENT[group];
|
|
481
773
|
out[group] =
|
|
482
|
-
|
|
483
|
-
stored_
|
|
484
|
-
: DEFAULT_REASONING_TABLE[group];
|
|
774
|
+
parent && valid(stored[parent]) ? stored[parent] : DEFAULT_REASONING_TABLE[group];
|
|
485
775
|
}
|
|
486
776
|
return out;
|
|
487
777
|
}
|
|
@@ -515,8 +805,16 @@ export function thinkingArgs(setting) {
|
|
|
515
805
|
}
|
|
516
806
|
/** One honest sentence per group, for the /task-config rows. */
|
|
517
807
|
export const REASONING_GROUP_HELP = {
|
|
518
|
-
research: 'The
|
|
519
|
-
+ '
|
|
808
|
+
research: 'The pi-worker subagent tool, and the fallback for any research worker below '
|
|
809
|
+
+ 'whose own level is unset. Read-only exploration loops.',
|
|
810
|
+
'research:files': 'Research worker 1 of 4: maps which files the task will touch. Read-heavy. '
|
|
811
|
+
+ 'Measured: the two arms tie, so it runs without thinking.',
|
|
812
|
+
'research:apis': 'Research worker 2 of 4: the symbols and signatures the task must call. '
|
|
813
|
+
+ 'Read-heavy, docs- and search-capable.',
|
|
814
|
+
'research:context': 'Research worker 3 of 4: how the project is put together. One of the two that '
|
|
815
|
+
+ 'burned wall-clock on restarts in the last full run.',
|
|
816
|
+
'research:tooling': 'Research worker 4 of 4: the commands that build, test and run the project. '
|
|
817
|
+
+ 'Measured: thinking wins on both quality and speed.',
|
|
520
818
|
phase: 'Refining your request, generating and answering the clarifying questions, '
|
|
521
819
|
+ 'writing the spec, and critiquing it.',
|
|
522
820
|
planning: "/task-auto's planners: splitting a design document into tasks and extracting "
|
|
@@ -120,13 +120,25 @@ export declare function toolItems(tools: readonly GuardableTool[], exempt: reado
|
|
|
120
120
|
}[];
|
|
121
121
|
/** Apply a per-tool watchdog toggle to the exemption list (idempotent both ways). */
|
|
122
122
|
export declare function applyToolToggle(exempt: readonly string[], toolName: string, watched: boolean): string[];
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
123
|
+
/**
|
|
124
|
+
* The label for one `think:` row.
|
|
125
|
+
*
|
|
126
|
+
* A group whose name carries a colon is a CHILD of the group before the colon —
|
|
127
|
+
* `research:files` is one of the four workers `research` fans out to. Rendered
|
|
128
|
+
* flat, those four read as four more peers of `research`, and the menu's one
|
|
129
|
+
* genuine hierarchy is invisible: the four are the only rows in the list whose
|
|
130
|
+
* parent is also a row.
|
|
131
|
+
*
|
|
132
|
+
* So a child is drawn as a tree branch under its parent and loses the repeated
|
|
133
|
+
* `think: research:` prefix, which is 15 columns of the same text on four
|
|
134
|
+
* consecutive lines. `└─` on the last child, `├─` on the rest, decided from the
|
|
135
|
+
* group's position in {@link REASONING_GROUPS} rather than a hand-kept list —
|
|
136
|
+
* adding a fifth worker moves the corner on its own.
|
|
137
|
+
*
|
|
138
|
+
* Leading spaces survive: SettingsList pads the label right, never trims it.
|
|
139
|
+
*/
|
|
140
|
+
export declare function reasoningRowLabel(group: ReasoningGroup): string;
|
|
141
|
+
export declare function reasoningItems(cfg: PiTaskConfig): PanelItem[];
|
|
130
142
|
/**
|
|
131
143
|
* Apply one group row's new value.
|
|
132
144
|
*
|
|
@@ -167,6 +179,14 @@ export type PanelItem = {
|
|
|
167
179
|
* — the header does not need its own branch anywhere.
|
|
168
180
|
*/
|
|
169
181
|
values?: string[];
|
|
182
|
+
/**
|
|
183
|
+
* What the headless one-line rendering calls this row, when `label` reads
|
|
184
|
+
* only in the panel. A tree branch means nothing on a line of `|`-joined
|
|
185
|
+
* rows: `├─ files` there names no parent, where `think: research:files`
|
|
186
|
+
* does. Set by the reasoning rows; every other row leaves it off and its
|
|
187
|
+
* `label` is used.
|
|
188
|
+
*/
|
|
189
|
+
headlessLabel?: string;
|
|
170
190
|
};
|
|
171
191
|
/**
|
|
172
192
|
* Builds the framed settings panel. Split out of the command handler so the
|
package/dist/config/register.js
CHANGED
|
@@ -314,10 +314,36 @@ export function applyToolToggle(exempt, toolName, watched) {
|
|
|
314
314
|
* whole point of having measured it.
|
|
315
315
|
*/
|
|
316
316
|
const REASON_ID_PREFIX = 'reason:';
|
|
317
|
+
/**
|
|
318
|
+
* The label for one `think:` row.
|
|
319
|
+
*
|
|
320
|
+
* A group whose name carries a colon is a CHILD of the group before the colon —
|
|
321
|
+
* `research:files` is one of the four workers `research` fans out to. Rendered
|
|
322
|
+
* flat, those four read as four more peers of `research`, and the menu's one
|
|
323
|
+
* genuine hierarchy is invisible: the four are the only rows in the list whose
|
|
324
|
+
* parent is also a row.
|
|
325
|
+
*
|
|
326
|
+
* So a child is drawn as a tree branch under its parent and loses the repeated
|
|
327
|
+
* `think: research:` prefix, which is 15 columns of the same text on four
|
|
328
|
+
* consecutive lines. `└─` on the last child, `├─` on the rest, decided from the
|
|
329
|
+
* group's position in {@link REASONING_GROUPS} rather than a hand-kept list —
|
|
330
|
+
* adding a fifth worker moves the corner on its own.
|
|
331
|
+
*
|
|
332
|
+
* Leading spaces survive: SettingsList pads the label right, never trims it.
|
|
333
|
+
*/
|
|
334
|
+
export function reasoningRowLabel(group) {
|
|
335
|
+
const colon = group.indexOf(':');
|
|
336
|
+
if (colon < 0)
|
|
337
|
+
return `think: ${group}`;
|
|
338
|
+
const parent = group.slice(0, colon);
|
|
339
|
+
const nextIsSibling = REASONING_GROUPS[REASONING_GROUPS.indexOf(group) + 1]?.startsWith(`${parent}:`);
|
|
340
|
+
return ` ${nextIsSibling ? '├─' : '└─'} ${group.slice(colon + 1)}`;
|
|
341
|
+
}
|
|
317
342
|
export function reasoningItems(cfg) {
|
|
318
343
|
return REASONING_GROUPS.map(group => ({
|
|
319
344
|
id: REASON_ID_PREFIX + group,
|
|
320
|
-
label:
|
|
345
|
+
label: reasoningRowLabel(group),
|
|
346
|
+
headlessLabel: `think: ${group}`,
|
|
321
347
|
description: REASONING_GROUP_HELP[group],
|
|
322
348
|
// The EFFECTIVE level, not cfg.reasoningLevels[group]: in default/on/off
|
|
323
349
|
// the stored table is not what runs, and a row that shows a value the
|
|
@@ -555,7 +581,7 @@ async function handleTaskConfig(_args, ctx, getTools = () => []) {
|
|
|
555
581
|
.filter(i => i.label !== '')
|
|
556
582
|
.map(i => i.values === undefined ?
|
|
557
583
|
`[${i.label.trim()}]`
|
|
558
|
-
: `${i.label.padEnd(22)} ${i.currentValue}`);
|
|
584
|
+
: `${(i.headlessLabel ?? i.label).padEnd(22)} ${i.currentValue}`);
|
|
559
585
|
ctx.ui.notify(lines.join(' | '), 'info');
|
|
560
586
|
return;
|
|
561
587
|
}
|
package/dist/task/phases.d.ts
CHANGED
|
@@ -123,6 +123,22 @@ export declare function resolveOwnedFreezeForThisTask(deps: PhaseDeps, spec: str
|
|
|
123
123
|
export declare function claimOwnedFreezeForThisTask(deps: PhaseDeps, refined: string): Promise<void>;
|
|
124
124
|
export declare const phaseRefine: (deps: PhaseDeps, raw: string, planContext?: string) => Promise<string>;
|
|
125
125
|
export declare function phaseVerifyTooling(deps: PhaseDeps, research: string): Promise<string>;
|
|
126
|
+
/**
|
|
127
|
+
* The worker channels the APIS research worker is given.
|
|
128
|
+
*
|
|
129
|
+
* `pi-worker-search` + `pi-worker-fetch` ride along only when the configured
|
|
130
|
+
* engine is usable (a keyless engine always is; brave needs its key). A tool
|
|
131
|
+
* without a key just errors, and a weak model burns calls on it — while search
|
|
132
|
+
* being ABSENT was structural in the other direction: three consecutive audited
|
|
133
|
+
* runs made 0 search calls because the child literally did not have the tool.
|
|
134
|
+
*
|
|
135
|
+
* Both halves of "given a channel" — the tools string and the `-e` path — come
|
|
136
|
+
* from the same rows, so they cannot disagree.
|
|
137
|
+
*/
|
|
138
|
+
export declare function apisWorkerChannels(): {
|
|
139
|
+
tools: string;
|
|
140
|
+
extensions: string[];
|
|
141
|
+
};
|
|
126
142
|
/**
|
|
127
143
|
* Is live web search configured for this process? The keyless providers (exa,
|
|
128
144
|
* ddg) always are; only brave needs its API key.
|
package/dist/task/phases.js
CHANGED
|
@@ -43,6 +43,7 @@ import { SessionUI } from '../remote/bridge.js';
|
|
|
43
43
|
import { isYoloMode, yoloPickAutoAnswer } from './yolo.js';
|
|
44
44
|
import { QaTranscript, GRILL_QA_POLICY } from './qa-transcript.js';
|
|
45
45
|
import { groupThinkingArgs } from '../config/reasoning-args.js';
|
|
46
|
+
import { RESEARCH_WORKER_GROUPS } from '../config/reasoning.js';
|
|
46
47
|
// ─── Re-export constants from their home modules ────────────────────────────
|
|
47
48
|
export { MAX_GRILL_QUESTIONS };
|
|
48
49
|
// ─── Tooling helpers ─────────────────────────────────────────────────────────
|
|
@@ -326,7 +327,7 @@ export async function phaseVerifyTooling(deps, research) {
|
|
|
326
327
|
* Both halves of "given a channel" — the tools string and the `-e` path — come
|
|
327
328
|
* from the same rows, so they cannot disagree.
|
|
328
329
|
*/
|
|
329
|
-
function apisWorkerChannels() {
|
|
330
|
+
export function apisWorkerChannels() {
|
|
330
331
|
return channelSet([
|
|
331
332
|
'pi-worker-docs',
|
|
332
333
|
...(searchConfigured() ? ['pi-worker-search', 'pi-worker-fetch'] : [])
|
|
@@ -901,11 +902,18 @@ export async function phaseResearch(deps, refined) {
|
|
|
901
902
|
cwd: deps.cwd,
|
|
902
903
|
signal: deps.signal,
|
|
903
904
|
spawn: deps.spawn,
|
|
904
|
-
//
|
|
905
|
-
//
|
|
906
|
-
//
|
|
907
|
-
//
|
|
908
|
-
|
|
905
|
+
// ONE CELL PER WORKER since 2026-08-28. They used to share
|
|
906
|
+
// the `research` cell on the grounds that they are the same
|
|
907
|
+
// job over four questions; the run logs disagree. All 40.7
|
|
908
|
+
// wasted research minutes in mx5-n were restarts in
|
|
909
|
+
// `tooling` and `context`, and `files`/`apis` never
|
|
910
|
+
// restarted — so the level that pays for one pair is being
|
|
911
|
+
// paid for the other. THE FOUR CELLS DO NOT SHIP IDENTICAL:
|
|
912
|
+
// `research:files` is `off` on a measured tie while the
|
|
913
|
+
// other three are `medium`, so this line changes what the
|
|
914
|
+
// FILES worker runs at for every default-mode user. The
|
|
915
|
+
// evidence is on each cell in reasoning.ts.
|
|
916
|
+
thinking: groupThinkingArgs(RESEARCH_WORKER_GROUPS[spec.section] ?? 'research'),
|
|
909
917
|
...(spec.tools ? { tools: spec.tools } : {}),
|
|
910
918
|
...(spec.extensions ? { extensions: spec.extensions } : {}),
|
|
911
919
|
// 5B SCALE arm — null unless both env vars are set. Only the
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { type ContextSnapshot, type SpawnFn } from '../shared/child-process.js';
|
|
2
|
-
import { type LoopHit } from '../task/loop-detector.js';
|
|
1
|
+
import { type ContextSnapshot, type LoopHit, type SpawnFn } from '../shared/child-process.js';
|
|
3
2
|
/**
|
|
4
3
|
* Tool calls that can GROUND an APIS claim — i.e. return content a signature or
|
|
5
4
|
* command could be cited from. `pi-worker-docs` (the primary), `read` and `grep`
|
|
@@ -125,6 +124,27 @@ export interface RunWorkerInput {
|
|
|
125
124
|
threshold?: number;
|
|
126
125
|
pathThreshold?: number;
|
|
127
126
|
} | false;
|
|
127
|
+
/**
|
|
128
|
+
* Whole-run progress guard (task/stall-detector.ts). Default ON.
|
|
129
|
+
*
|
|
130
|
+
* WHY BOTH. LoopDetector judges ARGUMENTS over a 20-call window, so a child
|
|
131
|
+
* that rotates through MORE DISTINCT CALLS THAN THE WINDOW HOLDS is invisible
|
|
132
|
+
* to it — every key occurs once per window and the count never reaches the
|
|
133
|
+
* threshold. Measured: mx5-n 2026-08-27, worker:tooling made 550 calls over
|
|
134
|
+
* exactly 20 distinct files, ~36 reads each, and neither the exact rule nor
|
|
135
|
+
* the path rule ever tripped. It died 20 minutes later on the absolute
|
|
136
|
+
* progress ceiling, having done 25s of useful work.
|
|
137
|
+
*
|
|
138
|
+
* StallDetector judges RESULTS, which a rotating reader cannot vary. It was
|
|
139
|
+
* written for exactly this class and was wired only into phase children
|
|
140
|
+
* (task/child-runner.ts) until this option existed.
|
|
141
|
+
*
|
|
142
|
+
* Pass `false` to disable, or override the thresholds (tests, harnesses).
|
|
143
|
+
*/
|
|
144
|
+
stallGuard?: {
|
|
145
|
+
limit?: number;
|
|
146
|
+
churnFactor?: number;
|
|
147
|
+
} | false;
|
|
128
148
|
/**
|
|
129
149
|
* Dead-backend stall guard override. Default ON: no output for
|
|
130
150
|
* STALL_AFTER_MS → probe the model endpoints pi is configured with →
|
|
@@ -4,6 +4,7 @@ import { CommandWatchdog, commandTimeoutHint, realTimerDeps } from '../shared/co
|
|
|
4
4
|
import { isGroundingRetrieval as isGrounding, workerChannel } from './worker-channels.js';
|
|
5
5
|
import { childBaseArgs } from '../shared/child-extensions.js';
|
|
6
6
|
import { LoopDetector } from '../task/loop-detector.js';
|
|
7
|
+
import { StallDetector, formatStallHint } from '../task/stall-detector.js';
|
|
7
8
|
import { LOOP_WINDOW, LOOP_THRESHOLD, MAX_LOOP_RESTARTS, formatLoopHint, isConnectionError, connectionRetryBackoffMs } from '../task/child-runner.js';
|
|
8
9
|
import { detectLeakedToolCall, leakedToolCallHint, MAX_LEAK_RETRIES } from '../shared/leaked-tool-call.js';
|
|
9
10
|
import { discoverModelEndpoints, probeModelEndpoints } from '../shared/model-endpoint.js';
|
|
@@ -282,9 +283,16 @@ const RESTART_RULES = [
|
|
|
282
283
|
// re-spawn avoids it. Bounded by the shared restart budget.
|
|
283
284
|
reason: 'loop',
|
|
284
285
|
detect: s => s.loopHit && s.restartBudgetSpent < MAX_LOOP_RESTARTS ?
|
|
285
|
-
{
|
|
286
|
+
{
|
|
287
|
+
// A stall hit carries no meaningful windowSize (rule 1 sets
|
|
288
|
+
// it to 0), so printing the loop shape would misname why the
|
|
289
|
+
// attempt died.
|
|
290
|
+
detail: s.loopHit.stall ?
|
|
291
|
+
`${s.loopHit.call.name} ${s.loopHit.stall} ×${s.loopHit.count}`
|
|
292
|
+
: `${s.loopHit.call.name} ×${s.loopHit.count}/${s.loopHit.windowSize}`
|
|
293
|
+
}
|
|
286
294
|
: null,
|
|
287
|
-
hint: s => formatLoopHint(s.loopHit),
|
|
295
|
+
hint: s => s.loopHit.stall ? formatStallHint(s.loopHit.stall) : formatLoopHint(s.loopHit),
|
|
288
296
|
counters: { shared: true }
|
|
289
297
|
},
|
|
290
298
|
{
|
|
@@ -499,6 +507,16 @@ export async function runWorker(input) {
|
|
|
499
507
|
const threshold = input.loop?.threshold ?? LOOP_THRESHOLD;
|
|
500
508
|
return new LoopDetector(window, threshold, input.loop?.pathThreshold ?? threshold);
|
|
501
509
|
})();
|
|
510
|
+
// Reset EACH attempt, like the loop detector: a restart discards the
|
|
511
|
+
// previous attempt's calls along with its text, so a fresh child must not
|
|
512
|
+
// inherit a dead streak it did not earn.
|
|
513
|
+
const stallDetector = input.stallGuard === false ?
|
|
514
|
+
null
|
|
515
|
+
: new StallDetector(input.stallGuard?.limit, input.stallGuard?.churnFactor);
|
|
516
|
+
// Arm the churn rule BEFORE the first tool call. pi's stream carries no
|
|
517
|
+
// context event (issue #16), so waiting for one leaves the rule
|
|
518
|
+
// permanently disarmed. The parent knows the window at spawn time.
|
|
519
|
+
stallDetector?.noteContext(input.contextWindow ?? 0);
|
|
502
520
|
// Capture the hit the detector reports (it also returns it to the unified
|
|
503
521
|
// runner, which kills the child on a hit). Without capturing it here the
|
|
504
522
|
// SIGTERM that kill produces would surface as a bare non-zero exit the
|
|
@@ -543,9 +561,11 @@ export async function runWorker(input) {
|
|
|
543
561
|
}
|
|
544
562
|
if (isGrounding(call.name))
|
|
545
563
|
groundingRetrievalCount++;
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
564
|
+
// Loop detector first: it names the offending call and its
|
|
565
|
+
// hint is the more specific one. The stall detector is the
|
|
566
|
+
// backstop for the thrash shapes a 20-call argument window
|
|
567
|
+
// cannot see.
|
|
568
|
+
const hit = loopDetector?.record(call) ?? stallDetector?.record(call) ?? null;
|
|
549
569
|
if (hit && !loopHit)
|
|
550
570
|
loopHit = hit;
|
|
551
571
|
return hit;
|
|
@@ -565,9 +585,15 @@ export async function runWorker(input) {
|
|
|
565
585
|
onToolResult: r => {
|
|
566
586
|
timeout.progress();
|
|
567
587
|
cmdWatch?.onEnd(r.toolCallId);
|
|
588
|
+
// The RESULT is what entered the child's context, so it —
|
|
589
|
+
// not the arguments — decides whether it learned anything.
|
|
590
|
+
stallDetector?.noteResult(r.text, r.isError);
|
|
568
591
|
input.onToolResult?.(r);
|
|
569
592
|
},
|
|
570
|
-
onContextUsage:
|
|
593
|
+
onContextUsage: snapshot => {
|
|
594
|
+
stallDetector?.noteContext(snapshot.contextWindow);
|
|
595
|
+
input.onContextUsage?.(snapshot);
|
|
596
|
+
},
|
|
571
597
|
...(input.contextWindow && input.contextWindow > 0 ?
|
|
572
598
|
{ contextWindow: input.contextWindow }
|
|
573
599
|
: {})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.23",
|
|
4
4
|
"description": "Deterministic task planning and spec-orchestration for local models — crash-safe /task pipelines with verify/enforce gates, a real-time remote web view, and web/docs/fetch/worker subagent tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|