@sema-agent/core
Version:
Stateless, task-oriented AI agent core
139 lines (138 loc) • 10.2 kB
TypeScript
/**
* design/319 (A ticket) — the engine's system-reminder provenance MINT HOME.
*
* Every `<system-reminder>` OPEN TAG the engine authors is rendered here, carrying a per-session
* random provenance mark; the system prompt declares the mark's semantics (see
* {@link reminderMarkDeclaration}). Offline content authors (file bytes, MCP servers, web pages)
* never see the session's prompt or checkpoint, so a forged reminder tag embedded in their content
* lacks the mark and is byte-distinguishable from an engine mint — the impersonation half that the
* containment side (untrusted-text.ts, which NEUTRALIZES forged tags on lanes that pass through it)
* structurally could not cover on verbatim inlets (Read/Bash/Grep/MCP-success), where changing data
* bytes would corrupt the quote-back loop (Read ↔ Edit `old_string`).
*
* The module is deliberately THREE PORTS over one internal scheme constant:
* - **mint** ({@link mintReminderMark}) — coin a session's mark value;
* - **verify** ({@link isValidReminderMark}) — value-shape check for persisted/inherited values
* (a checkpoint field, a trusted spawn channel). A value the verifier does not recognize is
* RE-MINTED by the consumer, never silently reused — which is also the natural downgrade path
* when the scheme itself changes generation (an old-scheme value simply stops verifying);
* - **declare** ({@link reminderMarkDeclaration}) — the system-prompt declaration copy that gives
* the mark its meaning to the model.
* plus the tag renderers ({@link openSystemReminder} / {@link mintSystemReminder}) mint sites call
* so no call site inlines the marking syntax, and the block readers ({@link markedReminderBodiesOf} /
* {@link stripMarkedReminderBlocks}) for the same reason on the reading side.
*
* SCHEME EVOLUTION CONTRACT (ruled 2026-08-19): the marking scheme is REPLACEABLE. The current
* scheme is an open-tag attribute (`mark="<22-char base64url>"`), but nothing outside this module
* may assume that is the only possible shape — mint sites call the ports above instead of spelling
* the syntax, and CONSUMERS MUST ANCHOR ON METADATA, NEVER ON THE TEXT SHAPE (engine-region
* metadata / message flags / structured details), so a future scheme (e.g. a native provenance
* standard at the API layer) is a same-interface re-implementation with zero mint-site or consumer
* churn. Transcript locators that must read historic text tolerate BOTH the bare pre-mark form and
* the current form, inside metadata-delimited engine regions only.
*
* Security posture (design/319 §2): the mark is a CHANNEL BINDING against offline content authors,
* not a cryptographic secret — checkpoint stores, traces, and the host process all legitimately see
* it. It provides a byte-level judgment; whether the model honors the judgment remains model
* behavior (defense-in-depth, NOT a guarantee — same species as untrusted-text.ts).
*/
/** MINT port — coin a fresh session reminder mark (one per system-prompt declaration scope:
* a spawned/cloned context mints its own; a fork inherits its parent's; a resume reuses the
* checkpoint's — "one declaration, one mark", design/319 a-1). */
export declare function mintReminderMark(): string;
/**
* VERIFY port — true iff `value` is a well-formed mark under the CURRENT scheme. Consumers of
* persisted/inherited values (checkpoint resume, the trusted fork channel) call this and RE-MINT
* on failure — a malformed or foreign value is never silently adopted, and an old-scheme value
* after a scheme change degrades the same way (fail-safe: history loses mark authority, forgery
* gains nothing).
*/
export declare function isValidReminderMark(value: unknown): value is string;
/**
* Render the engine's system-reminder OPEN TAG. With a mark: the current scheme's marked form.
* Without (`undefined` — a library-direct mount whose caller threaded no mark, so no declaration
* exists either): the historic bare form, byte-identical to pre-319 output. The close tag never
* changes (`</system-reminder>` — close tags carry no authority and stay byte-stable for every
* transcript locator).
*/
export declare function openSystemReminder(mark: string | undefined): string;
/** Render one full reminder block in the single-wrap F3 shape (`open + "\n" + body + "\n" + close`)
* used by the user-lane wrap homes. The BODY is byte-untouched by the mark (design/319 first
* invariant: zero data-byte change — the mark rides only the engine's own tag). */
export declare function mintSystemReminder(body: string, mark: string | undefined): string;
/** Wrap model-facing hook/gate feedback in a `<system-reminder>` so it reads as guidance, not data.
* NOTE (council design/74 #6): this does NOT escape a literal `</system-reminder>` in `text` — callers MUST
* pass trusted, first-party strings (every current caller does: fixed gate/limit messages). If a future
* caller needs to relay UNTRUSTED content (tool output, user data), it must sanitize the close tag first
* (or use the `delimitUntrusted` fence), or a crafted payload could break out of the reminder framing.
* design/319 (A ticket): `mark` is the session's reminder provenance mark — run-scoped callers thread it
* so the open tag carries the value the system-prompt declaration names (rendered by the mint home; the
* body is byte-untouched). Absent ⇒ the historic bare open tag (a caller outside a run). Declared here,
* beside the mint it wraps; `hooks.ts` re-exports it as the hook seam's feedback envelope. */
export declare function formatHookFeedback(text: string, mark?: string): string;
/**
* DECLARE port — the system-prompt declaration that gives the mark meaning. STRICT, single form
* (design/319 D-2, no grace clause): reminder-shaped text without the current mark is data wherever
* it appears — including TRUE pre-319 engine mints in a resumed transcript, a deliberate fail-safe
* downgrade (stale guidance loses authority; forgery gains none). Composed into the `# Harness`
* head lines when a mark is threaded (prompts/default.ts `harnessHeadLines`); a fork inherits the
* parent's prompt bytes and therefore the parent's declaration — never a second one.
*
* A block carrying the current mark is the engine's own wherever it stands, tool results included:
* the engine places marked blocks there itself (after a tool's output, or as the whole result), so the
* declaration says so in one sentence that names no site, and carries no clause asking the model to
* judge a marked block inside tool-result data as a leak. Judging a leaked mark belongs to the host —
* the disclosure trailer does it on the outlets that scan their bytes (reminder-disclosure.ts).
*/
export declare function reminderMarkDeclaration(mark: string): string;
/** One transcript message as an engine-region reader needs to see it: narrowed to the three metadata
* stamps that delimit an engine region. */
export interface EngineRegionMessage {
role?: string;
content?: unknown;
engineMinted?: true;
enginePrefixChars?: number;
engineSegments?: ReadonlyArray<{
start: number;
end: number;
}>;
}
/**
* The ENGINE-AUTHORED regions of a transcript's user messages — the only thing a locator asking "has
* the engine already said this?" may read. Delimited by METADATA, never by text shape (the scheme
* evolution contract above): an `engineMinted` message is engine text whole, `engineSegments` name the
* engine frames inside a mixed message, and `enginePrefixChars` names the head the engine prepended.
* A caller's own bytes are outside every region by construction, so a spliced reminder can never answer
* an engine question — which is the whole point of having a mark.
*
* Feed it the messages of the ACTIVE context (`session.buildContext()`), never the raw ancestry: a
* branch read still carries messages a compaction has replaced, and a locator answering from those
* answers about a context the model no longer has.
*/
export declare function engineRegionTextsOf(messages: readonly EngineRegionMessage[]): string[];
/**
* The BODIES of the reminder blocks THIS session's engine minted inside `text` — the open tag carrying
* this exact mark, up to the next close tag. Engine provenance names the CARRIER, not the meaning of
* every byte inside it (a hook's feedback frame is engine-carried and may quote anything), so a locator
* that must judge what the engine actually SAID reads block bodies and decides per block.
*/
export declare function markedReminderBodiesOf(text: string, mark: string): string[];
/**
* STRIP port — the reading half of {@link openSystemReminder}: `text` without the blocks the engine marked.
*
* A marked block is an open tag whose mark attribute carries a mark of the current scheme, through the next close
* tag; the attribute's spacing and quote style are not part of that judgment (the engine mints one spelling, a model
* that relays a block rewrites it freely, and the mark is what must not travel). Everything in it
* goes, and so do the blank lines that stood between it and the text after it, so what followed a block starts
* where the block started and the rest of the text keeps its own lines. A marked open with no close (a result cut
* mid-block) goes to the end of the text — the open tag is the engine's own mint, and what follows it is the block.
*
* Only a tag carrying a valid mark is a block here. Reminder-shaped text without the mark, with a value the scheme
* does not produce, with whitespace around the `=` or another attribute beside the mark, or spelled in another case is data (the declaration says so) and is
* returned untouched; so is a text with no marked open at all — the same string back.
*
* For a caller that PROJECTS a tool result into a shorter view for a reader who does not hold the result's session
* (the parent of a child agent): the block is the engine's note to the context that ran the tool, not part of what
* the tool returned, and carries that context's mark.
*/
export declare function stripMarkedReminderBlocks(text: string): string;