@sema-agent/core 5.47.0 → 5.48.0

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +4 -0
  3. package/dist/agents/agent-transcript-tool.js +10 -3
  4. package/dist/agents/send-message-tool.d.ts +43 -1
  5. package/dist/agents/send-message-tool.js +50 -11
  6. package/dist/agents/subagent.d.ts +18 -0
  7. package/dist/agents/subagent.js +102 -2
  8. package/dist/config/defaults.d.ts +20 -0
  9. package/dist/config/defaults.js +5 -0
  10. package/dist/core/background-agent-store.d.ts +1 -0
  11. package/dist/core/background-agent-store.js +13 -0
  12. package/dist/core/mcp.d.ts +6 -1
  13. package/dist/core/mcp.js +34 -7
  14. package/dist/core/reminder-disclosure.d.ts +90 -0
  15. package/dist/core/reminder-disclosure.js +64 -0
  16. package/dist/core/runner/prepare-acquire-reconcile.d.ts +6 -0
  17. package/dist/core/runner/prepare-acquire-reconcile.js +1 -1
  18. package/dist/core/runner/prepare-hands-readface.d.ts +4 -0
  19. package/dist/core/runner/prepare-hands-readface.js +1 -0
  20. package/dist/core/runner/prepare-task.d.ts +15 -0
  21. package/dist/core/runner/prepare-task.js +48 -30
  22. package/dist/core/runner/runtask.js +3 -1
  23. package/dist/core/session-store.d.ts +59 -1
  24. package/dist/core/session-store.js +82 -14
  25. package/dist/core/session.d.ts +83 -1
  26. package/dist/core/task-registry-agent.d.ts +28 -0
  27. package/dist/core/task-registry-agent.js +63 -2
  28. package/dist/core/task-registry.d.ts +21 -0
  29. package/dist/core/task-registry.js +4 -1
  30. package/dist/core/types.d.ts +60 -0
  31. package/dist/core/untrusted-text.d.ts +63 -0
  32. package/dist/core/untrusted-text.js +48 -0
  33. package/dist/core/wiring-manifest.d.ts +35 -0
  34. package/dist/core/wiring-manifest.js +21 -1
  35. package/dist/engine/harness/types.d.ts +36 -1
  36. package/dist/index.d.ts +5 -4
  37. package/dist/index.js +4 -3
  38. package/dist/internal/harness-types.d.ts +1 -0
  39. package/dist/stores/file/index.d.ts +19 -3
  40. package/dist/stores/file/index.js +24 -1
  41. package/dist/stores/file/session-store.d.ts +18 -4
  42. package/dist/stores/file/session-store.js +73 -12
  43. package/dist/tools/fs/fs-pdf.d.ts +12 -1
  44. package/dist/tools/fs/fs-pdf.js +17 -3
  45. package/dist/tools/fs/fs-read.d.ts +2 -1
  46. package/dist/tools/fs/fs-read.js +33 -5
  47. package/dist/tools/fs/fs-shared.d.ts +6 -2
  48. package/dist/tools/fs/index.d.ts +7 -0
  49. package/dist/tools/fs/index.js +1 -1
  50. package/dist/tools/web.js +21 -2
  51. package/package.json +3 -2
  52. package/test/export-surface.snapshot.json +15 -1
@@ -16,6 +16,69 @@
16
16
  * own (instructions and data share one channel). The real boundary is decorrelation + reading the objective
17
17
  * artifact (the diff / working tree) rather than the worker's self-report (design/53 §3, design/54 §3).
18
18
  */
19
+ /** design/319 (B ticket) — what one reminder-shaped scan of an external projection found. */
20
+ export interface ReminderShapedScan {
21
+ /** ≥1 reminder-shaped tag (open or close; attribute/case/whitespace tolerant) is present. */
22
+ hit: boolean;
23
+ /** Some reminder-shaped TAG's own bytes contain the session's exact mark value — the strongest
24
+ * verdict (a full scan decides this, never the first match: a bare decoy ahead of a marked
25
+ * forgery must still be judged marked). `false` whenever `mark` is `undefined`. */
26
+ hadCurrentMark: boolean;
27
+ }
28
+ /**
29
+ * design/319 (B ticket) — the ONE detection predicate for reminder-shaped text in EXTERNAL data
30
+ * (the Read/notebook/PDF/MCP disclosure trailers and the MCP/web exact-mark defuse all judge with
31
+ * THIS scan). It reads the same {@link SYSTEM_REMINDER} regex the neutralizer rewrites with, so
32
+ * "what gets defused on contained lanes" and "what gets disclosed on verbatim lanes" can never
33
+ * drift apart — do not hand-copy the tag pattern anywhere else (same single-source discipline as
34
+ * {@link defuseFenceMarkers}). Attribute-tolerant / case-insensitive / tag-internal whitespace
35
+ * tolerant by construction; a tag the regex does not recognize is NOT a hit (the residual gap
36
+ * between this grammar and what a model might read as authority is an accepted, documented
37
+ * residual — same class as the Unicode-lookalike note on {@link defuseFenceMarkers}).
38
+ *
39
+ * The scan is read-only over the FINALIZED projection (post concatenation/truncation, pre any
40
+ * trusted tail) — callers must scan BEFORE any byte-changing transform (design/319 §3e four-step
41
+ * order): a defused/neutralized projection no longer carries the exact mark, so scanning after
42
+ * would misreport a marked forgery as the bare form (the lying-disclosure shape).
43
+ */
44
+ export declare function scanReminderShaped(text: string, mark: string | undefined): ReminderShapedScan;
45
+ /**
46
+ * design/319 §3b (B ticket) — EXACT-MARK defuse for the MCP-success/web outlets: insert a ZWSP
47
+ * inside every occurrence of the session's exact mark value so the bytes no longer spell the
48
+ * current mark. This is the ONE sanctioned byte change on those lanes (their zero-byte-change
49
+ * invariant's single exception): a server/page legitimately producing the 22-char session mark has
50
+ * definitionally zero probability — the value lives only in this session's prompt and checkpoint —
51
+ * so an occurrence is either a forgery or a leak echo, and defusing it turns a silent authority
52
+ * forgery into a visible mismatch (the accepted failure direction; a legitimate round-trip echo of
53
+ * a leaked mark defuses too, and the next hop then fails loudly instead of re-arming the forgery).
54
+ * NEVER applied on Read/Bash/Grep (the strong quote-back lanes stay verbatim — ruled, observation
55
+ * seat only). Idempotent: the rewritten text no longer contains the exact mark (ZWSP is not a
56
+ * base64url character, so the insertion can also never synthesize a new occurrence).
57
+ */
58
+ export declare function defuseExactMark(text: string, mark: string): string;
59
+ /**
60
+ * design/319 §3b — the SEGMENT form of {@link defuseExactMark} (adversarial round: a server can
61
+ * split the mark across adjacent MCP text blocks; the block seam is invisible in the model-facing
62
+ * concatenation, so a per-segment defuse would be a bypass). Judged over the JOINED projection and
63
+ * the ZWSP insertion mapped back into the owning segment, so the post-defuse concatenation NEVER
64
+ * contains the exact mark — including occurrences that straddle one or more seams (one ZWSP inside
65
+ * the occurrence's span breaks it wherever the split fell). Returns the same array (`changed:
66
+ * false`) when the joined projection has no occurrence; otherwise fresh segment strings whose
67
+ * concatenation equals the single-string form's output byte for byte (it IS the single-string
68
+ * form's implementation).
69
+ *
70
+ * OVERLAP-AWARE (adversarial round 2): a BORDERED mark (first character = last character — ~1/64
71
+ * random mints) admits overlapping occurrences (`mark + mark.slice(1)` carries two, one starting on
72
+ * the other's final character), so enumeration advances ONE character per match, never by the mark
73
+ * length — a skip-by-length walk defused the first and left the second exact while the trailer
74
+ * claimed neutralization. Every enumerated occurrence gets a ZWSP inside its own span; ZWSP is not
75
+ * a base64url character, so insertions can never synthesize a new occurrence and the result is
76
+ * idempotent (no exact mark survives ⇒ a second pass is byte-identical).
77
+ */
78
+ export declare function defuseExactMarkInSegments(segments: readonly string[], mark: string): {
79
+ segments: string[];
80
+ changed: boolean;
81
+ };
19
82
  /**
20
83
  * Neutralize structural break-out sequences in UNTRUSTED text so it can't escape its framing into
21
84
  * model-facing instructions. Always neutralizes `<system-reminder>` / `</system-reminder>` (the codebase's
@@ -12,6 +12,54 @@ function breakoutRe(extraTags) {
12
12
  }
13
13
  return re;
14
14
  }
15
+ export function scanReminderShaped(text, mark) {
16
+ let hit = false;
17
+ let hadCurrentMark = false;
18
+ for (const m of text.matchAll(SYSTEM_REMINDER)) {
19
+ hit = true;
20
+ if (mark !== undefined && m[0].includes(mark)) {
21
+ hadCurrentMark = true;
22
+ break;
23
+ }
24
+ if (mark === undefined)
25
+ break;
26
+ }
27
+ return { hit, hadCurrentMark };
28
+ }
29
+ export function defuseExactMark(text, mark) {
30
+ return defuseExactMarkInSegments([text], mark).segments[0];
31
+ }
32
+ export function defuseExactMarkInSegments(segments, mark) {
33
+ const joined = segments.join("");
34
+ if (mark.length === 0 || !joined.includes(mark))
35
+ return { segments: [...segments], changed: false };
36
+ const positions = [];
37
+ for (let at = joined.indexOf(mark); at !== -1; at = joined.indexOf(mark, at + 1))
38
+ positions.push(at + 1);
39
+ const out = [];
40
+ let start = 0;
41
+ let pi = 0;
42
+ for (const seg of segments) {
43
+ const end = start + seg.length;
44
+ if (pi >= positions.length || positions[pi] > end) {
45
+ out.push(seg);
46
+ }
47
+ else {
48
+ const parts = [];
49
+ let prev = 0;
50
+ while (pi < positions.length && positions[pi] > start && positions[pi] <= end) {
51
+ const local = positions[pi] - start;
52
+ parts.push(seg.slice(prev, local), ZWSP);
53
+ prev = local;
54
+ pi++;
55
+ }
56
+ parts.push(seg.slice(prev));
57
+ out.push(parts.join(""));
58
+ }
59
+ start = end;
60
+ }
61
+ return { segments: out, changed: true };
62
+ }
15
63
  export function sanitizeUntrustedText(text, extraTags = []) {
16
64
  return text.replace(breakoutRe(extraTags), (m) => m.replace("<", "<" + ZWSP));
17
65
  }
@@ -126,9 +126,18 @@ export interface WiringManifest {
126
126
  session: {
127
127
  store: ManifestDurability;
128
128
  };
129
+ /** `subagentTranscripts` — subagent transcript persistence: the deployment-level delegation-
130
+ * transcript durability TIER, a pure declaration read ({@link resolveSubagentTranscriptTier}):
131
+ * `none` = no agent-row store (pre-151 in-memory lifecycle); `rows` = rows + terminal snapshots
132
+ * survive a restart, transcripts follow the session store's fate; `full` = the C18 promise —
133
+ * a completed/failed/killed(non-user) subagent minted through the deps-visible assembly is
134
+ * continuable across a process restart. A statement about the DEPS-VISIBLE assembly only: a
135
+ * caller-mounted Agent tool over its own runner is outside this manifest's sight (per-read
136
+ * honest degrade + the integrity notice own that case, never a fabricated tier). */
129
137
  fleet: {
130
138
  backgroundAgentStore: boolean;
131
139
  hostChildEventSink: boolean;
140
+ subagentTranscripts: SubagentTranscriptTier;
132
141
  };
