@sema-agent/core
Version:
Stateless, task-oriented AI agent core
118 lines (117 loc) • 5.93 kB
TypeScript
import { type PromptEpochArtifact, type PublishedPromptArtifactEnvelope } from "./artifact.js";
export interface PromptArtifactStore {
/** Verified read by digest — undefined on miss OR on any verification failure of stored bytes. */
get(artifactDigest: string, opts: {
engineVersion: string;
}): Promise<PublishedPromptArtifactEnvelope | undefined>;
/** Idempotent immutable write (same digest ⇒ same bytes; the caller verified before putting). */
put(envelope: PublishedPromptArtifactEnvelope): Promise<void>;
}
export declare class MemoryPromptArtifactStore implements PromptArtifactStore {
private readonly byDigest;
get(artifactDigest: string, opts: {
engineVersion: string;
}): Promise<PublishedPromptArtifactEnvelope | undefined>;
put(envelope: PublishedPromptArtifactEnvelope): Promise<void>;
}
/** `artifacts/<hex>.json` under a caller-owned directory; atomic tmp+rename writes (§9.2). */
export declare class FilePromptArtifactStore implements PromptArtifactStore {
private readonly dir;
constructor(dir: string);
private fileFor;
get(artifactDigest: string, opts: {
engineVersion: string;
}): Promise<PublishedPromptArtifactEnvelope | undefined>;
put(envelope: PublishedPromptArtifactEnvelope): Promise<void>;
}
export type PromptSourceState = {
state: "active";
artifactDigest: string;
sourceRevision: string;
} | {
state: "disabled";
sourceRevision: string;
};
export interface PromptSourceStateStore {
load(): Promise<PromptSourceState | undefined>;
save(state: PromptSourceState): Promise<void>;
}
export declare class MemoryPromptSourceStateStore implements PromptSourceStateStore {
private state;
load(): Promise<PromptSourceState | undefined>;
save(state: PromptSourceState): Promise<void>;
}
/** `source-state.json` sibling of the artifact dir; atomic tmp+rename; malformed file = undefined
* (stateless boot — conservative: no candidate until the next verified response). */
export declare class FilePromptSourceStateStore implements PromptSourceStateStore {
private readonly file;
constructor(dir: string);
load(): Promise<PromptSourceState | undefined>;
save(state: PromptSourceState): Promise<void>;
}
/** The narrow face the Runner consumes (RunnerDeps.promptSource) — resolution only, no publish. */
export interface PromptArtifactSource {
/** The current validated candidate for NEW sessions, with its publish provenance
* (undefined = bundled-only, today's shape). */
current(): {
artifact: PromptEpochArtifact;
sourceRevision: string;
} | undefined;
/** Resolve a PINNED digest (resume path). Undefined = unavailable — the caller fails loud. */
get(artifactDigest: string): Promise<PromptEpochArtifact | undefined>;
/** RB-31 — the tri-state the adoption boundary keys on: `"disabled"` = an EXPLICIT
* validated revocation (applyDisabled) ⇒ pinned sessions roll back to bundled at the next
* compaction; `"active"` = a validated candidate is live; `"unavailable"` = no verified state
* (never-configured face, store-miss boot) ⇒ conservative restate — an outage must never strip
* policy, only an explicit revocation does. Optional (older sources read as "unavailable"). */
sourceState?(): "active" | "disabled" | "unavailable";
}
/**
* The candidate state machine (§9.2 full pipeline). Publish-side entry points:
* - `applySnapshot(raw)` — a fetched effective-snapshot body: verify → store.put → persist
* active state → atomic in-memory swap. Any verification failure leaves EVERYTHING unchanged.
* - `applyDisabled(sourceRevision)` — an explicit, successful disable/delete response: persist
* disabled state + drop the candidate. (Network failure/304 = simply don't call anything.)
* - `restore()` — boot: re-resolve persisted active state through the verified store; a miss or
* verify failure boots stateless (no candidate) without touching the persisted record.
*/
export declare class CenterPromptSource implements PromptArtifactSource {
private readonly store;
private readonly stateStore;
private readonly engineVersion;
private candidate;
private state;
/** Hardening 2026-07-31 — serialization tail for the three STATE-MUTATING entry points. Each of
* them is a multi-await sequence ending in a synchronous swap of (candidate, state); with two in
* flight, an interleaving where B's `stateStore.save` resolves and swaps before A's leaves the
* persisted record naming B's digest while the in-memory candidate names A's — `current()` then
* serves A to every new session until the next `restore()` silently switches to B. `CenterPromptSource`
* is a public export driven by a caller's poll loop, so two overlapping applies are an ordinary
* schedule, not an exotic race. Serializing costs nothing (these are publish-rate operations) and
* makes the durable record and the in-memory candidate agree by construction. */
private tail;
/** Run `fn` after every previously-queued mutation settles (success OR failure — a failed apply
* must not wedge the queue). */
private exclusive;
constructor(opts: {
store: PromptArtifactStore;
stateStore?: PromptSourceStateStore;
engineVersion: string;
});
current(): {
artifact: PromptEpochArtifact;
sourceRevision: string;
} | undefined;
get(artifactDigest: string): Promise<PromptEpochArtifact | undefined>;
restore(): Promise<void>;
sourceState(): "active" | "disabled" | "unavailable";
applySnapshot(raw: unknown): Promise<{
ok: true;
artifactDigest: string;
} | {
ok: false;
code: string;
reason: string;
}>;
applyDisabled(sourceRevision: string): Promise<void>;
}