/**
 * eventTail — a bounded, honest copy of an event stream.
 *
 * Pattern: ring buffer with an explicit drop counter.
 * Role:    the ONE place the library decides how much of a run's event
 *          stream it keeps in memory, and how it tells a reader that it
 *          kept less than everything.
 *
 * Two features hold a tail of the same stream for the same reason — a
 * recording (`recordRun`) and the self-explaining agent's evidence
 * (`SelfExplainBinding`) — and both need the same two properties:
 *
 *   1. BOUNDED. A long-running server must not grow an array per run
 *      forever. Past the cap the OLDEST events go, because the end of a
 *      turn is the part a reader asks about.
 *   2. HONEST. A tail that dropped events says how many. Silently
 *      starting a timeline mid-run is the failure this exists to
 *      prevent — a consumer would read the remainder as the whole run.
 *
 * Written once here rather than twice, because the drift that matters is
 * not the ring buffer (easy) — it is the two features disagreeing about
 * what "dropped" means and one of them forgetting to report it.
 *
 * ── A REPEAT HOLDS NO SLOT ──────────────────────────────────────────────
 * Every model call re-announces what is in its context: each slot's pieces
 * fire `agentfootprint.context.injected` again on every iteration — by
 * design, because that is the per-call record of what the model was shown
 * (`recorders/core/ContextRecorder.ts`, and the answer account reads it as
 * its witness). Counted like any other event, those repeats grow with the
 * square of the iteration count and evicted the START of long runs:
 * measured, a 100-iteration agent run fired ~11,400 events into the default
 * 10,000-event cap and its recording opened at iteration ~33.
 *
 * So the cap counts DISTINCT events. An announcement whose payload is
 * identical, field for field, to one the tail still holds is a REPEAT: it is
 * kept — the stream stays whole and in order — but it holds no slot, and it
 * is stored with the held announcement's payload object (identical content,
 * one copy). What a repeat costs is its envelope. Identity is decided on the
 * full payload (`samePayload`), never on the 32-bit `contentHash`, which only
 * narrows the candidates: two different pieces are never merged. Deciding
 * reads the payload once, `rawContent` included — the same order of work as
 * the content hash the slot already computed to announce it.
 *
 * Eviction is unchanged in kind: the oldest events go first, repeats with
 * them, so the retained events are always ONE contiguous suffix of the
 * stream and `firstRetainedIndex` still says where it starts.
 *
 * The tail stays BOUNDED all the same. A repeat holds no slot, but it is still
 * an envelope in memory (its own type and meta beside the shared payload), so
 * the whole tail — slots and repeats together — is capped at
 * {@link REPEAT_ALLOWANCE} times the cap: 100,000 events at the default. A
 * 100-iteration run (~11,500 events, ~10,000 of them repeats) keeps every one;
 * a run long enough to cross that loses its oldest events, reported as
 * dropped, exactly as before this rule.
 *
 * Internal: not exported from any barrel. Both consumers wrap it in their
 * own surface (`RunRecorder.droppedEvents`, the toolpack's ⚠ marker).
 */
import type { AgentfootprintEvent } from './registry.js';
/**
 * Default retained-event cap — a few hundred KB for a typical turn, and
 * far more than any single turn fires.
 */
export declare const DEFAULT_MAX_EVENTS = 10000;
/** A frozen read of the tail: the events kept, and the count of those not. */
export interface EventTailSnapshot {
    /** A fresh copy — the tail may keep growing behind the caller's back. */
    readonly events: readonly AgentfootprintEvent[];
    /** How many events were discarded to stay under the cap. `0` normally. */
    readonly dropped: number;
    /**
     * The ORIGINAL stream position of `events[0]` (9.60.0) — the retained
     * window is `[firstRetainedIndex, firstRetainedIndex + events.length)`
     * of the stream as fired, so `events[i]` was stream event
     * `firstRetainedIndex + i`.
     *
     * Under oldest-first eviction from one stream this is always equal to
     * `dropped` — stated as its own named field because a drop COUNT alone
     * cannot say WHICH range is gone: a reader aligning this tail against
     * another record of the same run (a commit log, a second recording)
     * needs the offset, not just the loss. Before this field, an archived
     * envelope could say "312 events dropped" and nothing could say where
     * the retained window starts.
     */
    readonly firstRetainedIndex: number;
}
/** A bounded tail of one event stream. */
export interface EventTail {
    /** Append one event, dropping the oldest if the cap is reached. A repeated
     *  announcement is kept without taking a slot — see the module header. */
    push(event: AgentfootprintEvent): void;
    /** How many events are currently retained. */
    readonly count: number;
    /** How many were discarded to stay under the cap. `0` on a normal turn. */
    readonly dropped: number;
    /** Original stream position of the oldest retained event — see
     *  {@link EventTailSnapshot.firstRetainedIndex}. */
    readonly firstRetainedIndex: number;
    /** Freeze the tail — a fresh array plus the drop count beside it. */
    snapshot(): EventTailSnapshot;
}
/**
 * Start a bounded tail.
 *
 * @param maxEvents cap on retained events (default {@link DEFAULT_MAX_EVENTS}).
 *                  Non-finite or non-positive values fall back to the default
 *                  rather than producing a tail that keeps nothing.
 */
export declare function eventTail(maxEvents?: number): EventTail;
/**
 * How many events — slots and repeats together — the tail holds per slot of
 * its cap: the bound on memory a repeat's envelope still costs. See the module
 * header.
 */
export declare const REPEAT_ALLOWANCE = 10;
