UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

112 lines (111 loc) • 9.64 kB
/** * 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 {};