@sema-agent/core
Version:
Stateless, task-oriented AI agent core
112 lines (111 loc) • 9.64 kB
TypeScript
/**
* The parent SPEC seat a run hands its children — the projection of the fields of the parent's own REBOUND spec
* that inherit copy-at-spawn (identity, process facts, deployment facts, the frozen question face) onto ONE value
* both delegation lanes copy onto a child spec: `ToolExecuteContext.parentSpecSeat` on the Task lane,
* `RunWorkflowToolDeps.parentSpecSeat` → `RunWorkflowOptions.parentSpecSeat` on the workflow lane. Minted once per
* prepare (the inherited-gate phase); applied by ONE function ({@link applyParentSpecSeat}) so the two lanes
* cannot drift on which fields travel or on the priority between a child's own value and the parent's.
*
* The checkpoint seat (`./checkpoint-seat.ts`) is the same shape for one field; this seat carries the rest.
*/
import type { OnQuestion } from "./ask-question.js";
import type { ModRegistration } from "./mods/kernel/index.js";
import type { EngineDollar } from "./mods/dollar.js";
import type { ToolExecuteContext } from "./tool-spec.js";
import type { TaskSpec } from "./types.js";
/** The fields of a spec the seat carries — one `Pick`, shared by the mint's input and the apply's target. */
export type ParentSpecSeatFields = Pick<TaskSpec, "principal" | "oneShot" | "clientContext" | "onQuestion" | "mods" | "additionalDirectories" | "additionalReadDirectories" | "envFacts" | "memoryPersistenceCapable" | "memory" | "compaction" | "skillListingBudgetFraction" | "skillListingMaxDescChars" | "fileHistory" | "promptForm" | "promptBinding">;
/**
* The seat a run hands its children. Every member is the parent's own value, frozen at mint (arrays copied and
* frozen; the facts object copied). Members present iff the parent carries them — an absent member is "nothing to
* inherit", never a value.
* · `principal` — the end-user identity (design/62: inherits verbatim, a worker cannot delegate under another).
* · `oneShot` — the process fact ("no later turn"): only `true` travels; it is a REMOVAL (see the apply law).
* · `clientContext` — the user's TZ/email facts (the child's environment block localizes the same way).
* · `onQuestion` — the parent's FROZEN question face (`spec.onQuestion ?? deps.onQuestion`, the value the parent's
* own AskUserQuestion resolves at): the child's question tool MOUNTS on it, so without it the child has no tool.
* · `mods` — the parent's TASK seat of the mods bus (`spec.mods`, the request-level registrations a host mounts from
* the user's settings). A deployment's `RunnerDeps.mods` already serve every leg; the task seat did not, so a
* `classic.Stop` handler a user wired ran for the leader and never for its teammates (CC runs one chain for the
* session and its subagents, the event carrying `agentId`). A child dispatches the travelled registrations as its
* own task seat (its events carry the child's identity); an ancestor's chain still rides the constraint fold, and
* collapses into the child's own phase 1 when the installation is the same. Same law as `onQuestion`: a child's
* own value wins, the seat fills an unset one.
* · `additionalDirectories` / `additionalReadDirectories` — the fs-fence widening (deployment property).
* · `envFacts` — the sandbox facts (profile / egress / scratchpad).
* · `memoryPersistenceCapable` — the deployment's DECLARED persistence statement: `false` is a REMOVAL (floor),
* `true` a default a child's own declaration may narrow.
* · `memoryOff` — the request-level "memory is off" FACT (`spec.memory.enabled === false`): a REMOVAL
* (floor) that REWRITES the child's own memory declaration instead of travelling as a value.
* · `compaction` — the request's CONTEXT-MANAGEMENT settings (enabled / withinTask / reserve / keepRecent /
* instructions / attachWorkingFiles / staleToolResultOffload / clampTolerance). One set of settings for the
* whole delegation tree, measured per leg against the leg's OWN window: the numbers are repaired against the
* child's model by the same sanitizer every leg runs (`sanitizeCompactionSettings`), so a parent's 128k-tuned
* reserve does not have to be re-tuned to be legal on a 6k child. `enabled: false` is NOT a removal — the
* reference implementation shares one settings object down the tree rather than flooring children, and a
* child that authors its own settings wins the whole object (an ORDINARY seat member).
* · `skillListingBudgetFraction` / `skillListingMaxDescChars` — the listing-budget knobs, the same shape for the
* same reason: a fraction the parent tuned is a fraction OF EACH LEG'S OWN window, so it travels as a number
* and every leg multiplies it by its own.
* · `fileHistory` — the request's file-history off switch (`"disabled"`, its only word), for the edits its delegates
* make on its behalf too. An ORDINARY member (fill law); with one word no child can author it back on.
* · `promptForm` — the host form of the system prompt (`"minimal"`, its only word: the core-behavior band is out). A
* host's product choice for the whole delegation tree, so every engine-built child (both delegation lanes and the
* observer sidecar) composes the same set of sections. An ORDINARY member (fill law).
* · `promptBinding` — the binding (a deployment's scenario name), a prompt selector value. A child composes its
* parent's center artifact, so its overlays' `when.binding` reads the same binding. An ORDINARY member (fill law).
*/
export interface ParentSpecSeat {
readonly principal?: string;
readonly oneShot?: true;
readonly clientContext?: TaskSpec["clientContext"];
readonly onQuestion?: OnQuestion;
readonly mods?: readonly ModRegistration<EngineDollar>[];
readonly additionalDirectories?: readonly string[];
readonly additionalReadDirectories?: readonly string[];
readonly envFacts?: TaskSpec["envFacts"];
readonly memoryPersistenceCapable?: boolean;
readonly memoryOff?: true;
readonly compaction?: TaskSpec["compaction"];
readonly skillListingBudgetFraction?: number;
readonly skillListingMaxDescChars?: number;
readonly fileHistory?: "disabled";
readonly promptForm?: "minimal";
readonly promptBinding?: string;
}
/** Mint the seat off the parent's REBOUND spec and its frozen question face (spec over deps — the caller resolves it once,
* the same value the parent's own question routing reads). Absent fields stay absent; `oneShot` only when `true`. */
export declare function parentSpecSeatOf(spec: Omit<ParentSpecSeatFields, "onQuestion">, frozenOnQuestion: OnQuestion | undefined): ParentSpecSeat;
/**
* Apply the seat to a child spec — ONE law for every member, both lanes:
* a value the child spec already carries WINS; the seat FILLS an unset one; the three REMOVAL values
* (`oneShot: true`, `memoryPersistenceCapable: false`, `memoryOff: true`) land UNCONDITIONALLY.
* "Already carries" is the child's own authorship — a deployment baseline, a trusted script, an agent definition's
* fold (the Task lane folds the definition's `memoryPersistenceCapable` before applying; the workflow lane applies at
* the launch site AFTER the agentType fold) — so a definition may narrow a parent `true` to `false`, a baseline-pinned
* principal stands, and nothing a child can author lifts a removal. Arrays and the facts object are copied per child
* (a retained child's frozen snapshot must not alias the parent's seat). Returns a NEW object; `child` is not mutated.
* No seat (a tool running outside a Runner task) ⇒ the child spec is returned as is.
*/
export declare function applyParentSpecSeat<S extends ParentSpecSeatFields>(child: S, seat: ParentSpecSeat | undefined): S;
/** The ONE way the memory-off floor is written onto a spec — the seat's apply above and the resume lane's
* clamp fold both call it: keep every field the spec authored (scopes, write scope, content-safety knobs)
* and state `enabled: false` over them. Returning a value rather than deleting the key IS the mechanism:
* the next seat mint reads `spec.memory.enabled === false`, so a spec whose `memory` was removed would end
* the floor one level down. A spec that declared nothing gets the bare `{ enabled: false }`, which the
* normalizer reads as "no usable scopes" — identical to the absent declaration it replaces. */
export declare function memoryTurnedOff(memory: TaskSpec["memory"]): NonNullable<TaskSpec["memory"]> & {
enabled: false;
};
/** The ctx keys that restate a seat member as THIS run's own fact (tools read them for scope / receipts / clamps). */
declare const SEAT_FACTS_ON_CTX: readonly ["principal", "oneShot", "onQuestion", "memoryPersistenceCapable"];
/**
* The seat a delegation boundary reads off a trusted ctx — ONE reader for both delegation tools. A Runner-filled ctx
* always carries the seat beside the run's own facts, and the two agree by construction (one mint). A HAND-BUILT ctx
* (a caller invoking a delegation tool's `execute` directly) that carries any of those facts WITHOUT the seat, or with
* a seat that disagrees with them, is REFUSED (`config.parent_spec_seat_missing`): spawning a principal-less child from a
* principal-bearing caller would offload into the shared namespace and re-key its journal — a silent tenant narrowing,
* never a fallback. A ctx with neither (a tool running outside a Runner task) hands down no seat.
*/
export declare function parentSpecSeatOfCtx(ctx: Pick<ToolExecuteContext, "parentSpecSeat" | (typeof SEAT_FACTS_ON_CTX)[number]>): ParentSpecSeat | undefined;
export {};