@sema-agent/core
Version:
Stateless, task-oriented AI agent core
161 lines (160 loc) • 12.7 kB
TypeScript
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;
}>;