/** * Failed turns of the in-process Crewly Agent runtime (crewly#1015 §2). * * A turn of the in-process runtime that throws (DeepSeek "No output * generated", an out-of-credit account, a crashed worker) used to be logged * and nothing else: the message was gone, nobody was told. On 2026-10-01 the * orchestrator failed about 80 turns that way over ~20 hours. * * Now: * 1. The same message is delivered once more after RETRY_DELAY_MS (through * `sendMessageToAgent`, so a runtime switch, the token cap or the * restart drain can queue it instead). * Not when the account is out of credit / quota (it fails the same way), * and not when the agent is stopped or not running (the message is * queued for its next start instead — that is no model failure). * 2. When that fails too, the owner messages the agent owes are parked by the * owner-message watchdog (owner told once, message kept and re-delivered * on a backing-off timer, never on a timer for credit / quota), and the * failure is reported ONCE per episode — for a member to the * orchestrator, for the orchestrator to the owner — until a turn succeeds. * 3. The agent's next successful turn re-delivers the parked owner messages. * * Harness text is English. The service never throws into its callers. * * @module services/agent/in-process-turn-failure.service * @see specs/2026-10-03-harness-drop-gaps.md §2 */ /** Outcome of a re-delivery (the shape `sendMessageToAgent` returns). */ export interface TurnRedeliveryResult { success: boolean; queued?: boolean; error?: string; } /** Injected behaviour. */ export interface InProcessTurnFailureDeps { /** Deliver the message to the agent again */ redeliver: (sessionName: string, message: string) => Promise; /** * Park the owner messages the agent owes and tell the owner (watchdog * `noteTurnFailed`); `needsCredit` stops timed re-deliveries. */ noteOwnerMessages?: (sessionName: string, detail: string, opts: { needsCredit: boolean; }) => Promise; /** Re-deliver parked owner messages (watchdog `resumeAfterRecovery`) */ resumeOwnerMessages?: (sessionName: string) => Promise; /** * Report the failure: a member's to the orchestrator, the orchestrator's * to the owner. `sample` is the message that failed (its origin header * may name the chat it came from). */ report: (sessionName: string, text: string, sample: string) => void; /** The owner stopped this agent (its messages wait for its next start) */ isOwnerStopped?: (sessionName: string) => boolean; /** Whether the agent's runtime is up (false: stopped / not initialised) */ isRunning?: (sessionName: string) => boolean; /** Hold a message for the agent's next start (persistent queue) */ queueForAgent?: (sessionName: string, message: string) => void; /** * Whether the failed turn already answered where the message came from * (an owner answer seen there since `since`); the retry is skipped then. */ answeredSince?: (sessionName: string, message: string, since: number) => boolean; /** Display name ("Orc", "Ella"); defaults to the session name */ displayName?: (sessionName: string) => string; /** Clock (tests) */ now?: () => number; /** Timer (tests) */ setTimer?: (fn: () => void, ms: number) => unknown; } /** An error the in-process runtime rejected a turn with. */ export type TurnError = unknown; /** * Whether a turn error means the account is out of credit / quota: nothing * helps until the owner tops up or the agent moves to another runtime. * * @param error - What the turn rejected with * @returns True for `billing` / `usage_limit` */ export declare function isCreditFailure(error: TurnError): boolean; /** * The reason of a failed turn in plain words, for the owner and the orchestrator. * * @param error - What the turn rejected with (`usageLimitKind` is set by the * external runtime when a usage-limit rule matched its stderr) * @returns A short phrase * * @example * ```typescript * describeTurnError(Object.assign(new Error('No output generated.'), { usageLimitKind: 'billing' })); * // 'the model account is out of credit' * ``` */ export declare function describeTurnError(error: TurnError): string; /** * Retries and reports failed in-process turns. * * One report per failure EPISODE: from the first reported failure until the * agent completes a turn again, later failures are counted, not reported * (crewly#1015 review B2: ~40 notices a day while credit was out). */ export declare class InProcessTurnFailureService { private readonly deps; private readonly logger; /** `\0` → retries made and when the first failure was */ private readonly attempts; /** Agents in a reported failure episode → failures since the report */ private readonly failing; /** * @param deps - Injected behaviour */ constructor(deps: InProcessTurnFailureDeps); private now; /** * A turn threw. Retry the message once (unless it cannot help); after * that, report — once per failure episode. * * @param sessionName - The agent * @param message - The message it was handling * @param error - What the turn rejected with */ onTurnFailed(sessionName: string, message: string, error: TurnError): void; /** * A turn completed: the failure episode is over. The parked owner * messages are re-delivered. * * @param sessionName - The agent */ onTurnSucceeded(sessionName: string): void; private agentIsDown; private hold; private retry; private giveUp; private prune; } /** @returns The wired service, or null before boot (and in tests) */ export declare function getInProcessTurnFailureService(): InProcessTurnFailureService | null; /** @param service - The service to expose (null to clear) */ export declare function setInProcessTurnFailureService(service: InProcessTurnFailureService | null): void; //# sourceMappingURL=in-process-turn-failure.service.d.ts.map