Dynamic Capabilities
Purpose and Scope
Dynamic capabilities are eve's runtime mechanism for deciding which instructions, tools, and skills a session should receive after the agent already knows something about the caller or environment. Instead of declaring every capability statically in the filesystem, you wrap a resolver with defineDynamic and subscribe it to session stream events. That resolver can inspect runtime context such as auth, tenant, channel, feature flags, or external data, then return the capabilities that should be visible for that scope. This is useful when one deployed agent must behave differently for different users without duplicating the whole project.
Sources: docs/guides/dynamic-capabilities.md
The important distinction is that dynamic capabilities are still eve capabilities. A dynamic tool is still created with defineTool, a dynamic instruction still participates as prompt context, and a dynamic skill is still model-loadable context. defineDynamic changes when the capability is resolved, not the public contract of the capability itself. That means developers can keep the filesystem-first project shape while deferring tenant-specific or data-driven decisions until the relevant session, turn, or step event has occurred.
Sources: docs/guides/dynamic-capabilities.md
Relevant Source Files
docs/guides/dynamic-capabilities.md- First-party guide fordefineDynamic, dynamic tool return shapes, event timing, naming, conflict behavior, execution order, and replay-safeexecutefunctions.
Core Primitives
The core primitive is defineDynamic, imported from the capability package where the dynamic module lives. The guide's tool example imports defineDynamic and defineTool from eve/tools, then exports a dynamic resolver from agent/tools/query.ts. The resolver has an events object. Each key is a stream event name, and each handler returns the capability or capabilities to expose for that event. Returning null means no capabilities are added for that resolver invocation.
Sources: docs/guides/dynamic-capabilities.md
For tools, every returned entry must be wrapped in defineTool. The wrapper is not only a type or authoring convenience; the guide states that it stamps the tool so its execute function survives workflow step boundaries. In practice, that makes dynamic tools compatible with durable execution. If a crash, resume, or replay causes eve to reconstruct a later step, the tool's executable body must still be recoverable from the transformed module rather than depending on rerunning the resolver at the right moment.
Sources: docs/guides/dynamic-capabilities.md
The returned shape determines the public names the model sees. If the resolver returns a single defineTool(...), the tool name follows the file slug, like a static tool in agent/tools/analytics.ts becoming analytics. If the resolver returns a record such as { export, query }, the bare record keys become the names. There is no automatic file-slug prefix for map entries, so namespacing belongs in the key itself when collisions are possible, for example tenant__export.
Sources: docs/guides/dynamic-capabilities.md
Runtime Resolution Flow
Dynamic resolvers subscribe to stream events at three useful granularities. session.started runs once per session and makes its tools available for every model call in that session. turn.started runs once per turn and makes its tools available for every model call in that turn. step.started runs before each model call and makes its tools available only for that model call. Choosing the event is therefore a context-size and freshness decision: session scope is stable and cheap, while step scope can reflect data that must be recomputed immediately before the model call.
Sources: docs/guides/dynamic-capabilities.md
When a stream event fires, eve processes capability changes in a defined order. First, the channel adapter handler runs and the event is written to the durable stream. Second, stream-event hooks fire. Third, dynamic tool resolvers subscribed to that event run and update the tool set. The tool loop then reads the current set right before each model call. This ordering matters because hook side effects and persisted event data can influence what the resolver sees, while the model only receives the final resolved capability set at the point it is about to act.
Sources: docs/guides/dynamic-capabilities.md
A typical use case is a warehouse-backed analytics agent. At session start, the resolver can call a tenant-aware listTables() helper, build one tool per table, and return a record keyed by table name. Each generated tool can describe the table and columns, validate input with a schema, and execute a read-only query through a tenant-scoped helper. The model then sees concrete tool names such as orders and users, rather than a generic database tool that has to discover everything inside one overloaded call.
Sources: docs/guides/dynamic-capabilities.md
import { defineDynamic, defineTool } from "eve/tools";
import { z } from "zod";
import { listTables, runReadOnly } from "../lib/warehouse.js";
export default defineDynamic({
events: {
"session.started": async () =>
Object.fromEntries(
(await listTables()).map((t) => [
t.name,
defineTool({
description: `Query ${t.name}. Columns: ${t.columns.join(", ")}`,
inputSchema: z.object({ sql: z.string() }),
execute: ({ sql }) => runReadOnly(t.name, sql),
}),
]),
),
},
});API and Behavior Reference
| Concept | Contract | Runtime effect |
|---|---|---|
defineDynamic | Exports an event-driven resolver for tools, skills, or instructions | Resolves capabilities from session events instead of only from static declarations |
events | Object keyed by stream event names | Selects when a resolver runs |
session.started | Handler runs once per session | Capabilities apply to every model call in the session |
turn.started | Handler runs once per turn | Capabilities apply to model calls in that turn |
step.started | Handler runs before each model call | Capabilities apply only to that model call |
| Single tool return | defineTool(...) | Tool is named after the file slug |
| Record return | Record<string, defineTool(...)> | Each bare key is the tool name |
| Empty return | null | Resolver contributes no capabilities |
Conflict handling is intentionally different for static-versus-dynamic and dynamic-versus-dynamic cases. A dynamic tool or skill with the same name as an authored one overrides the authored capability, which lets a per-caller resolver replace a default implementation by name. However, two dynamic resolvers emitting the same name create an ambiguity and throw. When multiple resolvers can produce related capabilities, namespace the keys yourself so the model receives stable, unambiguous names.
Sources: docs/guides/dynamic-capabilities.md
The guide calls out one durability-sensitive authoring rule: execute must be an inline function expression, arrow function, or method shorthand directly on the tool definition. Do not write execute: myFn or execute: makeFn(). Those shapes may work on the first step, but the bundler transform does not detect them for replay. On later durable steps, eve reconstructs each execute from stored closure variables instead of rerunning the resolver, so the inline body is the durable unit the transform can preserve.
Sources: docs/guides/dynamic-capabilities.md
Dynamic Instructions, Tools, and Skills Together
Use dynamic instructions when the always-on prompt must depend on runtime context such as tenant policy, authenticated role, or channel-specific disclosure language. Use dynamic tools when the set of actions depends on external data, for example one read-only query tool per table or one integration tool per enabled feature. Use dynamic skills when optional procedures should be resolved for the caller without loading every possible procedure into every session. The shared design is the same: resolve the right capability at the event boundary where the needed context is available.
Sources: docs/guides/dynamic-capabilities.md
The safest design is to start with the broadest stable scope that satisfies the product need. If the capability set is determined by the authenticated tenant, prefer session.started so every turn sees the same tools and instructions. If the set depends on the incoming message or current channel event, turn.started can keep the session stable while adapting to the user's latest request. Reserve step.started for cases where freshness before each model call is worth the added resolver frequency and potential context churn.
Sources: docs/guides/dynamic-capabilities.md
Implementation Guidance
Treat dynamic capability names as part of your agent's API to the model. Because record keys become bare tool names, short names are convenient but can collide across resolvers. Prefer explicit namespaces for generated families, especially in multi-tenant agents or integrations where a static capability and a dynamic replacement may coexist. If you intentionally override an authored tool or skill, keep the replacement's description and schema compatible enough that the model's learned plan still makes sense.
Sources: docs/guides/dynamic-capabilities.md
Keep resolver code focused on selection and construction. Expensive or unsafe work should usually stay inside the tool execute function, where input schemas, approval policies, and tool-call boundaries can apply. The resolver can fetch metadata, feature flags, or tenant configuration, then create small, specific capabilities. That separation makes replay behavior easier to reason about: the resolver decides what exists at the event boundary, while each inline execute defines the durable action that will run when the model calls the tool.
Sources: docs/guides/dynamic-capabilities.md
Next Steps
After adding a dynamic resolver, test the exact event scope you selected. Start a session for at least two tenants or roles, confirm the expected tool names appear, and verify no generated names collide. For dynamic tools, simulate a multi-step run and keep execute inline so replay remains durable. Then read the related pages for the static capability you are making dynamic: instructions for prompt behavior, tools and approvals for executable actions, skills for on-demand procedures, and execution model and durability for replay boundaries.