koneck 2.67.0 → 2.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/cycle.d.ts +107 -0
  2. package/dist/cycle.d.ts.map +1 -0
  3. package/dist/cycle.js +136 -0
  4. package/dist/cycle.js.map +1 -0
  5. package/dist/engine.d.ts.map +1 -1
  6. package/dist/engine.js +63 -0
  7. package/dist/engine.js.map +1 -1
  8. package/dist/ink-chat.d.ts.map +1 -1
  9. package/dist/ink-chat.js +98 -9
  10. package/dist/ink-chat.js.map +1 -1
  11. package/dist/mention-resolve.d.ts +64 -0
  12. package/dist/mention-resolve.d.ts.map +1 -0
  13. package/dist/mention-resolve.js +139 -0
  14. package/dist/mention-resolve.js.map +1 -0
  15. package/dist/types.d.ts +11 -0
  16. package/dist/types.d.ts.map +1 -1
  17. package/dist/web/api.d.ts.map +1 -1
  18. package/dist/web/api.js +63 -0
  19. package/dist/web/api.js.map +1 -1
  20. package/dist/web/events.d.ts +13 -0
  21. package/dist/web/events.d.ts.map +1 -1
  22. package/dist/web/events.js.map +1 -1
  23. package/dist/web/mention-signals.d.ts +41 -0
  24. package/dist/web/mention-signals.d.ts.map +1 -0
  25. package/dist/web/mention-signals.js +117 -0
  26. package/dist/web/mention-signals.js.map +1 -0
  27. package/dist/web/mention.d.ts +121 -0
  28. package/dist/web/mention.d.ts.map +1 -0
  29. package/dist/web/mention.js +211 -0
  30. package/dist/web/mention.js.map +1 -0
  31. package/dist/web/sessions.d.ts.map +1 -1
  32. package/dist/web/sessions.js +25 -2
  33. package/dist/web/sessions.js.map +1 -1
  34. package/dist/web/ui-client.d.ts.map +1 -1
  35. package/dist/web/ui-client.js +270 -1
  36. package/dist/web/ui-client.js.map +1 -1
  37. package/dist/web/ui-css.d.ts.map +1 -1
  38. package/dist/web/ui-css.js +39 -0
  39. package/dist/web/ui-css.js.map +1 -1
  40. package/dist/web/ui.d.ts.map +1 -1
  41. package/dist/web/ui.js +9 -0
  42. package/dist/web/ui.js.map +1 -1
  43. package/package.json +1 -1
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Going round in circles, as opposed to repeating one call.
3
+ *
4
+ * The identical-command guard catches "the same thing twelve times". It does not catch the shape
5
+ * that cost 20.9 million tokens: kill the server, start the server, wait and read the log, curl the
6
+ * endpoint, read the log, kill the server — sixty-four times round, every individual call
7
+ * reasonable, none repeated back to back, and `lsof` and a one-off `node -e` probe spliced in at
8
+ * irregular intervals so there is no fixed period to match.
9
+ *
10
+ * ## What the data actually supports
11
+ *
12
+ * This was calibrated against the real sessions on the machine, not chosen. For each, the fewest
13
+ * distinct actions found in any window containing no file writes:
14
+ *
15
+ * session W=12 W=20 W=30
16
+ * finished properly (89 calls) 5 8 11
17
+ * the runaway (290 calls) 1 5 8
18
+ * a second loop (49 calls) 2 2 10
19
+ * a third loop (20 calls) 1 7 –
20
+ *
21
+ * The honest reading is that the separation is thin. A long session that finished properly is
22
+ * genuinely repetitive — its worst twelve-call window held five distinct actions and seven repeats,
23
+ * which is *more* repetitive than the runaway's cycle phase, where six distinct actions accounted
24
+ * for twelve calls. No window size separates them cleanly, and the entire legitimate sample is one
25
+ * session.
26
+ *
27
+ * ## So this warns and never stops
28
+ *
29
+ * A signal this thin can identify something worth mentioning. It cannot justify ending somebody's
30
+ * work: the cost of being wrong is a run killed mid-task, and one legitimate session is not evidence
31
+ * enough to risk that. The identical-command guard stops runs because its evidence is unambiguous —
32
+ * the same command, unchanged, with nothing altered between. This one talks.
33
+ *
34
+ * Two conditions, both required. No file may have been written in the window, because iterating on a
35
+ * file is repetitive by nature and is the most valuable thing an agent does. And the window is
36
+ * twenty calls rather than twelve, where the numbers above leave a real gap: five and two for the
37
+ * loops against eight for the run that finished.
38
+ */
39
+ /** One tool call, as much of it as this needs. */
40
+ export interface Called {
41
+ name: string;
42
+ /** The one-line detail: a path, a command, a url. */
43
+ detail: string;
44
+ /** Files this call changed, when that is known. Empty or absent means it changed nothing. */
45
+ changed?: readonly string[] | undefined;
46
+ }
47
+ /**
48
+ * How many recent calls are considered.
49
+ *
50
+ * Twenty, from the table above: at twelve the loops and the legitimate run overlap, and at thirty
51
+ * the legitimate run's own figure falls to eleven while a known loop sits at ten.
52
+ */
53
+ export declare const WINDOW = 20;
54
+ /**
55
+ * How few distinct actions count as circling.
56
+ *
57
+ * Six. The loops measured five and two; the session that finished measured eight. Six leaves a
58
+ * margin on both sides of a boundary drawn from one legitimate sample, which is the most that sample
59
+ * can honestly carry.
60
+ */
61
+ export declare const CHURN_DISTINCT = 6;
62
+ /**
63
+ * A signature: the tool and what it was pointed at, tidied and nothing more.
64
+ *
65
+ * The first version stripped digits and long hex runs, reasoning that the same loop reappears with a
66
+ * different port, a fresh pid or a regenerated key. Two things settled that.
67
+ *
68
+ * It caught a false positive immediately: `grep pattern-1` through `grep pattern-8` collapsed into
69
+ * one action, so reading or searching eight numbered things — which is progress — read as a cycle of
70
+ * one. And it bought nothing. Measured across the real sessions, the fewest distinct actions in a
71
+ * no-write window came out identical with the stripping and without it: 8 for the run that finished,
72
+ * 5 and 7 and 2 for the loops. The variation it was written for does not appear in any session on
73
+ * the machine, while the harm it caused appeared in the first test.
74
+ *
75
+ * So it does what the evidence supports and stops. If a loop that varies a token every round turns
76
+ * up later, it can be handled then, with that loop to look at.
77
+ */
78
+ export declare function signatureOf(call: Called): string;
79
+ export interface Churn {
80
+ /** How many distinct actions accounted for the window. */
81
+ distinct: number;
82
+ /** How many calls were second-or-later occurrences of something already in the window. */
83
+ repeats: number;
84
+ /** The actions themselves, most-repeated first, for saying what is being circled. */
85
+ actions: Array<{
86
+ what: string;
87
+ times: number;
88
+ }>;
89
+ }
90
+ /**
91
+ * Whether the last stretch of calls is circling, or null when it is not.
92
+ *
93
+ * Null while there is not yet a full window: a judgement about whether somebody is going in circles
94
+ * cannot be made from four calls, and guessing early is how a guard becomes something people learn
95
+ * to ignore.
96
+ */
97
+ export declare function findChurn(history: readonly Called[], window?: number, maxDistinct?: number): Churn | null;
98
+ /**
99
+ * What to tell the model, naming what it is circling.
100
+ *
101
+ * The actions are quoted because a model that has lost its place is usually corrected by seeing the
102
+ * list: "you have been doing these four things" is actionable in a way that "you appear to be
103
+ * looping" is not. And it says what to do instead, because a complaint without an alternative is
104
+ * just a complaint.
105
+ */
106
+ export declare function describeChurn(churn: Churn, window?: number): string;
107
+ //# sourceMappingURL=cycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cycle.d.ts","sourceRoot":"","sources":["../src/cycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,kDAAkD;AAClD,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,6FAA6F;IAC7F,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CACzC;AAED;;;;;GAKG;AACH,eAAO,MAAM,MAAM,KAAK,CAAC;AAEzB;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,IAAI,CAAC;AAOhC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMhD;AAED,MAAM,WAAW,KAAK;IACpB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,0FAA0F;IAC1F,OAAO,EAAE,MAAM,CAAC;IAChB,qFAAqF;IACrF,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,MAAM,SAAS,EACf,WAAW,SAAiB,GAC3B,KAAK,GAAG,IAAI,CA4Bd;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,GAAG,MAAM,CAUnE"}
package/dist/cycle.js ADDED
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Going round in circles, as opposed to repeating one call.
3
+ *
4
+ * The identical-command guard catches "the same thing twelve times". It does not catch the shape
5
+ * that cost 20.9 million tokens: kill the server, start the server, wait and read the log, curl the
6
+ * endpoint, read the log, kill the server — sixty-four times round, every individual call
7
+ * reasonable, none repeated back to back, and `lsof` and a one-off `node -e` probe spliced in at
8
+ * irregular intervals so there is no fixed period to match.
9
+ *
10
+ * ## What the data actually supports
11
+ *
12
+ * This was calibrated against the real sessions on the machine, not chosen. For each, the fewest
13
+ * distinct actions found in any window containing no file writes:
14
+ *
15
+ * session W=12 W=20 W=30
16
+ * finished properly (89 calls) 5 8 11
17
+ * the runaway (290 calls) 1 5 8
18
+ * a second loop (49 calls) 2 2 10
19
+ * a third loop (20 calls) 1 7 –
20
+ *
21
+ * The honest reading is that the separation is thin. A long session that finished properly is
22
+ * genuinely repetitive — its worst twelve-call window held five distinct actions and seven repeats,
23
+ * which is *more* repetitive than the runaway's cycle phase, where six distinct actions accounted
24
+ * for twelve calls. No window size separates them cleanly, and the entire legitimate sample is one
25
+ * session.
26
+ *
27
+ * ## So this warns and never stops
28
+ *
29
+ * A signal this thin can identify something worth mentioning. It cannot justify ending somebody's
30
+ * work: the cost of being wrong is a run killed mid-task, and one legitimate session is not evidence
31
+ * enough to risk that. The identical-command guard stops runs because its evidence is unambiguous —
32
+ * the same command, unchanged, with nothing altered between. This one talks.
33
+ *
34
+ * Two conditions, both required. No file may have been written in the window, because iterating on a
35
+ * file is repetitive by nature and is the most valuable thing an agent does. And the window is
36
+ * twenty calls rather than twelve, where the numbers above leave a real gap: five and two for the
37
+ * loops against eight for the run that finished.
38
+ */
39
+ /**
40
+ * How many recent calls are considered.
41
+ *
42
+ * Twenty, from the table above: at twelve the loops and the legitimate run overlap, and at thirty
43
+ * the legitimate run's own figure falls to eleven while a known loop sits at ten.
44
+ */
45
+ export const WINDOW = 20;
46
+ /**
47
+ * How few distinct actions count as circling.
48
+ *
49
+ * Six. The loops measured five and two; the session that finished measured eight. Six leaves a
50
+ * margin on both sides of a boundary drawn from one legitimate sample, which is the most that sample
51
+ * can honestly carry.
52
+ */
53
+ export const CHURN_DISTINCT = 6;
54
+ /** Tools that change files. A window containing one of these is progress, whatever its shape. */
55
+ const WRITES = new Set([
56
+ 'write_file', 'search_replace', 'apply_diff', 'delete_file', 'move_file',
57
+ ]);
58
+ /**
59
+ * A signature: the tool and what it was pointed at, tidied and nothing more.
60
+ *
61
+ * The first version stripped digits and long hex runs, reasoning that the same loop reappears with a
62
+ * different port, a fresh pid or a regenerated key. Two things settled that.
63
+ *
64
+ * It caught a false positive immediately: `grep pattern-1` through `grep pattern-8` collapsed into
65
+ * one action, so reading or searching eight numbered things — which is progress — read as a cycle of
66
+ * one. And it bought nothing. Measured across the real sessions, the fewest distinct actions in a
67
+ * no-write window came out identical with the stripping and without it: 8 for the run that finished,
68
+ * 5 and 7 and 2 for the loops. The variation it was written for does not appear in any session on
69
+ * the machine, while the harm it caused appeared in the first test.
70
+ *
71
+ * So it does what the evidence supports and stops. If a loop that varies a token every round turns
72
+ * up later, it can be handled then, with that loop to look at.
73
+ */
74
+ export function signatureOf(call) {
75
+ const detail = String(call.detail ?? '')
76
+ .replace(/\s+/g, ' ')
77
+ .trim()
78
+ .slice(0, 80);
79
+ return `${call.name}::${detail}`;
80
+ }
81
+ /**
82
+ * Whether the last stretch of calls is circling, or null when it is not.
83
+ *
84
+ * Null while there is not yet a full window: a judgement about whether somebody is going in circles
85
+ * cannot be made from four calls, and guessing early is how a guard becomes something people learn
86
+ * to ignore.
87
+ */
88
+ export function findChurn(history, window = WINDOW, maxDistinct = CHURN_DISTINCT) {
89
+ if (history.length < window)
90
+ return null;
91
+ const recent = history.slice(-window);
92
+ /*
93
+ * Anything written is progress, whatever shape the calls make.
94
+ *
95
+ * This is the condition that makes the thin signal safe to act on at all: write, test, write, test
96
+ * is the most repetitive thing an agent does and the most valuable, and it is exempt by
97
+ * construction rather than by a threshold that might or might not hold.
98
+ */
99
+ if (recent.some(call => WRITES.has(call.name) || (call.changed?.length ?? 0) > 0))
100
+ return null;
101
+ const counts = new Map();
102
+ for (const call of recent) {
103
+ const sig = signatureOf(call);
104
+ counts.set(sig, (counts.get(sig) ?? 0) + 1);
105
+ }
106
+ if (counts.size > maxDistinct)
107
+ return null;
108
+ const actions = [...counts.entries()]
109
+ .map(([what, times]) => ({ what, times }))
110
+ .sort((a, b) => b.times - a.times || a.what.localeCompare(b.what));
111
+ return {
112
+ distinct: counts.size,
113
+ repeats: window - counts.size,
114
+ actions,
115
+ };
116
+ }
117
+ /**
118
+ * What to tell the model, naming what it is circling.
119
+ *
120
+ * The actions are quoted because a model that has lost its place is usually corrected by seeing the
121
+ * list: "you have been doing these four things" is actionable in a way that "you appear to be
122
+ * looping" is not. And it says what to do instead, because a complaint without an alternative is
123
+ * just a complaint.
124
+ */
125
+ export function describeChurn(churn, window = WINDOW) {
126
+ // All of them, not the top few: there are at most six by definition, and the one left off the end
127
+ // of a truncated list was the one that made the cycle recognisable.
128
+ const listed = churn.actions
129
+ .map(action => `${action.what.replace('::', ' ')} (${action.times}×)`)
130
+ .join('; ');
131
+ return `Your last ${window} tool calls were ${churn.distinct} distinct actions repeated, and `
132
+ + `nothing has been written to disk in that time: ${listed}. Restarting something and checking `
133
+ + `it again is not progress. Use what the last attempt told you, try a different approach, or `
134
+ + `say plainly what is blocking you — the answer is not going to arrive on the next round.`;
135
+ }
136
+ //# sourceMappingURL=cycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cycle.js","sourceRoot":"","sources":["../src/cycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAWH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,EAAE,CAAC;AAEzB;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAEhC,iGAAiG;AACjG,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC;IACrB,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW;CACzE,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;SACrC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE;SACN,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChB,OAAO,GAAG,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;AACnC,CAAC;AAWD;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CACvB,OAA0B,EAC1B,MAAM,GAAG,MAAM,EACf,WAAW,GAAG,cAAc;IAE5B,IAAI,OAAO,CAAC,MAAM,GAAG,MAAM;QAAE,OAAO,IAAI,CAAC;IACzC,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC;IAEtC;;;;;;OAMG;IACH,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAE/F,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,GAAG,WAAW;QAAE,OAAO,IAAI,CAAC;IAE3C,MAAM,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;SAClC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;SACzC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACrE,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,IAAI;QACrB,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,IAAI;QAC7B,OAAO;KACR,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,KAAY,EAAE,MAAM,GAAG,MAAM;IACzD,kGAAkG;IAClG,oEAAoE;IACpE,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO;SACzB,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,MAAM,CAAC,KAAK,IAAI,CAAC;SACrE,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,OAAO,aAAa,MAAM,oBAAoB,KAAK,CAAC,QAAQ,kCAAkC;UAC1F,kDAAkD,MAAM,sCAAsC;UAC9F,6FAA6F;UAC7F,yFAAyF,CAAC;AAChG,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
package/dist/engine.js CHANGED
@@ -10,6 +10,7 @@ import { hasWorkspaceOwnedGitRepo, gitScope, createWorktree, mergeWorktree, pres
10
10
  import { resolveProvider, getApiKey, keyProvenance, servesLocally, servesOwnWeights, isPrivateHost } from './providers.js';
11
11
  import { wireEffort, looksLikeEffortUnsupported } from './pace.js';
12
12
  import { triggerFor, pickNext, describeSwitch, askSwitch, keyOf as failoverKey } from './failover.js';
13
+ import { findChurn, describeChurn, WINDOW as CHURN_WINDOW } from './cycle.js';
13
14
  import { claim, release, describeClaims } from './claims.js';
14
15
  import { handoffFrom, describeHandoff, worthSaying } from './handoff.js';
15
16
  import { readAll as readLedger, relevant as relevantFindings, withStaleness, describeFindings, deriveFindings, record as recordFindings, changedSince, currentSha } from './ledger.js';
@@ -1026,6 +1027,16 @@ detectedContext = { current: 0 }) {
1026
1027
  * no files, which is exactly why running it sixty-four times is a loop.
1027
1028
  */
1028
1029
  const commandRuns = new Map();
1030
+ /*
1031
+ * A rolling record of what has been called, for noticing a cycle rather than a repeat.
1032
+ *
1033
+ * The identical-command guard catches "the same thing twelve times". It cannot catch kill, start,
1034
+ * wait, curl, read, kill — sixty-four times round, no call repeated back to back. This holds
1035
+ * enough history to see that shape, and nothing more: it is trimmed to the window it is read over.
1036
+ */
1037
+ const callHistory = [];
1038
+ /** Whether the model has already been told it is circling, so it is said once and not per turn. */
1039
+ let churnMentioned = false;
1029
1040
  /**
1030
1041
  * What each call returned, so a repeat can be answered rather than refused.
1031
1042
  *
@@ -1979,6 +1990,34 @@ detectedContext = { current: 0 }) {
1979
1990
  tui.printWarning(`${config.model} repeated itself ${looping.count} times — cut short.`);
1980
1991
  config.onLooping?.(looping.count);
1981
1992
  }
1993
+ /*
1994
+ * Circling, as opposed to repeating: said once, and never used to end the run.
1995
+ *
1996
+ * Calibrated against the real sessions on this machine rather than chosen. In any window with no
1997
+ * file writes, the fewest distinct actions were: five for a long session that finished properly,
1998
+ * one and two for two known loops. The separation is thin — the finished session's worst
1999
+ * twelve-call window was *more* repetitive than the runaway's cycle phase — and the entire
2000
+ * legitimate sample is one session.
2001
+ *
2002
+ * A signal that thin can identify something worth mentioning. It cannot justify ending somebody's
2003
+ * work, because the cost of being wrong is a run killed mid-task. So this talks and the
2004
+ * identical-command guard stops, which is the right division: that one's evidence is
2005
+ * unambiguous, and this one's is a judgement call made on four sessions.
2006
+ */
2007
+ if (!churnMentioned) {
2008
+ const circling = findChurn(callHistory);
2009
+ if (circling) {
2010
+ churnMentioned = true;
2011
+ const said = describeChurn(circling);
2012
+ messages.push({ role: 'system', content: said });
2013
+ trajectory?.add({ t: 'stop', at: Date.now(),
2014
+ reason: `noticed ${circling.distinct} actions repeated over ${CHURN_WINDOW} calls with `
2015
+ + `nothing written` });
2016
+ config.onCircling?.(said, circling);
2017
+ if (!silent)
2018
+ tui.printWarning(said);
2019
+ }
2020
+ }
1982
2021
  if (toolCalls.length === 0) {
1983
2022
  // A model may narrate its next step ("Now let me read X…") without attaching the
1984
2023
  // tool call, which used to end the run mid-investigation. When the text clearly
@@ -2145,6 +2184,18 @@ detectedContext = { current: 0 }) {
2145
2184
  });
