import type { SessionEvent } from '../../../types/session/index.js'; import type { IterationContext } from './phases/index.js'; /** * How long a finishing run waits for a background worker it launched. * * Derived from the turn rather than fixed, because a constant is wrong in both * directions at once. The 120 seconds this replaces held a turn configured for * a twenty-second timeout open for 120,267 ms — six times its own budget, and * unreachable by the guard, which only checks between iterations — while on an * hour-long run it abandoned workers measured at 4m21s, 5m58s and 8m04s, all * of them well inside the hour the delegation tools themselves declare. * * **Bounded by construction, and against the right boundary.** The input is * time-to-FINALIZE, not time-to-deadline (see * `GuardCoordinator.remainingBeforeFinalizeMs`). Measuring to the deadline was * the first attempt and it was wrong in a way that looked safe: a hold cannot * outlive the deadline either way, but half of the time-to-deadline started * just under the warning threshold ends at 95% of the budget — so the slice * that exists for the turn to produce a closing answer is half spent waiting * for the result that answer was supposed to use. Against the finalize point * the hold cannot reach the reserve at all, which is what makes the guard's * inability to interrupt a hold a non-issue rather than a smaller issue. * * **The floor of zero is a decision, not a clamp artefact.** A turn with no * time left before it must start finishing has no turn in which to read a * notification, so waiting could only delay a stop that is already due. * Nothing is lost by it: `CompletionInbox.waitForArrival` returns before it * looks at its timer when a completion is already in hand, so a zero grace * still delivers everything that has arrived. No minimum is invented on top, * because zero is exactly what a turn past the threshold should wait — and * reading the remainder at hold time rather than trusting `forceFinalize`, * which is sampled at the top of the iteration, is what makes a long iteration * that crossed the line in between compute it. * * **The ceiling is the longest anything in this subsystem waits for a * delegated worker.** It binds only for a host whose turn timeout exceeds * roughly two and a quarter hours; below that the fraction is smaller. */ export declare function settleGraceMs(remainingBeforeFinalizeMs: number): number; /** * The same share of the turn, under {@link DEFAULT_JOB_HOLD_MAX_MS}. * * `NAMZU_JOB_HOLD_MAX_MS` overrides the ceiling for a host that wants a * longer or shorter park, the way `NAMZU_JOB_WAIT_TIMEOUT_MS` overrides * `wait_for_job`'s own bound — and it is the same parse, so a value that is * not a positive whole number of milliseconds leaves the default standing * rather than holding a turn for `NaN`. Called here rather than at module * load, because a host that sets it after import is not ignored. */ export declare function awaitedJobGraceMs(remainingBeforeFinalizeMs: number): number; /** * Hold the turn open for work that has not finished, and deliver it. * * Returns whether a completion, a job exit or an operator message entered * the transcript — the caller continues on `true`, so the model gets a turn * to respond. That turn is the entire justification for waiting, which * is why only the exits that can still take one call this. * * Two kinds of work qualify and they are raced together, because a turn has * one settle point and one grace period to spend at it: * * - a delegated task the `CompletionInbox` is still expecting; * - a background job the model told `wait_for_job` it is waiting on. * * The job half is deliberately narrow. Intent comes from the wait and from * nothing else — a dev server the model started and never waited on is * running because somebody wanted it running, and a hold for it would add * the grace period to the end of every turn for the rest of the session. * * Each leg is opened only when it has something pending: both * `waitForArrival` implementations resolve immediately when their own side * is idle, so racing an idle one would end the hold before it began. * * Bounded by `settleGraceMs` and by `maxIterations`, so work that never * finishes cannot keep the turn open. On a turn with a deadline the grace is * a share of what is LEFT of it rather than a fresh allowance, so a * `wait_for_job` call that already spent minutes has shortened this hold * by the same minutes. On a turn without one — the CLI's default — there is * no remainder to take a share of, and the job leg's own ceiling * (`awaitedJobGraceMs`) is what keeps a timed-out wait from being followed * by an hour of silence. */ export declare function holdForOutstandingWork(ctx: IterationContext, iterationNum: number, hasToolCalls: boolean, deliverInbound: () => number): AsyncGenerator; /** * Put the job exits this hold was waiting for in front of the model. * * Through `jobNotices`, which is the channel a job exit already travels on * — `attachNotice` rides it out on the next tool result — rather than a * second one built for this path. A turn that called no tools has no such * result, so the queued text becomes a `runtime-context` message instead, * exactly as `deliverInbound` does for steering that found no tool result * to attach to. * * That drain is also what keeps one exit from being delivered twice: the * channel hands its text over once, so an exit already attached to a tool * result earlier in the turn leaves nothing here — and the record of it * went with that delivery, so this returns `false` rather than buying a * turn to re-read what the model has read. * * `takeDelivery` is what pairs the two. Taking the exits first and then * finding no notice would discard them, which is the one way this path * can lose an exit outright; neither is taken unless both are there. * * The channel is not per-job, so the text taken here can include a notice * for a job nobody awaited that ended while the hold was open. Delivering * it is right — it is unread either way, and the alternative is stranding * it — but it is not a reason to WAIT, which is why what opens this hold * is `AwaitedJobs`, and the two are asked separately. */ export declare function deliverAwaitedJobExits(ctx: IterationContext): boolean; /** * Account for outstanding work on the way out: deliver what arrived, and * say what did not. * * A turn that ends with a worker outstanding must not leave the impression * that the worker's result was delivered. There are exactly two honest * outcomes and this does both: * * - **What has already arrived is delivered.** It makes no false claim, * and dropping it is pure loss — the message rides out on * `Turn.messages`, so a host reads it and the next turn of a continued * thread starts with it. This does NOT wait: a hold buys the model a * turn in which to USE a result, and on an exit whose answer is already * decided there is no such turn, so waiting would delay a settled answer * to append text this turn will not read. The bounded hold stays where it * was, on the exits that do have a turn left. * - **What is still running is NAMED, not cancelled.** Giving up on a wait * is a statement about the waiter, not about the work — the rule * `wait-with-idle-bound.ts` already states for the same subsystem — and * "the parent answered early" is a weaker warrant for killing a child * than "the clock ran out", not a stronger one. Killing a worker that * may be mid-write is a policy only the host can judge, and it has * `cancel_task` and the turn controller to judge it with. */ export declare function settleOutstandingWork(ctx: IterationContext): void; /** Work this turn walked away from. See {@link settleOutstandingWork}. */ export declare function recordAbandonedWork(ctx: IterationContext): void; export declare function deliverArrivedCompletions(ctx: IterationContext): void; /** * The job half of {@link deliverArrivedCompletions}: an exit that arrived * too late to earn a turn is still delivered on the way out. * * The window this closes is one tick wide and it is nobody else's. An * awaited job that exits between the hold's grace expiring and the turn * settling was never delivered — the hold had already looked — and is no * longer named either, because the exit took it off the outstanding list * on its way past, so `abandonedJobIds` would be lying to claim it. The * host's own listener is no help: the CLI queues an exit for the next * turn only when no turn is in flight, and this one is still in flight. * Delivered here it reaches `Turn.messages`, so the transcript has it and * a continued thread opens with it. * * Before `recordAbandonedWork`, which then reports only what is still * running, and after `deliverArrivedCompletions`, so the two appended * messages land in the order the work finished in. */ export declare function deliverArrivedJobExits(ctx: IterationContext): void; //# sourceMappingURL=outstanding-work.d.ts.map