Remote Agents
Call another eve deployment as a subagent with defineRemoteAgent: the same tool call as a local subagent, with outbound auth and durable callbacks.
defineRemoteAgent calls a separately deployed eve agent as if it were a local subagent. Reach for it when the specialist you delegate to is a separately owned agent behind its own URL rather than a directory in your repo.
The file lives under agent/subagents/, so its tool name is derived from the path. There's no name field.
defineRemoteAgent accepts:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
url | string | (() => string | Promise<string>) | Yes | n/a | Base URL of the remote eve deployment to call. A string is baked at compile time; a function is resolved at runtime (see Runtime URLs). |
description | string | Yes | n/a | Model-visible delegation description. |
auth | OutboundAuthFn | No | none | Outbound auth hook from eve/agents/auth. |
forwardPrincipal | boolean | No | false | Forward the dispatching turn's session principal to the remote deployment (see Forwarding the caller identity). |
headers | HeadersValue | No | none | Static or lazily resolved request headers. |
path | string | No | /eve/v1/session | Route appended to url for the create-session request. |
tool | boolean | No | true | Expose the remote agent as a tool to the parent model. Set false to allow only ctx.agent() calls from authored workflow tools. |
Dynamic remote agents
Wrap the file in defineDynamic when the target or its availability depends on
the current session. Return defineRemoteAgent(...) to expose it and null to
omit it:
Dynamic remote subagents support session.started and turn.started. The
returned definition may select different remote settings at either scope. eve
resolves function-valued URLs when the event handler runs. Auth and headers
remain lazy and resolve before each outbound request without entering durable
workflow state.
Author auth and headers directly in the defineRemoteAgent({ ... }) object
and keep their functions self-contained with module imports, module-level
constants, or environment variables. They are rehydrated outside the event
handler, so they cannot close over _event, ctx, or handler-local values. The
build fails with an error that names the handler-local value when they do.
eve compiles only auth and headers written directly in the
defineRemoteAgent({ ... }) object in the subagent file. When they arrive
through a spread, a variable passed as the argument, or a definition built in
another module, eve rejects the definition at runtime, logs the error, and runs
the agent without that subagent.
Runtime URLs
A string url is read at compile time and frozen into the build. When the target comes from a runtime env var — known only once the deployment runs — pass a function instead. eve calls it when it resolves the agent graph at runtime, so it can read process.env:
The function may be async and must return a non-empty string. auth and headers are resolved at runtime the same way.
Calling a remote agent
By default, a remote agent is another subagent tool to the model. The model calls it the same way it calls a local subagent, with a message and an optional taskId. Set tool: false when an authored workflow tool should be the only model-facing routing surface; the workflow can still call the remote agent by its path-derived name through ctx.agent(). The message must carry the full task, including any context the remote agent needs, because it never receives the parent's conversation history.
To require structured output, open a session with the remote agent from an authored workflow tool with ctx.agent(name) and pass outputSchema to send(). The schema applies to that turn, the structured value is the response's data, and the session accepts follow-up messages until the workflow run finishes. See Delegate work: ctx.agent.
Outbound auth
Use vercelOidc() from eve/agents/auth when one Vercel-deployed eve agent calls another, as shown in the first example on this page.
For calls between different Vercel projects, allow the calling project on the receiving agent's eve channel:
Set teamSlug, projectName, and environment to the calling deployment's Vercel OIDC subject. See subject patterns and vercelSubject(...) for other environments and wildcard matching.
If Vercel Deployment Protection is active on the receiving project, also configure Trusted Sources to allow the calling project and environment. The eve subject allowlist and Trusted Sources are separate checks; cross-project calls need both.
Forwarding the caller identity
Outbound auth authenticates your deployment to the remote, so by default the remote session runs as your calling app — not as the end user who is talking to your agent. That breaks per-user workloads on the remote deployment, most directly per-user Vercel Connect, which requires an authenticated user principal on the session.
Set forwardPrincipal: true to forward the dispatching turn's session principal across the hop:
The create-session request carries the parent turn's session.auth.current and session.auth.initiator as a forwardedPrincipal body field (initiator is optional on the wire; when absent, the receiver seeds both from current). A continuation by taskId carries the session.auth.current of the call that continues the task; the remote session keeps its original auth.initiator. Only principal metadata crosses the wire — never tokens or credentials. The receiving deployment resolves its own per-user credentials through its own connections.
This makes caller authority turn-scoped even when the remote child session is persistent. If Alice starts the child and Bob later continues it with its taskId, the follow-up runs with Bob as auth.current, not Alice. If the parent turn's auth is null, a local child clears auth.current, while a remote child uses the freshly verified transport principal; neither inherits Alice. eve's in-step bearer cache is also keyed by the resolved principal and is not serialized across steps. The external authorization provider may preserve each user's server-side OAuth grant, but a later turn can resolve only the grant belonging to its own auth.current principal.
Identity forwarding does not make a persistent session private to one caller. Conversation history, tool outputs, and other child-session state still persist. If those values must not be visible across users, give each user a distinct child session or enforce that ownership at the application boundary.
Forwarding identity is explicit on both sides. The receiver names which deployments it trusts with eveChannel({ trustedForwarders }) and can limit each one to the principals it may assert (see Auth & route protection); refusing the forwarder or what it asserts rejects a forwarded principal with a 403. The same trust decision covers parent session lineage and, with principal forwarding, trace-content constraints.
Trace propagation
A remote child starts its own trace with a root invoke_agent span. When caller
trace context is available, the first turn links to the dispatch span with
eve.link.type=agent.dispatch. The receiving deployment applies its sampler.
Later turns start new traces without the original dispatch link. The first turn
of a local child can use the remote agent's trace as a nested span.
Use gen_ai.conversation.id to find all turns and child sessions for one
conversation. The child has its own session ID, message history, and agent
state. Trace context identifies related work. It does not give authorization.
See OpenTelemetry for the trace structure.
eve carries parent session lineage separately. The receiver accepts it only
when trustedForwarders approves the authenticated caller; otherwise, trace
correlation continues without it.
On Vercel, a remote agent's spans and its local subagents' spans carry the
remote session's ID as vercel.session_id, so the receiving project groups
them as their own session. gen_ai.conversation.id still names the sender's
conversation, and the remote agent's agent.parent_run.id names the calling
session.
Preserving trace content
With forwardPrincipal: true, a sampled remote dispatch forwards its original
audience and the maximum input and output content the next hop may record. eve
sends this policy as W3C Baggage.
The receiving deployment uses the policy only after it trusts the calling deployment:
The request must include a callback and a valid sampled traceparent.
trustedForwarders is the authorization boundary. When the assertion is
accepted, the receiver uses the forwarded audience instead of reclassifying
the child session with its local channel.
The receiver combines the forwarded ceiling with its own trace policy. Each hop may narrow content capture, but cannot restore inputs or outputs removed by an earlier hop. Missing, malformed, unsampled, or untrusted assertions do not widen capture and use metadata-only tracing.
How remote dispatch and callbacks work
A remote subagent call runs a child session in the remote deployment as a task:
- The parent starts a persistent conversation session on the remote's
POST /eve/v1/session, passing a framework callback URL. - The remote accepts the child and runs its turn while the task works.
- The callback delivers the child's reply, which becomes the task's result.
The parent stream carries the same task.started, agent.started, and task.settled events as local delegation. For a remote child, agent.started.data.remote.url records the target.
Clients follow a remote child through the parent. session.agent(started).stream() reads the agent.started event's streamPath, a route on the parent deployment. The parent verifies that its session recorded the child for that tool call, resolves the remote agent's auth and headers, and relays the child's stream. A browser never calls the remote deployment or holds its credentials; it only needs access to the parent session.
Cancelling the parent turn or the agent task with task_cancel also cancels the remote child's current turn. eve resolves the remote's headers and auth again for every cancellation attempt, so rotating credentials work the same way as they do for session creation. Cancellation always uses the standard eve cancel path on url, even when path customizes only the create-session endpoint. The remote child reports turn.cancelled → session.waiting on its own stream; an older or unreachable remote is logged but cannot turn the parent's cancellation into a failure.
When the workflow run that opened a remote child finishes, eve sends an authenticated POST /eve/v1/session/:childSessionId/reset for it. An agent task's run finishes when the parent session ends. Reset retires the parked remote session and recursively cleans up its descendants. The request uses freshly resolved headers and auth; failures are logged so an unreachable remote cannot block parent finalization.
The parent names its eve remote agent protocol version when it creates the child, and the remote answers with the version it serves. A remote also serves parents on protocol 1 (eve 0.66 through 0.68), including their approvals and sign-in requests, so upgrade remote agents before their callers; see Upgrade remote agents before their callers. Any other mismatch fails the call at start with an error naming both versions. A failed start fails the call immediately. After a remote starts, a terminal failure callback fails the call with the remote's error. Terminal callback delivery runs as a durable step on the underlying workflow engine (see Execution model & durability). A failed callback POST is rethrown rather than completing the call, so the engine retries it.
What to read next
- Local delegation and the isolation boundary → Subagents
- Securing the receiving deployment → Auth & route protection