feat(eve): prototype the per-session notification consumer#1194
feat(eve): prototype the per-session notification consumer#1194ruiconti wants to merge 14 commits into
Conversation
Producers append; consumers react. Each session gets one companion reactor run parked on a notify hook: producers (the remote callback route, the local subagent proxy, the callee's emit path) deliver curated child events with one fire-and-forget resumeHook, and the reactor appends the wrapped subagent.event to the session stream and performs the side effects (channel rendering, callback forwarding) in journaled steps. The session workflow is never woken for notifications, which closes the parked-for-input delivery hole and unifies the local and remote child-event paths into one shape. Backed by #1170. Signed-off-by: Rui Conti <ruiconti@gmail.com>
Per-lane wake accounting: the caller already pays a queue wake per notification (resumeHook into the session workflow), so the reactor there replaces a wake rather than adding one. The callee's outbound forwarding is the only lane where a reactor would introduce a new per-event wake, so v1 keeps the direct POST and merely defers it off the emitting step's critical path (today the caller's RTT is awaited inside the emit). Higher-frequency event types are admitted to the lane only with cursor-based coalescing, so N events during one invocation cost one wake. Signed-off-by: Rui Conti <ruiconti@gmail.com>
…tract No new channel authoring surface. The reactor invokes the same adapter event handler the session workflow's proxy step invokes for these events today, so channels render notifications with zero changes; the run doing the invoking is the only difference. Adapter state mutated while handling a notification persists in the reactor's own run state, which also resolves the edit-in-place rendering question without a stateless-hook contract. The subagent.event wrapper stays a stream-representation concern for followers and clients. Signed-off-by: Rui Conti <ruiconti@gmail.com>
The reactor is a framework-registered workflow function (workflow//eve//notificationReactor) in the same compiled bundle as the entry and turn workflows, executing as queue-driven invocations of the caller's own deployment — no new compute surface. Started plainly by the entry run (no latest-deployment routing): it holds the entry's stream writable and channel context, so it is pinned to the entry's deployment for the session's lifetime. Signed-off-by: Rui Conti <ruiconti@gmail.com>
…nsumers Same design; the run is a consumer, and the doc now says so everywhere, including the workflow id (workflow//eve//notificationConsumer) and the file name. Signed-off-by: Rui Conti <ruiconti@gmail.com>
…tion Lead the summary with the model rather than the bug: the durable stream is the propagation backbone, producers append, and consumption is a separately scheduled concern — followers already work this way, and the notification consumer is the first non-follower consumer moved onto it. The failure list stays as the evidence, and the lane is named as the on-ramp for future event classes (progress, proxied HITL) and for converging the remaining emit-coupled consumers. Signed-off-by: Rui Conti <ruiconti@gmail.com>
Same design, a third shorter: cut repeated rationale and filler; every section now states its point once. Signed-off-by: Rui Conti <ruiconti@gmail.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Bundle + Package Summary:
|
| Area | Metric | Baseline | Current | Delta |
|---|---|---|---|---|
| Package | Packed tarball | 4.05 MB | 4.05 MB | +830 B |
| Package | Unpacked publish size | 14.72 MB | 14.72 MB | +4.8 kB |
| Package | Installed footprint | 53.87 MB | 53.88 MB | +4.8 kB |
| Package | Published files | 2748 | 2753 | +5 |
| Package | Installed files | 5988 | 5993 | +5 |
| Runtime | Unique function payloads | 2 | 2 | 0 |
| Runtime | Total function bytes | 11.67 MB | 11.69 MB | +15.6 kB |
| Runtime | Public routes | 10 | 10 | 0 |
Changed function payloads vs rui/remote-agent-callback-notifications (413ce48) (2)
| Function | Status | Baseline | Current | Delta | Route changes |
|---|---|---|---|---|---|
functions/.well-known/workflow/v1/flow.func |
changed | 7.27 MB | 7.28 MB | +13.1 kB |
none |
functions/__server.func |
changed | 4.41 MB | 4.41 MB | +2.5 kB |
none |
eve init install
| Metric | Baseline | Current | Delta |
|---|---|---|---|
| Installed footprint | 92.27 MB | 92.28 MB | +4.8 kB |
| Installed packages | 122 | 122 | 0 |
| dependencies | 4 | 4 | 0 |
| devDependencies | 2 | 2 | 0 |
| Dependency package bytes | 29.00 MB | 29.00 MB | +4.8 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-07-24T20:34:33.823Z
- Route aliases: 10 public, 1 internal (11 total aliases)
- Vercel routes in config: 11
- Severity legend: 🔴 dominant/large, 🟠 notable, 🟡 watch, ⚪ small
Package Drill-Down
Package Details
- Package:
eve@0.27.3 - Package directory:
packages/eve - Tarball: 4.05 MB (
eve-0.27.3.tgz) - Unpacked payload: 14.72 MB across 2753 published files
- Installed footprint: 53.88 MB across 5993 installed files
- Installed root package: 13.44 MB
- Installed dependencies: 40.44 MB
- Runtime dependencies: 1
- Peer dependencies: 5 (4 optional)
Installed footprint is measured from an isolated temporary npm install of the packed tarball.
Heavy installed dependencies
@rolldown/binding-linux-x64-gnu: 18.96 MB (35.2%)eve: 13.44 MB (24.9%)ai: 6.43 MB (11.9%)zod: 5.04 MB (9.4%)nitro: 2.41 MB (4.5%)
Publish payload breakdown
Published file size
🟠 dist/src/compiled/experimental-ai-sdk-code-mo... [###.....................] 1.51 MB 10.2%
🟡 dist/src/compiled/@vercel/sandbox/index.js [#.......................] 632.4 kB 4.3%
🟡 dist/src/compiled/_chunks/workflow/undici-C2Z... [#.......................] 502.4 kB 3.4%
🟡 dist/src/compiled/@chat-adapter/slack/index.js [#.......................] 440.5 kB 3.0%
🟡 dist/src/compiled/@vercel/oidc/index.js [#.......................] 379.5 kB 2.6%
🔴 Other published files [########################] 11.26 MB 76.5%
Installed footprint breakdown
Installed package size
🔴 @rolldown/binding-linux-x64-gnu [########################] 18.96 MB 35.2%
🔴 eve [#################.......] 13.44 MB 24.9%
🔴 ai [########................] 6.43 MB 11.9%
🔴 zod [######..................] 5.04 MB 9.4%
🟠 nitro [###.....................] 2.41 MB 4.5%
🟡 @ai-sdk/provider-utils [#.......................] 852.0 kB 1.6%
🔴 Other installed packages [#########...............] 6.75 MB 12.5%
Runtime dependencies (1)
| Package | Range | Notes |
|---|---|---|
nitro |
3.0.260610-beta |
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: 92.28 MB across 7861 installed files
- Installed packages: 122 total (116 transitive-only)
- dependencies: 4 direct packages totaling 29.00 MB
- devDependencies: 2 direct packages totaling 5.04 MB
- Other transitive package files: 58.24 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 (30.3%)@rolldown/binding-linux-x64-gnu: 18.96 MB (20.5%)eve: 13.44 MB (14.6%)zod: 9.00 MB (9.7%)ai: 6.43 MB (7.0%)
Installed footprint breakdown
Installed package size
🔴 @typescript/typescript-linux-x64 [########################] 27.95 MB 30.3%
🔴 @rolldown/binding-linux-x64-gnu [################........] 18.96 MB 20.5%
🔴 eve [############............] 13.44 MB 14.6%
🔴 zod [########................] 9.00 MB 9.7%
🔴 ai [######..................] 6.43 MB 7.0%
🟠 @types/node [##......................] 2.54 MB 2.8%
🔴 Other installed packages [############............] 13.97 MB 15.1%
dependencies (4)
| Package | Range | Installed size | Share |
|---|---|---|---|
@vercel/connect |
0.4.2 |
135.8 kB | 0.1% |
ai |
^7.0.34 |
6.43 MB | 7.0% |
eve |
file:eve-0.27.3.tgz |
13.44 MB | 14.6% |
zod |
4.4.3 |
9.00 MB | 9.7% |
devDependencies (2)
| Package | Range | Installed size | Share |
|---|---|---|---|
@types/node |
24.x |
2.54 MB | 2.8% |
typescript |
7.0.2 |
2.50 MB | 2.7% |
Function Drill-Down
Payload Size Graph
Unique function payload size and share of total
🔴 functions/.well-known/workflow/v1/flow.func [########################] 7.28 MB 62.3%
🟠 functions/__server.func [###############.........] 4.41 MB 37.7%
Top Function Payloads
🟠 functions/.well-known/workflow/v1/flow.func • 1 public route • 7.28 MB
| Metric | Value |
|---|---|
| Public routes | /.well-known/workflow/v1/flow |
| Runtime | nodejs24.x |
| Handler | index.mjs |
| Payload | 7.28 MB |
| Function files | 7.28 MB across 40 files |
| Traced dependencies | 0 B |
| Signal | 🟠 Bundled file __eve_nitro_handler__.mjs is 2.72 MB (37.4%) |
🟠 🔎 Dependency Analysis
📦 Bundled files:
Bundled file size
🟠 __eve_nitro_handler__.mjs [########################] 2.72 MB 37.4%
🟡 _chunks/world-vercel.mjs [########................] 903.2 kB 12.4%
🟡 _chunks/sandbox.mjs [#######.................] 767.6 kB 10.5%
🟡 _libs/@ai-sdk/gateway+[...].mjs [####....................] 432.5 kB 5.9%
🟡 _chunks/token-util-B6qBs3-0.mjs [###.....................] 379.1 kB 5.2%
🟠 Other bundled files [##################......] 2.08 MB 28.5%
🧾 Vercel Config
{
"handler": "index.mjs",
"launcherType": "Nodejs",
"shouldAddHelpers": false,
"supportsResponseStreaming": true,
"runtime": "nodejs24.x",
"environment": {
"WORKFLOW_PRECONDITION_GUARD": "1",
"NODE_OPTIONS": "--experimental-require-module"
},
"maxDuration": "max",
"experimentalTriggers": [
{
"type": "queue/v2beta",
"topic": "__eve776561746865722d6167656e74_wkf_workflow_*",
"consumer": "default",
"retryAfterSeconds": 5,
"initialDelaySeconds": 0
}
]
}🟠 functions/__server.func • 9 public routes, 1 internal alias • 4.41 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]/stream |
| Internal aliases | /__server |
| Runtime | nodejs24.x |
| Handler | index.mjs |
| Payload | 4.41 MB |
| Function files | 4.41 MB across 30 files |
| Traced dependencies | 0 B |
| Signal | 🟠 Bundled file _chunks/runtime-artifacts.mjs is 1.41 MB (32.1%) |
🟠 🔎 Dependency Analysis
📦 Bundled files:
Bundled file size
🟠 _chunks/runtime-artifacts.mjs [########################] 1.41 MB 32.1%
🟠 _chunks/world-vercel.mjs [###############.........] 900.3 kB 20.4%
🟠 _chunks/sandbox.mjs [#############...........] 767.6 kB 17.4%
🟡 _chunks/token-util-B6qBs3-0.mjs [######..................] 379.1 kB 8.6%
🟡 _chunks/dist-BX517Nmz.mjs [######..................] 348.7 kB 7.9%
🟡 Other bundled files [##########..............] 598.0 kB 13.6%
🧾 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.46 s -> 2.41 s (-52.7 ms) vs
rui/remote-agent-callback-notifications (413ce48). - Timing is informational: shared GitHub runners are too variable for a hard timing budget.
Detailed phase timings vs `rui/remote-agent-callback-notifications (413ce48)`
| Phase | Baseline | Current | Delta |
|---|---|---|---|
extension.check |
1.1 ms | 1.1 ms | 0.0 ms |
project.resolve |
0.6 ms | 0.6 ms | 0.0 ms |
workspace.create |
0.8 ms | 0.7 ms | -0.1 ms |
host.prepare |
169.1 ms | 141.4 ms | -27.7 ms |
vercel.service-prefix.resolve |
2.0 ms | 1.8 ms | -0.2 ms |
nitro.app.create |
24.4 ms | 24.3 ms | -0.1 ms |
nitro.app.cache.prepare |
0.3 ms | 0.2 ms | -0.1 ms |
nitro.app.prepare |
1.0 ms | 0.7 ms | -0.3 ms |
nitro.app.public-assets |
0.7 ms | 0.7 ms | 0.0 ms |
nitro.app.prerender |
0.5 ms | 0.5 ms | 0.0 ms |
nitro.app.bundle |
665.3 ms | 653.6 ms | -11.7 ms |
nitro.app.cache.write |
0.4 ms | 0.4 ms | 0.0 ms |
sandbox.prewarm |
226.2 ms | 241.1 ms | +14.9 ms |
nitro.flow.create |
163.0 ms | 156.7 ms | -6.3 ms |
nitro.flow.cache.prepare |
0.2 ms | 0.2 ms | 0.0 ms |
nitro.flow.prepare |
0.7 ms | 0.9 ms | +0.2 ms |
nitro.flow.public-assets |
0.2 ms | 0.2 ms | 0.0 ms |
nitro.flow.prerender |
0.0 ms | 0.0 ms | 0.0 ms |
nitro.flow.bundle |
1.03 s | 993.7 ms | -41.1 ms |
nitro.flow.cache.write |
0.3 ms | 0.3 ms | 0.0 ms |
nitro.flow.close |
0.2 ms | 0.2 ms | 0.0 ms |
workflow.emit |
161.8 ms | 181.6 ms | +19.8 ms |
agent-summary.emit |
0.5 ms | 0.5 ms | 0.0 ms |
nitro.app.close |
0.1 ms | 0.0 ms | -0.1 ms |
output.publish |
3.6 ms | 3.4 ms | -0.2 ms |
workspace.remove |
5.7 ms | 5.9 ms | +0.2 ms |
…llbacks
Session callbacks now carry an event envelope discriminated by
event.status ("notification" | "termination"; "working" and
"input_required" are reserved and rejected). A remote callee forwards
its authorization.required/authorization.completed events to the caller
as notification callbacks, which the caller re-emits on its own stream
without resolving the pending call — the callback-URL analog of the
local subagent adapter's subagent-authorization-event proxying. A parent
that is itself a remote callee relays proxied child events one more hop
up its own callback URL.
Notification delivery is best-effort (an unreachable caller is logged on
the callee and never fails its turn); termination stays the single
durable resume path. The wire shape changed: mixed-version caller/callee
deployments reject each other's callbacks.
parseSessionCallback is renamed to parseCallbackMetadata to distinguish
the create-session callback metadata from the new callback events.
Signed-off-by: Rui Conti <ruiconti@gmail.com>
Models routinely emit outputSchema: {} for the optional per-call param
on the lowered subagent tool. Local delegation filters non-empty objects
(subagent-tool.ts) but remote dispatch forwarded the empty object, which
forced the callee into structured-output task completion and failed the
call with OUTPUT_SCHEMA_NOT_FULFILLED even when the callee produced a
correct prose result. Remote dispatch now applies the same non-empty
filter, falling back to the definition's declared schema.
Signed-off-by: Rui Conti <ruiconti@gmail.com>
When one model step requested both an approval-gated tool call and a runtime action (subagent or remote-agent call), the park classification returned on the runtime-action batch before the input-request park ran, silently dropping the approval: input.requested was never emitted, and the action-result resume called the model with the gated call's tool_use still unanswered — AI_MissingToolResultsError, failing the turn. The classification now persists both batches (the input batch with empty responseMessages, since the action batch owns the step's assistant messages) and emits input.requested immediately. On the action-result resume, executeStepBody's existing staged resolution re-parks on the still-unanswered batch, committing the resolved action results to history and closing the turn with the standard waiting boundary. Signed-off-by: Rui Conti <ruiconti@gmail.com>
… HITL collision New agent-remote-callbacks fixture whose probe-remote subagent calls the fixture's own deployment (WORKFLOW_LOCAL_BASE_URL locally, VERCEL_URL on Vercel; loopback callback hosts are allowed by the SSRF guard), keeping the suite self-contained while exercising the full remote dispatch and session-callback wire. Three evals: - auth-propagation: the callee parks on requestAuthorization, the forwarded authorization.required surfaces on the caller's stream, the eval completes the webhookUrl with a ?code, and the code round-trips through the callee's resume into the caller's final reply. - hitl-collision: one turn holds a pending release_gate approval and an in-flight remote call; the callee's auth completes (deterministic <childSessionId>:auth hook) while the caller is parked for input, and both resolve independently. Caught the dropped-approval harness bug. - notification-propagation: the callee's local credential-probe subagent relays its authorization events two hops to the caller, each exactly once and in order, without resolving the pending call. Also caught the empty-outputSchema dispatch bug. The hitl eval uses plain fetch for the hook: Client.fetch treats its whole path argument as pathname, so a ?code= query would be percent-encoded into the path and 404 the hook. Signed-off-by: Rui Conti <ruiconti@gmail.com>
The compiled manifest bakes the baseUrl evaluated during eve build, where a deployment's own origin (dev-server port, VERCEL_URL) may not exist yet. Runtime resolution already re-imports the authored module so live values can reference ambient state — auth, spec, operations — but kept the compile-time url copy. Prefer the re-imported module's baseUrl when present, consistent with the module's own contract. Signed-off-by: Rui Conti <ruiconti@gmail.com>
One companion workflow run per session (workflow//eve//notificationConsumer, stable-named beside the entry and turn workflows), started by the entry with the driver's stream writable and serialized channel context, parked on <sessionId>:notify. Each delivery runs one journaled step: invoke the channel's existing adapter event handler against the curated child event and append the wrapped subagent.event (now carrying childSessionId) to the session stream. Producers ring it fire-and-forget: callback metadata bakes a notifyUrl targeting the consumer hook, the callee posts notifications there (falling back to the legacy url for pre-consumer callers), and the callback route rings the token with a brief HookNotFound retry for the session-opening race — always 202 once well-formed, drop and log otherwise. The session workflow is never woken for notifications, which also delivers them while the turn is parked for input — previously a silent drop. Termination is untouched. Prototype: local subagent proxying still uses the legacy re-emit, and the childSessionId protocol addition trips the rule-36 capability-epoch ceremony, deferred to the real change. Signed-off-by: Rui Conti <ruiconti@gmail.com>
The fixture triggers authorization.required through existing public API only. An interactive OpenAPI connection per hop (probe, nested-probe) fronts a bearer-gated /probe/credential channel route on the fixture's own deployment; completeAuthorization mints the bearer from the callback's ?code, so the credential still proves the round trip. A custom AuthFn on the eve channel projects the probe-remote bearer into the user principal interactive authorization requires. The connections read their self baseUrl through a property getter resolved live at runtime (the compile-time copy bakes a placeholder) and keep no cross-step token cache: every eval session shares one user principal, so a module-level cache would bleed one eval's token into the next session and suppress its authorization.required. Evals assert the consumer's wrapped subagent.event instead of top-level re-emits. hitl-collision completes the hook from the wrapped notification itself — live-stream first, durable-stream poll when the approval parks the turn before the notification lands — covering parked-for-input delivery, which the pre-consumer architecture dropped. Signed-off-by: Rui Conti <ruiconti@gmail.com>
fa78d44 to
80cc51e
Compare
|
Restacking: this lands before #1167, not after — replacement PR based on the research branch follows. |
Stacked on #1167 (base:
rui/remote-agent-callback-notifications) — only the top three commits are this PR. Prototype for the plan in #1172 / issue #1170: validate the consumer design end to end and surface what the real implementation has to deal with. Draft on purpose.What
workflow//eve//notificationConsumer— one companion run per session, started by the entry workflow with the driver's stream writable and serialized channel context, parked on<sessionId>:notify. Each delivery is one journaled step: invoke the channel's existing adapter event handler against the curated child event, append the wrappedsubagent.event(now carryingchildSessionId) to the session stream. The session workflow is never woken for notifications.notifyUrltargeting the consumer hook; the callee posts notifications there (legacyurlfallback for pre-consumer callers); the route rings the token and 202s once the payload is well-formed — a disposed consumer logs and drops.fix(eve): OpenAPIbaseUrlresolves from the live authored module. Found while making the fixture work without theeve/toolsexposure: the compiled manifest bakes the build-time value, where the deployment's own origin doesn't exist yet, whileauth/spec/operationsalready resolve live. Standalone fix, extractable.eve/toolsexposure (reverts that commit from the base branch): auth triggers are now interactive OpenAPI connections fronting a bearer-gated/probe/credentialroute on the fixture's own deployment, with a customAuthFnminting the user principal interactive auth requires. Existing public API only.All three evals pass through the consumer — 34/34 gates, ~17s locally — including the new coverage that motivated everything:
hitl-collisionreceives and completes an authorization notification while the turn is parked for input, the state where today's architecture silently drops it (resumeHook→ 404).What the prototype surfaced
HookNotFoundErrorretry in the route covers it; hooks are creator-owned so entry-side pre-creation isn't available.start(reference)silently creates a run that later diesWorkflowNotRegisteredErrorunless the function name is inSTABLE_WORKFLOW_NAMES— that set is the whole contract between the bundler and the reference template.Known-red, deferred to the real change
childSessionIdonSubagentChildEventStreamEventtrips the rule-36 capability-epoch ceremony on four contracts (dynamicTool,hook,dynamicSkill,dynamicInstructions) — additive/compatible, ceremony not run here, soguard:invariantsfails on this branch.finally; a crash before it leaks a parked run.Unit suite 5368 passed, typecheck 33/33, lint clean.