export { StoredSession } from "../internal/harness.js";
export type { Session } from "../internal/harness.js";
export { InMemorySessionRepo, InMemorySessionStorage, uuidv7, validateEntriesForImport, StreamingImportValidator, } from "../internal/harness.js";
export { BaseSessionStorage, leafIdAfterEntry, } from "../internal/harness.js";
export type { SessionStorage, SessionRepo, SessionMetadata, SessionTreeEntry, } from "../internal/harness.js";
export { SessionError } from "../internal/harness.js";
export type { SessionWriteOptions } from "../internal/harness.js";
import type { Session } from "../internal/harness.js";
import type { SessionTreeEntry } from "../internal/harness.js";
export type { SessionPlacementRecord } from "../internal/harness.js";
/** True when `err` is a session optimistic-lock conflict (a concurrent writer won the branch leaf). Both
 *  halves read what a HOST `SessionStorage` threw, so both are guarded — unreadable is not a conflict,
 *  never a raise inside the classify-and-rethrow guard. The class test is KEPT: widening the verdict to
 *  any value carrying the word would arm the CAS retry loop on a foreign error (KL-942b). */
export declare function isSessionConflict(err: unknown): boolean;
/**
 * Subagent transcript persistence — the ACQUIRE-form placement declaration a trusted spawner passes
 * when it creates a subagent's transcript session: the session is created INTO the store's subagent
 * partition instead of the host lane. The minter reports the fact; the store never guesses residency
 * from call shape. `placedAt` is stamped by the store at creation (see
 * {@link SessionPlacementRecord}). Reaches stores ONLY through the engine's trusted internals chain
 * (never a public TaskSpec key — a public key would let any caller push arbitrary sessions into the
 * partition and poison store-side retention/enumeration).
 */
export interface SessionPlacement {
    kind: "subagent";
    /** Tenant key, same source as the durable agent row's `scope`. */
    scope?: string;
    /** The spawning host session (retrieval/partition metadata; authorization stays on the row predicate). */
    parentSessionId?: string;
    /** The delegation tree's root host session (fixed point). */
    rootSessionId?: string;
    /** The durable agent row key (`a…`) — the store-side join key for orphan GC. */
    handle?: string;
}
/**
 * One row of {@link SessionStore.listPlaced}. The row-store join key is the FULL `(scope, handle)`
 * tuple — a listing that cannot supply both returns the discriminated `tupleIncomplete` variant
 * instead (consumers fail closed on it: never age-reaped, reported honestly).
 */
export type PlacedSessionRow = {
    sessionId: string;
    placedAt: number;
} & ({
    scope: string;
    handle: string;
} | {
    tupleIncomplete: true;
});
/** A session handed back by a {@link SessionStore}: the harness `Session` plus its resolved id. */
export interface AcquiredSession {
    session: Session;
    sessionId: string;
}
/**
 * A lightweight, read-only projection of one session for a `/resume` list (shell K-5 — re-attach without an
 * N+1 walk). Returned in bulk by {@link SessionStore.list}, newest-first by `lastActiveAt` by convention.
 * `lastTaskId` is the engine's term for what the service/wire surfaces as the session's **`lastRunId`** — the
 * most recent task run on this session, so a shell re-attaches in ONE hop.
 *
 * Named `*StoreSummary` (not the bare `SessionSummary` an earlier version used) because that name collided
 * with an unrelated, differently-shaped `SessionSummary` downstream (the SDK's wire GET /v1/sessions row) —
 * same name, two shapes, the exact "同名异形" pattern a cross-repo cleanup was independently eradicating
 * elsewhere. This type is this store interface's own projection, never itself
 * serialized onto the wire — a service backend re-projects/renames its fields (`lastTaskId` → `lastRunId`)
 * when building the actual wire response.
 */
