Skip to content

feat(eve): versioned wire schema for session inbox hook payloads - #1767

Draft
ruiconti wants to merge 5 commits into
rui/hook-deliver-wire-compatfrom
rui/session-inbox-wire-schema
Draft

feat(eve): versioned wire schema for session inbox hook payloads#1767
ruiconti wants to merge 5 commits into
rui/hook-deliver-wire-compatfrom
rui/session-inbox-wire-schema

Conversation

@ruiconti

@ruiconti ruiconti commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Related to #1765. Stacked on #1752 (base branch: rui/hook-deliver-wire-compat); review only the commits above that base until it merges.

Research plan (research/session-inbox-wire-schema.md) plus a working prototype: durable hook payloads become declared, validated, versioned wire formats instead of an emergent property of whatever type flows into resumeHook — the gap that let #1586 ship silent message loss as a "refactor".

Design

  • Generic core (execution/wire/codec.ts): createWireCodec({ family, versions }) where each version is exactly one pure structural zod schema (vendored #compiled/zod) plus a separate normalizer. Version 0 is the unversioned era (legacy shapes and control payloads). Changing a shipped shape means adding a version — editing one is mechanically detectable (below).
  • Encode goes through the codec too. createWireCodec takes the family's build function and validates its output against the current version's schema before it persists (WireEncodeError) — producer drift dies at the producer, not at a pinned consumer weeks later. Reference identity of the built envelope survives validation.
  • Decode validates, never trusts. Dispatch on v, parse with that version's schema; unknown versions, unrecognized kinds, and malformed/truncated payloads all throw a family-tagged WireDecodeError at the boundary. Consumers drop the payload through a recorded reporting step (durable trace + structured error log; the driver bundle rejects direct logging's Node builtins) and keep the session alive.
  • Validation stops at the envelope. Adapter-owned interiors (DeliverPayload, auth, caller) cross as opaque objects (z.custom<T>, object-ness only) — deep-validating them would turn every adapter field addition into a wire change. The frozen shapes render them as {}, making the opacity boundary visible in the contract itself.
  • Consumer-version stamp (moved here from fix(eve): persist session sends as the durable deliver envelope #1752 to keep that PR a pure mitigation): session inbox hooks carry metadata: { eveVersion }, readable pre-resume via getHookByToken, so future wire changes can be version-gated per consumer. The version comes from an fs-free stamp-based module (internal/package-version.ts) because the stamp site runs in workflow context. Markerless hooks mean the consumer predates the stamp.
  • Session inbox is the first family (execution/wire/session-inbox-wire.ts): v0 = legacy deliver + raw 0.30.3–0.30.8 sends + controls; v1 = the fix(eve): persist session sends as the durable deliver envelope #1752 hybrid envelope + v: 1 (byte-compatible with every live cohort, so no producer gating yet). The scattered kind === "send" branches in both consumers collapse into decodeSessionInbox.

Backwards-compatibility checks

The contract test (session-inbox-wire.test.ts) freezes three things:

  1. Shapes — each version's schema serialized to JSON Schema, byte-frozen. Editing a shipped schema fails; proven red by adding a field to v1 without a bump.
  2. Fixtures — frozen payload JSON per shipped version must keep decoding on the current build, with frozen normalizations.
  3. Round-tripencode output is byte-frozen, declares currentVersion, and parses under the current schema.

Plus two mechanical guards in the lint job — rule 37 (inline send/deliver literals at resumeHook outside execution/wire/ are violations unless routed through an encode* wire function; proven red on a raw literal) and rule 38 (every *-wire.ts family must carry a colocated FROZEN_SHAPES/FROZEN_FIXTURES contract test; proven red on a family without one) — and the e2e byte gate asserting the emitted version end-to-end. The research doc records the layered enforcement story and why protobuf was rejected for this boundary (eve doesn't own the bytes — @workflow's devalue serde does; proto's silent unknown-field tolerance is the failure mode we're eliminating; no new runtime dependency).

Validation

  • pnpm test:unit (579 files / 6050 passed, includes the wire contract and generic codec suites)
  • pnpm --filter eve exec vitest run --config vitest.integration.config.ts src/execution/session-command-inbox.integration.test.ts src/execution/turn-cancellation.integration.test.ts (14 passed — frozen ≤0.30.2 and 0.30.7-style consumers accept the v1 envelope)
  • EVE_E2E_MODEL=mock pnpm --filter agent-channels exec eve eval --strict --exclude-tag real-model (2 evals, 13 gates against rebuilt dist)
  • pnpm typecheck — also proves vendored zod passes the workflow-bundle constraint that rejects Node builtins
  • pnpm guard:invariants, pnpm fmt, pnpm lint green; rule 37 and the shape freeze both proven red

Reviewer attention

  • Decode normalizes: wire-only fields (v, the mirror) and the send shape never reach in-memory buffers.
  • zod now enters the workflow driver bundle (pure JS; bundle-size cost accepted for boundary validation).
  • The v2 phase (mirror removal, stamp-gated emit) is deliberately unimplemented; it gets its own frozen-consumer red/green when the 0.30.3–0.30.8 cohort ages out.

Checklist

  • I linked an issue with prior discussion confirming this change is wanted
  • I ran the relevant checks from CONTRIBUTING.md
  • I added tests and documentation where relevant
  • I added a changeset if this touches the published eve package
  • DCO sign-off passes for every commit (git commit --signoff)

Research plan plus prototype for #1765. Session inbox payloads now cross
durable hooks through a single wire module: encodeSessionSend emits the
v1 envelope (the deliver hybrid plus an explicit `v` field, byte-
compatible with every live cohort) and decodeSessionInbox is the only
consumer-side interpretation, normalizing legacy shapes and throwing
SessionInboxWireError on anything unrecognized instead of reinterpreting
it. Drops are surfaced through a recorded reporting step because the
workflow driver bundle cannot import the logging module's Node builtins.

Enforcement: guard rule 37 bans inline send/deliver literals at
resumeHook outside execution/wire/ (proven red on a raw literal), and a
frozen byte snapshot pins the v1 fixture so editing a shipped shape
requires editing a file that says so.

Signed-off-by: Rui Conti <ruiconti@gmail.com>
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
eve-docs Ready Ready Preview Aug 7, 2026 4:42am
eve-docs-4759 Ready Ready Preview, v0 Aug 7, 2026 4:42am

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Bundle + Package Summary: apps/fixtures/weather-agent

Key takeaways

  • Bundle warning: function payloads grew 16.8%.
  • Runtime delta: function payloads 17.00 MB -> 19.85 MB (+2.85 MB ⚠️); 2 changed payloads.

❌ Bundle Warning: Action Will Fail

This action will fail because the bundle warning policy was exceeded. Add the acknowledge-bundle-warning label to acknowledge the regression and allow the check to pass without regenerating this report.

Area Warning Details
Runtime Total function bytes 17.00 MB -> 19.85 MB; +2.85 MB ⚠️ (16.8%) over limit 10.0%

Delta vs rui/hook-deliver-wire-compat (39c20ce)

Area Metric Baseline Current Delta
Package Packed tarball 7.66 MB 7.66 MB +4.6 kB ⚠️
Package Unpacked publish size 28.70 MB 28.71 MB +9.3 kB ⚠️
Package Installed footprint 72.13 MB 72.14 MB +9.3 kB ⚠️
Package Published files 3039 3045 +6
Package Installed files 6747 6753 +6
Runtime Unique function payloads 2 2 0
Runtime Total function bytes 17.00 MB 19.85 MB +2.85 MB ⚠️
Runtime Public routes 13 13 0
Changed function payloads vs rui/hook-deliver-wire-compat (39c20ce) (2)
Function Status Baseline Current Delta Route changes
functions/__server.func changed 8.50 MB 9.93 MB +1.43 MB ⚠️ none
functions/.well-known/workflow/v1/flow.func changed 8.50 MB 9.93 MB +1.43 MB ⚠️ none

eve init install

Metric Baseline Current Delta
Installed footprint 110.54 MB 110.55 MB +9.3 kB ⚠️
Installed packages 121 121 0
dependencies 4 4 0
devDependencies 2 2 0
Dependency package bytes 43.18 MB 43.19 MB +9.3 kB ⚠️
devDependency package bytes 5.04 MB 5.04 MB 0 B ➖
Build Metadata
  • Preset: vercel
  • Nitro: nitro@3.0.260610-beta
  • Output directory: apps/fixtures/weather-agent/.vercel/output
  • Build metadata timestamp: 2026-08-07T04:43:22.173Z
  • Route aliases: 13 public, 1 internal (14 total aliases)
  • Vercel routes in config: 16
  • Severity legend: 🔴 dominant/large, 🟠 notable, 🟡 watch, ⚪ small
Package Drill-Down

Package Details

  • Package: eve@0.31.0
  • Package directory: packages/eve
  • Tarball: 7.66 MB (eve-0.31.0.tgz)
  • Unpacked payload: 28.71 MB across 3045 published files
  • Installed footprint: 72.14 MB across 6753 installed files
  • Installed root package: 27.36 MB
  • Installed dependencies: 44.78 MB
  • Runtime dependencies: 2
  • Peer dependencies: 5 (4 optional)

Installed footprint is measured from an isolated temporary npm install of the packed tarball.

Heavy installed dependencies

  • eve: 27.36 MB (37.9%)
  • @rolldown/binding-linux-x64-gnu: 19.35 MB (26.8%)
  • ai: 6.67 MB (9.2%)
  • zod: 5.07 MB (7.0%)
  • undici: 3.50 MB (4.9%)
Publish payload breakdown
Published file size
🔴 dist/src/compiled/shadcn-registry/index.js       [################........] 9.68 MB 33.7%
🟠 dist/src/compiled/@photon-ai/chat-adapter-ime... [####....................] 2.42 MB 8.4%
🟠 dist/src/compiled/experimental-ai-sdk-code-mo... [###.....................] 1.51 MB 5.2%
🟡 dist/src/compiled/_chunks/node/undici-DWL_MYm... [#.......................] 502.4 kB 1.7%
🟡 dist/src/compiled/_chunks/workflow/undici-DWL... [#.......................] 502.4 kB 1.7%
🔴 Other published files                            [########################] 14.10 MB 49.1%
Installed footprint breakdown
Installed package size
🔴 eve                             [########################] 27.36 MB 37.9%
🔴 @rolldown/binding-linux-x64-gnu [#################.......] 19.35 MB 26.8%
🔴 ai                              [######..................] 6.67 MB 9.2%
🔴 zod                             [####....................] 5.07 MB 7.0%
🟠 undici                          [###.....................] 3.50 MB 4.9%
🟠 nitro                           [##......................] 2.41 MB 3.3%
🔴 Other installed packages        [#######.................] 7.79 MB 10.8%
Runtime dependencies (2)
Package Range Notes
nitro 3.0.260610-beta
undici 8.9.0
Peer dependencies (5)
Package Range Notes
@opentelemetry/api ^1.0.0 optional peer
ai catalog:
braintrust ^3.0.0 optional peer
just-bash ^3.0.0 optional peer
microsandbox ^0.5.0 optional peer
eve init install drill-down

eve init install details

  • Command: eve init my-agent
  • Package manager: npm
  • Installed footprint: 110.55 MB across 8629 installed files
  • Installed packages: 121 total (115 transitive-only)
  • dependencies: 4 direct packages totaling 43.19 MB
  • devDependencies: 2 direct packages totaling 5.04 MB
  • Other transitive package files: 62.32 MB

Installed footprint is measured from an isolated temporary eve init my-agent using the current packed eve tarball.

Heavy installed dependencies

  • @typescript/typescript-linux-x64: 27.95 MB (25.3%)
  • eve: 27.36 MB (24.7%)
  • @rolldown/binding-linux-x64-gnu: 19.35 MB (17.5%)
  • zod: 9.02 MB (8.2%)
  • ai: 6.67 MB (6.0%)
Installed footprint breakdown
Installed package size
🔴 @typescript/typescript-linux-x64 [########################] 27.95 MB 25.3%
🔴 eve                              [#######################.] 27.36 MB 24.7%
🔴 @rolldown/binding-linux-x64-gnu  [#################.......] 19.35 MB 17.5%
🔴 zod                              [########................] 9.02 MB 8.2%
🔴 ai                               [######..................] 6.67 MB 6.0%
🟠 undici                           [###.....................] 3.50 MB 3.2%
🔴 Other installed packages         [##############..........] 16.70 MB 15.1%
dependencies (4)
Package Range Installed size Share
@vercel/connect 0.4.3 141.0 kB 0.1%
ai ^7.0.38 6.67 MB 6.0%
eve file:eve-0.31.0.tgz 27.36 MB 24.7%
zod 4.4.3 9.02 MB 8.2%
devDependencies (2)
Package Range Installed size Share
@types/node 24.x 2.54 MB 2.3%
typescript 7.0.2 2.50 MB 2.3%
Function Drill-Down

Payload Size Graph

Unique function payload size and share of total
🔴 functions/.well-known/workflow/v1/flow.func     [########################] 9.93 MB 50.0%
🔴 functions/__server.func                         [########################] 9.93 MB 50.0%

Top Function Payloads

🟠 functions/.well-known/workflow/v1/flow.func • 1 public route • 9.93 MB
Metric Value
Public routes /.well-known/workflow/v1/flow
Runtime nodejs24.x
Handler index.mjs
Payload 9.93 MB
Function files 9.93 MB across 43 files
Traced dependencies 0 B
Signal 🟠 Bundled file index.mjs is 3.75 MB (37.7%)

🟠 🔎 Dependency Analysis

📦 Bundled files:

Bundled file size
🟠 index.mjs                       [########################] 3.75 MB 37.7%
🟠 _chunks/runtime-artifacts.mjs   [##########..............] 1.62 MB 16.3%
🟡 _libs/undici.mjs                [######..................] 980.5 kB 9.9%
🟡 _chunks/sandbox.mjs             [#####...................] 769.0 kB 7.7%
🟡 _libs/@ai-sdk/gateway+[...].mjs [###.....................] 432.8 kB 4.4%
🟠 Other bundled files             [###############.........] 2.38 MB 24.0%

🧾 Vercel Config

{
  "handler": "index.mjs",
  "launcherType": "Nodejs",
  "shouldAddHelpers": false,
  "supportsResponseStreaming": true,
  "runtime": "nodejs24.x",
  "maxDuration": "max",
  "experimentalTriggers": [
    {
      "type": "queue/v2beta",
      "topic": "__eve776561746865722d6167656e74_wkf_workflow_*",
      "consumer": "default",
      "retryAfterSeconds": 5,
      "initialDelaySeconds": 0
    }
  ],
  "environment": {
    "WORKFLOW_PRECONDITION_GUARD": "1"
  }
}

🟠 functions/__server.func • 12 public routes, 1 internal alias • 9.93 MB
Metric Value
Public routes /
/eve/v1/callback/[token]
/eve/v1/connections/[name]/callback/[token]
/eve/v1/health
/eve/v1/info
/eve/v1/session
/eve/v1/session/[sessionId]
/eve/v1/session/[sessionId]/cancel
/eve/v1/session/[sessionId]/clear
/eve/v1/session/[sessionId]/compact
/eve/v1/session/[sessionId]/reset
/eve/v1/session/[sessionId]/stream
Internal aliases /__server
Runtime nodejs24.x
Handler index.mjs
Payload 9.93 MB
Function files 9.93 MB across 43 files
Traced dependencies 0 B
Signal 🟠 Bundled file index.mjs is 3.75 MB (37.7%)

🟠 🔎 Dependency Analysis

📦 Bundled files:

Bundled file size
🟠 index.mjs                       [########################] 3.75 MB 37.7%
🟠 _chunks/runtime-artifacts.mjs   [##########..............] 1.62 MB 16.3%
🟡 _libs/undici.mjs                [######..................] 980.5 kB 9.9%
🟡 _chunks/sandbox.mjs             [#####...................] 769.0 kB 7.7%
🟡 _libs/@ai-sdk/gateway+[...].mjs [###.....................] 432.8 kB 4.4%
🟠 Other bundled files             [###############.........] 2.38 MB 24.0%

🧾 Vercel Config

{
  "handler": "index.mjs",
  "launcherType": "Nodejs",
  "shouldAddHelpers": false,
  "supportsResponseStreaming": true,
  "runtime": "nodejs24.x"
}

Build Timing: e2e/fixtures/agent-tools-sandbox

This is an informational timing measurement inside eve build, from preflight through publication. Output-size measurement and profile writing are excluded.

Build mode: deployable Vercel build with sandbox template prewarm included.

  • Build pipeline: 2.82 s -> 2.46 s (-353.9 ms) vs rui/hook-deliver-wire-compat (39c20ce).
  • Timing is informational: shared GitHub runners are too variable for a hard timing budget.
Detailed phase timings vs `rui/hook-deliver-wire-compat (39c20ce)`
Phase Baseline Current Delta
extension.check 6.6 ms 1.1 ms -5.5 ms
project.resolve 5.4 ms 0.6 ms -4.8 ms
workspace.create 4.7 ms 0.7 ms -4.0 ms
host.prepare 907.4 ms 212.0 ms -695.4 ms
vercel.service-prefix.resolve 2.7 ms 2.8 ms +0.1 ms
nitro.create 213.5 ms 442.1 ms +228.6 ms
sandbox.prewarm 296.3 ms 360.0 ms +63.7 ms
nitro.cache.prepare 0.2 ms 0.2 ms 0.0 ms
nitro.prepare 0.9 ms 0.8 ms -0.1 ms
nitro.public-assets 0.8 ms 0.7 ms -0.1 ms
nitro.prerender 0.4 ms 0.6 ms +0.2 ms
nitro.bundle 1.35 s 1.41 s +58.3 ms
nitro.cache.write 0.3 ms 0.4 ms +0.1 ms
vercel.workflow-function.materialize 22.0 ms 23.4 ms +1.4 ms
agent-summary.emit 0.5 ms 0.5 ms 0.0 ms
nitro.close 0.1 ms 0.2 ms +0.1 ms
output.publish 3.6 ms 6.8 ms +3.2 ms
workspace.remove 2.2 ms 2.5 ms +0.3 ms

…rsion

Reshapes the prototype per review: the versioning discipline moves into a
generic core (execution/wire/codec.ts) and the session inbox becomes its
first family. Each wire version is exactly one pure structural zod schema
(vendored #compiled/zod) with a separate normalizer; v0 is the unversioned
era including control payloads. Decode validates instead of trusting:
unknown versions, unrecognized kinds, and malformed shapes all throw a
family-tagged WireDecodeError. Validation stops at the envelope — adapter-
owned interiors cross as opaque objects.

The contract test freezes each version's JSON Schema (editing a shipped
schema goes red; proven), pins backwards compatibility with frozen payload
fixtures per version, and round-trips encode through the current schema.
The drop-report step and guard rule 37 are generalized to any wire family.

Signed-off-by: Rui Conti <ruiconti@gmail.com>
…ntracts

Encode now goes through the codec too: createWireCodec takes the family's
build function and validates its output against the current version's
schema before it persists (WireEncodeError), so producer drift dies at the
producer instead of at a pinned consumer weeks later. Reference identity
of the built envelope is preserved past validation.

Guard rule 38 requires every execution/wire/*-wire.ts family to carry a
colocated contract test with FROZEN_SHAPES and FROZEN_FIXTURES (proven
red on a family without one), and rule 37's stale session-inbox-specific
wording is generalized to wire families. The research doc records the
encode rule, the layered enforcement story, and why protobuf was
rejected for this boundary.

Signed-off-by: Rui Conti <ruiconti@gmail.com>
ruiconti added a commit that referenced this pull request Aug 7, 2026
…to the follow-up

The eveVersion hook-metadata stamp (and its fs-free package-version
module) is future version-gating infrastructure, not part of the
mitigation. It re-lands with the versioned wire codec in #1767 so this
PR stays a pure forward fix: hybrid deliver envelope, both producers
routed through the encoder, legacy decodes, and the regression guards.

Signed-off-by: Rui Conti <ruiconti@gmail.com>
Re-lands the hook-metadata stamp moved out of #1752 to keep that PR a
pure mitigation. Producers read it pre-resume via getHookByToken to
version-gate future wire changes; the version comes from the fs-free
stamp-based module so workflow bundles stay clear of node:fs. Markerless
hooks mean the consumer predates the stamp.

Signed-off-by: Rui Conti <ruiconti@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant