@sema-agent/core 7.9.1 → 7.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/agents/child-model-seat.d.ts +51 -0
- package/dist/agents/child-model-seat.js +34 -0
- package/dist/agents/subagent.d.ts +4 -2
- package/dist/agents/subagent.js +16 -12
- package/dist/core/roles.d.ts +8 -0
- package/dist/core/runner/contracts.d.ts +3 -2
- package/dist/core/runner/prepare-caps-and-workflow.js +2 -2
- package/dist/core/runner/runtask.d.ts +2 -1
- package/dist/core/runner/runtask.js +1 -1
- package/dist/orchestration/workflow.js +21 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 7.9.2 — 2026-09-09
|
|
4
|
+
|
|
5
|
+
### Fixes — a delegated child runs on the parent run's model, not the deployment's boot default (#641 · B-050; @cli @server)
|
|
6
|
+
- **One seat, one rule.** `childModelSeat` (subagent.ts) is now the only place an engine-spawned run's model is decided — the delegation child (`buildChildSpec`, every lane: sync / background / fork / revival) and the observer sidecar share it: an EXPLICIT model for the spawn (per-call `model` > agent definition `model` > tool-level `model`) > the deployment's STATED subagent tier (`roles.subagent` naming a model source of its own — the literal `"default"` spells "no tier of my own", a `select` form counts as stated) > the **parent run's effective model** (the caller's current `Model` object, per-model routing and credentials included) > the bare `subagent` role (only reachable outside a Runner-enriched ctx). The cell that changed is the last resort: it used to be `FALLBACK.subagent → "default"`, i.e. the boot default — a run whose main turn ran on model B spawned children on boot model A, and a bare deployment (catalog, no roles) spawned children that died at prepare with `No model for role "subagent"`. Both now run on the parent's model. A deployment that stated a cheap tier keeps it (that statement is operator intent; children do not follow the parent there).
|
|
7
|
+
- **Role identity rides the seat.** Every non-explicit child spec carries `modelRole: "subagent"` beside the inherited `model`, so a deployment's `roles.subagent` `systemPrompt` / `thinking` preset still applies when the model is the parent's (the explicit-model branch of `resolveTaskModel` keys presets on the role).
|
|
8
|
+
- **Precedence note (definition lane).** The tool-level `model` (`createSubagentTool({ model })`) is an explicit seat and now outranks "inherit the parent" for a definition that names no model; it used to sit below the parent snapshot on that lane only. Fork children are unchanged (always the caller's model).
|
|
9
|
+
- **The workflow lane reads the same seat.** A model-less script child (`agent()` / `agentStream()`) used to take the host's `Model` unconditionally; the workflow mount's host-model getter now folds the deployment's stated tier first (`deploymentSubagentTierModel(runnerSelf.agentCatalog) ?? host model`, prepare-caps-and-workflow.ts — read off the LIVE catalog per call, so a `swapModels` between two spawns is seen by the second), so the Agent lanes and the workflow lane can no longer answer one deployment two ways. A script `model` and an `agentType` model keep outranking it (explicit), the allow-list keeps gating script words. A stated tier whose catalog ref is dead fails THAT call on the recorded path (failed agent record + `agent_end`, then the throw the script's own `catch` sees — the unknown-`agentType` shape; the next call's slot is released), never a substituted model. Both lanes' pre-launch catch now stamps an ordinary throw's message as the record's `errorMessage` (no machine code minted), so the row says why the ordinal has no child — previously only a review refusal or a budget stop named its cause.
|
|
10
|
+
- **The durable row names the inherited model.** A background child's record (`model`, the tier-3 revival key) records the seat's model — the parent's id for an inherited child, exactly what the child's own ticks say — so a revival from another parent keeps running the model the row was spawned on instead of silently moving to the reviver's; a stated-tier child's row stays blank as before (the role resolves it).
|
|
11
|
+
- **Read face (additive, @server @cli):** `Runner.agentCatalog` / `RunnerSelfSeat.agentCatalog` gains `roles?: ModelRoles` (a copy of `RunnerDeps.roles`) — the seat's reader. No wire change; no request-lane roles seat (the deployment/request boundary stands).
|
|
12
|
+
- **Engine side channels are unchanged by design** (`@contract roles.side_channels_deployment_only`): the auto-mode classifier, the compaction/summarize lane, memory consolidation and next-prompt suggestion resolve on the deployment roles alone and never take a run's model — they are deployment tools, not children of the run.
|
|
13
|
+
- Consumers: `@cli` the `_subagent-model-follows-run-live-pty.mjs` probe's `EXPECTED_VERDICT=b` door flips to `a` on this engine; `@server` zero code (`roles.subagent = "default"` when `MODEL_CHEAP_ID` is unset is exactly the "not stated" spelling).
|
|
14
|
+
|
|
15
|
+
### Docs — the sub-run inheritance matrix (#643; @cli @server @test)
|
|
16
|
+
- `docs/SUBRUN-INHERITANCE.md`: every inheritance face (model, approver seat + park facility, posture + spawn review, inherited gate + ancestor rules, thinking, role fallback, budgets/TTLs, event-forwarding identity keys, principal + auth hook, session/run lineage, contentOrigin) × every child lane (Agent sync / background / workflow agents / fork·team·synthesize / scheduler wake-up / observer·verifier·advisor / resume-rebuilt / tier-3 revival), each cell "inherits / falls back to (file:line) / gated?". Appendix A lists every deployment-default fallback position with its verdict (design → contract sentence; defect → ticket); Appendix B lists every fail-closed deny mouth with who reaches it and where it is visible.
|
|
17
|
+
- **B-049 (#642) is DEFINED here, not fixed in this release** (see the matrix §B-049): a workflow-lane child whose inherited ask the host approver answers `unavailable` denies fail-closed exactly like a SYNC delegation child (pinned three-lane: `test/backlog642-child-ask-lanes.test.ts`); only the BACKGROUND lane parks, because only it is handed the parent's `durableApproval` (§7.3 park family). Making workflow children park is the same park family extended to the workflow row (`wa*` row parked state + the workflow's own pause cascade + a resume path) — a design slice, scheduled separately; the interim is the loud terminal (the deny sentence in the tool result and `tool_end.gate.settlement.kind = "approver_unavailable"`).
|
|
18
|
+
|
|
3
19
|
## 7.9.1 — 2026-09-08
|
|
4
20
|
|
|
5
21
|
### Wire (additive) — `mcp.injection_dropped`: the request-lane MCP drop reaches the user (L-167; @server @cli @client-core)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Model, ModelRef, ModelRoles } from "../core/types.js";
|
|
2
|
+
/** The deployment role map behind the runner (`Runner.agentCatalog.roles`) — same posture as `runnerModelCatalog` (subagent.ts):
|
|
3
|
+
* host code, a throwing/absent stand-in reads as "no role map", never as a failed delegation. */
|
|
4
|
+
export declare function runnerRoleMap(runner: unknown): ModelRoles | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* Did the deployment STATE a model for its subagents? True iff `roles.subagent` names a model source of its
|
|
7
|
+
* own: a string ref other than the literal `"default"`, a `Model` object, or a `{ model | select }` config
|
|
8
|
+
* whose `model` is not that literal. The word `"default"` is how a deployment spells "no subagent tier of my
|
|
9
|
+
* own — same as the main model" (a catalog key of that name is the boot default), so it reads as NOT stated;
|
|
10
|
+
* a role entry carrying only a `thinking` / `systemPrompt` preset states no model either.
|
|
11
|
+
*/
|
|
12
|
+
export declare function deploymentStatesSubagentModel(roles: ModelRoles | undefined): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The deployment's stated subagent tier as a resolved `Model` — for a spawn lane that needs the concrete
|
|
15
|
+
* model up front (the workflow lane keys call identity and the display label on it), so both lanes read the
|
|
16
|
+
* same rule: `undefined` when the deployment stated nothing (the caller then falls to the parent's model),
|
|
17
|
+
* else the role chain's answer for `subagent`. A stated ref the catalog cannot resolve throws here, loudly,
|
|
18
|
+
* exactly where a child's own prepare would have thrown.
|
|
19
|
+
*/
|
|
20
|
+
export declare function deploymentSubagentTierModel(deps: {
|
|
21
|
+
models?: Record<string, Model>;
|
|
22
|
+
roles?: ModelRoles;
|
|
23
|
+
} | undefined): Model | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* The child run's model seat — ONE rule for every engine-spawned run (the delegation child at
|
|
26
|
+
* `buildChildSpec`, the observer sidecar): an EXPLICIT model for this spawn wins (per-call `model`, the
|
|
27
|
+
* agent definition's `model`, the tool-level `model`); else a deployment that STATED a subagent tier gets
|
|
28
|
+
* it (`{ modelRole: "subagent" }` — the role chain resolves the stated entry, `select` forms included);
|
|
29
|
+
* else the child runs on the PARENT run's effective model (the caller's current `Model` object, so
|
|
30
|
+
* per-model routing/credentials ride along); else — no parent model readable, which only happens outside
|
|
31
|
+
* a Runner-enriched ctx — the bare role, whose chain ends at the deployment default.
|
|
32
|
+
*
|
|
33
|
+
* The role identity `modelRole: "subagent"` stays on every non-explicit seat: the explicit-model branch of
|
|
34
|
+
* `resolveTaskModel` keys the role's `systemPrompt` / `thinking` presets on it, so a deployment's
|
|
35
|
+
* `roles.subagent` preset is not lost when the MODEL is inherited from the parent.
|
|
36
|
+
*
|
|
37
|
+
* Why "default" was the wrong last resort: `FALLBACK.subagent = ["default"]` resolves to the deployment's
|
|
38
|
+
* boot default, which is not the model the parent run was switched to — a run whose main turn was moved to
|
|
39
|
+
* model B by the caller spawned children on boot model A (and a deployment whose boot default was removed
|
|
40
|
+
* from the roster spawned children that failed at prepare). The parent's effective model is the honest
|
|
41
|
+
* default; the deployment's stated tier keeps outranking it because that statement is operator intent.
|
|
42
|
+
*/
|
|
43
|
+
/** The seat: an explicit ref alone, or the role identity with an optional inherited Model (see {@link childModelSeat}). */
|
|
44
|
+
export type ChildModelSeat = {
|
|
45
|
+
model: ModelRef;
|
|
46
|
+
modelRole?: undefined;
|
|
47
|
+
} | {
|
|
48
|
+
modelRole: "subagent";
|
|
49
|
+
model?: Model;
|
|
50
|
+
};
|
|
51
|
+
export declare function childModelSeat(explicit: ModelRef | undefined, deploymentStated: boolean, parent: Model | undefined): ChildModelSeat;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { resolveTaskModel } from "../core/roles.js";
|
|
2
|
+
export function runnerRoleMap(runner) {
|
|
3
|
+
try {
|
|
4
|
+
return runner?.agentCatalog?.roles;
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function deploymentStatesSubagentModel(roles) {
|
|
11
|
+
const s = roles?.subagent;
|
|
12
|
+
if (s == null)
|
|
13
|
+
return false;
|
|
14
|
+
if (typeof s === "string")
|
|
15
|
+
return s !== "default";
|
|
16
|
+
if ("id" in s && "api" in s)
|
|
17
|
+
return true;
|
|
18
|
+
const cfg = s;
|
|
19
|
+
if (cfg.model !== undefined)
|
|
20
|
+
return cfg.model !== "default";
|
|
21
|
+
return cfg.select !== undefined;
|
|
22
|
+
}
|
|
23
|
+
export function deploymentSubagentTierModel(deps) {
|
|
24
|
+
return deps !== undefined && deploymentStatesSubagentModel(deps.roles) ? resolveTaskModel({ modelRole: "subagent" }, deps).model : undefined;
|
|
25
|
+
}
|
|
26
|
+
export function childModelSeat(explicit, deploymentStated, parent) {
|
|
27
|
+
if (explicit !== undefined)
|
|
28
|
+
return { model: explicit };
|
|
29
|
+
if (deploymentStated)
|
|
30
|
+
return { modelRole: "subagent" };
|
|
31
|
+
if (parent !== undefined)
|
|
32
|
+
return { modelRole: "subagent", model: parent };
|
|
33
|
+
return { modelRole: "subagent" };
|
|
34
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RunInternals, RunnerSelfSeat } from "../core/runner/contracts.js";
|
|
2
2
|
import type { AgentDefinition, Model, ModelRef, TaskLimits, TerminalCause, ToolSpec } from "../core/types.js";
|
|
3
|
+
export { deploymentSubagentTierModel } from "./child-model-seat.js";
|
|
3
4
|
import { type ExecutionEnv } from "../internal/harness.js";
|
|
4
5
|
import type { TaskNotificationPayload } from "../core/task-notification.js";
|
|
5
6
|
import { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX } from "../config/defaults.js";
|
|
@@ -679,12 +680,13 @@ export interface SubagentToolOptions {
|
|
|
679
680
|
*/
|
|
680
681
|
ensureChildSessionDurable?: (sessionId: string) => Promise<void>;
|
|
681
682
|
};
|
|
682
|
-
/** Model the child runs on
|
|
683
|
+
/** Model the child runs on when neither the call nor the agent definition names one (the tool-level explicit seat).
|
|
684
|
+
* Omitted too ⇒ the deployment's stated `roles.subagent` tier, else the parent run's effective model (`childModelSeat`). */
|
|
683
685
|
model?: ModelRef;
|
|
684
686
|
/**
|
|
685
687
|
* Declarative sub-agents the model can pick from (design/38 1B). When set, the tool exposes an `agent`
|
|
686
688
|
* parameter (enum of these names) and the chosen definition's config builds the child task — its
|
|
687
|
-
* `allowTools`/`denyTools` REPLACE the tool-level ones, `model` omitted =
|
|
689
|
+
* `allowTools`/`denyTools` REPLACE the tool-level ones, `model` omitted = tool-level `model` > stated `roles.subagent` tier > the caller's model (`childModelSeat`).
|
|
688
690
|
* design/141 件A: when OMITTED, defaults to the runner's deployment catalog (`RunnerDeps.agents`) —
|
|
689
691
|
* one Runner, one registry across the Agent-tool and workflow lanes. Pass explicitly to narrow.
|
|
690
692
|
*/
|
package/dist/agents/subagent.js
CHANGED
|
@@ -7,6 +7,8 @@ import { newDelegationProvenanceAggregate, reduceDelegationAttestation } from ".
|
|
|
7
7
|
import { registerDelegationLaunch, replayExternalSettlementEffects, settleDelegation } from "../core/memory-engine/delegation-settlement.js";
|
|
8
8
|
import { enqueueMemoryAnnouncement } from "../core/memory-engine/layout.js";
|
|
9
9
|
import { resolveModel, resolveModelDisplayLabel } from "../core/roles.js";
|
|
10
|
+
import { childModelSeat, deploymentStatesSubagentModel, runnerRoleMap } from "./child-model-seat.js";
|
|
11
|
+
export { deploymentSubagentTierModel } from "./child-model-seat.js";
|
|
10
12
|
import { OUTPUT_TOOL_NAME, REPORT_BLOCKED_TOOL_NAME } from "../core/runner/synthetic-tools.js";
|
|
11
13
|
import { TOOL_SEARCH_NAME } from "../core/runner/tool-disclosure.js";
|
|
12
14
|
import { OFFLOAD_TOOL_NAME } from "../core/tool-result-store.js";
|
|
@@ -1466,7 +1468,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1466
1468
|
})),
|
|
1467
1469
|
model: Type.Optional(Type.String({
|
|
1468
1470
|
description: rosterNames !== undefined && rosterNames.length > 0
|
|
1469
|
-
? `Optional model override for this agent. Takes precedence over the agent definition's model. If omitted, uses the agent definition's model,
|
|
1471
|
+
? `Optional model override for this agent. Takes precedence over the agent definition's model. If omitted, uses the agent definition's model, else the deployment's subagent tier, else inherits from the parent. A value outside this deployment's model catalog is REFUSED — the call does not fall back silently. Ignored for subagent_type: "${FORK_SUBAGENT_TYPE}" — forks always inherit the parent model.`
|
|
1470
1472
|
: `Optional model override request. No model catalog is configured here, so a requested name cannot be resolved — the agent runs its definition's model (or inherits from the parent) and the result notes the override was not applied. Ignored for subagent_type: "${FORK_SUBAGENT_TYPE}".`,
|
|
1471
1473
|
})),
|
|
1472
1474
|
isolation: Type.Optional(Type.Literal("worktree", {
|
|
@@ -1784,9 +1786,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1784
1786
|
const childTools = def
|
|
1785
1787
|
? resolveToolSubset(childPool, def.allowTools, def.denyTools)
|
|
1786
1788
|
: resolveToolSubset(childPool, opts.allowTools, opts.denyTools);
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1789
|
+
const explicitModelChain = wantsFork ? [ctx.model, opts.model] : [perCallModel, def?.model, opts.model];
|
|
1790
|
+
let childModel = explicitModelChain.find((c) => c !== undefined);
|
|
1791
|
+
const deploymentStatedSubagentModel = deploymentStatesSubagentModel(runnerRoleMap(opts.runner));
|
|
1790
1792
|
if (reviveClaim !== undefined && typeof childModel === "string") {
|
|
1791
1793
|
const resolvable = (ref) => {
|
|
1792
1794
|
try {
|
|
@@ -1799,24 +1801,26 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1799
1801
|
};
|
|
1800
1802
|
if (!resolvable(childModel)) {
|
|
1801
1803
|
const dead = childModel;
|
|
1802
|
-
const rest =
|
|
1804
|
+
const rest = explicitModelChain.slice(explicitModelChain.indexOf(dead) + 1);
|
|
1803
1805
|
childModel = rest.find((c) => c !== undefined && (typeof c !== "string" || resolvable(c)));
|
|
1804
1806
|
if (modelNote === undefined) {
|
|
1805
1807
|
modelNote = `note: model "${inlineUntrusted(dead, 80)}" from this agent type's configuration is not on the current model roster and was NOT applied — the revived agent ran on its default (inherited) model.`;
|
|
1806
1808
|
}
|
|
1807
1809
|
}
|
|
1808
1810
|
}
|
|
1809
|
-
const
|
|
1811
|
+
const childSeat = childModelSeat(childModel, deploymentStatedSubagentModel, ctx.model);
|
|
1812
|
+
const seatModel = childSeat.model;
|
|
1813
|
+
const spawnModel = typeof seatModel === "string"
|
|
1810
1814
|
? (() => {
|
|
1811
1815
|
try {
|
|
1812
|
-
return resolveModel(
|
|
1816
|
+
return resolveModel(seatModel, judgedModels).id;
|
|
1813
1817
|
}
|
|
1814
1818
|
catch {
|
|
1815
|
-
return resolveModelDisplayLabel(
|
|
1819
|
+
return resolveModelDisplayLabel(seatModel);
|
|
1816
1820
|
}
|
|
1817
1821
|
})()
|
|
1818
|
-
:
|
|
1819
|
-
const rowModelKey = perCallModel !== undefined ? requestedModel ?? perCallModel.id : typeof
|
|
1822
|
+
: seatModel?.id;
|
|
1823
|
+
const rowModelKey = perCallModel !== undefined ? requestedModel ?? perCallModel.id : typeof seatModel === "string" ? seatModel : seatModel?.id;
|
|
1820
1824
|
const childSystemPrompt = def?.systemPrompt ?? opts.systemPrompt;
|
|
1821
1825
|
const childDefaultPersona = childSystemPrompt === undefined && !wantsFork ? SUBAGENT_PROMPT : undefined;
|
|
1822
1826
|
const defMaxTurns = typeof def?.maxTurns === "number" && Number.isFinite(def.maxTurns) && def.maxTurns > 0 ? def.maxTurns : undefined;
|
|
@@ -1888,7 +1892,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1888
1892
|
sessionId: sid,
|
|
1889
1893
|
...(resume ? { requireExistingSession: true } : {}),
|
|
1890
1894
|
objective: objectiveText,
|
|
1891
|
-
...(
|
|
1895
|
+
...childModelSeat(undefined, deploymentStatesSubagentModel(runnerRoleMap(opts.runner)), ctx.model),
|
|
1892
1896
|
...(observerDef.systemPrompt !== undefined && !resume ? { systemPrompt: observerDef.systemPrompt } : {}),
|
|
1893
1897
|
...(ctx.principal !== undefined ? { principal: ctx.principal } : {}),
|
|
1894
1898
|
...(ctx.getApiKeyAndHeaders !== undefined ? { getApiKeyAndHeaders: ctx.getApiKeyAndHeaders } : {}),
|
|
@@ -2140,7 +2144,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2140
2144
|
: undefined;
|
|
2141
2145
|
const buildChildSpec = (signal) => ({
|
|
2142
2146
|
objective: prompt,
|
|
2143
|
-
...
|
|
2147
|
+
...childSeat,
|
|
2144
2148
|
...(childThinking !== undefined ? { thinking: childThinking } : {}),
|
|
2145
2149
|
tools: childTools,
|
|
2146
2150
|
systemPrompt: childSystemPrompt,
|
package/dist/core/roles.d.ts
CHANGED
|
@@ -98,6 +98,14 @@ export interface ResolvedRole {
|
|
|
98
98
|
* Resolve the model (and default thinking) for a task. Explicit `spec.model` wins; otherwise walk the
|
|
99
99
|
* `modelRole`'s fallback chain over the merged roles (task overrides runner) and use the first role
|
|
100
100
|
* that supplies a model. Throws a helpful error if nothing resolves.
|
|
101
|
+
*
|
|
102
|
+
* @contract roles.side_channels_deployment_only — an engine SIDE CHANNEL (the auto-mode classifier,
|
|
103
|
+
* the compaction/summarize lane, memory consolidation, next-prompt suggestion) resolves here with a
|
|
104
|
+
* `modelRole` and NO run model: it runs on the deployment's roles alone — the boot default at the end of
|
|
105
|
+
* its chain — and never follows the model a run was switched to. Only a run's CHILDREN follow the parent:
|
|
106
|
+
* the delegation seat (`childModelSeat`) passes the parent's `Model` as `spec.model` with the role kept
|
|
107
|
+
* on `modelRole`, so the role's presets still apply. A side channel that must follow a run passes that
|
|
108
|
+
* run's model explicitly; nothing here infers it.
|
|
101
109
|
*/
|
|
102
110
|
export declare function resolveTaskModel(spec: {
|
|
103
111
|
model?: ModelRef;
|
|
@@ -43,7 +43,7 @@ import type { SystemInjectionPriority, TaskNotificationPayload } from "../task-n
|
|
|
43
43
|
import type { OnAsk, PermissionResult, ResolvedAsk, ToolCallRequest, ToolPolicy } from "../tool-policy.js";
|
|
44
44
|
import type { RemoteExecutionEnv, SnapshotId, WorkspaceHandle } from "../remote-env.js";
|
|
45
45
|
import type { ToolDisclosureManifest } from "../trace.js";
|
|
46
|
-
import type { AgentDefinition, Brain, CompactOutcome, NestedUsageAccum, RunnerDeps, TaskEvent, TaskResult, TaskSpec, TaskStream, ToolActivity, ToolEffect } from "../types.js";
|
|
46
|
+
import type { AgentDefinition, Brain, CompactOutcome, ModelRoles, NestedUsageAccum, RunnerDeps, TaskEvent, TaskResult, TaskSpec, TaskStream, ToolActivity, ToolEffect } from "../types.js";
|
|
47
47
|
import type { WiringManifest } from "../wiring-manifest.js";
|
|
48
48
|
import type { ActiveSkillFrame } from "./active-skill-scope.js";
|
|
49
49
|
import type { BudgetAxis } from "./assemble-result.js";
|
|
@@ -2377,11 +2377,12 @@ export interface RunnerDepsSeat {
|
|
|
2377
2377
|
export interface RunnerSelfSeat {
|
|
2378
2378
|
/** The session store this Runner was constructed over — the delegation tools acquire / release / fork through it. */
|
|
2379
2379
|
readonly sessions: SessionStore;
|
|
2380
|
-
/** The deployment agent + model catalog, READ-ONLY (a fresh copy per read; the definitions are the shared objects). */
|
|
2380
|
+
/** The deployment agent + model catalog, READ-ONLY (a fresh copy per read; the definitions are the shared objects); `roles` = `RunnerDeps.roles` (the delegation seat's `childModelSeat` reads `roles.subagent`). */
|
|
2381
2381
|
readonly agentCatalog: {
|
|
2382
2382
|
agents?: AgentDefinition[];
|
|
2383
2383
|
builtinAgents?: boolean;
|
|
2384
2384
|
models?: Record<string, Model>;
|
|
2385
|
+
roles?: ModelRoles;
|
|
2385
2386
|
};
|
|
2386
2387
|
/** The deployment-level gating baseline (deps toolPolicy / hooks) as delegate wrappers — for assemblers that compose with it. */
|
|
2387
2388
|
readonly gateBaseline: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isSyntheticApiErrorMessage } from "../../internal/harness.js";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { forkGovernanceDenial } from "../../agents/subagent.js";
|
|
3
|
+
import { deploymentSubagentTierModel, forkGovernanceDenial } from "../../agents/subagent.js";
|
|
4
4
|
import { CROSS_SESSION_CLASSIFIER_RULE } from "../../agents/cross-session-envelope.js";
|
|
5
5
|
import { createRunWorkflowTool } from "../../orchestration/run-workflow-tool.js";
|
|
6
6
|
import { isSelfOrchestrationActive } from "../../orchestration/workflow-script-runner.js";
|
|
@@ -243,7 +243,7 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
243
243
|
taskOwner: hostTaskId,
|
|
244
244
|
limits: deps.workflowLimits,
|
|
245
245
|
sourceTaskId: hostTaskId,
|
|
246
|
-
parentModel: () => harnessRef.current?.getModel(),
|
|
246
|
+
parentModel: () => deploymentSubagentTierModel(runnerSelf.agentCatalog) ?? harnessRef.current?.getModel(),
|
|
247
247
|
...(spec.getApiKeyAndHeaders !== undefined ? { parentGetApiKeyAndHeaders: spec.getApiKeyAndHeaders } : {}),
|
|
248
248
|
...(frozenOnAsk !== undefined ? { parentOnAsk: frozenOnAsk } : {}),
|
|
249
249
|
principal: spec.principal,
|
|
@@ -3,7 +3,7 @@ import { type CheckpointToken, type ResumeOutcome } from "../checkpoint-store.js
|
|
|
3
3
|
import { type TaskOutcome } from "../task-outcome.js";
|
|
4
4
|
import { type SideQuerySpec, type SideQueryResult } from "../side-query.js";
|
|
5
5
|
import type { SessionStore } from "../session.js";
|
|
6
|
-
import type { AgentDefinition, ModelRef, RunnerDeps, TaskEvent, TaskResult, TaskSpec, TaskStream } from "../types.js";
|
|
6
|
+
import type { AgentDefinition, ModelRef, ModelRoles, RunnerDeps, TaskEvent, TaskResult, TaskSpec, TaskStream } from "../types.js";
|
|
7
7
|
import type { ResumeRun, ResumeTaskConfig, RunInternals, RunnerSelfSeat } from "./contracts.js";
|
|
8
8
|
export type { ResumeTaskConfig } from "./contracts.js";
|
|
9
9
|
/**
|
|
@@ -74,6 +74,7 @@ export declare class Runner implements RunnerSelfSeat {
|
|
|
74
74
|
agents?: AgentDefinition[];
|
|
75
75
|
builtinAgents?: boolean;
|
|
76
76
|
models?: Record<string, Model>;
|
|
77
|
+
roles?: ModelRoles;
|
|
77
78
|
};
|
|
78
79
|
/** design/125 (实现审 B1) — the deployment-level gating baseline, exposed READ-ONLY so thin
|
|
79
80
|
* assemblers over the Runner (`runSpec`) can COMPOSE with it. The task-level engine semantic is a
|
|
@@ -138,7 +138,7 @@ export class Runner {
|
|
|
138
138
|
return {
|
|
139
139
|
...(this.deps.agents ? { agents: [...this.deps.agents] } : {}),
|
|
140
140
|
...(this.deps.builtinAgents !== undefined ? { builtinAgents: this.deps.builtinAgents } : {}),
|
|
141
|
-
...(this.deps.models ? { models: { ...this.deps.models } } : {}),
|
|
141
|
+
...(this.deps.models ? { models: { ...this.deps.models } } : {}), ...(this.deps.roles ? { roles: { ...this.deps.roles } } : {}),
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
144
|
get gateBaseline() {
|
|
@@ -1028,13 +1028,22 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1028
1028
|
const phaseInstance = resolveAgentPhase(agentOpts.phase);
|
|
1029
1029
|
const groupId = currentGroup;
|
|
1030
1030
|
const typeDefModel = agentOpts.agentType !== undefined ? agentRegistry.find((d) => d.name === agentOpts.agentType)?.model : undefined;
|
|
1031
|
-
|
|
1031
|
+
let inheritedModelSnap;
|
|
1032
|
+
let inheritedModelError;
|
|
1033
|
+
if (spec.model === undefined && typeDefModel === undefined) {
|
|
1034
|
+
try {
|
|
1035
|
+
inheritedModelSnap = opts.defaultModel?.();
|
|
1036
|
+
}
|
|
1037
|
+
catch (err) {
|
|
1038
|
+
inheritedModelError = err instanceof Error ? err : new Error(String(err));
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1032
1041
|
const specForIdentity = inheritedModelSnap !== undefined ? { ...spec, model: inheritedModelSnap } : spec;
|
|
1033
1042
|
const callKey = workflowAgentCallKey(run.agents.length, specForIdentity, agentOpts);
|
|
1034
1043
|
const prompt = boundedRedactedSummary(spec.systemPrompt ? `${spec.systemPrompt}\n\n${spec.objective}` : spec.objective, MAX_TRANSCRIPT_CHARS);
|
|
1035
1044
|
const specForLabel = spec.model === undefined && typeDefModel !== undefined ? { ...spec, model: typeDefModel } : specForIdentity;
|
|
1036
1045
|
const model = workflowModelLabel(specForLabel, modelCatalog);
|
|
1037
|
-
return { label, phase, phaseInstance, groupId, inheritedModelSnap, callKey, prompt, model };
|
|
1046
|
+
return { label, phase, phaseInstance, groupId, inheritedModelSnap, inheritedModelError, callKey, prompt, model };
|
|
1038
1047
|
};
|
|
1039
1048
|
const reviewSpawnBeforeLaunch = async (lane, label, callKey, runSpec, effectiveSignal) => {
|
|
1040
1049
|
const review = opts.autoModeReview;
|
|
@@ -1181,7 +1190,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1181
1190
|
},
|
|
1182
1191
|
async agent(spec, agentOpts = {}) {
|
|
1183
1192
|
const effectiveSignal = assertAgentSpawnAllowed("ctx.agent", spec, agentOpts);
|
|
1184
|
-
const { label, phase, phaseInstance, groupId, inheritedModelSnap, callKey, prompt, model } = prepareAgentCall(spec, agentOpts);
|
|
1193
|
+
const { label, phase, phaseInstance, groupId, inheritedModelSnap, inheritedModelError, callKey, prompt, model } = prepareAgentCall(spec, agentOpts);
|
|
1185
1194
|
if (opts.resumeFromRunId !== undefined && !diverged) {
|
|
1186
1195
|
const cached = replayByOrdinal[run.agents.length];
|
|
1187
1196
|
if (cached && cached.callKey === callKey && TERMINAL_CAUSE_IS_REPLAYABLE[cached.result.terminal.kind]) {
|
|
@@ -1265,6 +1274,8 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1265
1274
|
rec.startedAt = now();
|
|
1266
1275
|
const bornChildSessionId = resolveChildSessionIdAtSpawn(spec);
|
|
1267
1276
|
bceSpawn(callKey, label, agentOpts.agentType, false, bornChildSessionId, model);
|
|
1277
|
+
if (inheritedModelError !== undefined)
|
|
1278
|
+
throw inheritedModelError;
|
|
1268
1279
|
const typedSpec0 = applyWorkflowAgentType(spec, agentOpts.agentType, agentRegistry);
|
|
1269
1280
|
const typedSpec = typedSpec0.model === undefined && inheritedModelSnap !== undefined ? { ...typedSpec0, model: inheritedModelSnap } : typedSpec0;
|
|
1270
1281
|
const framedSpec = withWorkflowChildPersona(typedSpec, agentOpts.schema ?? typedSpec.outputSchema);
|
|
@@ -1490,6 +1501,8 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1490
1501
|
rec.errorCode = err.code;
|
|
1491
1502
|
rec.errorMessage = boundedRedactedSummary(err.message, MAX_TRANSCRIPT_CHARS);
|
|
1492
1503
|
}
|
|
1504
|
+
if (rec.errorMessage === undefined && err instanceof Error && err.message !== "")
|
|
1505
|
+
rec.errorMessage = boundedRedactedSummary(err.message, MAX_TRANSCRIPT_CHARS);
|
|
1493
1506
|
if (err instanceof WorkflowAgentStalledError && err.attempts > 1) {
|
|
1494
1507
|
rec.attempts = err.attempts;
|
|
1495
1508
|
rec.lastAttemptReason = "stalled";
|
|
@@ -1529,7 +1542,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1529
1542
|
if (budgetTotal !== null && spent() >= budgetTotal) {
|
|
1530
1543
|
throw new WorkflowBudgetExceededError(spent(), budgetTotal);
|
|
1531
1544
|
}
|
|
1532
|
-
const { label, phase, phaseInstance, groupId, inheritedModelSnap, callKey, prompt, model } = prepareAgentCall(spec, agentOpts);
|
|
1545
|
+
const { label, phase, phaseInstance, groupId, inheritedModelSnap, inheritedModelError, callKey, prompt, model } = prepareAgentCall(spec, agentOpts);
|
|
1533
1546
|
noteDivergence(run.agents.length, "ctx.agentStream results are never replayed");
|
|
1534
1547
|
diverged = true;
|
|
1535
1548
|
const rec = { label, callKey, ...(groupId !== undefined ? { groupId } : {}), phase, prompt, ...(model !== undefined ? { model } : {}), status: "running", queuedAt: now() };
|
|
@@ -1581,6 +1594,8 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1581
1594
|
bceSpawn(callKey, label, agentOpts.agentType, false, childSessionId, model);
|
|
1582
1595
|
let stream;
|
|
1583
1596
|
try {
|
|
1597
|
+
if (inheritedModelError !== undefined)
|
|
1598
|
+
throw inheritedModelError;
|
|
1584
1599
|
const typedSpec0 = applyWorkflowAgentType(spec, agentOpts.agentType, agentRegistry);
|
|
1585
1600
|
const typedSpec = typedSpec0.model === undefined && inheritedModelSnap !== undefined ? { ...typedSpec0, model: inheritedModelSnap } : typedSpec0;
|
|
1586
1601
|
const framedSpec = withWorkflowChildPersona(typedSpec, agentOpts.schema ?? typedSpec.outputSchema);
|
|
@@ -1622,6 +1637,8 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1622
1637
|
stream = workflowDepthStore.run({ depth: depth + 1 }, () => runner.runTaskStream(runSpec, undefined, runInternals));
|
|
1623
1638
|
}
|
|
1624
1639
|
catch (err) {
|
|
1640
|
+
if (!finalized && rec.errorMessage === undefined && err instanceof Error && err.message !== "")
|
|
1641
|
+
rec.errorMessage = boundedRedactedSummary(err.message, MAX_TRANSCRIPT_CHARS);
|
|
1625
1642
|
if (!finalized && err instanceof WorkflowAgentBlockedError) {
|
|
1626
1643
|
rec.errorCode = WORKFLOW_SPAWN_BLOCKED_ERROR_CODE;
|
|
1627
1644
|
rec.errorMessage = boundedRedactedSummary(err.message, MAX_TRANSCRIPT_CHARS);
|