@sema-agent/core
Version:
Stateless, task-oriented AI agent core
166 lines (165 loc) • 12.3 kB
TypeScript
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;