/** * Going round in circles, as opposed to repeating one call. * * The identical-command guard catches "the same thing twelve times". It does not catch the shape * that cost 20.9 million tokens: kill the server, start the server, wait and read the log, curl the * endpoint, read the log, kill the server — sixty-four times round, every individual call * reasonable, none repeated back to back, and `lsof` and a one-off `node -e` probe spliced in at * irregular intervals so there is no fixed period to match. * * ## What the data actually supports * * This was calibrated against the real sessions on the machine, not chosen. For each, the fewest * distinct actions found in any window containing no file writes: * * session W=12 W=20 W=30 * finished properly (89 calls) 5 8 11 * the runaway (290 calls) 1 5 8 * a second loop (49 calls) 2 2 10 * a third loop (20 calls) 1 7 – * * The honest reading is that the separation is thin. A long session that finished properly is * genuinely repetitive — its worst twelve-call window held five distinct actions and seven repeats, * which is *more* repetitive than the runaway's cycle phase, where six distinct actions accounted * for twelve calls. No window size separates them cleanly, and the entire legitimate sample is one * session. * * ## So this warns and never stops * * A signal this thin can identify something worth mentioning. It cannot justify ending somebody's * work: the cost of being wrong is a run killed mid-task, and one legitimate session is not evidence * enough to risk that. The identical-command guard stops runs because its evidence is unambiguous — * the same command, unchanged, with nothing altered between. This one talks. * * Two conditions, both required. No file may have been written in the window, because iterating on a * file is repetitive by nature and is the most valuable thing an agent does. And the window is * twenty calls rather than twelve, where the numbers above leave a real gap: five and two for the * loops against eight for the run that finished. */ /** One tool call, as much of it as this needs. */ export interface Called { name: string; /** The one-line detail: a path, a command, a url. */ detail: string; /** Files this call changed, when that is known. Empty or absent means it changed nothing. */ changed?: readonly string[] | undefined; } /** * How many recent calls are considered. * * Twenty, from the table above: at twelve the loops and the legitimate run overlap, and at thirty * the legitimate run's own figure falls to eleven while a known loop sits at ten. */ export declare const WINDOW = 20; /** * How few distinct actions count as circling. * * Six. The loops measured five and two; the session that finished measured eight. Six leaves a * margin on both sides of a boundary drawn from one legitimate sample, which is the most that sample * can honestly carry. */ export declare const CHURN_DISTINCT = 6; /** * A signature: the tool and what it was pointed at, tidied and nothing more. * * The first version stripped digits and long hex runs, reasoning that the same loop reappears with a * different port, a fresh pid or a regenerated key. Two things settled that. * * It caught a false positive immediately: `grep pattern-1` through `grep pattern-8` collapsed into * one action, so reading or searching eight numbered things — which is progress — read as a cycle of * one. And it bought nothing. Measured across the real sessions, the fewest distinct actions in a * no-write window came out identical with the stripping and without it: 8 for the run that finished, * 5 and 7 and 2 for the loops. The variation it was written for does not appear in any session on * the machine, while the harm it caused appeared in the first test. * * So it does what the evidence supports and stops. If a loop that varies a token every round turns * up later, it can be handled then, with that loop to look at. */ export declare function signatureOf(call: Called): string; export interface Churn { /** How many distinct actions accounted for the window. */ distinct: number; /** How many calls were second-or-later occurrences of something already in the window. */ repeats: number; /** The actions themselves, most-repeated first, for saying what is being circled. */ actions: Array<{ what: string; times: number; }>; } /** * Whether the last stretch of calls is circling, or null when it is not. * * Null while there is not yet a full window: a judgement about whether somebody is going in circles * cannot be made from four calls, and guessing early is how a guard becomes something people learn * to ignore. */ export declare function findChurn(history: readonly Called[], window?: number, maxDistinct?: number): Churn | null; /** * What to tell the model, naming what it is circling. * * The actions are quoted because a model that has lost its place is usually corrected by seeing the * list: "you have been doing these four things" is actionable in a way that "you appear to be * looping" is not. And it says what to do instead, because a complaint without an alternative is * just a complaint. */ export declare function describeChurn(churn: Churn, window?: number): string; //# sourceMappingURL=cycle.d.ts.map