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