UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

161 lines (160 loc) • 12.7 kB
import type { Model, ModelRef, ModelRoles, ThinkingLevel } from "../core/types.js"; /** The deployment role map behind the runner (`Runner.agentCatalog.roles`) — same posture as `runnerModelCatalog` (subagent.ts): * host code, a throwing/absent stand-in reads as "no role map", never as a failed delegation. */ export declare function runnerRoleMap(runner: unknown): ModelRoles | undefined; /** * Did the deployment STATE a model for its subagents? True iff `roles.subagent` names a model source of its * own: a string ref other than the literal `"default"`, a `Model` object, or a `{ model | select }` config * whose `model` is not that literal. The word `"default"` is how a deployment spells "no subagent tier of my * own — same as the main model" (a catalog key of that name is the boot default), so it reads as NOT stated; * a role entry carrying only a `thinking` / `systemPrompt` preset states no model either. */ export declare function deploymentStatesSubagentModel(roles: ModelRoles | undefined): boolean; /** * Did the deployment STATE a thinking level for its subagents — and which? The `thinking` member of an * object-form `roles.subagent` entry; a string ref, a `Model` object or an entry without the member states * none (`undefined`). The thinking axis's twin of {@link deploymentStatesSubagentModel}: the SAME seat is * read (`roles.subagent` only — never the fallback chain, whose thinking the parent's own effective level * already reflects when the parent ran on it). */ export declare function deploymentSubagentThinking(roles: ModelRoles | undefined): ThinkingLevel | undefined; /** * Did the deployment STATE a system prompt for its subagents — and which? The `systemPrompt` member of an * object-form `roles.subagent` entry; a string ref, a `Model` object or an entry without the member states * none. The third axis, read off the SAME seat as {@link deploymentSubagentThinking}. It has no parent rung: * a child never inherits its PARENT's system prompt, only the deployment's statement for subagents. Which * station needs to read it: one whose default persona is written onto the spec itself (the workflow lane * frames an empty `systemPrompt` with the workflow-subagent persona, so the preset must be written first, as * a custom persona to that frame); the delegation station's default rides the trusted internals channel * below the role chain, so prepare reads the preset there without this helper. */ export declare function deploymentSubagentSystemPrompt(roles: ModelRoles | undefined): string | undefined; /** * The child run's THINKING seat — {@link childModelSeat}'s sister, the same rung order on the other axis * (#651): an EXPLICIT level for this spawn wins (the agent definition's `thinking`; a fork's explicit is the * caller's own level, as its model is the caller's); else the deployment's STATED subagent level * ({@link deploymentSubagentThinking}); else the PARENT run's effective level (the harness's current level — * a mid-run degrade/boost carries into new children); else nothing (the role chain decides, as before). * * The stated level is WRITTEN onto the spec rather than left to the role chain, unlike the model axis, * because the two axes resolve differently inside `resolveTaskModel`: the role chain reads `thinking` off * the role that SUPPLIED THE MODEL (the `subagent` entry when it states one, else the fallback role that * did), so a `roles.subagent` entry stating a level but no model would have its level skipped in favour of * the fallback role's. Writing the literal is the same value the seat rule resolves, on every seat shape; * the seat's role identity (`modelRole: "subagent"`, on every engine-spawned seat) still keys the * `systemPrompt` preset, which has no such fallback drift. * * Why the parent's level used to be wrong here: the parent's RESOLVED run level (`"off"` when the parent * declared nothing — the harness contract) was inherited as if it were an explicit declaration, and * `spec.thinking` outranks the role preset, so a deployment that stated `roles.subagent.thinking` watched * every child run with thinking off. The parent's level is the LAST rung now, where the model axis has it. */ export declare function childThinkingSeat(explicit: ThinkingLevel | undefined, deploymentStated: ThinkingLevel | undefined, parent: ThinkingLevel | undefined): { thinking?: ThinkingLevel; }; /** * The deployment's stated subagent tier as a resolved `Model` — for a spawn lane that needs the concrete * model up front (the workflow lane keys call identity and the display label on it), so both lanes read the * same rule: `undefined` when the deployment stated nothing (the caller then falls to the parent's model), * else the role chain's answer for `subagent`. A stated ref the catalog cannot resolve throws here, loudly, * exactly where a child's own prepare would have thrown. */ export declare function deploymentSubagentTierModel(deps: { models?: Record<string, Model>; roles?: ModelRoles; } | undefined): InheritedModelPick | undefined; /** * The child run's model seat — ONE rule for every engine-spawned run (the delegation child at * `buildChildSpec`, the observer sidecar, the workflow launch site): an EXPLICIT model for this spawn wins * (per-call `model`, the agent definition's `model`, the tool-level `model`); else a deployment that STATED * a subagent tier gets it (no model on the seat — the role chain resolves the stated entry, `select` forms * included); else the child runs on the PARENT run's effective model — taken as the REF that chose it and * resolved against the catalog generation in force at THIS spawn ({@link inheritedParentModel}), so * per-model routing/credentials ride along and a hot-swapped catalog reaches the child; else — no parent * model readable, which only happens outside a Runner-enriched ctx — the bare role, whose chain ends at the * deployment default. * * THE ROLE IDENTITY IS CONSTANT: every seat carries `modelRole: "subagent"`, the explicit-model seat too. * `resolveTaskModel` keys the role's `systemPrompt` / `thinking` presets on `modelRole` in BOTH of its * branches (an explicit model reads `roles[modelRole]`'s presets; a role-resolved model reads them along the * chain), so a deployment's `roles.subagent` preset reaches a child whatever decided its model. An explicit * seat without the identity read the `default` role's presets instead — a child spawned with a per-call or * definition model silently ran under the deployment's MAIN persona while its sibling with no explicit model * ran under the subagent one, and the workflow station (which writes the stated persona onto the spec) * disagreed with the delegation station on the same input. The two arms below differ ONLY in whether a * model rides the seat; the identity never varies. A FORK is not a seat of this rule — see {@link forkModelSeat}. * * Why "default" was the wrong last resort: `FALLBACK.subagent = ["default"]` resolves to the deployment's * boot default, which is not the model the parent run was switched to — a run whose main turn was moved to * model B by the caller spawned children on boot model A (and a deployment whose boot default was removed * from the roster spawned children that failed at prepare). The parent's effective model is the honest * default; the deployment's stated tier keeps outranking it because that statement is operator intent. */ /** The seat: the role identity, with the model that rides it (explicit or inherited) or none (the stated tier resolves through the role chain). */ export type ChildModelSeat = { modelRole: "subagent"; model?: ModelRef; }; /** * The PARENT rung's value, resolved at THIS spawn tick. The parent leg travels as the REF that chose its * model (`ToolExecuteContext.modelRef`), not as the object it has been holding since its own prepare, so: * · a catalog word resolves against the catalog generation in force NOW — a child spawned after a hot * swap runs on the generation its own prepare would have seen, which is what the swap law already * promises every string ref ("resolves against the new catalog at the next prepare"); * · a word the swap RETIRED throws here, loudly, under the same unknown-ref policy a caller-written * retired word meets — never a silent fall back to the retired object, which is the shape that let a * child run for its whole life on a generation the deployment had withdrawn; * · a `Model` OBJECT is its own ref and passes through pinned, the swap law's other existing arm. * Resolved lazily by {@link childModelSeat}, on the arm actually taken: a spawn that names its own model, * or a deployment that stated a subagent tier, never consults the parent rung and so never inherits its * refusal. A word with NO catalog to ask refuses under that same policy rather than travelling on as a * word: a seat that cannot name the model it seats is not a seat, and the caller learns it here. * * @contract child_seat.parent_model_travels_as_its_catalog_word — an engine-spawned run that inherits its * parent's model inherits the parent's CURRENT model BY THE REF THAT CHOSE IT, resolved once against the * catalog generation in force at the child's own spawn: a catalog word therefore lands the child on the * generation its own prepare would have seen (and refuses loudly, under the shared unknown-ref policy, when * the word has been retired), while a parent whose model is a `Model` object travels as that object and is * never re-resolved. The resolution happens at the spawn, not in the child's prepare, so the model the spawn * frames name and the model the child runs on stay one value. */ export declare function inheritedParentModel(parent: ModelRef | undefined, catalog: Record<string, Model> | undefined): Model | undefined; /** * What a spawn seats, and WHICH catalog word chose it. * * The two travel on different channels ON PURPOSE. `seat` is spread onto the child SPEC and carries the * RESOLVED object, so the model the spawn frames name is the model the child runs — nothing re-resolves * between the judgement and the child's prepare. `modelRef` is the word that object came from, and it rides * the TRUSTED internals channel (`RunInternals.inheritedModelRef`) so the child is not silently PINNED by * having been handed an object: its own children inherit the word in turn, and the deployment's per-model * window overlay — which is keyed by word — still reaches it. Absent `modelRef` = there is no word (an * object the caller pinned, a `select` criteria), and a leg with no word is never re-resolved and never * overlaid, which is exactly what pinning means. */ /** A resolved model together with the catalog WORD that chose it — the shape a lane hands its launch site * when the two travel separately (the object onto the child spec, the word onto its trusted channel). */ export interface InheritedModelPick { readonly model: Model; readonly modelRef?: ModelRef; } export interface ChildModelSeatPick<S> { readonly seat: S; readonly modelRef?: ModelRef; } export declare function childModelSeat(explicit: ModelRef | undefined, deploymentStated: boolean, parent: ModelRef | undefined, catalog?: Record<string, Model>, /** The word the CALLER resolved `explicit` from, when the caller resolved one (a per-call model word a * delegation gate judged into an object before the seat ever saw it). An explicit STRING needs none — it * travels as itself and the child's own prepare resolves it, which already leaves the child its word. */ explicitRef?: ModelRef): ChildModelSeatPick<ChildModelSeat>; /** * The FORK's model seat — the caller CONTINUED, not a subagent to the role chain. A fork always runs on the * caller's model (the caller's current `Model`, else the delegation tool's own `model`), and it carries NO * role identity: its system prompt is meant to be the PARENT's (the delegation station leaves the fork's * `systemPrompt` unset and threads no default persona, so prepare resolves the same role presets the * caller's own explicit-model run reads — the `default` role's — never the subagent persona a deployment * stated for its workers). No caller model readable (only outside a Runner-enriched ctx) ⇒ an empty seat: the * role chain's `default` start, exactly what a caller run with no `modelRole` of its own resolves. */ export declare function forkModelSeat(callerModel: ModelRef | undefined, inheritedRef?: ModelRef, catalog?: Record<string, Model>): ChildModelSeatPick<{ model?: ModelRef; }>;