133
142
  /** design/179 — the persisted allow-rule seam. The ONE loosening seam in the assembly, so its presence
134
143
  * is a fact an operator has to be able to read off the manifest rather than infer. `false` on a
@@ -201,6 +210,11 @@ export interface WiringFacts {
201
210
  checkpointDurability?: StoreDurability;
202
211
  sessionDurability: StoreDurability;
203
212
  backgroundAgentStoreWired: boolean;
213
+ /** Subagent transcript persistence — the declared delegation-transcript tier (see
214
+ * {@link WiringManifest.fleet}). Optional for external `deriveWiringManifest` callers: absent
215
+ * derives fail-closed from `backgroundAgentStoreWired` alone (`rows`/`none` — under-promise,
216
+ * never `full`); the engine's own halves always pass the resolved value. */
217
+ subagentTranscriptTier?: SubagentTranscriptTier;
204
218
  /** design/179 — a persisted allow-rule store provider is wired. */
205
219
  permissionRuleStoreWired: boolean;
206
220
  /** design/182 §9 — the deployment declared that it drives cloud sync for that store. */
@@ -227,6 +241,27 @@ export type StaticWiringSpec = Pick<TaskSpec, "onAsk" | "onQuestion" | "checkpoi
227
241
  export declare function resolveDeclaredDurability(store: {
228
242
  readonly durability?: StoreDurability;
229
243
  } | undefined, storeName: string): StoreDurability;
244
+ /** Subagent transcript persistence — the deployment-level delegation-transcript durability tier
245
+ * (see {@link WiringManifest.fleet}). */
246
+ export type SubagentTranscriptTier = "none" | "rows" | "full";
247
+ /**
248
+ * Subagent transcript persistence — the ONE deployment-level tier derivation (pure declaration
249
+ * read, no probing, no class-name sniffing — the wiring-manifest posture): `none` without an
250
+ * agent-row store; with one, `full` iff the session store DECLARES its subagent placement
251
+ * partition durable, else `rows`. A junk placement declaration is refused loudly
252
+ * (`config.store_durability_invalid` — the resolveDeclaredDurability rule: an unparseable
253
+ * declaration silently folded to either arm would make the manifest lie in whichever direction the
254
+ * fold picked). This is the DEPLOYMENT-level judgment only; the per-handle "did this row's
255
+ * transcript actually land" question belongs to `durableAgentRowProbe` (the S1b release-flip gate)
256
+ * — two different questions, deliberately two named faces (do not merge them back into one).
257
+ */
258
+ export declare function resolveSubagentTranscriptTier(agentStoreWired: boolean, sessionStore: {
259
+ readonly placements?: {
260
+ subagent?: {
261
+ durability: StoreDurability;
262
+ };
263
+ };
264
+ } | undefined): SubagentTranscriptTier;
230
265
  /**
231
266
  * The ONE `ask.effective` derivation (design/173 codex 5) — shared by the effective manifest AND
232
267
  * the posture door (`config.interaction_posture`), so "what does the door require" and "what does
@@ -14,6 +14,21 @@ export function resolveDeclaredDurability(store, storeName) {
14
14
  throw e;
15
15
  }
16
16
  const manifestDurabilityOf = (d) => (d === "durable" ? "declared_durable" : "process_local");
17
+ export function resolveSubagentTranscriptTier(agentStoreWired, sessionStore) {
18
+ if (!agentStoreWired)
19
+ return "none";
20
+ const declared = sessionStore?.placements?.subagent?.durability;
21
+ if (declared === undefined)
22
+ return "rows";
23
+ if (declared === "durable")
24
+ return "full";
25
+ if (declared === "process-local")
26
+ return "rows";
27
+ const e = new Error(`sessionStore.placements.subagent.durability declares ${JSON.stringify(declared)} — not a recognized ` +
28
+ `StoreDurability ("durable" | "process-local"). Fix the declaration; an unparseable durability cannot be folded to either arm.`);
29
+ e.code = "config.store_durability_invalid";
30
+ throw e;
31
+ }
17
32
  export function deriveAskEffective(form, parkEffective) {
18
33
  switch (form) {
19
34
  case "callback":
@@ -85,7 +100,11 @@ export function deriveWiringManifest(facts) {
85
100
  elicit: { seamWired: facts.elicitSeamWired, serversOptedIn: facts.elicitServersOptedIn },
86
101
  parkLane,
87
102
  session: { store: manifestDurabilityOf(facts.sessionDurability) },
88
- fleet: { backgroundAgentStore: facts.backgroundAgentStoreWired, hostChildEventSink: facts.hostChildEventSinkWired },
103
+ fleet: {
104
+ backgroundAgentStore: facts.backgroundAgentStoreWired,
105
+ hostChildEventSink: facts.hostChildEventSinkWired,
106
+ subagentTranscripts: facts.subagentTranscriptTier ?? (facts.backgroundAgentStoreWired ? "rows" : "none"),
107
+ },
89
108
  permissionRules: {
90
109
  storeWired: facts.permissionRuleStoreWired,
91
110
  syncWired: facts.permissionRuleSyncWired,
@@ -182,6 +201,7 @@ export function describeStaticWiring(deps, spec = {}) {
182
201
  ...(capable ? { checkpointDurability: resolveDeclaredDurability(checkpointStore, "checkpointStore") } : {}),
183
202
  sessionDurability: resolveDeclaredDurability(deps.sessionStore, "sessionStore"),
184
203
  backgroundAgentStoreWired: deps.backgroundAgentStore !== undefined,
204
+ subagentTranscriptTier: resolveSubagentTranscriptTier(deps.backgroundAgentStore !== undefined, deps.sessionStore),
185
205
  permissionRuleStoreWired: deps.permissionRuleStore !== undefined,
186
206
  permissionRuleSyncWired: deps.permissionRuleSyncWired === true,
187
207
  permissionRuleOrgGoverned: deps.permissionRuleOrg !== undefined,
@@ -157,7 +157,7 @@ export declare class CompactionError extends Error {
157
157
  code: CompactionErrorCode;
158
158
  constructor(code: CompactionErrorCode, message: string, cause?: Error);
159
159
  }
160
- export type SessionErrorCode = "not_found" | "invalid_session" | "invalid_entry" | "invalid_fork_target" | "storage" | "conflict" | "unknown";
160
+ export type SessionErrorCode = "not_found" | "invalid_session" | "invalid_entry" | "invalid_fork_target" | "storage" | "conflict" | "placement_refused" | "unknown";
161
161
  /** Error thrown by session storage, repositories, and session tree operations. */
