Skip to content

Large thread decodes validate every known timeline item twice #18856

Closed as not planned

Description

@saphid

Performance

Decoding large thread snapshots and history pages validates every known timeline item twice.

ForwardCompatibleUnionArray (packages/contracts/src/baseSchemas.ts) and projectedTurnItemArray (packages/contracts/src/orchestrationV2.ts) decode each element through ForwardCompatibleUnion, drop unknown members, and then decodeTo(Schema.Array(Schema.toType(...))). That second step validates and rebuilds every surviving element again. Both turnItems and projected timeline rows pay for it. A large ordinary turn stays whole in bounded snapshots, so a turn with thousands of items pays it on every open and page.

Measured on 511d20c in Node, 6,000 known items, alternating old and new codecs in one process, 5 warmups and 15 samples each:

Decode Current Single validation
Item array 68.4 ms 47.4 ms
OrchestrationV2ThreadHistoryPage 89.5 ms 61.5 ms

A counting field check confirms that each known member is validated twice today. vp run test:perf:v2-wire passes either way: it checks payload sizes, not decode CPU.

Why this is an issue and not a PR

A working single-pass version exists on saphid:t3code/sweep/contracts-single-pass-items (148 contract tests pass, identical output in 20 old/new edge cases, wire suite before / after). It needs a custom Schema.declareConstructor that skips revalidation by comparing transformation identity, and it relies on Effect Schema's internal ~constructor hook to keep make/is and class constructors working. That is more machinery than a ~20 ms win justifies if Effect's internals change. Two smaller directions may exist:

  • An Effect Schema primitive for "decode an array and drop elements matching a predicate" without a typed second pass (possibly an upstream Effect request).
  • Accept unknown members in the decoded type (ReadonlyArray<Known | UnknownUnionMember>) and filter at the few read sites, which removes the second schema pass entirely.

Found during an agent-driven audit (GPT-6.1 Sol, high), with the reference implementation by GPT-6.1 Sol (high) and review by Claude Opus 5.5 (high) in T3 Code. Filed by Claude Opus 5.5 in Claude Code via T3 Code on behalf of Alex Southwell.

Activity

  1. juliusmarminge commented on Oct 12, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Thanks for the detailed write-up and measurements. Confirmed on main: ForwardCompatibleUnionArray (baseSchemas.ts) and projectedTurnItemArray (orchestrationV2.ts) decode each element through ForwardCompatibleUnion, then decodeTo(Schema.Array(Schema.toType(...))) validates the surviving known members a second time. Nothing requires that second pass for forward compatibility. It's a side effect of narrowing the decoded type so unknown members get filtered out.

    Still, this is a performance improvement, not a bug. Output is correct, and the cost you measured is about 20–28 ms on a 6,000-item thread, which is an extreme case. As you say yourself, the single-pass branch depends on Effect Schema internals (~constructor, a custom declareConstructor), and that's more risk than the win is worth. So I'm closing this as not planned and tracking decode cost under the perf tracker #9661.

    If you want to pursue it, we'd welcome a PR for your second option: keep Known | UnknownUnionMember in the decoded type and filter at the read sites. That removes the second pass without touching Effect internals. Please include the same before/after numbers. An upstream Effect primitive for "decode and drop" would also be worth raising with Effect.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    via-triageFiled through npx t3 triagewontfixThis will not be worked on

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions