Skip to content

refactor(runtime): make the tool-result archive capability indivisible #2026

Description

@Astro-Han

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions