UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

166 lines (165 loc) • 12.3 kB
import type { ImageContent, TextContent } from "../llm/index.js"; import type { AgentMessage } from "../loop/types.js"; import type { AnnouncedListingSnapshot, AnnouncedListingSnapshotInput, GitAnnouncementState, Session, SessionContext, SessionMetadata, SessionStorage, SessionTreeEntry, WorkspaceState } from "../harness/types.js"; import type { BuildContextOptions } from "../harness/types.js"; import type { PromptEpochDescriptor } from "../../prompt-assembly/epoch.js"; /** Build model context from the active session branch and its latest state markers. * `opts` (window awareness, see {@link BuildContextOptions}): when the caller declares the * consuming model's window, the compaction summary's retained invoked-skills area is defensively * re-clamped so 「summary + retained area」 can never push the rebuilt context out of that window * on its own. Absent ⇒ byte-identical legacy rendering. */ export declare function buildSessionContext(pathEntries: SessionTreeEntry[], opts?: BuildContextOptions): SessionContext; /** High-level session API backed by pluggable tree storage. Implements the vendored `Session` CONTRACT * (`SessionContract`) the harness consumes — dependency inversion so the vendored substrate needs no * `import` of this concrete class (de-vendoring prep); `implements` makes the contract drift caught here. */ export declare class StoredSession<TMetadata extends SessionMetadata = SessionMetadata> implements Session<TMetadata> { private storage; constructor(storage: SessionStorage<TMetadata>); getMetadata(): Promise<TMetadata>; getStorage(): SessionStorage<TMetadata>; getLeafId(): Promise<string | null>; getEntry(id: string): Promise<SessionTreeEntry | undefined>; getEntries(): Promise<SessionTreeEntry[]>; getBranch(fromId?: string): Promise<SessionTreeEntry[]>; buildContext(opts?: BuildContextOptions): Promise<SessionContext>; getLabel(id: string): Promise<string | undefined>; getSessionName(): Promise<string | undefined>; private appendTypedEntry; appendMessage(message: AgentMessage): Promise<string>; appendThinkingLevelChange(thinkingLevel: string): Promise<string>; appendModelChange(provider: string, modelId: string): Promise<string>; /** * S3 (prompt-assembly §9.2): persist the session's prompt-epoch pin as a first-class * typed entry. Written at session start (BEFORE the first conversation entry — the resume path * must never see user entries with no pin) and on legacy migration; compactions restate the pin * in their own `details.promptEpoch` instead (same-CAS with the new baseline). */ appendPromptEpoch(descriptor: PromptEpochDescriptor): Promise<string>; /** * design/150 (RB-25): persist the listing announced name-sets as a first-class typed entry. * Called best-effort at every listing COMMIT point (the run loop's announce mirror sites) — * an append failure degrades to the checkpoint-mirror/transcript-probe rungs, never fails the run. */ appendAnnouncedListing(snapshot: AnnouncedListingSnapshotInput): Promise<string>; /** * Git-status frame announced state (branch-authority mirror): persist the `(kind, hash)` tuple — * announced form carries the frame's own entry id; a compaction restates the tuple as `pending` * inside its `details.gitAnnouncement` instead (same-CAS with the new baseline). Called * best-effort at every frame COMMIT point (append receipt in hand) — an append failure degrades * to a conservative re-announcement on the next leg (duplicate-tolerant), never fails the run. */ appendGitAnnouncement(state: GitAnnouncementState): Promise<string>; /** * Branch-authority read of the git announced state: the nearest carrier walking back from the * leaf (a `git_announcement` entry or a compaction's `details.gitAnnouncement` restatement; * SNAPSHOT semantics — first hit wins). The pending gate is a MANDATORY one: a nearest carrier * that is pending, or an announced carrier whose frame entryId is NOT on the active branch (a * rewind cut the frame while a descendant mirror survived), reads as `status:"pending"` — the * caller must conservatively re-announce rather than trust a mirror whose frame the model cannot * see. Malformed carriers are skipped (defense in depth behind the import door). Undefined ⇒ no * carrier visible on this branch (pre-migration session / bounded-tail floor cut every carrier) — * callers fall back to the checkpoint mirror rung, then to conservative re-announcement. */ getGitAnnouncement(): Promise<{ kind: GitAnnouncementState["kind"]; hash: string; status: "announced" | "pending"; entryId?: string; } | undefined>; /** * design/155: persist the settle-time workspace state (tracked cwd + active * EnterWorktree session) as a first-class typed entry. Called best-effort at task settle when the * state changed — an append failure degrades to "the next turn starts at the task root", exactly * the pre-155 behavior, never fails the run. The same shape gate guards append, read, and the * import door (self-generated data must round-trip its own validator). */ appendWorkspaceState(state: WorkspaceState): Promise<string>; /** * design/319 (A ticket): persist the session's reminder provenance mark as a first-class typed * entry (see {@link ReminderMarkEntry} for the full restore-ladder narrative). Written once by * the first prepare that mints/adopts a mark this session (best-effort — an append failure * degrades to a per-leg re-mint under the strict declaration, never fails the run). The shape * gate is the SCHEME-AGNOSTIC bounded-token one; the caller has already run the mint home's * verify port on the value it is persisting. */ appendReminderMark(mark: string): Promise<string>; /** * design/319: recover the session's reminder provenance mark for the ACTIVE branch — the nearest * `reminder_mark` entry walking back from the leaf (SNAPSHOT semantics: first hit wins). * Malformed entries are skipped (defense in depth behind the import door). Undefined ⇒ no entry * VISIBLE on this branch (a fresh session, a pre-319 session, or a bounded-tail backend whose * load floor cut it) — the caller degrades to a fresh mint, which is fail-safe under the strict * declaration (historic reminders read as data; forgery gains nothing). The value returned is * the STORE's opaque token — the consumer still applies the mint home's verify port and re-mints * on failure (scheme evolution: an old-scheme value simply stops verifying). */ getReminderMark(): Promise<string | undefined>; /** * design/155: recover the workspace-state snapshot for the ACTIVE branch — the nearest * `workspace_state` entry walking back from the leaf (SNAPSHOT semantics: first hit wins; a CLEAR * snapshot — no cwd, no worktree — is a real hit that shadows older state). Malformed entries are * skipped (defense in depth behind the import door). Undefined ⇒ no snapshot VISIBLE on this * branch: a fresh session, a pre-155 session, or a bounded-tail backend whose load floor cut every * snapshot — all degrade to fresh defaults (the task root), the pre-155 behavior. Rewind gets the * correct semantics for free: the branch path ends at the cut, so the walk finds the state that * was current THEN. */ getWorkspaceState(): Promise<WorkspaceState | undefined>; /** * design/150 (RB-25): recover the announced listing snapshot for the ACTIVE branch — the nearest * carrier walking back from the leaf (an `announced_listing` entry or a compaction's * `details.announcedListings` restatement; SNAPSHOT semantics: first hit wins). Malformed * carriers are skipped (defense in depth behind the import door), never returned. * Undefined ⇒ no carrier VISIBLE on this branch — callers fall back to the checkpoint mirror / * transcript probe. Honest bound (RB-33): a bounded-tail backend whose load floor * cut every carrier (and a rewind cut between the floor and the newest compaction) also returns * undefined — the ladder degrades to the lower rungs (worst case a duplicate re-announce, never a * loss). A floor-anchor seam (the getEpochAnchor analog) is deliberately deferred until a real * deployment hits that window. */ getAnnouncedListing(): Promise<AnnouncedListingSnapshot | undefined>; /** * Recover the pinned prompt epoch for the ACTIVE branch (stage S3). Walks the branch from the * leaf backwards: the nearest `compaction` entry's `details.promptEpoch` restatement wins (the * bounded-tail/F3 floor may have cut everything older); else the nearest `prompt_epoch` entry * (root pin / legacy migration). Undefined ⇒ pre-epoch session (upgrade-in-place migration path). * Rewind/resumeAt legs get the SAME semantics for free: the branch path already ends at the * historical cut point, so the walk finds the pin that was current THEN, not the latest. */ getPromptEpoch(): Promise<import("../../prompt-assembly/epoch.js").PromptEpochDescriptor | undefined>; /** * RB-103 (2026-07-25, defect probe): the compaction entry CARRIES the branch's derived conversation * state — thinking level and active model — restated automatically here. * * Both are reconstructed by scanning path entries ({@link buildSessionContext}), so a bounded-tail (F3) * wake that loads only `[firstKeptEntryId .. leaf]` cannot see a `thinking_level_change` / * `model_change` that sits below the floor: the user's chosen thinking level silently reverted to "off" * and the model to null. Until RB-95 the compaction cut point's back-off loop happened to drag those * entries above the floor much of the time — an accident, not a mechanism, and one that broke the * floor's own "always a user/assistant message" contract to do it. * * Unlike `promptEpoch` / `announcedListings` — which the CALLER restates because selecting them needs * caller-side logic — these two are derived from this session's own branch, so the session computes * them itself and every caller of `appendCompaction` gets the carry for free. A caller-supplied * `details` still wins if it names the same keys (explicit intent beats the automatic restatement). */ appendCompaction(summary: string, firstKeptEntryId: string, tokensBefore: number, details?: unknown, fromHook?: boolean): Promise<string>; /** Append a non-LLM transcript marker for harness-specific state. */ appendCustomEntry(customType: string, data?: unknown): Promise<string>; /** Append harness-specific content that can also be replayed into model context. */ appendCustomMessageEntry(customType: string, content: string | (TextContent | ImageContent)[], display: boolean, details?: unknown): Promise<string>; /** Record or clear the display label for an existing session entry. */ appendLabel(targetId: string, label: string | undefined): Promise<string>; appendSessionName(name: string): Promise<string>; } /** * Falsification round 1 (F3) — is `entryId` not merely ON the active branch but inside the region * a context rebuild can actually contain? A branch walk sees the whole tree; `buildSessionContext` * replays only [latest compaction's firstKeptEntryId .. leaf] (plus the summary). A git frame that * sits BEFORE the latest compaction's kept tail is therefore gone from every future request even * though its id is a legitimate branch member — an announced mirror pointing at it must read as * PENDING (re-announce), or the announced hash would suppress re-sends of bytes the model can no * longer see. Shared by the session mirror walk and the run loop's checkpoint seeding rung (one * rule, both readers). Conservative on damage: an unresolvable firstKeptEntryId reads as invisible. */ export declare function gitFrameContextVisible(branch: SessionTreeEntry[], entryId: string): boolean;