export interface SessionStoreSummary {
    /** The session id. */
    sessionId: string;
    /** When the session was created ({@link SessionMetadata.createdAt}, ISO string); absent when the backend's
     *  cache view doesn't carry it. */
    createdAt?: string;
    /** Epoch ms the session was last acquired / touched / ran — the newest-first sort key for "recent sessions". */
    lastActiveAt: number;
    /** The most recent task run on this session (core's `taskId`; the service projects it as `lastRunId`).
     *  Absent until a task has run on the session (recorded via {@link SessionStore.noteTaskRun}). */
    lastTaskId?: string;
    /**
     * An ENGINE-minted run identity for this session ({@link import("./types.js").TaskResult.runId}),
     * recorded by the most recent {@link SessionStore.noteTaskRun} write to LAND.
     * {@link lastTaskId} above is `spec.taskId ?? sessionId`, so on the ordinary deployment that supplies no
     * host task id it answers the SESSION's own id: a "last run" handle that names the session it hangs off
     * cannot say WHICH run it means. This one names a specific run — read the ORDERING clause below for
     * exactly how strong "most recent" is here, which is weaker than the sibling field's wording implies.
     *
     * Named `lastTaskRunId`, NOT `lastRunId`: the paragraph above this interface records that a service
     * backend renames `lastTaskId` → the WIRE field `lastRunId`, so a store field spelled `lastRunId` would
     * be the same name carrying a different identity one hop apart — a mixed-version mapper (or an object
     * spread) could then publish either value under the one wire name with no type error. Same
     * collision-avoidance as {@link import("../orchestration/workflow-types.js").WorkflowAgentRun.taskRunId}.
     *
     * ORDERING (honest limit): the Runner records this fire-and-forget, exactly as it always has for
     * `lastTaskId`, so on a store whose write is asynchronous two runs of one session race and the loser's
     * id can land last. That race was invisible while both runs wrote the same `lastTaskId`; it is
     * observable here. Read this as "an id of a recent run", and take strict ordering from the runs'
     * own results, not from this projection.
     *
     * Additive and optional: absent on a store that predates it, absent until a task has run, and absent
     * whenever the recording caller had no run id (see {@link SessionStore.noteTaskRun}).
     */
    lastTaskRunId?: string;
    /** F7 fork-GC seam: the parent session this one was forked from ({@link SessionMetadata.forkedFrom}).
     *  Lets a consumer render/reap fork lineage; absent on non-forked sessions and on cache views that
     *  predate the fork. The durable source of truth is the repo's persisted metadata. */
    forkedFrom?: string;
}
/**
 * The session lifecycle/caching layer the {@link Runner} depends on.
 *
 * `TtlSessionStore` is the default (in-memory, 7-day idle TTL). Provide a custom implementation
 * to back sessions with durable, external storage (the "Session center"): `acquire` reconstructs
 * a session from the persisted event log so any stateless runner instance can resume it.
 *
 * Implementations must be safe under concurrent `acquire` of the same id (return one session).
 */
