@sema-agent/core
Version:
Stateless, task-oriented AI agent core
139 lines (138 loc) • 7.84 kB
TypeScript
/**
* The prompt catalog's shape: every model-visible system-prompt text is one catalog MESSAGE, placed into
* the system prompt by a catalog SECTION, and chosen among its VARIANTS by a closed selector (traits and
* facts). Catalog entries are literal data — no closures — so the catalog is hashable as a whole and the
* hash moves whenever any field of any entry moves.
*
* What stays code is a RENDERER (renderers.ts): a section whose bytes come from prepare-time inputs (the
* role base the caller chose, the environment tail, the memory tail, the MCP instructions frame, the
* reminder-mark declaration). Renderers carry a version; changing a renderer's output means bumping it.
*/
/** The band a section belongs to: a POLICY class (who may write it, which sink it reaches), never a position.
* Position is the section's own `rank`. */
export type BandId = "role" | "scenario" | "task-append" | "behavior" | "core-behavior" | "constitution" | "discovery" | "environment" | "memory";
/** The source classes that write a band through overlays; `bundled` is the catalog itself. */
export type OverlaySource = "center" | "deploy" | "task";
/** Whether `v` is one of a closed set's words — the one membership reading the selector vocabularies share. */
export declare function isWordOf<W extends string>(words: readonly W[], v: unknown): v is W;
/** The four overlay operations (a closed set; docs/CLOSED-SETS.md). */
export declare const OVERLAY_OPS: readonly ["replace", "append", "add", "drop"];
export type OverlayOp = (typeof OVERLAY_OPS)[number];
export interface BandRow {
readonly id: BandId;
/** The policy class in words (the band table's first column). */
readonly policy: "overridable" | "add-only" | "overridable-restricted" | "locked" | "fact";
/** The ops each overlay source may apply in this band. All empty ⇒ the band is locked. */
readonly ops: Readonly<Record<OverlaySource, readonly OverlayOp[]>>;
/** Where an `add` lands, when the band admits one. */
readonly addRank?: number;
/** The one mods event whose answers may write this band (absent ⇒ no mods event writes it). */
readonly modsEvent?: "prompt.compose" | "prompt.section";
}
/** The host role a run is assembled for — which role base `core/role.base` selects. Written by whoever builds the
* task (`TaskSpec.promptRole`); absent ⇒ `general`. */
export declare const HOST_ROLES: readonly ["general", "code", "coordinator", "cowork"];
export type HostRole = (typeof HOST_ROLES)[number];
/** The host form: `minimal` leaves the core-behavior band out (the locked bands stay). Written only by
* `TaskSpec.promptForm`; absent ⇒ `standard`. */
export declare const HOST_FORMS: readonly ["standard", "minimal"];
export type HostForm = (typeof HOST_FORMS)[number];
export interface HostCondition {
readonly role?: HostRole;
readonly form?: HostForm;
}
/**
* The model-capability traits a variant may be selected on — the reference's capability names, spelled
* as the reference spells them, so an anchor upgrade can be diffed mechanically. The producer
* ({@link import("../select.js").promptTraitsOf}) yields exactly these two today; a name joins this set when a
* producer can yield it, never before.
*/
export declare const PROMPT_TRAITS: readonly ["lean_prompt", "fable_5_mitigations"];
export type PromptTrait = (typeof PROMPT_TRAITS)[number];
/** The boolean facts a section admit or a variant `when` may test. All but the last are runtime facts the
* prepare phase produces; `executionEnvironmentDeclared` is derived once from two of them, so no `when`
* ever needs a disjunction. An optional fact that is absent reads as `false`. */
export declare const PROMPT_FACT_NAMES: readonly ["policyEnabled", "hooksEnabled", "isolationEnabled", "readFaceOpen", "writeFaceOpen", "withinTaskCompactionEnabled", "supervisorEnabled", "orchestrationEnabled", "orchestrationDeferred", "awarenessEnabled", "goalEnabled", "worktreeIsolated", "teammateEnabled", "isSubagent", "executionEnvironmentDeclared"];
export type PromptFactName = (typeof PROMPT_FACT_NAMES)[number];
/** A conjunction of fact equalities — the whole predicate language. No disjunction, no expressions. */
export type FactConjunction = Readonly<Partial<Record<PromptFactName, boolean>>>;
export interface TraitCondition {
readonly allOf?: readonly PromptTrait[];
readonly noneOf?: readonly PromptTrait[];
}
/** How a text relates to the reference. `cc` entries use the byte-span locator grammar the anchor gate resolves
* (scripts/verify-anchor-claims.mjs): corpus file, byte span, and a backticked identifier that occurs in it. */
export interface Attribution {
readonly form: "verbatim" | "adapted" | "sema-owned";
readonly cc?: readonly string[];
}
export interface Variant {
/** Reported in the manifest — one of the answers to "why this sentence". */
readonly id: string;
readonly when: CatalogVariantWhen;
readonly text: string;
}
/** A condition on the run's binding (`TaskSpec.promptBinding`, a deployment's scenario name): exactly one list. A run
* with no binding is outside every `anyOf` and inside every `noneOf`. */
export type BindingCondition = {
readonly anyOf: readonly string[];
readonly noneOf?: never;
} | {
readonly noneOf: readonly string[];
readonly anyOf?: never;
};
/** The selector condition a variant (or an overlay) is chosen on: a conjunction over traits, host, facts and binding. */
export interface VariantWhen {
readonly traits?: TraitCondition;
readonly host?: HostCondition;
readonly facts?: FactConjunction;
readonly binding?: BindingCondition;
}
/** The condition a BUNDLED catalog variant is chosen on: a {@link VariantWhen} without the binding. A binding is a
* deployment's own vocabulary (scenario names the engine cannot know), so no catalog variant can name one — the type
* refuses it where the catalog data is written. */
export type CatalogVariantWhen = Omit<VariantWhen, "binding">;
export interface CatalogMessage {
/** `core/<area>.<name>`. */
readonly id: string;
/** Variants in declaration order; the first whose `when` holds is selected. None holds ⇒ the message is absent. */
readonly variants: readonly [Variant, ...Variant[]];
readonly attribution: Attribution;
}
/** The renderers the catalog may place by id — data here, so the catalog checks its sections without loading the
* renderer code; renderers.ts holds the code and the version table, one row per id (its record type is total). */
export declare const RENDERER_IDS: readonly ["role-base", "model-guidance", "mcp-instructions", "reminder-mark", "environment", "memory"];
export type RendererId = (typeof RENDERER_IDS)[number];
export type SectionBody = {
readonly message: string;
}
/** One message per line, each with an optional line-level admit, joined by `joiner`. */
| {
readonly lines: readonly {
readonly message: string;
readonly admit?: FactConjunction;
}[];
readonly joiner: "\n" | "\n\n";
} | {
readonly renderer: RendererId;
};
export interface CatalogSection {
/** `core/…` — the id the manifest, `context_usage` and the center tier read. */
readonly id: string;
readonly band: BandId;
/** Global emission order (unique). */
readonly rank: number;
/** The `prompt.assembled` block view: adjacent sections sharing a block id merge into one block. */
readonly block: {
readonly id: string;
readonly joiner?: "\n" | "\n\n";
};
readonly admit?: FactConjunction;
readonly body: SectionBody;
}
export interface PromptCatalog {
readonly id: string;
readonly bands: readonly BandRow[];
readonly messages: readonly CatalogMessage[];
readonly sections: readonly CatalogSection[];
}