import type { MemoryStore } from '../types.js'; import type { MessageData } from '../../types/messages.js'; import type { Model } from '../../models/model.js'; import type { Tracer } from '../../telemetry/tracer.js'; import type { ResolvedExtractionConfig } from './resolve-extraction-config.js'; /** * A store paired with its fully-resolved extraction config. * @internal */ export interface ExtractionBinding { /** The memory store to extract into. */ store: MemoryStore; /** The store's fully-resolved extraction config (triggers, extractor, filter). */ config: ResolvedExtractionConfig; } /** * Number of consecutive save failures after which a store backs off (stops trying every turn). * @internal */ export declare const SAVE_FAILURES_BEFORE_BACKOFF = 10; /** * While backed off, a store retries only once every this many save attempts (a probe). * @internal */ export declare const BACKOFF_PROBE_INTERVAL = 3; /** * Saves conversation messages to memory stores, in the background, without slowing the agent down. * * How it works, in three pieces: * * 1. **The buffer.** Every message the agent produces is copied into one shared list (`_pending`). * Each gets a number (`seq`) that only ever counts up, so we can tell which messages are newer. * We keep our own copy here (rather than reading the agent's live message list) because the agent * can delete old messages to stay within its context window; our copy means we never lose one * before it's saved. * * 2. **Per-store progress.** Each store can save at its own pace, so we remember, per store, the * `seq` of the last message it has already saved (`_marks`). When a store saves, it only looks at * messages newer than that number, so the same message is never saved twice to the same store. * * 3. **One save at a time per store.** A store might be asked to save again while a previous save is * still running. We chain each store's saves one after another (`_chains`) so they can't overlap * or run out of order. * * If a store fails to save {@link SAVE_FAILURES_BEFORE_BACKOFF} times in a row, it backs off: instead * of trying every turn, it retries only once every {@link BACKOFF_PROBE_INTERVAL} attempts (a probe). * A successful probe clears the failure streak and resumes normal saving - so a transient outage * recovers on its own and the messages buffered during it are saved once the store comes back. A * permanently broken store keeps probing and logs an error each time, surfacing the misconfiguration. * * Saving itself either runs the store's extractor to pull out facts, or hands the raw messages to the * store - see {@link _write}. * @internal */ export declare class ExtractionCoordinator { private readonly _stores; /** Per store: its resolved extraction config (triggers, extractor, filter). */ private readonly _storeToExtractionConfig; private readonly _defaultModel; private readonly _tracer; /** The shared list of messages waiting to be saved, oldest first. Each is tagged with its `seq`. */ private _pending; /** The number to give the next message added to the buffer. Only ever increases. */ private _nextSeq; /** Per store: the `seq` of the last message that store has already saved. Starts at -1 (saved none). */ private readonly _marks; /** Per store: a promise for that store's currently-running save, so the next save waits its turn. */ private readonly _chains; /** Per store: how many saves have failed in a row. Reset to 0 on a successful save. */ private readonly _consecutiveFailures; /** Per store: while backed off, counts save requests so we can let every Nth through as a probe. */ private readonly _backoffCounters; /** * @param stores - The extraction-configured stores this coordinator manages, each with its resolved config * @param defaultModel - The agent's model, passed to extractors that don't configure their own * @param tracer - Tracer for extraction spans, shared with the {@link MemoryManager} */ constructor(stores: ExtractionBinding[], defaultModel: Model, tracer: Tracer); /** Adds a message to the buffer. */ record(message: MessageData): void; /** * Saves this store's unsaved messages, in the background. Queued behind the store's previous save so * two never run at once. Failures are logged and swallowed - saving must never break the agent loop. * The returned promise is for {@link flush}; callers (triggers) ignore it so they don't block. */ process(store: MemoryStore): Promise; /** Queues a save for the store behind its previous one, so saves never overlap or reorder. */ private _enqueue; /** * Whether to attempt a save now. A healthy store always attempts. A backed-off store (too many * failures in a row) attempts only once every {@link BACKOFF_PROBE_INTERVAL} requests - a probe to * see if it has recovered - and skips the rest. */ private _shouldAttempt; /** * Saves every store's remaining messages and waits for all saves to finish. Call this once at a * boundary you control - typically app shutdown - to make sure nothing in the buffer is lost. * * It first tells every store to save (even one whose trigger hasn't fired this turn, or one that is * backed off - flush bypasses backoff so a recovered store still writes its backlog); stores with * nothing to save do nothing. Then it waits, re-checking until no new save has started, so saves * that begin while we're waiting are also covered. */ flush(): Promise; private _extract; /** * Handles a failed save. Puts the mark back so the messages retry next time. Once a store has failed * {@link SAVE_FAILURES_BEFORE_BACKOFF} times in a row it logs an error and enters backoff (it then * retries only on probes - see {@link _shouldAttempt}); before that it logs a warning. The messages * stay buffered either way, so a store that recovers saves them. */ private _onSaveFailed; /** * Saves the messages to the store, one of two ways: * - store has an extractor: run it to pull out facts, then write each fact via `add`. * - no extractor: hand the raw messages to `addMessages` so the store keeps their roles. * * Fact writes run in parallel. If any fails we throw, which makes the caller retry the whole batch * next time - so a fact that already saved may be written again (stores should expect duplicates). * * @returns The number of entries written (extracted facts, or raw messages). */ private _write; /** * Removes messages from the buffer once every store has saved them (so none still needs them). * * A store that hasn't saved a message yet - because its trigger hasn't fired, or it's failing and * waiting to recover - keeps that message buffered. So a store stuck failing for good slowly grows * the buffer; that surfaces as repeated error logs and is bounded by the (non-persisted) session. */ private _trim; } //# sourceMappingURL=coordinator.d.ts.map