2146
2185
  for (const tc of toolCalls) {
2147
2186
  const sig = tc.name + '::' + tc.arguments;
2187
+ /*
2188
+ * Recorded on attempt, not on execution.
2189
+ *
2190
+ * The first wiring pushed to this record after a call ran, so every call a guard suppressed
2191
+ * was invisible to the cycle check — and the cycle check needs exactly those. Measured: a
2192
+ * six-step cycle had eighteen calls executed out of thirty-seven attempted, so a window of
2193
+ * twenty never filled and the check never ran once. What is being judged is what the model
2194
+ * keeps trying, which is the thing it attempted.
2195
+ */
2196
+ callHistory.push({ name: tc.name, detail: toolDetailFor(tc.name, tc.arguments) });
2197
+ while (callHistory.length > CHURN_WINDOW * 2)
2198
+ callHistory.shift();
2148
2199
  /*
2149
2200
  * The same command, again, with nothing changed since.
2150
2201
  *
@@ -2555,6 +2606,18 @@ detectedContext = { current: 0 }) {
2555
2606
  detail: toolDetailFor(effectiveName, tc.arguments),
2556
2607
  resultChars: result.length,
2557
2608
  });
2609
+ /*
2610
+ * What this call changed, added to the record made when it was attempted.
2611
+ *
2612
+ * The change list is what makes the cycle check safe to act on: a window containing a write is
2613
+ * progress whatever shape the calls make, so iterating on a file can never be mistaken for
2614
+ * going round in circles.
2615
+ */
2616
+ if (changedByCommand && changedByCommand.length > 0) {
2617
+ const last = callHistory[callHistory.length - 1];
2618
+ if (last && last.name === effectiveName)
2619
+ last.changed = changedByCommand;
2620
+ }
2558
2621
  resultsBySignature.set(sig, spilled.text.slice(0, 20_000));
2559
2622
  messages.push({ role: 'tool', tool_call_id: tc.id, content: spilled.text });
2560
2623
  }