UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

139 lines (138 loc) • 10.2 kB
/** * 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;