UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

125 lines (124 loc) • 9.44 kB
import type { AnnouncedListingSnapshot, AnnouncedListingSnapshotInput, AnnouncedMcpFailure } from "../../internal/harness.js"; import type { SessionTreeEntry } from "../../internal/harness-types.js"; import type { DateAnchorRead } from "./contracts.js"; import type { McpDroppedTool, McpServerStatus } from "../mcp.js"; import type { RunnerDeps } from "../types.js"; /** Upper bound on the advisory-digest arm kept in the record (oldest dropped past it). A digest is * 64 hex chars; a session accumulates a handful of distinct advisories, never hundreds — the bound * keeps a pathological deployment (a warning whose text embeds a per-run value) from growing the * snapshot without limit while still holding every realistic set. */ export declare const ANNOUNCE_ONCE_MAX_DIGESTS = 200; /** Stable digest of one announcement: `sha256(lane \n text)` as hex. Opaque on the wire; only ever * compared for equality. */ export declare function announceDigest(lane: string, text: string): string; /** The digest of an operator-lane warning: its `code` (empty when uncoded) and message. The message is * passed through the secret-tier redaction FIRST: an advisory that embeds a credential (a misconfigured * header, a URL with userinfo) must be recognized as the same advisory when the credential rotates, and * the record must never persist a secret-bearing text's exact digest either — the digest is opaque, but * an equality oracle on a secret is still an oracle. The forwarded line itself is untouched (the sink * owns its own redaction posture); only the identity is judged on the redacted form. */ export declare function warningDigest(err: unknown): string; /** The digest of one model-facing dropped-tool entry (server, tool, reason — the three fields the * frame prints). */ export declare function droppedToolDigest(d: McpDroppedTool): string; /** `existing ∪ add`, insertion-ordered, bounded to {@link ANNOUNCE_ONCE_MAX_DIGESTS} (oldest first * out). Returns a NEW array; the input arrays are never mutated. */ export declare function withAnnounceDigests(existing: readonly string[] | undefined, add: readonly string[]): string[]; /** The session face this ledger needs: the nearest announced snapshot and the append. */ export interface AnnounceOnceSession { getAnnouncedListing(): Promise<AnnouncedListingSnapshot | undefined>; appendAnnouncedListing(snapshot: AnnouncedListingSnapshotInput): Promise<string>; } /** * Flush the PARKED announced-listing snapshot write (`FrameReceiptLedger.listingSnapshotOwed`, set by a * frame receipt's commit inside the `message_end` walk) at a serialization point — the boundary's end, * the prompt's settle. Best-effort: a lost append is a duplicate announcement next run. Never while * prepare's branch read failed: a whole-snapshot write would replace the branch's record with this * leg's partial view (the flag is cleared either way — nothing is owed on such a leg). */ export declare function flushOwedAnnouncedSnapshot(prepared: { frameReceipts: { listingSnapshotOwed: boolean; }; announcedSnapshotRecovered: boolean; announcedListingsRef: AnnouncedListingSnapshotInput; session: Pick<AnnounceOnceSession, "appendAnnouncedListing">; }): Promise<void>; /** The run-local ledger — see the module header for the two commit disciplines it serves. */ export interface AnnounceOnceLedger { /** The nearest announced snapshot on the branch as read at prepare (undefined = pre-entry * session). Exposed so the listing seed can reuse this ONE read instead of walking the branch again. */ readonly entry: AnnouncedListingSnapshot | undefined; /** Whether the branch read SUCCEEDED (a pre-entry session reads as `undefined` and IS recovered; a * throwing read is not). While false, no whole-snapshot write may happen on this leg — the nearest * `announced_listing` has snapshot semantics, so a write seeded from a failed read would REPLACE * the branch's real record with the few arms this leg happens to know, hiding every other arm. * A skipped write degrades to a duplicate announcement next run, never a loss. The run loop * reads the same flag (`Prepared.announcedSnapshotRecovered`) before its own once-arm flush. */ readonly recovered: boolean; /** The operator-lane sink, deduplicated: a warning whose `(code, message)` digest is already in * the record is dropped; a new one is forwarded to `deps.onError` and recorded. With no sink wired * nothing is recorded either — "announced" means delivered. Same signature as `RunnerDeps.onError`. */ onError: NonNullable<RunnerDeps["onError"]>; /** The MODEL-face pending set for this run: the currently failed servers (name/error neutralized + * bounded at intake, the reminder's own bounds) MINUS the pairs already announced on this branch. * As a side effect the record's `mcpFailed` arm is pruned to the pairs that are STILL failing — * a recovered server leaves the record silently. Call once per prepare. */ mcpFailuresToAnnounce(statuses: ReadonlyArray<McpServerStatus>): AnnouncedMcpFailure[]; /** Remove, IN PLACE, the dropped-tool entries whose model-facing digest is already recorded (the * frame was delivered on an earlier run of this session). The operator line about each entry is a * separate digest — run that loop BEFORE pruning so both faces are judged on the full list. */ pruneDroppedTools(dropped: McpDroppedTool[]): void; /** The whole announced snapshot as it stands — the seed for the run loop's `announcedListingsRef` * mirror. Listing arms (agents/skills/models) are copied from ONE source, the checkpoint mirror * when it carries any of them, else the entry (rung-pure: a roster and its catalog come from the * same commit); the once-per-session arms come from their own rung selection. Present-only: an * empty arm is omitted, so a session that never announced anything serializes no * `announcedListings` at all. The listing arms are a state-only carry: a commit of any ONE arm * rewrites the nearest snapshot whole, so the other arms must already be on the object or the * commit erases them from the branch's record. */ arms(): AnnouncedListingSnapshot; /** End-of-prepare settlement: fold the operator-lane digests recorded during prepare into `ref` * (the mirror the run loop and the checkpoint serializer read) and, when the record advanced, * persist the whole snapshot — the listing arms as they stand on `ref` (or as read at prepare), * plus the two arms this module owns. Best-effort: an append failure degrades to a duplicate * announcement on the next run, never a lost one. */ settle(session: AnnounceOnceSession, ref: AnnouncedListingSnapshotInput): Promise<void>; } /** * Load the ledger for this prepare. Read ladder for the two arms: the resume checkpoint's mirror * first (cut atomically with the suspend; the entry is a best-effort write that can lag it), then * the nearest `announced_listing` carrier on the branch. A snapshot without the arms (an entry * written before they existed) reads as the empty set — additive schema, same posture as the * checkpoint's own listing arms. A session read failure also reads as empty: the worst case of * an unseeded ledger is a duplicate announcement, never a lost one. */ export declare function loadAnnounceOnceLedger(session: AnnounceOnceSession, checkpointMirror: AnnouncedListingSnapshot | undefined, onError: RunnerDeps["onError"]): Promise<AnnounceOnceLedger>; /** The suspend-time projection of the run loop's announced mirror onto the checkpoint field: * present-only arms, copied; `undefined` when nothing was announced on any face (the resume seed * then falls to the lower rungs — unchanged contract). */ export declare function serializeAnnouncedListings(ref: AnnouncedListingSnapshotInput): AnnouncedListingSnapshot | undefined; /** The custom-entry kind that carries the session's date anchor (engine-owned; never model-visible). */ export declare const DATE_ANCHOR_CUSTOM_TYPE = "engine:date-anchor"; /** * Read the session's date anchor off the active branch: the nearest anchor entry NEWER than the nearest * compaction, honored only when it was written for that same segment. An anchor written by a leg whose * prefix predates a compaction that landed in between therefore reads as absent — the one rule that makes a * compaction re-anchor the date. An unreadable branch returns undefined (the leg renders today, writes nothing). */ export declare function readDateAnchor(session: { getBranch(): Promise<ReadonlyArray<SessionTreeEntry>>; }): Promise<DateAnchorRead | undefined>; /** * The anchor write a leg owes when it settles, or undefined: owed when prepare found no anchor for the segment * (the leg rendered today) or the model has since been told a newer date; never after an unreadable branch. * Best-effort — a lost write costs one re-rendered prefix or one repeated reminder next leg, never a wrong date. */ export declare function owedDateAnchor(session: { appendCustomEntry(customType: string, data?: unknown): Promise<string>; }, seat: { legDate: string; anchor: DateAnchorRead | undefined; } | undefined, state: { announcedDate: string; } | undefined): (() => Promise<void>) | undefined;