@sema-agent/core 7.0.0 → 7.0.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 +32 -0
- package/dist/agents/launch-receipt-contract.d.ts +34 -0
- package/dist/agents/launch-receipt-contract.js +5 -0
- package/dist/agents/subagent.d.ts +134 -2
- package/dist/agents/subagent.js +160 -35
- package/dist/core/file-history-store.js +24 -2
- package/dist/core/memory-engine/engine.d.ts +62 -5
- package/dist/core/memory-engine/engine.js +90 -19
- package/dist/core/memory-engine/index.d.ts +1 -1
- package/dist/core/memory-engine/layout.d.ts +11 -3
- package/dist/core/roles.d.ts +6 -1
- package/dist/core/roles.js +10 -1
- package/dist/core/runner/prepare-memory.js +29 -22
- package/dist/core/runner/prepare-task.d.ts +3 -2
- package/dist/core/runner/prepare-task.js +13 -8
- package/dist/core/runner/runtask.js +2 -0
- package/dist/core/task-notification.d.ts +20 -0
- package/dist/core/types.d.ts +84 -3
- package/dist/core/wiring-manifest.d.ts +18 -1
- package/dist/index.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +9 -1
- package/dist/orchestration/run-workflow-tool.js +13 -8
- package/dist/orchestration/workflow.d.ts +9 -1
- package/dist/orchestration/workflow.js +22 -14
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 7.0.2 — 2026-08-30
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **`MemoryEngine.sessionMemoryStatus(sessionId)`** (#511①, design/383 §S-7 — the signed-design read face missed at build): `{captureOptedOut?, committedCount?, foldedCount?, optOutSource?, lastCaptureAt?}`. Every key optional — absence means that fact's source was unreadable, never a coined false/0 stand-in; a store fault answers `optOutSource:"fault"` with `captureOptedOut` ABSENT (the indeterminate axis projected); `foldedCount` counts evidence-only input rows over the full product blocks; the face never throws. New exported type `SessionMemoryStatus` (export surface 1841→1842). Server projects the wire endpoint as a pure derivation ([5786]).
|
|
7
|
+
- **`SessionCaptureRecordStore` dual form** (#511②): the three legs widen to `T | Promise<T>` — a SQL/async backend plugs in with Promise signatures while every existing sync implementation compiles untouched (the additive mechanical proof). The dual form propagates: sync deployments stay sync-transparent end to end; the sync-facing public seats (`MemoryEngineSession.captureOptOut.*`, `ToolExecuteContext.memoryCaptureOptedOut/…Indeterminate`, `parentMemoryCaptureState`) go dual with the await law documented — per-leg failure mapping preserved (mark rejection ⇒ unpersisted, read rejection ⇒ the indeterminate fault axis).
|
|
8
|
+
- **`best` model alias** (#511③, CC 250 $L parity): with `RunnerDeps.tiers` configured, `"best"` resolves to the first DIRECTLY bound tier walking `ultra→max→pro→flash→lite` from the head ("best available", deliberately not a fixed alias-table row). Explicit spellings win over the dynamic walk: a deployment catalog key named `best`, or an explicit `tiers.best` binding. No tiers ⇒ the word stays an unknown ref (refused, never coined).
|
|
9
|
+
|
|
10
|
+
### Changed — narrowing (named)
|
|
11
|
+
- **Agent tool `model` argument: non-string values on the schema-外 direct-call lane refuse typed** (#511④, test [5781] codex observation): a number/array/boolean/garbage-object now refuses `model.invalid` (closed-set ADDITION beside `model.empty`/`model.unknown` — downstream pinning the details.error set gains a row) instead of crashing or silently no-oping; a legal Model-shape object (string id+api) passes through as the per-call model; strings keep every existing arm byte-identical.
|
|
12
|
+
- **`modelNote` fork-ignored and roster-less arms defuse the model-authored word** (#511⑤, census P2): both arms now run the same `inlineUntrusted` fold (newline fold + fence/envelope-tag neutralization + 80 cap) the family's other three arms already ran — a hostile `model` word can no longer carry raw newlines or fence sentinels into a top-level report line. Clean short words render byte-identical.
|
|
13
|
+
|
|
14
|
+
## 7.0.1 — 2026-08-30
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **`task_progress.model` + `BackgroundChildEvent.model`** (#508①, cli [5725] item-B): every spawn-progress tick and background child event now carries the SERVED model id (PREPARED semantics — the id the child's prepare actually resolved, not the word the caller wrote). The fleet row publishes the same served id, so row/tick/card lanes state one fact; a replayed workflow leg reports the cached model on every face.
|
|
18
|
+
- **`apiFailure` on the completed-agent details card and both notification lanes** (#506 residual): terminal API-failure evidence now reaches every reader of a settled child, not only the host-face `TaskResult`.
|
|
19
|
+
|
|
20
|
+
### Changed — narrowing (named)
|
|
21
|
+
- **Unknown/empty delegation `model` argument: soft note → typed reject** (#508②, CC $L closed-set anchored): `model.unknown` (echoes the accepted catalog) and `model.empty` replace the old accept-with-note behavior. A caller that previously leaned on the note-and-serve-default path now gets a refusal naming the legal values. Positive control: every cataloged id serves exactly as before. **Two lanes are exempt** (pre-ship rescan, the caller-census duty): the tier-3 revival lane feeds the durable row's MACHINE-minted recorded word back through this gate, so it keeps the soft-degrade (loud `modelNote` + inherited model — a catalog rotation must not permanently strand a named teammate and its queued mail), and its fallback chain skips string candidates the live catalog cannot resolve (a degrade that re-selects the retired word would revive a child dead on arrival); `subagent_type: "fork"` keeps the schema's own "model is ignored" promise for the empty string exactly as for any other word. Membership itself hardened: the catalog read is own-property (`Object.hasOwn`), so `Object.prototype` words (`constructor`, `toString`, …) refuse as `model.unknown` instead of passing the gate and crashing the child.
|
|
22
|
+
- **Per-call model binding is resolved AT the gate** (rescan): a judged per-call word now rides the child spec as the resolved `Model` object, so the judged roster is the authoritative binding — an explicit `opts.models` table naming a key the runner's own catalog lacks now serves on the judged binding instead of passing the gate and dying inside the child's prepare. `BackgroundChildEvent.model` / roster-row docs restated to the served-id-first rule (display label is the unresolvable-ref fallback only); the residual one-generation lag window for definition/tool-level string refs after `swapModels` is documented, not claimed away. The background roster record gains optional `modelFallback` (additive; fork lane already carried it).
|
|
23
|
+
- **File-history mode domain, both doors** (security): the import door refuses a version-row mode that is not an integer in [0, 0o7777] and refuses set-ID modes (setuid/setgid) BY NAME; on replay, `applyRecordedMode` STRIPS the set-ID bits and applies the remainder (`mode & 0o1777`, sticky preserved) under the loud `modeNotPreserved` account — set-ID bits never reach chmod (capture may legitimately record one; replay is where the escalation lives). A file already at exactly the recorded mode answers `unchanged` with no account (the equality fast path precedes the escalation arm), and `previewFileDelta` compares against what replay would actually apply, so a set-ID-recorded drift converges instead of being promised forever.
|
|
24
|
+
|
|
25
|
+
### Changed — widening (named)
|
|
26
|
+
- **The delegation tool's model roster is read at JUDGEMENT time, not frozen at mount** (#508 residual): after `Runner.swapModels`, a long-lived mount no longer hard-rejects the models the deployment just started serving (the old refusal echoed the RETIRED generation as the accepted values). Explicit `opts.models` keeps snapshot semantics; only the runner-derived roster re-reads live. The schema/announce projection remains a mount-time snapshot — the refusal therefore carries its own accepted-values echo.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- Flaky-guard ledger: the three new pin files registered as bounded effect-polling (gate was red since the #508 batch landed).
|
|
30
|
+
|
|
31
|
+
### Docs
|
|
32
|
+
- #509 core half: the subagent transcript LANDING law (three store shapes + the CC 2.1.250 unconditional-landing anchor) spelled out at `SubagentToolOptions.runner`, the background placement mint gate, and `resolveSubagentTranscriptTier`; reproduced-and-located, wiring fix owned by the delegation runner's store choice.
|
|
33
|
+
- 6.0.0 release-criteria acceptance corrections (test [5719]/[5721]): G1.a added-count 65→77, F7 absent-seat claim restated.
|
|
34
|
+
|
|
3
35
|
## 7.0.0 — 2026-08-30
|
|
4
36
|
|
|
5
37
|
> Ships as its own major immediately after 6.0.0 (ruled: the three-dimensional rule scope travels alone so downstream adapts to it in isolation). The 6.0.0 artifact is this same tree with the design/382 P1 surface excised whole (type-driven surgery in an isolated worktree, tag v6.0.0); `npm run handoff:diff v6.0.0 v7.0.0` reproduces exactly this surface.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HANDLING contract shared by every tool that launches background work and hands the model a
|
|
3
|
+
* receipt: the Agent tool's async-launch card and `run_workflow`'s backgrounded launch receipt.
|
|
4
|
+
*
|
|
5
|
+
* Why a module rather than two copies. Both receipts state the same two things — "these bytes are
|
|
6
|
+
* plumbing, do not paste them at the user" and "say what you launched in your own words" — and they
|
|
7
|
+
* had drifted into two wordings of it (`; ` vs ` — `, "this result" vs "this tool result"). Neither
|
|
8
|
+
* difference carried meaning; each was just the copy that happened to be typed second. A model reading
|
|
9
|
+
* one receipt and a model reading the other should be reading the same instruction, and a later edit
|
|
10
|
+
* to one of them should not be able to leave the other behind.
|
|
11
|
+
*
|
|
12
|
+
* What deliberately stays PER-LANE (the earlier ruling, narrowed rather than reversed): each receipt
|
|
13
|
+
* hands back different artifacts, so each names its own. The Agent card names `task_id`; the workflow
|
|
14
|
+
* receipt names the ids plus the host filesystem `scriptPath` it may have written. That enumeration is
|
|
15
|
+
* a fact about the lane and cannot be centralized without one of them lying. So the SCOPE clause is the
|
|
16
|
+
* caller's, and the sentence around it is this module's. Likewise the envelopes stay as they were —
|
|
17
|
+
* the Agent lane renders prose, the workflow lane a JSON field — which is why these are string parts
|
|
18
|
+
* rather than a rendered receipt.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* The no-quote clause for one lane. `artifactScope` is the caller's own enumeration of what "any part
|
|
22
|
+
* of it" covers, spliced in verbatim INCLUDING its leading separator — a comma clause
|
|
23
|
+
* (`", including the task_id below,"`) and a parenthetical (`" (the ids above, and scriptPath when
|
|
24
|
+
* present)"`) punctuate differently and the caller owns which it uses. Pass `""` for a lane with
|
|
25
|
+
* nothing to enumerate; the sentence still reads.
|
|
26
|
+
*/
|
|
27
|
+
export declare function launchReceiptNoQuoteClause(artifactScope: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* The closing instruction, whole and identical on every launch lane: report the launch in the model's
|
|
30
|
+
* own words instead of echoing the receipt. Callers append their own lane-specific continuation (how
|
|
31
|
+
* the result will or will not arrive) after a single space; this clause is a complete sentence on its
|
|
32
|
+
* own so a lane with nothing to add can use it unchanged.
|
|
33
|
+
*/
|
|
34
|
+
export declare const LAUNCH_RECEIPT_OWN_WORDS_CLAUSE = "In your own words, briefly tell the user what you launched \u2014 do not echo this tool result.";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
const NO_QUOTE_HEAD = "This tool result is internal metadata — never quote or paste any part of it";
|
|
2
|
+
export function launchReceiptNoQuoteClause(artifactScope) {
|
|
3
|
+
return `${NO_QUOTE_HEAD}${artifactScope} into a user-facing reply.`;
|
|
4
|
+
}
|
|
5
|
+
export const LAUNCH_RECEIPT_OWN_WORDS_CLAUSE = "In your own words, briefly tell the user what you launched — do not echo this tool result.";
|
|
@@ -4,6 +4,60 @@ import { type ExecutionEnv } from "../internal/harness.js";
|
|
|
4
4
|
import type { RunInternals } from "../core/runner/prepare-task.js";
|
|
5
5
|
import type { TaskNotificationPayload } from "../core/task-notification.js";
|
|
6
6
|
import { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX } from "../config/defaults.js";
|
|
7
|
+
/**
|
|
8
|
+
* #505 residual — the two GRANULARITY facts a delegation's terminal `usage` was missing beside
|
|
9
|
+
* tokens/turns/cost, closing a real reporting asymmetry rather than adding decoration: the workflow
|
|
10
|
+
* lane's completion notification has published `tool_uses` (summed `stats.toolCalls`) and `duration_ms`
|
|
11
|
+
* (wall clock) since it was written, and the Agent tool's own SYNC report publishes both in its
|
|
12
|
+
* `<usage>` footer — only the delegation TERMINAL faces (the background/fork/revive notification and its
|
|
13
|
+
* observer twin) reported neither, so the same child answered "how much work, how long" on one lane and
|
|
14
|
+
* refused on the other.
|
|
15
|
+
*
|
|
16
|
+
* PROVENANCE is the workflow lane's — `stats.toolCalls`, the RUN's own count — because that is the
|
|
17
|
+
* quantity the other side of the comparison publishes, and because it is available on every terminal
|
|
18
|
+
* lane, including the revive lane, which has no parent-side forwarding observer at all.
|
|
19
|
+
*
|
|
20
|
+
* WHAT THIS DOES NOT CLOSE, stated because the honest scope is narrower than "one name, one meaning":
|
|
21
|
+
* the Agent tool's SYNC `<usage>` footer keeps its own `tool_uses`, counted by the parent-side
|
|
22
|
+
* `toolStatsCounter`, and the two numbers differ in BOTH directions for the same child — that counter
|
|
23
|
+
* EXCLUDES the delegation tool's own calls and INCLUDES descendants' (CC's nested fold), while
|
|
24
|
+
* `stats.toolCalls` counts the direct child's calls and counts its delegations among them. A child that
|
|
25
|
+
* itself delegates therefore reports two different `tool_uses` depending on whether it was run
|
|
26
|
+
* synchronously or in the background. That footer is a byte-pinned CC-anchored face; reconciling the
|
|
27
|
+
* two is a separate ruling, and until it is made this comment is the place the divergence is written
|
|
28
|
+
* down rather than a claim that it is gone.
|
|
29
|
+
*
|
|
30
|
+
* Knownness follows `delegatedCostField`'s rule exactly: `toolCalls` is optional on the stats face
|
|
31
|
+
* ("undefined means the gateway reported no usage", and a checkpointed resume folds it per-leg), so the
|
|
32
|
+
* key is ABSENT rather than zeroed — a fabricated 0 reads as "ran no tools", which is a claim, not a
|
|
33
|
+
* gap. Duration is always knowable (the caller holds the lane's launch instant), so it is unconditional.
|
|
34
|
+
*
|
|
35
|
+
* TWO SPELLINGS, one meaning, matching each face's established vocabulary: the observer
|
|
36
|
+
* ({@link import("../core/types.js").BackgroundChildEvent}) `usage` face is camelCase and already
|
|
37
|
+
* DECLARES `toolUses`/`durationMs`; the model-facing `TaskNotificationPayload.usage` is rendered into
|
|
38
|
+
* `<usage>` as JSON where the workflow lane and the Agent sync footer both spell them snake_case. No new
|
|
39
|
+
* word is minted on either side.
|
|
40
|
+
*
|
|
41
|
+
* Exported at MODULE level (not from the package index — the same posture as `completedAgentCard` and
|
|
42
|
+
* `classifySubagentError`, and for the same reason): the ABSENT arm is the point of the knownness rule
|
|
43
|
+
* and it is not reachable through a real child. This engine's runner initializes `stats.toolCalls` to a
|
|
44
|
+
* number on every path, so only a `TaskResult` from elsewhere — a foreign runner, an older checkpoint —
|
|
45
|
+
* can carry the optional-and-missing shape the stats face declares. A pin that only ever sees a live
|
|
46
|
+
* child cannot tell "absent" apart from "zero", which is precisely the confusion being prevented.
|
|
47
|
+
*/
|
|
48
|
+
export declare function delegatedGranularityFields(stats: {
|
|
49
|
+
toolCalls?: number;
|
|
50
|
+
}, elapsedMs: number): {
|
|
51
|
+
toolUses?: number;
|
|
52
|
+
durationMs: number;
|
|
53
|
+
};
|
|
54
|
+
/** {@link delegatedGranularityFields}'s snake_case twin for the model-facing notification `usage`. */
|
|
55
|
+
export declare function delegatedGranularityFieldsWire(stats: {
|
|
56
|
+
toolCalls?: number;
|
|
57
|
+
}, elapsedMs: number): {
|
|
58
|
+
tool_uses?: number;
|
|
59
|
+
duration_ms: number;
|
|
60
|
+
};
|
|
7
61
|
export { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX };
|
|
8
62
|
import { SubagentRetainLedger } from "./retain-ledger.js";
|
|
9
63
|
import { type PeerSelfRef } from "./peer-admission.js";
|
|
@@ -268,6 +322,17 @@ export declare function completedAgentCard(child: {
|
|
|
268
322
|
blockedReason?: string;
|
|
269
323
|
errorMessage?: string;
|
|
270
324
|
errorCode?: string;
|
|
325
|
+
/** The child run's provider-boundary fault assertion, verbatim from its `TaskResult.apiFailure`.
|
|
326
|
+
* PRESENCE is the claim ("this child's terminal failure came from the transport/provider, not from
|
|
327
|
+
* this deployment refusing to send, a limit, or unusable model output"); the members are whatever
|
|
328
|
+
* the failing attempt stated about itself (`status` when the provider answered with one, `requestId`
|
|
329
|
+
* when it named one). An EMPTY object is therefore meaningful and must not be normalized away: it
|
|
330
|
+
* says "provider fault, unlabelled", which is a different statement from absence ("not a provider
|
|
331
|
+
* fault"). Absent on every non-provider terminal and on every completed child. */
|
|
332
|
+
apiFailure?: {
|
|
333
|
+
status?: number;
|
|
334
|
+
requestId?: string;
|
|
335
|
+
};
|
|
271
336
|
retryAfterMs?: number;
|
|
272
337
|
degraded?: unknown;
|
|
273
338
|
structuredOutput?: unknown;
|
|
@@ -514,9 +579,61 @@ export declare function delegationEntryLedgerFootprint(registry: object): {
|
|
|
514
579
|
* catalog) after mounting must REBUILD the ToolSpec — call `createSubagentTool` again with the fresh
|
|
515
580
|
* roster and serve the new tool — for the change to take effect; already-running tasks keep the roster
|
|
516
581
|
* they were prepared with.
|
|
582
|
+
*
|
|
583
|
+
* The MODEL roster ({@link SubagentToolOptions.models}) deliberately does NOT follow that rule when it
|
|
584
|
+
* is left to the runner: a hot-swapped catalog is a first-class runner verb (`Runner.swapModels`) with
|
|
585
|
+
* no rebuild step, so the tool re-reads it at each judgement instead of requiring a remount. Only the
|
|
586
|
+
* schema/announce projection of it stays a mount-time snapshot (cache-prefix stability). Passing
|
|
587
|
+
* `models` explicitly opts back into snapshot semantics for that table — see the field.
|
|
517
588
|
*/
|
|
518
589
|
export interface SubagentToolOptions {
|
|
519
|
-
/**
|
|
590
|
+
/**
|
|
591
|
+
* Runner used to execute child tasks.
|
|
592
|
+
*
|
|
593
|
+
* **This runner's session store decides where a child TRANSCRIPT lives — and therefore whether one
|
|
594
|
+
* exists at all after the fact.** A SYNC or BACKGROUND child mints its session through
|
|
595
|
+
* `runner.sessions`, never through the parent leg's store, so a deployment that mounts the Agent
|
|
596
|
+
* tool on a DIFFERENT runner than the one serving root tasks has split the transcript lane away
|
|
597
|
+
* from the session lane. (The FORK lane is the deliberate exception and shows the split is a known
|
|
598
|
+
* shape: a fork branches the CALLER's session through `ctx.hostSessionFork` — the host Runner's
|
|
599
|
+
* own store — precisely because `runner` may be a split child-execution runner whose store never
|
|
600
|
+
* held the source. A fork child therefore follows the HOST store's fate, an ordinary child this
|
|
601
|
+
* runner's, and the same deployment can land one on disk and lose the other.) Three shapes for the
|
|
602
|
+
* ordinary lanes, all reachable and all silent:
|
|
603
|
+
* - durable store on THIS runner whose `release` PRESERVES history (`evict:"forget"`, the
|
|
604
|
+
* `FileStorageBackend` default), no {@link SubagentToolOptions.background}.`agentStore` — the
|
|
605
|
+
* child transcript lands UNPLACED and survives the settle leg's eager release (a cache drop),
|
|
606
|
+
* so post-hoc forensics work even though nothing can ADDRESS it through an `a*` handle. The
|
|
607
|
+
* qualifier is load-bearing, not decorative: durability and release semantics are INDEPENDENT
|
|
608
|
+
* declarations, and a store that declares itself durable while deleting on release
|
|
609
|
+
* (`new FileStorageBackend({ evict: "delete" })`, or any custom store with the same policy)
|
|
610
|
+
* destroys the transcript at settle — the settle leg calls `release`, and there is no placement
|
|
611
|
+
* to make the deletion refuse. "Durable" is a statement about restarts, never about release;
|
|
612
|
+
* - durable store + `agentStore` — the placed partition (see the mint gate in the background
|
|
613
|
+
* lane): addressable across a restart, list-excluded, joint-reaped with its row. This arm is
|
|
614
|
+
* also the one immune to the evict policy above (the settle leg keeps a confirmed row's
|
|
615
|
+
* transcript instead of releasing it);
|
|
616
|
+
* - transient/process-local store on THIS runner (e.g. a throwaway sub-task store, or a routing
|
|
617
|
+
* store that sends ordinary child acquires to a private cache) — the child transcript NEVER
|
|
618
|
+
* reaches disk whatever the root leg's store is. Nothing in the engine compensates: the
|
|
619
|
+
* completion notification's recent steps and the in-memory registry are the only readers, and
|
|
620
|
+
* both die with the process.
|
|
621
|
+
* The asymmetry this produces is worth naming, because it reads as a bug from the outside: the
|
|
622
|
+
* `run_workflow` tool is mounted by the ENGINE on the run's own runner (prepare-task's
|
|
623
|
+
* `runnerSelf`), so a workflow node's child session always follows the ROOT store, while this tool
|
|
624
|
+
* is mounted by the DEPLOYMENT and follows whatever runner it was handed. One data root can
|
|
625
|
+
* therefore hold every workflow node's transcript and none of the Agent tool's.
|
|
626
|
+
* There is deliberately NO manifest field that answers "does a child transcript reach disk here":
|
|
627
|
+
* `WiringManifest.fleet.subagentTranscripts` is the ADDRESSABILITY tier and folds to `"none"` on
|
|
628
|
+
* the absent-row-store arm before it ever looks at a session store, so it cannot separate the
|
|
629
|
+
* first shape above from the third (the landing pins carry that as an explicit arm). The facts
|
|
630
|
+
* that DO answer it are this runner's store's own declarations — `sessions.durability` and its
|
|
631
|
+
* release/evict policy — read together, which is why they are named here rather than folded into
|
|
632
|
+
* a word. Upstream anchor (CC 2.1.250): a sub-agent transcript is written unconditionally to
|
|
633
|
+
* its own file beside the parent session (`projects/<projectKey>/<sessionId>/subagents/agent-<id>
|
|
634
|
+
* .jsonl` + a `.meta.json` sidecar), so "the child conversation is readable afterwards" is a
|
|
635
|
+
* property CC never makes conditional on a store tier.
|
|
636
|
+
*/
|
|
520
637
|
runner: Runner;
|
|
521
638
|
/**
|
|
522
639
|
* design/115 P3 — background sub-agents (CC `run_in_background`). When set, the tool exposes the
|
|
@@ -599,7 +716,22 @@ export interface SubagentToolOptions {
|
|
|
599
716
|
* wins — fork-shadowing precedent).
|
|
600
717
|
*/
|
|
601
718
|
builtinAgents?: boolean;
|
|
602
|
-
/**
|
|
719
|
+
/**
|
|
720
|
+
* Model catalog the tool validates model words against — an agent definition's string `model` ref at
|
|
721
|
+
* assembly time (fail-fast), and the per-call `model` argument at call time.
|
|
722
|
+
*
|
|
723
|
+
* OMITTED is the normal deployment shape: the tool then reads the runner's CURRENT catalog
|
|
724
|
+
* (`Runner.agentCatalog.models`) at each of those moments, so a catalog hot-swapped after mount is
|
|
725
|
+
* the one a delegation is judged against. Passing this explicitly is a deliberate NARROWING and
|
|
726
|
+
* pins the tool to exactly this table for the tool's whole life — the runner's generation changes
|
|
727
|
+
* no longer reach it. And this table is AUTHORITATIVE for the per-call word, not merely advisory:
|
|
728
|
+
* a word judged against it leaves the gate as this table's resolved `Model` object and the child
|
|
729
|
+
* runs that binding — including a key the runner's own catalog does not carry, and including a
|
|
730
|
+
* same-name key the runner binds differently. (The judged-then-re-resolved split this replaced
|
|
731
|
+
* admitted such words and then killed the child inside its own prepare, or ran a binding the spawn
|
|
732
|
+
* frame never named.) A definition's string `model` ref is different: it is validated here at
|
|
733
|
+
* assembly time but still resolves at the child's own prepare through the runner's live table.
|
|
734
|
+
*/
|
|
603
735
|
models?: Record<string, Model>;
|
|
604
736
|
/** Tools available to the child (a deliberately narrowed subset). */
|
|
605
737
|
tools?: ToolSpec[];
|