export interface SessionStore {
    /**
     * design/173 §2.3 — durability declaration (see
     * {@link import("./checkpoint-store.js").StoreDurability}): whether sessions this store accepts
     * SURVIVE A PROCESS RESTART. That is the whole claim of this axis — multi-replica
     * coordination/concurrency is a separate backend property this field does not license (review
     * fold r1-F2/r2-F3; a single-instance file-backed store declares `"durable"` honestly).
     * Declaration, never duck-typing (the wiring manifest relays it verbatim — it never sniffs class
     * names); ABSENT is read fail-closed as `"process-local"` (under-promise, never over-promise).
     * `TtlSessionStore` over a durable {@link SessionRepo} declares via its `durability` option — the
     * store cannot inspect the repo's persistence, so the DEPLOYMENT that wired the repo states the
     * fact.
     */
    readonly durability?: import("./checkpoint-store.js").StoreDurability;
    /**
     * Subagent transcript persistence — the store's PER-PLACEMENT durability declaration: "this store
     * understands and honors placement semantics (partitioning + the obligations below), and sessions
     * placed into the `subagent` partition have THIS durability". Declaration, never duck-typing (the
     * wiring manifest relays it verbatim). The store-level {@link durability} continues to describe
     * only UN-placed sessions — a routing/composite store whose transient host lane and durable
     * subagent partition differ can therefore be honest on both axes. ABSENT = the store does not
     * understand placement: an `acquire` carrying one is legally IGNORED (the session is then an
     * ordinary one, and the deployment's delegation-durability tier reads as rows-only).
     *
     * Declaring this commits the store to the placement obligations:
     *  1. a placed session enjoys the declared durability (`acquire(id, {requireExisting:true})`
     *     reaches it after a restart when `"durable"`);
     *  2. placed sessions do NOT appear on host enumeration faces (`list()` and every deployment
     *     listing path — restart-stable);
     *  3. acquire semantics for sessions WITHOUT a placement are byte-unchanged;
     *  4. `release(placedId)` is a REAL deletion (durable history included) — the joint reap's
     *     "row and session both gone" depends on it;
     *  5. placement is FIRST-WRITE IMMUTABLE — a re-open (`requireExisting`, with or without a
     *     placement argument) returns the same session and never rewrites the tuple;
     *  6. a placed id cannot be CLAIMED: a later claim-form acquire (no `requireExisting`, no
     *     creation placement) of an existing placed id refuses loudly
     *     (`SessionError("placement_refused")`) instead of attaching.
     */
    readonly placements?: {
        subagent?: {
            durability: "durable" | "process-local";
        };
    };
    /** Get an existing session by id, or create one (optionally with a caller-supplied id). design/114 Phase3:
     *  `opts.requireExisting` ⇒ a store MUST fail loud (throw a `not_found` {@link SessionError}) on a genuinely
     *  missing id rather than create-on-miss — so a reuse-style warm-resume of a gone session errors instead of
     *  silently starting a fresh empty run. A store that cannot honor it MUST still not silently create (either
     *  implement the check or reject the option).
     *  `opts.placement` (subagent transcript persistence): CREATE the session into the named placement
     *  partition (see {@link SessionPlacement} — trusted-internals channel only). Ignored by stores
     *  that do not declare {@link placements}; on a store that does, the creating call is exempt from
     *  obligation 6 above and idempotent re-acquire by the same trusted chain stays legal. */
    acquire(sessionId?: string, opts?: {
        requireExisting?: boolean;
        placement?: SessionPlacement;
    }): Promise<AcquiredSession>;
    /** Mark a session recently active (resets idle TTL where applicable). May be async for durable stores. */
    touch(sessionId: string): void | Promise<void>;
    /** Drop a session from the live set (e.g. a throwaway task). Durable history may be retained by the backend. */
    release(sessionId: string): Promise<void>;
    /**
     * Drop only the CACHED view of a session — **never** deletes history (unlike `release`, which with the
     * in-memory `TtlSessionStore`'s default `evict: "delete"` policy removes the session from the repo too).
     * The Runner uses this where the intent is "discard a stale view and re-wake", and on a prepare-phase
     * throw after acquire (audit B-17 + the 1.93.0 throw-cleanup contract gap: calling `release` there
     * silently DELETED an existing session's whole history on the default store — the re-acquire then
     * continued on an empty same-id session). **Optional, but custom stores SHOULD implement it**: when
     * absent the Runner performs NO fallback (it never substitutes `release`, whose contract permits
     * deletion — B-17 council) — the cached view is simply not dropped, so a persistent reconcile conflict
     * exhausts its retry budget and propagates instead of risking history. A store whose `release` is
     * already history-safe can implement `forget = release`.
     */
    forget?(sessionId: string): void | Promise<void>;
    /**
     * Protect a session from idle eviction while a design/45 checkpoint references it (a suspended task
     * must still be resumable). `pin` before suspend, `unpin` on resume/expiry. **Optional**: a durable
     * backend that never idle-GCs sessions (service's `tidb-session-store`) implements both as no-ops and
     * is still safe; only the in-memory `TtlSessionStore` needs a real pin (its `sweep` skips pinned ids).
     * See design/45 §5 / M3.
     */
    pin?(sessionId: string): void | Promise<void>;
    unpin?(sessionId: string): void | Promise<void>;
    /**
     * Record that a task run STARTED on a session (the Runner calls this at task start, when both ids are
     * known) so the store can surface it as {@link SessionStoreSummary.lastTaskId} — letting a `/resume` list
     * re-attach to the most recent run, even one still in-flight or suspended. **Optional + best-effort**: a
     * store that surfaces no session list may ignore it; it MUST NOT throw. The engine's `taskId` is the
     * service's `lastRunId`.
     *
     * `runId` (#499, additive third parameter — an existing implementation keeps typechecking and keeps
     * working): the engine's own identity for THIS run, for {@link SessionStoreSummary.lastTaskRunId}. It
     * is supplied because `taskId` cannot answer "which run": with no host task id it IS the session id.
     *
     * The two seats describe ONE run and MUST move together: a call that omits `runId` records the new
     * `lastTaskId` and CLEARS `lastTaskRunId`. Keeping the previous run's id beside a newer task id would
     * assert that an id belonging to an older run identifies the latest one — absence is the honest answer
     * to "which run was last" when the caller does not know.
     */
    noteTaskRun?(sessionId: string, taskId: string, runId?: string): void | Promise<void>;
    /**
     * Enumerate known sessions as lightweight {@link SessionStoreSummary} projections (newest-first by
     * `lastActiveAt`) so a shell's `/resume` lists sessions + their last run in ONE call. **Optional**: the
     * in-memory `TtlSessionStore` lists its live cache; a durable backend lists persisted sessions. A store
     * that cannot enumerate omits this (the shell then has no session-list affordance — honest degrade).
     */
    list?(): Promise<SessionStoreSummary[]>;
    /**
     * Subagent transcript persistence — enumerate the PLACED partition (the input to the joint reap's
     * partition leg: sessions whose owning agent row is gone are the orphan shape it collects).
     * **Optional**: absent = the partition-GC leg is simply unavailable on this store (documented,
     * fail-closed — placed sessions then age out only through the row-joined reap path). `olderThanMs`
     * filters by session AGE (last activity where knowable, else `placedAt`); `scope` narrows to one
     * tenant. Rows return the FULL `(scope, handle)` join tuple or the honest `tupleIncomplete`
     * variant (see {@link PlacedSessionRow}) — consumers must skip incomplete tuples, never guess.
     */
    listPlaced?(kind: "subagent", opts?: {
        olderThanMs?: number;
        scope?: string;
    }): Promise<PlacedSessionRow[]>;
    /**
     * design/110 — FORK a session: copy `sourceId`'s durable history (root→leaf) into a NEW session and return its
     * id (or `null` if the source doesn't exist). The forked session is independent (writes to it don't touch the
     * source). Backs CC `/fork` (continue with the parent's full context) AND the inheriting-context subagent fork
     * (run a child on the fork so it inherits the parent context + shares the prompt-cache prefix). **Optional**:
     * a store implements it only if it can replay history. `TtlSessionStore` DOES — it delegates to its
     * {@link SessionRepo} (so {@link hasSessionFork} is true) — BUT the fork is only as durable as that repo:
     * an in-memory repo forks IN-PROCESS yet loses the copy on restart, while a durable repo (file/pg) makes the
     * fork survive restart/replica. **Capability presence ≠ durability** — a cross-replica consumer (design/114
     * warm-resume) MUST wire a durable repo; core cannot verify durability, only fail-loud on a missing source at
     * fork time (returns `null`). `owner` (if given) scopes the new session for multi-tenant stores, and a store that
     * also implements {@link register} MUST make the fork's ownership row agree with it — a fork COPIES the source's
     * history, so the copy must never be weaker than the original: with no `owner` argument the fork inherits the
     * SOURCE's row (a source with no row mints none, keeping the authorizer's claim branch intact). An EXPLICIT
     * `owner: null` follows the same inheritance, deliberately: "anonymous" on a copy of an OWNED source would
     * make that history claimable by anyone — a downgrade this face exists to prevent — so `null` only takes
     * effect when the source itself is unowned/anonymous. MUST copy the COMMITTED leaf (an in-flight
     * uncommitted turn of an active source is not included — a fork is a snapshot at the fork point).
     */
    fork?(sourceId: string, owner?: string | null): Promise<string | null>;
    /**
     * Session-ownership capability face (multi-tenant admission — the deployment's authorizer probes for
     * BOTH methods and runs its owner gate only when they exist; a store without them forces the
     * deployment into its fail-closed arm, refusing caller-supplied session ids under multi-tenancy).
     *
     * `ownerOf` is THREE-state and the states must never collapse:
     *  - `undefined` — the session has NO ownership row (unknown id): the authorizer claims it for the
     *    calling principal (or 404s under require-existing).
     *  - `null` — the session EXISTS and is anonymous (unowned): admitted (single-tenant/dev sharing).
     *  - `string` — owned: a different principal is refused.
     *  Collapsing `undefined` into `null` breaks the gate in both directions at once (an unknown id
     *  reads as an admissible anonymous session; an anonymous session reads as claimable).
     */
    ownerOf?(sessionId: string): Promise<string | null | undefined>;
    /**
     * Idempotent ownership upsert, FIRST-WRITE-WINS and atomic: the first registered owner is kept,
     * later calls neither overwrite nor throw. Deliberately makes NO authorization decision — the
     * caller re-reads {@link ownerOf} after registering and judges there (which is exactly why the
     * first-write-wins read-back must be atomic: a concurrent loser has to SEE the winner's owner on
     * re-read, or it silently attaches to the winner's session). In-memory synchronous writes satisfy
     * this trivially; durable implementations need a real uniqueness guarantee, not read-then-write.
     * The ownership row must never expire BEFORE its session (a live session reading as `undefined`
     * would be re-claimable by anyone); same-lifetime or longer both satisfy the contract — so a store that
     * deletes the row when it deletes the history must gate that on the history delete having SUCCEEDED.
     * An implementation must never store a value that {@link ownerOf} reads back as `undefined` (a row that
     * EXISTS yet reads as "no row" is permanently claimable and never ownable): a FIRST write whose `owner` is
     * neither a string nor `null` is refused loudly instead — the no-overwrite-no-throw rule above governs
     * LATER calls, which stay silent no-ops whatever they carry.
     */
    register?(sessionId: string, owner: string | null): Promise<void>;
    /**
     * WHICH occupant holds `sessionId` now: the session row's {@link SessionMetadata.incarnation} cell,
     * minted by the repo when the row came into existence and stable for the row's whole life (restarts,
     * cache eviction, replicas). `undefined` = UNKNOWN — no row for this id, or a row written before the
     * cell existed; a reader treats unknown as "cannot tell occupants apart", never as a different one.
     * A rejection is a read fault, not an answer.
     *
     * The BY-ID read, for a reader that did not acquire the session (hosts; this read and the metadata of
     * a session `acquire` hands back must agree — the incarnation kit pins both). A run's own memory seat
     * reads the session it ACQUIRED instead, never a second lookup: the cached view `acquire` returns and
     * a by-id read can answer two different rows. **Optional**: absent ⇒ no by-id read.
     */
    incarnationOf?(sessionId: string): Promise<string | undefined>;
    /** Number of live/cached sessions (best-effort). */
    readonly size: number;
    /** Release resources (timers, connections). Best-effort; must not throw. */
    dispose(): void | Promise<void>;
}
/** Structural detection: does this store expose the optional {@link SessionStore.fork}? Mounts the fork
 *  capability only when true (INERT otherwise — mirrors `hasScheduler`/`hasBackgroundShell`). */
