Problem
The tool-result archive capability is currently three independent optional fields spread across two different configuration surfaces:
archiveToolResult (writer) → backend options
readToolResultArchive (replay hydration reader) → backend options
archiveResources (ref-addressed reader powering ArchiveRead) → tool options
Nothing structurally ties them together, so "writer enabled, reader tool absent" is a representable state. It has already bitten twice: once on the child-agent path (patched locally via CHILD_RECOVERY_TOOL_NAMES in packages/runtime/src/subagent-tools.ts:35) and once at the headless top-level surface (#2025).
Desktop happens to be correct only because both sides were wired by hand in two separate places (apps/desktop/src/main/tool-assembly.ts:328,398 and apps/desktop/src/main/session-stream.ts:333).
Proposal
Add a factory in packages/runtime that returns one indivisible bundle:
buildToolResultArchiveCapability({ recorder, resourceReader }) => {
archiveToolResult, // → backend input
readToolResultArchive, // → replay hydration
tools: [ArchiveRead], // → host tool surface
}
Hosts supply only the host-specific storage pieces (desktop: artifactStore; Harbor: local archive dir). A host either has the whole capability or none of it. CLI keeps "none", which stays a valid state.
This makes the illegal state unrepresentable by construction rather than caught by an after-the-fact assertion.
Explicitly out of scope
- No generic
capability → tool derivation framework. Archiving is currently the only real instance of this "writer implies reader tool" coupling. runtimeResources and snapshotImage look similar but differ in kind: when missing they fail at the call site rather than leaving a dangling reference. Generalize when a second real case appears.
- No bidirectional catalog invariant. The catalog is the full product vocabulary and host bindings are legitimately a subset (economy policy narrows surfaces, desktop-only tools are absent on headless,
disabledSurfaceIds: ['agent'] is intentional). catalog ⊆ bound is never true; the correct invariant is the conditional implication archiveToolResult present ⟹ ArchiveRead ∈ bound.
- No consolidation of
CHILD_RECOVERY_TOOL_NAMES and DEEP_RESEARCH_ALLOWED_TOOL_NAMES. They are consumer-side allowlists with distinct responsibilities (surface-narrowing recovery rule vs. mode-level product policy), not duplicate registration state. Extracting an exported ARCHIVE_READ_TOOL_NAME constant to remove string drift is sufficient.
- No activation of the catalog's
effects field. It cannot express same-authority coupling between writer and reader.
Dependencies
Blocked by #2025. That one fixes behavior; this one is a behavior-neutral consolidation. They must not be combined — mixing a refactor with a behavior change makes benchmark deltas unattributable.
Problem
The tool-result archive capability is currently three independent optional fields spread across two different configuration surfaces:
archiveToolResult(writer) → backend optionsreadToolResultArchive(replay hydration reader) → backend optionsarchiveResources(ref-addressed reader poweringArchiveRead) → tool optionsNothing structurally ties them together, so "writer enabled, reader tool absent" is a representable state. It has already bitten twice: once on the child-agent path (patched locally via
CHILD_RECOVERY_TOOL_NAMESinpackages/runtime/src/subagent-tools.ts:35) and once at the headless top-level surface (#2025).Desktop happens to be correct only because both sides were wired by hand in two separate places (
apps/desktop/src/main/tool-assembly.ts:328,398andapps/desktop/src/main/session-stream.ts:333).Proposal
Add a factory in
packages/runtimethat returns one indivisible bundle:Hosts supply only the host-specific storage pieces (desktop:
artifactStore; Harbor: local archive dir). A host either has the whole capability or none of it. CLI keeps "none", which stays a valid state.This makes the illegal state unrepresentable by construction rather than caught by an after-the-fact assertion.
Explicitly out of scope
capability → toolderivation framework. Archiving is currently the only real instance of this "writer implies reader tool" coupling.runtimeResourcesandsnapshotImagelook similar but differ in kind: when missing they fail at the call site rather than leaving a dangling reference. Generalize when a second real case appears.disabledSurfaceIds: ['agent']is intentional).catalog ⊆ boundis never true; the correct invariant is the conditional implicationarchiveToolResult present ⟹ ArchiveRead ∈ bound.CHILD_RECOVERY_TOOL_NAMESandDEEP_RESEARCH_ALLOWED_TOOL_NAMES. They are consumer-side allowlists with distinct responsibilities (surface-narrowing recovery rule vs. mode-level product policy), not duplicate registration state. Extracting an exportedARCHIVE_READ_TOOL_NAMEconstant to remove string drift is sufficient.effectsfield. It cannot express same-authority coupling between writer and reader.Dependencies
Blocked by #2025. That one fixes behavior; this one is a behavior-neutral consolidation. They must not be combined — mixing a refactor with a behavior change makes benchmark deltas unattributable.