@sema-agent/core
Version:
Stateless, task-oriented AI agent core
110 lines (109 loc) • 7.32 kB
TypeScript
import type { HarvestRejection, MemorySessionHandle } from "./types.js";
export declare const LAYER_OBSERVATIONS_FILE = "layer-observations.json";
/** One recorded unexpected write into a read-only layer. */
export interface LayerObservation {
/** The layer (scope) the bytes were observed in. */
scope: string;
/** Scope-home-relative, `/`-separated path the bytes were observed at (disclosure only). */
rel: string;
/** The entry id the observed file carries; absent ⇔ an id-less note. */
entryId?: string;
/** The entry's committed rev when observed; absent ⇔ the id had no committed row. */
rev?: string;
/** The observed content's identity: its entry rev (what the read side computes from the file), or
* for an id-less note the sha256 of its bytes. */
observedRev: string;
/** The observing session, when the harvest was given one. */
sessionId?: string;
observedAt: number;
/** Whether a commit backs these bytes could not be established when they were observed (a
* transaction journal was pending — the committed account could not be read whole). The row — one
* per observation — then stands against ANY committed rev (never adopted) until the first session
* that may write settles it, once its preflight has recovered the journal
* ({@link settleUndecidedLayerObservations}): the committed account backs its bytes at its seat ⇒
* this observation is removed (the bytes are committed content); it does not ⇒ pinned to the
* committed rev read, an ordinary row from then on, retired when that rev moves (superseded, as
* every ordinary row is); the account still cannot be read ⇒ it stays undecided. Only id-bearing
* rows (a note has no commit question) and never with `rev`. */
undecided?: true;
/** An undecided row's own identity (random, minted with the row): two observations alike in every
* other field — the same session in the same millisecond — stay two rows. Undecided rows only. */
observationId?: string;
}
/** Record observations (locked, journaled; an existing key keeps its first row). Nothing is pruned
* here: the rev a harvest read before this lock need not be current any more, so it cannot decide
* which other rows are stale — and a row against a rev the entry has left can never match anyway. */
export declare function recordLayerObservations(controlDir: string, rows: readonly LayerObservation[]): void;
/** The table as a read-side lookup. */
export interface LayerObservationLookup {
/** Whether any row names `entryId` (a cheap pre-test before the committed rev is fetched). */
covers(entryId: string): boolean;
/** The row naming exactly this content (`observedRev`) of `entryId` against its committed rev
* `rev` (absent ⇔ the id has no committed row) — or an undecided row for that content, which stands
* against any rev until it is settled — else undefined. */
find(entryId: string, rev: string | undefined, observedRev: string): LayerObservation | undefined;
/** The row naming exactly these bytes (`digest`) of an id-less note at (`scope`, `rel`), or undefined. */
findNote(scope: string, rel: string, digest: string): LayerObservation | undefined;
}
/** Read the table (throws the strict family's refusal on a corrupt table). */
export declare function layerObservationLookup(controlDir: string): LayerObservationLookup;
/** The disclosure line for a recorded divergence met on the adopting read (one spelling, every
* door). The verdict leads: the render boundary caps an item at a label's length. */
export declare function observedDivergenceNotice(rel: string, served: boolean, undecided?: boolean): string;
/** The undecided rows ({@link LayerObservation.undecided}) awaiting settlement. */
export declare function undecidedLayerObservations(controlDir: string): LayerObservation[];
/** Settle undecided rows (locked, journaled), each by its own key (one observation — never another
* observation of the same entry): `"backed"` removes the row (the committed account backs its bytes
* at its seat: those bytes are committed content, so no unexpected write is lost), `{ rev }` pins
* it to the committed rev the settlement read (`rev` absent ⇔ no committed row) — an ordinary row,
* retired like any other when that rev moves. A row without a verdict stays undecided. */
export declare function settleUndecidedLayerObservations(controlDir: string, verdicts: ReadonlyMap<LayerObservation, "backed" | {
rev?: string;
}>): void;
/** The census digest: whole-file bytes (sha256), so "the bytes that stood when the window opened"
* means the bytes. */
export declare function layerDigest(text: string): string;
/** The read-only layers' census (materialize, zero-copy): canonical path → {@link layerDigest} of
* every entry file under `homes`, scanned at the read side's depth (what the adopting read sees). */
export declare function readOnlyLayerCensus(homes: readonly string[]): Map<string, string>;
/** A file of a read-only layer whose bytes changed since the materialize-time census. */
export interface ReadOnlyObservationCandidate {
path: string;
scope: string;
rel: string;
slug: string;
digest: string;
id?: string;
/** The entry rev of the bytes, or their digest for an id-less note. */
observedRev: string;
}
/** The observation half's candidates: files of the layers `handle` holds read-only whose bytes differ
* from its materialize-time census (none without a census). Throws when the scope registry cannot
* be read (the homes are resolved through it). */
export declare function readOnlyLayerCandidates(memoryDir: string, controlDir: string, handle: Pick<MemorySessionHandle, "layerBaseline" | "scopes" | "writeScope">): ReadOnlyObservationCandidate[];
/** What a harvest's observation half judged at detection: the rows (written AS JUDGED — a retry writes
* the same rows, never a re-judgment against a later account), the rejections and undecided
* candidates they carry, whether they landed, and the failure of the last attempt that did not. */
export interface ObservationBatch {
rows: LayerObservation[];
rejections: HarvestRejection[];
/** Each undecided candidate with the row it was recorded as (its observation identity). */
undecided: Array<{
candidate: ReadOnlyObservationCandidate;
row: LayerObservation;
}>;
landed: boolean;
failure?: string;
}
/** The undecided candidates a LANDED batch recorded (an unlanded one claims none). */
export declare function landedUndecided(batch: ObservationBatch | undefined): ObservationBatch["undecided"];
/** Whether two rows are the same observation (the table's key — for an undecided row, one
* observation: session and time included). */
export declare function sameObservation(a: LayerObservation, b: LayerObservation): boolean;
/** Whether the committed row backs an observation's bytes at its seat (same rev, bound there) — the
* observation was a commit, not an unexpected write. */
export declare function observationBacked(row: Pick<LayerObservation, "scope" | "rel" | "observedRev">, committed: {
rev: string;
scope?: string;
slug?: string;
} | undefined): boolean;