export declare function hasSessionFork(store: SessionStore): store is SessionStore & {
    fork: NonNullable<SessionStore["fork"]>;
};
/**
 * F3 bounded-wake recipe, packaged. Given the **path-ordered** entries of a branch
 * (root→leaf, e.g. `await session.getBranch(leafId)` or a durable store's leaf-walk),
 * returns the smallest self-contained tail a stateless runner needs to resume:
 *
 *  - `floorEntryId` = the **latest** compaction's `firstKeptEntryId` (null if never compacted),
 *  - `tail` = entries from that floor to the leaf — i.e. the surviving compaction summary
 *    plus every message kept after it. Everything before the floor is already folded into
 *    that summary, so it can stay cold in durable storage.
 *
 * Hand the result straight to a floored storage:
 * `new InMemorySessionStorage({ entries: tail, floorEntryId })` (or your durable equivalent).
 * `buildContext()` on that is identical to waking from the full tree — see `session-floor.test.ts`.
 *
 * Pure and allocation-light: it scans for the last compaction and slices. If the tree was never
 * compacted (or the floor isn't on this path — shouldn't happen for a real branch) it returns the
 * full input unchanged with `floorEntryId: null`, so callers can always use the result verbatim.
 */
export declare function boundedTail(pathEntries: readonly SessionTreeEntry[]): {
    tail: SessionTreeEntry[];
    floorEntryId: string | null;
};
/**
 * The ONE "has this session seen conversation?" predicate — the freshness question is asked three
 * times in one prepare (project-memory phase, design/148 center adoption, the epoch-pin
 * discriminator), and an entry-COUNT proxy at ANY of them re-creates the same lie: a host may write
 * session_info / model_change / … rows before the first turn (naming a new chat, recording a model
 * pick), and none of that is evidence of a prior session (the count proxy
 * survived at the adoption arm after the pin arm was fixed, silently costing a pre-named session
 * its published center prompt for life). Conversation content — and only that — is the evidence.
 */
export declare function hasConversationContent(branch: ReadonlyArray<{
    type: string;
}>): boolean;