162
162
  export declare class SessionError extends Error {
163
163
  /** Session subsystem error code. */
@@ -170,6 +170,30 @@ export declare class AgentHarnessError extends Error {
170
170
  code: AgentHarnessErrorCode;
171
171
  constructor(code: AgentHarnessErrorCode, message: string, cause?: Error);
172
172
  }
173
+ /**
174
+ * The PERSISTED placement tuple of a session created into a placement partition (subagent
175
+ * transcript persistence — the durable half of the delegation continuation contract). Written ONCE
176
+ * at creation (first-write immutable: a later acquire never rewrites it) and carried on
177
+ * {@link SessionMetadata.placement}. The FULL tuple is persisted, not just a tag: `scope`+`handle`
178
+ * are the row-store join key the retention orchestration needs after a restart (a bare label could
179
+ * not tell "no row" from "a row in another tenant"), and `placedAt` anchors the age-based GC arm.
180
+ * A tuple missing either join half is treated by consumers as INCOMPLETE (fail-closed: never
181
+ * age-reaped, reported as such by placed listings).
182
+ */
183
+ export interface SessionPlacementRecord {
184
+ /** The one placement kind this contract defines today. */
185
+ kind: "subagent";
186
+ /** Tenant scope of the owning durable agent row (row-store join key half 1). */
187
+ scope?: string;
188
+ /** The spawning host session (partition/retrieval metadata — authorization stays on the row predicate). */
189
+ parentSessionId?: string;
190
+ /** The delegation tree's root host session (fixed point; enumeration anchor). */
191
+ rootSessionId?: string;
192
+ /** The durable agent row key (`a…` handle; row-store join key half 2). */
193
+ handle?: string;
194
+ /** Epoch ms the placement was created (stamped by the store at creation). */
195
+ placedAt: number;
196
+ }
173
197
  /** Metadata for one filesystem object in a {@link FileSystem}. */
174
198
  export interface FileInfo {
175
199
  /** Basename of {@link path}. */
@@ -644,6 +668,10 @@ export interface SessionMetadata {
644
668
  * fork lineage (e.g. reap forks whose parent is gone/expired). Absent on a non-forked session and on
645
669
  * forked sessions persisted before this field shipped. */
646
670
  forkedFrom?: string;
671
+ /** Subagent transcript persistence — the placement tuple this session was CREATED into (see
672
+ * {@link SessionPlacementRecord}). Absent on every ordinary (host-lane) session; a repo that does
673
+ * not understand placement never surfaces it (additive, old readers tolerate). */
674
+ placement?: SessionPlacementRecord;
647
675
  }
648
676
  export interface JsonlSessionMetadata extends SessionMetadata {
649
677
  cwd: string;
@@ -796,6 +824,13 @@ export interface Session<TMetadata extends SessionMetadata = SessionMetadata> {
796
824
  }
797
825
  export interface SessionCreateOptions {
798
826
  id?: string;
827
+ /** Subagent transcript persistence — create this session INTO a placement partition. The repo
828
+ * stamps `placedAt` and persists the tuple on its meta record (first-write immutable). A repo
829
+ * that does not understand placement IGNORES this key (legal degrade — the session is then an
830
+ * ordinary one; the store-level `placements` declaration is what promises understanding). */
831
+ placement?: Omit<SessionPlacementRecord, "placedAt"> & {
832
+ placedAt?: number;
833
+ };
799
834
  }
800
835
  export interface SessionForkOptions {
801
836
  entryId?: string;
package/dist/index.d.ts CHANGED
@@ -64,7 +64,7 @@ export { GOVERNANCE_CODES, governanceRetryClass, type GovernanceCode, type Gover
64
64
  export { NOTICE_AUDIENCE, noticeAudienceOf } from "./core/governance-codes.js";
65
65
  export { TtlSessionStore, type TtlSessionStoreOptions, type EvictPolicy } from "./core/session-store.js";
66
66
  export { reconcileInterruptedSession, findOrphanToolCalls, type OrphanToolCall, type ReconcileReport, } from "./core/session-reconcile.js";
67
- export { StoredSession, InMemorySessionRepo, InMemorySessionStorage, BaseSessionStorage, leafIdAfterEntry, validateEntriesForImport, StreamingImportValidator, boundedTail, SessionError, isSessionConflict, hasSessionFork, uuidv7, type Session, type SessionStore, type AcquiredSession, type SessionStoreSummary, type SessionStorage, type SessionRepo, type SessionMetadata, type SessionTreeEntry, type SessionWriteOptions, } from "./core/session.js";
67
+ export { StoredSession, InMemorySessionRepo, InMemorySessionStorage, BaseSessionStorage, leafIdAfterEntry, validateEntriesForImport, StreamingImportValidator, boundedTail, SessionError, isSessionConflict, hasSessionFork, uuidv7, type Session, type SessionStore, type AcquiredSession, type SessionStoreSummary, type SessionStorage, type SessionRepo, type SessionMetadata, type SessionTreeEntry, type SessionWriteOptions, type SessionPlacement, type SessionPlacementRecord, type PlacedSessionRow, } from "./core/session.js";
68
68
  export { warmResume } from "./core/warm-resume.js";
69
69
  export { StubExecutionEnv } from "./core/stub-env.js";
70
70
  export { NodeExecutionEnv, FileError, ExecutionError } from "./internal/harness.js";
@@ -111,7 +111,7 @@ export { createFsWriteGatePolicy, type FsWriteGatePolicyOptions } from "./core/f
111
111
  export { RETIRED_TOOL_NAMES } from "./core/tool-name-aliases.js";
112
112
  export { DEFAULT_SUBAGENT_TOOL_NAME } from "./agents/subagent.js";
113
113
  export { renderTaskNotificationXml, taskNotificationDedupKey, isDelegatedAgentTerminal, SystemInjectionQueue, type TaskNotificationPayload, type TaskNotificationStatus, type ExternalNotificationInput, type SystemInjection, type SystemInjectionPriority, } from "./core/task-notification.js";
114
- export { describeStaticWiring, deriveWiringManifest, deriveAskEffective, resolveDeclaredDurability, resolveAskSeamForm, resolveQuestionSeam, countElicitOptIns, type WiringManifest, type WiringFacts, type WiringLegKind, type AskSeamForm, type AskEffective, type QuestionChannelState, type SeamProvenance, type ParkLaneReason, type ManifestDurability, type StaticWiringDeps, type StaticWiringSpec, } from "./core/wiring-manifest.js";
114
+ export { describeStaticWiring, deriveWiringManifest, deriveAskEffective, resolveDeclaredDurability, resolveSubagentTranscriptTier, type SubagentTranscriptTier, resolveAskSeamForm, resolveQuestionSeam, countElicitOptIns, type WiringManifest, type WiringFacts, type WiringLegKind, type AskSeamForm, type AskEffective, type QuestionChannelState, type SeamProvenance, type ParkLaneReason, type ManifestDurability, type StaticWiringDeps, type StaticWiringSpec, } from "./core/wiring-manifest.js";
115
115
  export { probeParkRoundTrip, durableParkGapOf, durableParkGapFor, PARK_SELFCHECK_SCOPE_PREFIX, type ParkSelfCheckResult, type ParkProbeFinding, type ParkProbeFindingCode, } from "./core/park-selfcheck.js";
116
116
  export { type StoreDurability } from "./core/checkpoint-store.js";
117
117
  export { type StoreFidelity } from "./core/checkpoint-store.js";
@@ -234,10 +234,11 @@ export { summarizeRedactions } from "./core/untrusted-egress.js";
234
234
  export { MemoryRosterStore, FileRosterStore, type RosterStore, type RosterEntry, type RosterAccess, type RosterGcOptions } from "./agents/roster-store.js";
235
235
  export { CONFIG_CATALOG_VERSION, describeConfigCatalog, resolveEffectiveConfig, type ConfigKnob, type ConfigOverrideDeclaration, type ConfigProvenance, type EffectiveConfigField, } from "./config/catalog.js";
236
236
  export { normalizeAgentName } from "./core/task-registry.js";
237
+ export { DELEGATION_MAX_CONCURRENT_DEFAULT, DELEGATION_MAX_PER_SESSION_DEFAULT, ORPHAN_ADOPT_WINDOW_MS_DEFAULT, ORPHAN_ADOPT_MAX_DEFAULT, SUBAGENT_TRANSCRIPT_RETENTION_DAYS_DEFAULT, } from "./config/defaults.js";
237
238
  export { COORDINATOR_ROLE_PROMPT, TEAMMATE_COMMUNICATION_ADDENDUM, TEAMMATE_TASK_LIST_ADDENDUM } from "./prompts/coordinator.js";
238
- export { createSubagentTool, FORK_SUBAGENT_TYPE, GENERAL_PURPOSE_SUBAGENT_TYPE, agentWhenToUseText, FORK_DIRECTIVE_FRAME, SUBAGENT_SYSTEM_NOTE, type SubagentToolOptions, type SubagentSpawnContext, type SubagentSteerHandle, type SubagentStep, type SubagentEditedFile, } from "./agents/subagent.js";
239
+ export { createSubagentTool, FORK_SUBAGENT_TYPE, GENERAL_PURPOSE_SUBAGENT_TYPE, agentWhenToUseText, FORK_DIRECTIVE_FRAME, SUBAGENT_SYSTEM_NOTE, type SubagentToolOptions, resolveDelegationEntryCaps, type ResolvedDelegationEntryCaps, type SubagentSpawnContext, type SubagentSteerHandle, type SubagentStep, type SubagentEditedFile, } from "./agents/subagent.js";
239
240
  export { getSessionRetainLedger, releaseSessionRetainLedger, } from "./agents/retain-ledger.js";
240
- export { createSendMessageTool, SEND_MESSAGE_TOOL_NAME, type SendMessageToolOptions } from "./agents/send-message-tool.js";
241
+ export { createSendMessageTool, createAgentContinuationVerb, SEND_MESSAGE_TOOL_NAME, type SendMessageToolOptions, type AgentContinuationReceipt } from "./agents/send-message-tool.js";
241
242
  export { createPeerAdmission, peerAdmissionFor, judgePeerAdmission, resolvePeerAdmissionConfig, PEER_ADMISSION_DEFAULTS, PEER_HOP_CHAIN_WINDOW, PEER_MESSAGE_NOTICE, createPeerSelfRef, createPeerInboundChainRef, peerAxisToken, appendHopToken, type PeerAdmission, type PeerAdmissionConfig, type PeerAdmissionOptions, type PeerAdmissionRequest, type PeerAdmissionVerdict, type PeerAdmissionRefusal, type PeerRefusalCode, type PeerAxisTag, type PeerIdentity, type PeerSelfRef, type PeerInboundChainRef, } from "./agents/peer-admission.js";
242
243
  export { createAgentTranscriptTool, AGENT_TRANSCRIPT_TOOL_NAME, type AgentTranscriptToolOptions, } from "./agents/agent-transcript-tool.js";
243
244
  export { defineAgent } from "./agents/agent-definition.js";
package/dist/index.js CHANGED
@@ -90,7 +90,7 @@ export { createFsWriteGatePolicy } from "./core/fs-write-gate-policy.js";
90
90
  export { RETIRED_TOOL_NAMES } from "./core/tool-name-aliases.js";
91
91
  export { DEFAULT_SUBAGENT_TOOL_NAME } from "./agents/subagent.js";
92
92
  export { renderTaskNotificationXml, taskNotificationDedupKey, isDelegatedAgentTerminal, SystemInjectionQueue, } from "./core/task-notification.js";
93
- export { describeStaticWiring, deriveWiringManifest, deriveAskEffective, resolveDeclaredDurability, resolveAskSeamForm, resolveQuestionSeam, countElicitOptIns, } from "./core/wiring-manifest.js";
93
+ export { describeStaticWiring, deriveWiringManifest, deriveAskEffective, resolveDeclaredDurability, resolveSubagentTranscriptTier, resolveAskSeamForm, resolveQuestionSeam, countElicitOptIns, } from "./core/wiring-manifest.js";
94
94
  export { probeParkRoundTrip, durableParkGapOf, durableParkGapFor, PARK_SELFCHECK_SCOPE_PREFIX, } from "./core/park-selfcheck.js";
95
95
  export {} from "./core/checkpoint-store.js";
96
96
  export {} from "./core/checkpoint-store.js";
@@ -193,10 +193,11 @@ export { summarizeRedactions } from "./core/untrusted-egress.js";
193
193
  export { MemoryRosterStore, FileRosterStore } from "./agents/roster-store.js";
194
194
  export { CONFIG_CATALOG_VERSION, describeConfigCatalog, resolveEffectiveConfig, } from "./config/catalog.js";
195
195
  export { normalizeAgentName } from "./core/task-registry.js";
196
+ export { DELEGATION_MAX_CONCURRENT_DEFAULT, DELEGATION_MAX_PER_SESSION_DEFAULT, ORPHAN_ADOPT_WINDOW_MS_DEFAULT, ORPHAN_ADOPT_MAX_DEFAULT, SUBAGENT_TRANSCRIPT_RETENTION_DAYS_DEFAULT, } from "./config/defaults.js";
196
197
  export { COORDINATOR_ROLE_PROMPT, TEAMMATE_COMMUNICATION_ADDENDUM, TEAMMATE_TASK_LIST_ADDENDUM } from "./prompts/coordinator.js";
197
- export { createSubagentTool, FORK_SUBAGENT_TYPE, GENERAL_PURPOSE_SUBAGENT_TYPE, agentWhenToUseText, FORK_DIRECTIVE_FRAME, SUBAGENT_SYSTEM_NOTE, } from "./agents/subagent.js";
198
+ export { createSubagentTool, FORK_SUBAGENT_TYPE, GENERAL_PURPOSE_SUBAGENT_TYPE, agentWhenToUseText, FORK_DIRECTIVE_FRAME, SUBAGENT_SYSTEM_NOTE, resolveDelegationEntryCaps, } from "./agents/subagent.js";
198
199
  export { getSessionRetainLedger, releaseSessionRetainLedger, } from "./agents/retain-ledger.js";
199
- export { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "./agents/send-message-tool.js";
200
+ export { createSendMessageTool, createAgentContinuationVerb, SEND_MESSAGE_TOOL_NAME } from "./agents/send-message-tool.js";
200
201
  export { createPeerAdmission, peerAdmissionFor, judgePeerAdmission, resolvePeerAdmissionConfig, PEER_ADMISSION_DEFAULTS, PEER_HOP_CHAIN_WINDOW, PEER_MESSAGE_NOTICE, createPeerSelfRef, createPeerInboundChainRef, peerAxisToken, appendHopToken, } from "./agents/peer-admission.js";
201
202
  export { createAgentTranscriptTool, AGENT_TRANSCRIPT_TOOL_NAME, } from "./agents/agent-transcript-tool.js";
202
203
  export { defineAgent } from "./agents/agent-definition.js";
@@ -14,3 +14,4 @@ export type { SessionWriteOptions, CompactionEntry } from "../engine/harness/typ
14
14
  export type { ActiveWorktreeSession, WorkspaceState } from "../engine/harness/types.js";
15
15
  export type { GitAnnouncementKind, GitAnnouncementState } from "../engine/harness/types.js";
16
16
  export type { SessionForkOptions } from "../engine/harness/types.js";
17
+ export type { SessionPlacementRecord, SessionCreateOptions } from "../engine/harness/types.js";
@@ -4,6 +4,9 @@ import type { ToolResultStore } from "../../core/tool-result-store.js";
4
4
  import { type EvictPolicy } from "../../core/session-store.js";
5
5
  import type { SessionStore } from "../../core/session.js";
6
6
  import { type FileCheckpointStoreOptions } from "./checkpoint-store.js";
7
+ import type { BackgroundAgentStore } from "../../core/background-agent-store.js";
8
+ import type { MailboxStore } from "../../core/mailbox-store.js";
9
+ import type { RosterStore } from "../../agents/roster-store.js";
7
10
  import type { SessionPolicyStore } from "../../core/session-policy-store.js";
8
11
  import type { FileSnapshotStore } from "../../core/file-snapshot-store.js";
9
12
  import type { WorkflowJournalStore } from "../../core/workflow-journal-store.js";
@@ -76,9 +79,8 @@ export interface FileStorageBackendOptions {
76
79
  * Reach TODAY: {@link FileStorageBackend.sessionPolicyStore} (rules read as ABSENT ⇒ an unconstrained
77
80
  * task), {@link FileStorageBackend.sessionStore}'s repo (a session listing that silently omits what it
78
81
  * could not read) and {@link FileStorageBackend.fileSnapshotStore} (a snapshot/scope read as missing,
79
- * and a blob GC that aborted). The roster and CC-mailbox stores also carry the seat, but a deployment
80
- * builds those itself and passes the sink directly. A store that grows the seat later is wired here in
81
- * the same constructor.
82
+ * and a blob GC that aborted). The aggregated roster and mailbox stores receive the same sink. A
83
+ * store that grows the seat later is wired here in the same constructor.
82
84
  *
83
85
  * The payload is the UNION shape ({@link FileStorageCorruptReadInfo}): `path` + `reason` always,
84
86
  * with the session-policy face's `sessionId`/`principal` present only when the reading store keys by
@@ -125,6 +127,18 @@ export declare class FileStorageBackend {
125
127
  * `RunnerDeps.usageWindowStore` so a deployment's `usageWindows` survive a restart (an in-memory
126
128
  * ledger silently re-grants every allowance on every boot). */
127
129
  readonly usageWindowStore: UsageWindowStore;
130
+ /** design/151 S1a / subagent transcript persistence — the durable background-agent execution
131
+ * ledger over the same data root. Wire into BOTH `RunnerDeps.backgroundAgentStore` AND (for a
132
+ * deployment-composed Agent tool) `SubagentToolOptions.background.agentStore` — SAME instance
133
+ * (the RB-37 pairing discipline). Aggregated here so the TOC full-persistence assembly is one
134
+ * constructor, the same reason the session/checkpoint/tool-result trio are. */
135
+ readonly backgroundAgentStore: BackgroundAgentStore;
136
+ /** design/151 §7 S3c — the durable SendMessage mailbox (tier-3 parking lane), same data root.
137
+ * Wire into `RunnerDeps.mailboxStore` (single-instance pairing, same as above). */
138
+ readonly mailboxStore: MailboxStore;
139
+ /** design/147 S1c — the durable name→agent roster (layer 0.5 resolution), `root/roster.json`.
140
+ * Wire into `RunnerDeps.rosterStore` so name-addressing survives a restart alongside the rows. */
141
+ readonly rosterStore: RosterStore;
128
142
  /**
129
143
  * design/84 Seam B (TOC) — the per-scope consolidation lock for `consolidateScope`'s `acquire` injection
130
144
  * point: `consolidateScope(scope, { store: backend.memoryStore, llm, acquire: backend.consolidationLock })`.
@@ -133,6 +147,8 @@ export declare class FileStorageBackend {
133
147
  */
134
148
  readonly consolidationLock: (scope: string) => (() => void) | undefined;
135
149
  private readonly lock;
150
+ private readonly fileAgentRows;
151
+ private readonly fileMailbox;
136
152
  private readonly fileCheckpoints;
137
153
  private readonly fileMemory;
138
154
  private readonly ttl;
@@ -11,6 +11,9 @@ import { FileSessionPolicyStore } from "./session-policy-store.js";
11
11
  import { FileFileSnapshotStore } from "./file-snapshot-store.js";
12
12
  import { FileWorkflowJournalStore } from "./workflow-journal-store.js";
13
13
  import { FileUsageWindowStore } from "./usage-window-store.js";
14
+ import { FileBackgroundAgentStore } from "./background-agent-store.js";
15
+ import { FileMailboxStore } from "./mailbox-store.js";
16
+ import { FileRosterStore } from "../../agents/roster-store.js";
14
17
  export { FileCheckpointStore } from "./checkpoint-store.js";
15
18
  export { FileMemoryStore } from "./memory-store.js";
16
19
  export { FileSessionRepo } from "./session-store.js";
@@ -33,8 +36,13 @@ export class FileStorageBackend {
33
36
  fileSnapshotStore;
34
37
  workflowJournalStore;
35
38
  usageWindowStore;
39
+ backgroundAgentStore;
40
+ mailboxStore;
41
+ rosterStore;
36
42
  consolidationLock;
37
43
  lock;
44
+ fileAgentRows;
45
+ fileMailbox;
38
46
  fileCheckpoints;
39
47
  fileMemory;
40
48
  ttl;
@@ -49,7 +57,7 @@ export class FileStorageBackend {
49
57
  const corruptRead = opts.onCorruptRead !== undefined ? { onCorruptRead: opts.onCorruptRead } : undefined;
50
58
  const repo = new FileSessionRepo(this.root, corruptRead);
51
59
  this.fileSessions = repo;
52
- this.ttl = new TtlSessionStore({ repo, evict: opts.evict ?? "forget", durability: "durable" });
60
+ this.ttl = new TtlSessionStore({ repo, evict: opts.evict ?? "forget", durability: "durable", placements: { subagent: { durability: "durable" } } });
53
61
  this.sessionStore = this.ttl;
54
62
  this.fileCheckpoints = new FileCheckpointStore(this.root, opts.checkpoint);
55
63
  this.checkpointStore = this.fileCheckpoints;
@@ -61,6 +69,11 @@ export class FileStorageBackend {
61
69
  this.fileWorkflowJournal = new FileWorkflowJournalStore(this.root);
62
70
  this.workflowJournalStore = this.fileWorkflowJournal;
63
71
  this.usageWindowStore = new FileUsageWindowStore(this.root);
72
+ this.fileAgentRows = new FileBackgroundAgentStore(this.root);
73
+ this.backgroundAgentStore = this.fileAgentRows;
74
+ this.fileMailbox = new FileMailboxStore(this.root, corruptRead ?? {});
75
+ this.mailboxStore = this.fileMailbox;
76
+ this.rosterStore = new FileRosterStore(join(this.root, "roster.json"), corruptRead ?? {});
64
77
  this.consolidationLock = createFileConsolidationLock(join(this.root, "consolidation-locks"));
65
78
  }
66
79
  catch (err) {
@@ -87,6 +100,16 @@ export class FileStorageBackend {
87
100
  }
88
101
  this.fileMemory.close();
89
102
  this.fileWorkflowJournal.dispose();
103
+ try {
104
+ this.fileAgentRows.close();
105
+ }
106
+ catch {
107
+ }
108
+ try {
109
+ this.fileMailbox.close();
110
+ }
111
+ catch {
112
+ }
90
113
  this.lock.release();
91
114
  }
92
115
  }
@@ -1,4 +1,5 @@
1
- import type { Session, SessionForkOptions, SessionMetadata, SessionRepo, SessionTreeEntry } from "../../internal/harness.js";
1
+ import type { Session, SessionCreateOptions, SessionForkOptions, SessionMetadata, SessionPlacementRecord, SessionRepo, SessionTreeEntry } from "../../internal/harness.js";
2
+ import type { PlacedSessionRow } from "../../core/session.js";
2
3
  export interface FileSessionRepoOptions {
3
4
  /**
4
5
  * Disclosure sink for a durable read this repo treats as ABSENT (ruled 2026-08-03). Same name and
@@ -47,11 +48,24 @@ export declare class FileSessionRepo implements SessionRepo {
47
48
  * means a stale holder can never revoke the rebuilt entry.
48
49
  */
49
50
  dispose(): Promise<void>;
50
- create(options?: {
51
- id?: string;
52
- }): Promise<Session>;
51
+ create(options?: SessionCreateOptions): Promise<Session>;
53
52
  open(metadata: SessionMetadata): Promise<Session>;
54
53
  list(): Promise<SessionMetadata[]>;
54
+ /**
55
+ * Subagent transcript persistence — the PLACED partition's own enumeration (the joint reap's
56
+ * partition-leg input; `TtlSessionStore` re-exposes it verbatim). Age (`olderThanMs`) is judged on
57
+ * the session FILE's mtime — the transcript's last append — falling back to `placedAt` when the
58
+ * stat fails (mtime is also the CC orphan-adoption window's basis, so the two age reads agree).
59
+ * Rows missing either `(scope, handle)` join half return the honest `tupleIncomplete` variant
60
+ * (consumers fail closed on it — never age-reaped, see the SessionStore contract).
61
+ */
62
+ listPlaced(kind: "subagent", opts?: {
63
+ olderThanMs?: number;
64
+ scope?: string;
65
+ }): Promise<PlacedSessionRow[]>;
66
+ /** Cheap placement probe for one id (the TtlSessionStore's cold-cache release consults it so the
67
+ * placed real-deletion obligation holds after a restart). `undefined` = missing file OR unplaced. */
68
+ placementOf(sessionId: string): Promise<SessionPlacementRecord | undefined>;
55
69
  delete(metadata: SessionMetadata): Promise<void>;
56
70
  fork(sourceMetadata: SessionMetadata, options?: SessionForkOptions): Promise<Session>;
57
71
  /** 2c session-sync ([275], the FILE half of [266]②): the FULL oldest-first log for export. The file backend