mirror of
https://github.com/vercel/eve.git
synced 2026-09-20 05:35:39 +08:00
387 lines
16 KiB
JSON
387 lines
16 KiB
JSON
|
|
{
|
||
|
|
"title": "eve session network hops",
|
||
|
|
"captured": "2026-09-11",
|
||
|
|
"baseCommit": "adb6beff622b274970d4e48b7178b7d44c11d456",
|
||
|
|
"workingTree": true,
|
||
|
|
"sdk": "@workflow/core 5.0.0-beta.50",
|
||
|
|
"evidence": "Source-derived operation inventory, not a latency benchmark. World, cache, replay, payload size, and authored integrations change request counts.",
|
||
|
|
"clocks": [
|
||
|
|
"HTTP request to first provider request",
|
||
|
|
"HTTP request to dispatch acceptance / returned Session identity",
|
||
|
|
"HTTP request to visible turn settlement"
|
||
|
|
],
|
||
|
|
"invariants": {
|
||
|
|
"sessionId": "original stream-owning run ID",
|
||
|
|
"ownerRunId": "current executor; changes on handoff",
|
||
|
|
"metadata": "{ sessionId }, accessed only when requested after resumption",
|
||
|
|
"startupHooks": "1 eve stable inbox + requested aliases + 1 SDK cancellation hook for the first turn"
|
||
|
|
},
|
||
|
|
"limitations": [
|
||
|
|
"Alias-only delivery cannot distinguish an unowned address from a handoff gap. Atomic handoff is an upstream requirement.",
|
||
|
|
"Rows name SDK operations, not a guaranteed count of lower-level HTTP retries or queue deliveries.",
|
||
|
|
"Authorization, sandbox, memory, telemetry and authored hooks must be measured for a specific deployed agent."
|
||
|
|
],
|
||
|
|
"rows": [
|
||
|
|
{
|
||
|
|
"id": "ingress",
|
||
|
|
"title": "HTTP request + request authorization",
|
||
|
|
"modes": ["new", "stable", "alias", "handoff"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "1 HTTP request; auth-dependent I/O",
|
||
|
|
"why": "Authenticate and authorize the resource before delivery. Auth provider/cache costs are application dependent.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/channel/channel-address.ts",
|
||
|
|
"symbol": "createChannelAddress"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "lookup",
|
||
|
|
"title": "SDK hook lookup",
|
||
|
|
"modes": ["stable", "alias", "handoff"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "1 hooks.getByToken",
|
||
|
|
"why": "resumeHook(token, command) resolves the live hook and its resume context. No eve preflight; no metadata hydration.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/node_modules/@workflow/core/dist/runtime/resume-hook.js",
|
||
|
|
"symbol": "async function resumeHookImpl"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "missing",
|
||
|
|
"title": "Try alias before creating",
|
||
|
|
"modes": ["new"],
|
||
|
|
"lane": "conditional",
|
||
|
|
"operations": "0 for ID-only; 1 hooks.getByToken for an unowned alias",
|
||
|
|
"why": "An ID-only create goes straight to start. A missing channel alias starts a candidate immediately; an ambiguous failure does not.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/channel/channel-address.ts",
|
||
|
|
"symbol": "const existing = await dispatch()"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "key",
|
||
|
|
"title": "Resume payload encryption",
|
||
|
|
"modes": ["stable", "alias", "handoff"],
|
||
|
|
"lane": "conditional",
|
||
|
|
"operations": "0 with stored public key; key/run reads on SDK fallback",
|
||
|
|
"why": "Current hooks carry resume context. Sealing to the public key is local; this is distinct from decrypting metadata.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/node_modules/@workflow/core/dist/runtime/resume-hook.js",
|
||
|
|
"symbol": "async function resolveHookEncryptionKey"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "receive",
|
||
|
|
"title": "Durably accept payload",
|
||
|
|
"modes": ["stable", "alias", "handoff"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "1 hook_received event write",
|
||
|
|
"why": "Commit the command before publishing its wake. Backend retry/dedup capabilities control transport retries.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/node_modules/@workflow/core/dist/runtime/resume-hook.js",
|
||
|
|
"symbol": "async function resumeHookImpl"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "wake",
|
||
|
|
"title": "Publish owner wake",
|
||
|
|
"modes": ["stable", "alias", "handoff"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "1 queue publish",
|
||
|
|
"why": "Targets the current owner on its pinned deployment; acceptance requires the durable write and publish.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/node_modules/@workflow/core/dist/runtime/resume-hook.js",
|
||
|
|
"symbol": "async function resumeHookImpl"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "identity",
|
||
|
|
"title": "Return stable identity to alias caller",
|
||
|
|
"modes": ["alias", "handoff"],
|
||
|
|
"lane": "response",
|
||
|
|
"operations": "0 hook reads; lazy metadata key access as needed",
|
||
|
|
"why": "Runs AFTER resume acceptance, concurrent with the awakened workflow. Known session addresses and forwarding-only senders do not access metadata.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/session-inbox/resume.ts",
|
||
|
|
"symbol": "get sessionId()"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "start",
|
||
|
|
"title": "Start original session workflow",
|
||
|
|
"modes": ["new"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "run_created + queue publish",
|
||
|
|
"why": "One session workflow, no child turn workflow. SDK start may overlap publication and persistence.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/workflow-runtime.ts",
|
||
|
|
"symbol": "async createSession"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "activation",
|
||
|
|
"title": "Workflow activation / event history",
|
||
|
|
"modes": ["new", "stable", "alias", "handoff"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "run lifecycle writes + history reads, capability/replay dependent",
|
||
|
|
"why": "Queue delivery is a compute hop, not an extra producer HTTP call. Retained VM and preload paths differ from cold replay.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/node_modules/@workflow/core/dist/runtime/suspension-handler.js",
|
||
|
|
"symbol": "handleSuspension"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "claims",
|
||
|
|
"title": "Register stable inbox and initial alias",
|
||
|
|
"modes": ["new"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "1 hook_created per address; one concurrent claim batch",
|
||
|
|
"why": "One stable address, plus the requested alias. getConflict is a registration fence preventing duplicate first-turn execution, not hooks.getByToken.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/session/entry.ts",
|
||
|
|
"symbol": "const [sessionCreation"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "state",
|
||
|
|
"title": "Initialize or restore session state",
|
||
|
|
"modes": ["new", "stable", "alias", "handoff"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "new: createSessionStep lifecycle; existing: inline snapshot hydration",
|
||
|
|
"why": "New-session initialization overlaps address claiming. Current snapshots are carried in durable step results, not fetched from a per-turn snapshot stream.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/create-session-step.ts",
|
||
|
|
"symbol": "async function createSessionStep"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "handoff-check",
|
||
|
|
"title": "Check idle handoff eligibility",
|
||
|
|
"modes": ["handoff"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "isSessionIdleForHandoffStep lifecycle",
|
||
|
|
"why": "Only a fresh delivery to an idle session can trigger transfer. Same-deployment and busy sessions do not run this step.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/session/handoff.ts",
|
||
|
|
"symbol": "async checkpoint"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "anchor",
|
||
|
|
"title": "Register terminal anchor, first handoff only",
|
||
|
|
"modes": ["handoff"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "1 hook_created on original owner's first attempt",
|
||
|
|
"why": "Keeps the original run and its public stream alive after its execution ownership transfers.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/session/entry.ts",
|
||
|
|
"symbol": "const ensureAnchor"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "release",
|
||
|
|
"title": "Release owned addresses and drain accepted reads",
|
||
|
|
"modes": ["handoff"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "1 hook_disposed per address",
|
||
|
|
"why": "If another accepted command is found, reclaim and abandon handoff. Release and claim are not atomic.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/session-inbox/inbox.ts",
|
||
|
|
"symbol": "async release()"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "successor",
|
||
|
|
"title": "Register activation callback and start successor",
|
||
|
|
"modes": ["handoff"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "activation hook_created + startSessionOwnerStep lifecycle + run_created + publish",
|
||
|
|
"why": "Activation callback is registered before starting a run that can signal it. Successor receives explicit anchorRunId, snapshot, address set, and configured timeout duration. Successful activation restarts that duration.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/session/handoff.ts",
|
||
|
|
"symbol": "async start"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "reclaim",
|
||
|
|
"title": "Validate and claim transferred addresses",
|
||
|
|
"modes": ["handoff"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "validation step lifecycle + 1 hook_created per address, batched",
|
||
|
|
"why": "No model work until all claims succeed. Preserves stable identity and uses the original stream writer.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/session/entry.ts",
|
||
|
|
"symbol": "await validateSessionCheckpointStep"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "ack",
|
||
|
|
"title": "Acknowledge successor activation",
|
||
|
|
"modes": ["handoff"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "signalSessionOwnerActivationStep + resume lookup/write/publish",
|
||
|
|
"why": "Old owner can now exit or park as anchor. Later owners signal the anchor only at session end.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/session/handoff-steps.ts",
|
||
|
|
"symbol": "signalSessionOwnerActivationStep"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "timeout",
|
||
|
|
"title": "Start deadline workflow concurrently",
|
||
|
|
"modes": ["new", "handoff"],
|
||
|
|
"lane": "background",
|
||
|
|
"operations": "startSessionTimeoutStep + timer run_created/publish/lifecycle + durable wait scheduling",
|
||
|
|
"why": "Default deadline infrastructure. Does not block the first model call; startup operations are joined before the session loop advances.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/session/entry.ts",
|
||
|
|
"symbol": "const initialTurn"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "abort",
|
||
|
|
"title": "SDK cancellation hook and abort stream",
|
||
|
|
"modes": ["new", "stable", "alias", "handoff"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "1 abrt_* hook_created for the new turn; abort stream read",
|
||
|
|
"why": "AbortSignal transport is required for cancelling active durable steps. No eve getConflict call and no steering abort.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/node_modules/@workflow/core/dist/serialization.js",
|
||
|
|
"symbol": "function reduceAbortWithListener"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "step",
|
||
|
|
"title": "Execute turnStep",
|
||
|
|
"modes": ["new", "stable", "alias", "handoff"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "step_started; step_created/queue delivery when not inline",
|
||
|
|
"why": "SDK may execute inline or queue a step. Registration awaiters affect inline eligibility; do not equate a step call with exactly one network request.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/node_modules/@workflow/core/dist/runtime/suspension-handler.js",
|
||
|
|
"symbol": "const lazyInlineCorrelationIds"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "prepare",
|
||
|
|
"title": "Rehydrate context, deliver message, prepare model",
|
||
|
|
"modes": ["new", "stable", "alias", "handoff"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "local work + authored hooks/connections/sandbox I/O",
|
||
|
|
"why": "No universal count: authored integrations, dynamic model selection, memory, sandbox, and tracing can add work before the provider call.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/session/turn-step.ts",
|
||
|
|
"symbol": "async function turnStep"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "writer",
|
||
|
|
"title": "Resolve stream writer encryption/routing",
|
||
|
|
"modes": ["new", "stable", "alias", "handoff"],
|
||
|
|
"lane": "conditional",
|
||
|
|
"operations": "run/key reads depending on writable serialization and caches",
|
||
|
|
"why": "Original owner uses getWritable(); a successor uses the anchor run's forwarded writable. Stream names are run-scoped.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/node_modules/@workflow/core/dist/runtime/run.js",
|
||
|
|
"symbol": "getWritable(options"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "preamble",
|
||
|
|
"title": "Publish pre-model events",
|
||
|
|
"modes": ["new", "stable", "alias", "handoff"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "stream write batches; batch count is variable",
|
||
|
|
"why": "Lifecycle events and authored handlers can be awaited before the model call. Count actual stream batches, not individual events.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/session/turn-step.ts",
|
||
|
|
"symbol": "await emitTurnPreamble"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "model",
|
||
|
|
"title": "MODEL REQUEST — stop time-to-model clock",
|
||
|
|
"modes": ["new", "stable", "alias", "handoff"],
|
||
|
|
"lane": "critical",
|
||
|
|
"operations": "1 provider request for this first call",
|
||
|
|
"why": "Provider response latency is outside time-to-model. Subsequent tool/model cycles have their own costs.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/session/turn-step.ts",
|
||
|
|
"symbol": "await runModelCallBatch"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "settle",
|
||
|
|
"title": "Commit results, admit steering, settle turn",
|
||
|
|
"modes": ["new", "stable", "alias", "handoff"],
|
||
|
|
"lane": "completion",
|
||
|
|
"operations": "step_completed + optional next step + settleTurnStep + stream batches",
|
||
|
|
"why": "Steering uses the same turn after a committed boundary; queue waits for settlement. No child turn run or owner-release handshake per ordinary turn.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/session/turn.ts",
|
||
|
|
"symbol": "async runTurn"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "read",
|
||
|
|
"title": "Client stream observer",
|
||
|
|
"modes": ["new", "stable", "alias", "handoff"],
|
||
|
|
"lane": "observer",
|
||
|
|
"operations": "stream GET + read-key resolution as needed; tail-info only when requested",
|
||
|
|
"why": "Separate clock/branch. An already-open stream is not a new request per chunk. Reconnects target the original run.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/workflow-runtime.ts",
|
||
|
|
"symbol": "async getEventStream"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "activity",
|
||
|
|
"title": "Optional channel activity collector",
|
||
|
|
"modes": ["new", "stable", "alias", "handoff"],
|
||
|
|
"lane": "conditional",
|
||
|
|
"operations": "new root collector start; hook + activity resume/write/publish batches",
|
||
|
|
"why": "Only channels with activity renderers. Collector startup can precede session start; separate from the ordinary ID-only path.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/workflow-runtime.ts",
|
||
|
|
"symbol": "let collectorRunId"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "reset",
|
||
|
|
"title": "Explicit reset release acknowledgement",
|
||
|
|
"modes": ["new", "stable", "alias", "handoff"],
|
||
|
|
"lane": "conditional",
|
||
|
|
"operations": "bounded hooks.getByToken polling after reset acceptance",
|
||
|
|
"why": "Not an ordinary send cost. Confirms the previous owner released the stable address before reset returns.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/workflow-runtime.ts",
|
||
|
|
"symbol": "async function waitForCommandHookRelease"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "resolve",
|
||
|
|
"title": "Explicit resolveSession()",
|
||
|
|
"modes": ["new", "stable", "alias", "handoff"],
|
||
|
|
"lane": "conditional",
|
||
|
|
"operations": "1 hooks.getByToken + lazy metadata hydration",
|
||
|
|
"why": "Requested read-only identity resolution. Never used as a send preflight.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/workflow-runtime.ts",
|
||
|
|
"symbol": "async resolveContinuation"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "task-owner",
|
||
|
|
"title": "Background task ownership resolution",
|
||
|
|
"modes": ["new", "stable", "alias", "handoff"],
|
||
|
|
"lane": "conditional",
|
||
|
|
"operations": "bounded hooks.getByToken polling inside durable work",
|
||
|
|
"why": "Not the ordinary turn path. Reads runId only; no metadata hydration.",
|
||
|
|
"source": {
|
||
|
|
"path": "packages/eve/src/execution/workflow-runtime.ts",
|
||
|
|
"symbol": "async function waitForCommandHookOwner"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|