Signed-off-by: Rui Conti <ruiconti@gmail.com>
68 KiB
issue, status, last_updated
| issue | status | last_updated |
|---|---|---|
| https://github.com/vercel/eve/issues/2347 | implemented | 2026-08-31 |
Programmatic agent sources
Decision
Compile one authoritative agent source graph. Filesystem discovery, extension packages, extension overrides, framework-owned programmatic modules, and subagent source nodes all produce candidates for path-derived slots. Candidate construction gives every executable source an explicit filesystem or programmatic binding before its definition can load. One composer selects a winner for each slot before normalization, and the ordinary compiler normalizes only those winners.
framework modules ───────┐
extension packages ──────┤
extension overrides ─────┼─> classify + compose ─> effective source graph
application sources ─────┤ │
subagent source nodes ───┘ ├─> compiled manifest + bindings
├─> composition + route plans
├─> generated and hydrated maps
├─> runtime + Nitro
└─> CLI + agent-info + tests
Logical identity and physical storage are separate. A logical path selects an
eve slot and derives its public name. A backing binding says how to load that
slot. agent/tools/search.ts and an immutable in-memory namespace registered
at tools/search.ts therefore compile identically without pretending that the
programmatic module exists on disk.
The framework API is internal. Programmatic modules export ordinary public eve
definitions: defineTool, defineDynamic, defineChannel, defineSandbox,
defineAgent, defineInstrumentation, and the existing connection, hook,
schedule, instruction, and skill factories. Framework-only native tools use a
closed internal definition variant so they do not carry fake throwing
executors. Neither form constructs Compiled* or Resolved* records.
Production runtime behavior continues to live in the eve package; generated
module maps only bind statically reachable module namespaces.
The compiled manifest, required bindings, persisted composition report, compiler-owned channel route plan, selected tools' handling descriptors, and closed host inventory are the only downstream authority. An active module-backed source is owned by its binding. An active non-module source is owned by its explicit compiled source record. A losing or disabled source is owned by its self-contained composition entry. No second winner-owner index, kernel plan, or out-of-band origin table may exist.
Every ordinary framework default is a first-class eve primitive at a canonical
logical path, including the default agent.ts, sandbox, home page, and the
eve channel carrying the public health and info endpoints. Native execution
may implement a selected primitive, but it may not create that primitive's
presence independently of the source graph.
Model-visible native behavior is keyed by a closed handling descriptor carried
by the selected tool — never inferred from its tool name, source ID, owner, or
logical path. Host registrations that cannot be ordinary sources live in a
separate, typed, exhaustive inventory.
This supersedes the runtime-tool contribution seam in #2347. Runtime contribution is too late for channels, schedules, sandboxes, host routes, bundling, and inspection, and would create another collision, durability, and dispatch system for tools.
Current state
The source-graph phase landed in #2516.
Derived slot composition landed in
#2539, and first-class memory now
uses that boundary through #2534.
Manifest v45 and agent-info v4 are current on main.
Those changes completed source selection, binding, loading, route planning, ownership, and memory integration. They deliberately preserved the dispatch seam for a second phase. The remaining work is narrower than the original kernel-effects proposal: make each selected tool carry its own handling and availability metadata through compilation and runtime preparation, then delete the harness and execution code that infers those facts from names, owners, paths, markers, or function identity.
Problem
Before #2516, the compiler tracked logicalPath and sourceId, but module
loading still assumed every source lived at agentRoot + logicalPath.
Framework and extension features worked around that assumption at different
layers:
- framework tools and channels fabricate resolved definitions and merge them after compilation;
connection_searchappends a synthetic dynamic resolver and reconstructs discovered tools from both durable state and message history;- Nitro creates a second framework-channel merge for host routes;
- extension packages compile, prefix, rebase, merge, disable, and deduplicate each primitive separately after the application manifest has compiled;
- the default sandbox appears through a runtime fallback rather than an
effective
sandbox.tssource; - the default agent config is synthesized inside normalization rather than
selected as an
agent.tssource; - the home page, public health endpoint, and
/eve/v1/infoinspection endpoint are native host routes that bypass channel source composition and use a second route-precedence system; - subagent composition results from
composeAgentSubagentSourcesandcomposeExtensionSubagentSourcesare consumed inline and discarded, while module-globalWeakSet/WeakMapstate stands in for compiled subagent identity at runtime; - two agent-info builders re-create framework status from different inputs;
- native harness tools are represented by metadata stubs that are not the definitions the harness advertises or executes.
The source graph removed those parallel composition paths, but native dispatch still infers semantics after resolution:
createExecutionNodeSteprecognizes frameworkagent,ask_question, task controls, andload_skillthrough owner-and-name checks;- the harness recognizes
ask_questionandweb_searchby model-visible name; - task control is classified through
TASK_TOOL_NAMESand dispatched by name; load_skillretains aframeworkActionmarker to classify action events even though it executes inline;- task-mode local subagent fanout uses a module-global
WeakSetof executor function identities; - root-only, delegated-caller-only, Workflow-callable, and request-input availability are separate booleans and name checks; and
- agent-info reconstructs
kernelEffectsfrom a hard-coded logical-slot table instead of the selected compiled tool.
final_output is different: eve creates it for one model call from that
turn's output schema, so it has no selected source to carry metadata. Its
terminal interception stays explicit and localized outside the source graph.
The result is duplicate machinery with inconsistent identity. Replacement, disablement, route order, source provenance, cold-start loading, and inspection depend on which path created a definition. The refactor succeeds only when those parallel paths are deleted, not hidden behind a new registry.
Scope
The complete work includes:
- introduce source-neutral candidate, composition, and loading boundaries;
- require complete module bindings at artifact construction, compilation, and load, including bindings created for application candidates before their definitions execute;
- compose framework defaults, extensions, overrides, and application sources once, including local subagent source nodes;
- compile one effective manifest consumed by runtime, Nitro, bundling, and inspection;
- migrate ordinary and native-handled framework tools,
connection_search, the eve channel (carrying the callbacks, health, and info), the home channel, the default sandbox, and the defaultagent.tsto programmatic eve modules; - separate public primitive definitions, execution implementations, and native handling metadata so each ordinary default has one definition value;
- carry a closed internal handling descriptor on each selected native-handled tool and each graph-derived delegation tool, covering availability, materialization, dispatch, and inspection without a parallel plan;
- limit non-source host behavior to an explicit closed host inventory and move process readiness away from the replaceable public health route;
- keep agent-info projected from the effective graph and selected tool descriptors rather than reconstructing framework behavior from paths;
- remove each superseded framework, extension-composition, fallback, and inspection path in the delivery PR that supersedes it.
The implementation does not expose a public registration API or public handling constructors, serialize functions, mutate a compiled graph, create programmatic subagent nodes, or virtualize markdown and skill asset files. Programmatic modules may be applied to already-discovered local nodes. The framework registry receives a narrow exception to provide the default config slot, but no registration may recursively introduce nodes, extensions, or raw workspace content.
Source model
Programmatic modules
Programmatic sources declare immutable, lazily loaded module namespaces at virtual agent-relative paths:
type ProgrammaticModuleNamespace = Readonly<Record<string, unknown>>;
interface ProgrammaticModuleLoadContext {
readonly dependencies: Readonly<Record<string, ProgrammaticModuleNamespace>>;
readonly parameters: JsonObject;
}
interface ProgrammaticAgentModule {
readonly exportName?: string;
readonly loadNamespace: (
context: ProgrammaticModuleLoadContext,
) => Promise<ProgrammaticModuleNamespace>;
readonly logicalPath: string;
readonly semanticRevision?: string;
}
interface ProgrammaticAgentSource {
readonly id: string;
readonly modules: readonly ProgrammaticAgentModule[];
readonly revision: string;
}
function defineProgrammaticAgentSource(input: ProgrammaticAgentSource): ProgrammaticAgentSource;
interface AgentSourceRegistration {
readonly applyTo: "root" | "all-local-nodes";
readonly source: ProgrammaticAgentSource;
}
interface AgentSourceRegistryOptions {
readonly templates?: readonly ProgrammaticAgentSource[];
}
interface RegisteredProgrammaticTemplate {
readonly module: ProgrammaticAgentModule;
readonly source: ProgrammaticAgentSource;
}
interface AgentSourceRegistry {
readonly registrations: readonly AgentSourceRegistration[];
readonly sources: ReadonlyMap<string, ProgrammaticAgentSource>;
readonly templates: ReadonlyMap<string, RegisteredProgrammaticTemplate>;
}
function createAgentSourceRegistry(
registrations: readonly AgentSourceRegistration[],
options?: AgentSourceRegistryOptions,
): AgentSourceRegistry;
logicalPath is a normalized POSIX path relative to an agent root. It must
match the existing grammar, cannot be absolute or traverse with .., and may
select only module-backed slots. The path derives identity; there is no name,
slug, kind, protocol, or precedence field.
The selected export follows existing ESM semantics and may be a zero-argument
sync or async factory. A namespace loader may ignore its context when it is an
ordinary overlay; a derived module receives selected dependency namespaces and
serialized parameters through that context. Construction shallow-copies and
freezes source and module data without invoking loadNamespace. The loader
returns the exact namespace and preserves brands, symbols, functions, and
durable callback metadata. Programmatic source IDs derive deterministically as
<source.id>:<logicalPath> and must be unique within each node.
Each source also declares a non-empty immutable revision. The revision
identifies the exact registered implementation, not a mutable deployment
label. A package-backed or generated registry uses its immutable build
revision. An in-memory registry uses a fresh opaque generation revision because
closed-over callback state cannot be derived faithfully from function source
text. Bindings persist that revision, module-map identity includes it, and
loaders reject a same-ID registry at a different revision before evaluating any
namespace. Reusing a source ID after changing callbacks therefore cannot
hydrate stale executable code under an unchanged artifact.
A module may declare a non-empty semanticRevision when its source-wide
revision intentionally covers unrelated modules. The compiled backing keeps
both values: the source revision still authenticates registry loading, while
selected-backing identity uses the module revision when present. The framework
default sandbox uses an explicit stable token so unrelated eve source changes
do not discard durable sandbox state.
Registries are explicitly assembled, statically imported, and immutable before
compilation. Registrations are source overlays; each template is a single
loadable module implementation used only by derived candidates and never
injects a candidate on its own. Registration returns an opaque template handle,
so candidate construction cannot accept an arbitrary unregistered source.
Registries are not global, side-effect-populated, or mutable at runtime. root
applies only to the application root. all-local-nodes is a finite overlay
applied after filesystem and extension nodes are discovered; it rejects
agent.ts, subagents/**, channels/**, schedules/**, and extensions/**.
A closed internal framework registration is the narrow exception that may
provide the default agent.ts for every already-discovered local node.
Arbitrary programmatic sources cannot use config to expand the graph recursively.
Framework registry modules contain literal dynamic imports inside namespace loaders. They do not statically import or evaluate definition modules while constructing the registry. Compilation and every module-map implementation invoke loaders only for selected programmatic bindings, so a shadowed source cannot execute during compilation or cold-start hydration.
Physical backing
Every module candidate carries its logical identity and an explicit physical binding:
type AgentSourceOwner =
| { readonly kind: "application" }
| { readonly feature: string; readonly kind: "framework" }
| {
readonly kind: "extension";
readonly namespace: string;
readonly packageName: string;
};
type AgentSourceLayer =
"framework-default" | "extension-package" | "extension-override" | "application";
type AgentSourceForm = "derived" | "direct";
interface AgentModuleCandidate {
readonly backing:
| {
readonly externalDependencies: readonly string[];
readonly extensionScope?: {
readonly namespace: string;
readonly sourceRoot: string;
};
readonly kind: "filesystem";
readonly sourcePath: string;
}
| {
readonly dependencies?: Readonly<Record<string, string>>;
readonly kind: "programmatic";
readonly moduleId: string;
readonly parameters?: JsonObject;
readonly registryId: string;
readonly revision: string;
readonly semanticRevision?: string;
};
readonly form: AgentSourceForm;
readonly layer: AgentSourceLayer;
readonly logicalPath: string;
readonly nodeId: string;
readonly owner: AgentSourceOwner;
readonly sourceId: string;
}
direct means contributed to a slot without derivation; it does not imply a
filesystem-authored or application-owned source.
logicalPath is never used as an import path. Filesystem loading uses
sourcePath plus its external-dependency and extension scope. Programmatic
loading uses the exact registry/module/revision tuple. Missing or
revision-mismatched programmatic bindings fail without probing a virtual path
on disk or invoking a namespace loader. sourceId remains stable provenance;
provider kind and precedence are never inferred from it.
Raw filesystem resources retain their physical source paths and participate in the same slot composition, but they do not gain a programmatic backing in this version.
Derived slot composition
A selected module may induce an ordinary candidate in another logical slot by instantiating a registered programmatic template:
selected memory/profile.ts ──dependency "memory"──> derived tools/profile.ts
│
ordinary slot composition and binding
function instantiateProgrammaticTemplate(input: {
readonly anchor: AgentModuleCandidate;
readonly dependencies: Readonly<Record<string, AgentModuleCandidate>>;
readonly logicalPath: string;
readonly owner: AgentSourceOwner;
readonly parameters?: JsonObject;
readonly template: RegisteredProgrammaticTemplate;
}): AgentModuleCandidate;
instantiateProgrammaticTemplate requires a registry-issued template and an
anchor candidate. It inherits the anchor's node and layer, derives stable
provenance from the template, target path, and anchor source ID, and records
dependency aliases as selected source IDs plus behavior-critical JSON
parameters in the programmatic backing. The anchor must be one of those
dependencies, every dependency must belong to its node, and all dependencies
must remain selected after composition. The constructor does not evaluate any
module. A direct candidate wins over a derived candidate within the same source
layer. Normal cross-layer precedence still applies, so an application-derived
capability can replace an extension contribution while a direct application
source can replace or disable the derived capability.
Templates are not an open projection callback or a mutable registry. A closed compiler feature decides when to instantiate one from the already-discovered source graph, then the ordinary composer, binding table, normalizers, and module maps take over. Compiled dependency graphs reject missing bindings and cycles. Compilation, in-memory hydration, and generated module maps resolve dependencies in order and cache each selected namespace once per phase. Namespace caching does not cache materialized zero-argument definition exports; a feature that requires one shared definition instance across multiple slots must provide a separate per-phase materialization boundary.
Required compiled bindings
Bindings are load-critical data and must not live only in optional compile
metadata. Each compiled node owns a total binding table keyed by the same
sourceId used by its manifest references:
interface CompiledAgentNode {
readonly bindings: Readonly<Record<string, CompiledModuleBinding>>;
readonly manifest: CompiledAgentNodeManifest;
}
interface CompiledModuleBinding {
readonly backing: AgentModuleCandidate["backing"];
readonly logicalPath: string;
readonly owner: AgentSourceOwner;
}
interface EffectiveAgentNodeSourceGraph {
readonly bindings: Readonly<Record<string, CompiledModuleBinding>>;
readonly composition: AgentSourceComposition;
readonly manifest: AgentSourceManifest;
}
Bindings and composition are required inputs to every compiled node and root
manifest constructor. They are not optional fields filled in by serialization
or reconstructed from a normalized manifest. Every serialized node and root
manifest carries sourceComposition; there is no empty default. Construction
fails unless every module-backed manifest reference has exactly one binding,
logical paths agree, extension-owned filesystem bindings carry
extensionScope, and every binding is referenced. The same semantic validator
runs after schema parsing in every disk and bundled artifact loader, before
module-map hydration. That validator is one module: new artifact checks
extend it, and sibling per-artifact validator files are a design failure, not
a pattern.
The binding table participates in artifact versioning and hashing. Development hydration, generated module maps, bundled artifacts, materialized artifacts, and cold-start reconstruction all read this same table. Optional diagnostic metadata may summarize compilation, but no process can load or inspect an agent without its bindings and composition.
Generated maps emit ordinary static imports for filesystem modules and
statically reachable registry lookups for programmatic modules. The resulting
CompiledModuleMap remains the only namespace map used by graph resolution.
logicalPath is never joined to agentRoot after candidate construction, and
sourceId is opaque outside equality, diagnostics, serialization, and module
map lookup.
Binding-safe compilation
Two invariants order compilation, because build settings derive from the selected config:
- The config slot composes and binds first, and the winning
agent.tsloads before any non-config definition. Build, task, and external-dependency settings derive from that selected config. - Losing candidates never receive bindings and never execute — not during compilation, and not during cold-start hydration.
The config normalizer receives only its selected candidate and required binding. Module-backed primitive normalizers require a binding. Direct injected definitions are not a production escape hatch: no candidate constructor accepts an evaluated definition value, and the only already-evaluated value passed between compilation phases is the compiler-owned selected config.
The two phases append candidates to one composition state. There is no partial-graph merge step, no post-hoc disjointness or integrity assertion, and no string phase discriminator — phase safety is a type, such as a phase-one state that cannot yet yield a manifest.
createProgrammaticCompiledModuleMap is asynchronous and resolves only the
programmatic bindings present in the compiled manifest. Generated,
materialized, bundled, hydrated, and in-memory maps await those same selected
loaders and expose the same (nodeId, sourceId) set.
One composition pass
The composer operates on canonical logical slots before loading or normalizing definitions. Precedence is:
framework default < extension package < extension override < application
derived < direct within one layer
For each slot it:
- classifies every candidate with the existing path grammar;
- rejects same-layer duplicate candidates;
- selects the highest-precedence candidate without executing any candidate;
- loads the selected export and, if it is a disable sentinel, validates that it targets a lower replaceable candidate before omitting the slot;
- normalizes only the selected non-disabled candidate;
- records the candidates, winner, replacement, or disablement in one compiler composition report persisted with the compiled node.
An invalid winner fails compilation and never falls back to the shadowed candidate. Disable sentinels and shadowed definitions do not survive in compiled or resolved runtime types. The composition report is for compiler diagnostics and inspection provenance; runtime behavior never recomposes it.
The composition report does not duplicate active ownership. An active
module-backed winner points to its required binding; an active non-module
winner carries owner on its compiled source record. A shadowed entry stores the
loser's complete source descriptor and only the winning sourceId. A disabled
entry stores the selected disable source descriptor because no active binding
exists. Parallel owner indexes such as a sourceComposition.sourceOwners map
or full duplicate winner descriptors are forbidden.
Identity uses the existing canonical equivalence rules: .js and .ts
variants select the same slot, connection file/folder forms collide, and tool
names flattened from distinct logical slots retain their uniqueness errors.
Those public-name collisions fail during compilation after slot composition;
they do not gain cross-layer precedence. Dynamic outputs remain subject to the
existing runtime lifecycle rules after their resolver source has composed.
Subagent source nodes
Local subagent identities participate in the same node composition report as module primitives. Each selected subagent carries explicit owner, backing, logical path, source ID, and child manifest into recursive compilation. The parent binding table does not pretend to own a child node reference.
Nested extension subagent records are fresh and immutable for each mount, so
mounting one extension twice cannot share provenance. The inline
composeAgentSubagentSources and composeExtensionSubagentSources composers
whose results are discarded, the module-global subagent executor-identity
state, and any out-of-band subagent origin table are removed. Shadowed and
supported disabled subagent candidates remain self-contained composition
entries for diagnostics and inspection.
Extensions are source projection, not a second compiler
An extension mount projects each package or override source into its final
consumer-visible logical path before composition. For example, a package
module at tools/search.ts mounted as crm becomes
tools/crm__search.ts; its filesystem backing still points to the package's
actual file. The ordinary tool normalizer then derives crm__search from that
canonical path exactly as it would for an application file.
The same projection applies to every extension-supported primitive and eligible
subagent source. Existing extension capability restrictions remain validation
policy on projected candidates: an extension still cannot enable root-owned
Workflow or configure the consumer's web-search provider. Consumer override
files project to the same slots at the higher extension-override layer, and a
root application source at the final qualified path may replace either.
This replaces per-primitive extension compilation, rebasing, prefix mutation, merge functions, disable passes, and name-based deduplication. Qualification happens once in path space; normalization happens once after selection.
The effective manifest is authoritative
The result is one effective compiled graph containing node manifests, required bindings, the composition report, and a compiler-owned channel route plan. Native handling is a field on each selected compiled tool, not a second plan. Instrumentation, the Workflow world, sandbox workspace assets, external dependencies, and prepared tool handling are per-binding data, ordinary slot content, existing config, or runtime projections. Introducing another named plan artifact requires amending this document first. Every downstream consumer reads the effective graph:
- graph resolution registers only compiled resources;
- Nitro registers only the compiled ordered channel routes;
- development and production module maps load only required bindings;
- CLI, Vercel build summaries, and agent info project the same resources;
- no consumer imports a framework tool or channel catalog and merges it again.
Channel modules compose by logical slot before route expansion. The compiler then preserves selected module order and produces one required route plan with effective routes, generated preflights, and retained shadowed routes. Runtime dispatch, WebSocket detection, CORS handling, Nitro development and production registration, and inspection consume that plan without a second drop or merge.
The test-only in-memory compiler must also become a source provider for this pipeline. It registers programmatic sources whose lazy loaders return its namespaces; it may not inject evaluated definitions or construct compiled descriptors or empty module-map entries by hand. Tests that need realistic compilation use the same composer and normalizers as production.
Framework migration
Default source inventory
After migration, the framework provides exactly these default identities through source composition:
| Identity | Definition | Registered for | Notes |
|---|---|---|---|
agent.ts |
defineAgent |
every local node | default model config; phase-one composition |
sandbox.ts |
defineSandbox({}) |
every local node | selects defaultSandbox(); stable semantic revision |
tools/bash.ts |
defineTool |
every local node | ordinary executor |
tools/read_file.ts |
defineTool |
every local node | ordinary executor |
tools/write_file.ts |
defineTool |
every local node | ordinary executor |
tools/todo.ts |
defineTool |
every local node | ordinary executor |
tools/web_fetch.ts |
defineTool |
every local node | ordinary executor |
tools/load_skill.ts |
defineTool |
every local node | ordinary executor |
tools/connection_search.ts |
defineDynamic |
every local node | discovers and qualifies connection tools |
tools/ask_question.ts |
internal native tool + request-input |
every local node | visibility: requires-request-input |
tools/agent.ts |
internal native tool + dispatch |
root node | action: subagent-call; visibility: root-session |
tools/task_update.ts |
internal native tool + dispatch |
root node | action: task-update; tasks mode; visibility: delegated-task-child |
tools/task_cancel.ts |
internal native tool + dispatch |
root node | action: task-cancel; tasks mode; visibility: root-session |
tools/web_search.ts |
webSearch sentinel + provider-tool |
every local node | materialized at eligible model calls |
channels/eve.ts |
eveChannel factory |
root node | complete /eve/v1 surface: protocol, callbacks, health, info |
channels/home.ts |
defineChannel |
root node | GET and HEAD at / |
Every identity above is replaceable and disableable through ordinary slot
composition. glob and grep are published at eve/tools/glob and
eve/tools/grep but never registered. Runtime-generated programs use an ordinary path-derived tool created with the provided
workflow() factory and existing ctx.agent target resolution; there is no framework workflow slot or sentinel. Authored instrumentation.ts likewise composes as an ordinary module slot with no
framework default and no dedicated plan artifact. Native behavior outside
these identities is limited to final_output and the closed host inventory.
Primitive ownership boundaries
Canonical tool definitions and contracts live under tools/; package-facing
modules under public/tools/ remain thin adapters. Execution-only
implementations and durable state live under execution/, while the harness
continues to own model-loop and pending-input behavior. A native-handling
descriptor does not justify a new kernel/ subsystem.
Each ordinary default has exactly one definition value. Its framework source
module and its public eve/tools/<name> subpath export import that same
value; there is no barrel export. Moving modules must
preserve durable state key identity for todo, read-before-write, skill,
connection-search, compaction, and task behavior. These boundaries hold
structurally: public modules do not import runtime/, ordinary framework
sources construct only canonical definitions, and execution code receives
prepared handling metadata instead of rediscovering source identity.
The mixed runtime/framework-tools subsystem and transitional re-export
wrappers are deleted after legitimate modules move. “Framework tool catalog”
terminology no longer describes ordinary primitives.
Default agent config
Register one immutable programmatic default agent.ts source for every local
node. It exports the ordinary default agent definition with the existing
default model. Config participates in phase-one composition before the winning
export executes. An authored agent.ts shadows the framework source without
invoking the framework namespace loader.
The selected config binding and owner are persisted with its composition
history, so filesystem and in-memory agents report the same provenance. The
framework's narrow config registration cannot introduce subagents, extensions,
or other recursive graph content. That exception is safe because config
composes in the first phase against already-discovered nodes: the registration
can never introduce a node, so it cannot expand the graph it composes into.
Remove the synthesized { model: DEFAULT_AGENT_MODEL_ID } normalizer branch,
undefined-config-source inspection conventions, and config loading that
bypasses the total binding table.
Ordinary tools
Author bash, read_file, write_file, todo, web_fetch, and
load_skill once as public defineTool values with plain executors. Register
those exact values at canonical paths and publish each at its own
eve/tools/<name> subpath. The eve/tools/defaults barrel export is
deleted. glob and grep are published the same way at eve/tools/glob and
eve/tools/grep but are never registered as defaults.
Their executors receive ordinary ToolContext. Remove the public/internal
converter, duplicate resolved-definition constants and wrappers, and the
sourceId.startsWith("eve:") calling convention. Framework ownership comes
from the binding, not a string prefix.
load_skill already has a real executor. Delete its
frameworkAction: "load-skill" marker. Its availability gate — whether the
node can load a static or dynamic skill — and its skill-specific action-event
presentation become selected tool metadata rather than a fabricated second
definition. Existing action request/result wire kinds may remain as protocol
presentation; they must not make load_skill a durable dispatch action.
Dynamic-skill and cold-start behavior use the same compiled source and selected
binding; no special source type or native fallback remains.
Native-handled tools
ask_question, agent, task_update, and task_cancel are closed internal
native-tool definitions, and web_search is the webSearch() sentinel. They
compose, replace, and disable in ordinary tool slots. Each framework definition
carries closed, internal behavior metadata; the compiler serializes that
metadata on the selected tool itself:
type CompiledToolHandling =
| { readonly kind: "request-input"; readonly request: "question" }
| {
readonly kind: "dispatch";
readonly action: "self-agent" | "task-update" | "task-cancel";
}
| { readonly kind: "provider-tool"; readonly provider: "web-search" };
interface CompiledToolBehavior {
readonly availability: readonly ToolAvailabilityCondition[];
readonly handling?: CompiledToolHandling;
readonly presentation?: "skill";
}
type PreparedDispatchTarget =
| { readonly kind: "subagent-call"; readonly nodeId: string; readonly subagentName: string }
| { readonly kind: "self-agent-call"; readonly nodeId: string; readonly subagentName: string }
| {
readonly kind: "remote-agent-call";
readonly nodeId: string;
readonly remoteAgentName: string;
}
| { readonly kind: "task-update" }
| { readonly kind: "task-cancel" };
interface PendingDispatchAction {
readonly callId: string;
readonly description: string;
readonly input: JsonObject;
readonly target: PreparedDispatchTarget;
readonly toolName: string;
}
type ToolAvailabilityCondition = "root-session" | "delegated-task-child" | "requires-request-input";
The internal constructor creates a truthful execute-less native definition and
stamps its behavior metadata; no public defineTool option exposes this form.
The normalizer reads the declaration only from the selected namespace and
persists it on CompiledToolDefinition; cold-start loading never reconstructs
it from the logical path. An application defineTool(...) at
tools/ask_question.ts therefore becomes an ordinary executor, while an
application webSearch(...) retains provider handling because the selected
sentinel declares it. load_skill remains an ordinary executable tool whose
behavior carries presentation, an empty availability set, and no native handling.
Runtime preparation expands self-agent and graph-derived local or remote
subagents into PreparedDispatchTarget values with concrete node identity.
Availability travels beside the prepared tool. The harness evaluates those
conditions and switches on the handling discriminator; it does not inspect the
tool's owner, name, path, executor identity, or a second plan. An absent
handling declaration means ordinary execution. Tasks mode may materialize an
agent-call dispatch as the existing background executor, but the prepared
dispatch kind remains available for Workflow filtering and local fanout.
Workflow remains a source-selected sentinel with no framework default. Its
program sandbox receives only prepared dispatch tools whose actions are
local or remote agent calls; task actions remain unavailable. Workflow reuses
the existing runtime-action dispatch and durable continuation paths rather
than minting an effect kind or global capability lifecycle. Despite the shared
name, defineAgent({ experimental: { workflow } }) still selects the durable
runtime world backing the agent's own execution and is not a tool identity.
connection_search
Register the existing public defineDynamic value at
tools/connection_search.ts for all local nodes. It becomes an ordinary
compiled resolver and retains the existing dynamic lifecycle for validation,
qualification, atomic replacement, collisions, durable callbacks, and replay.
ConnectionSearchResultsKey becomes the sole durable record of discovered
connection tools. Delete message-history rescanning, context/history merging,
the synthetic connection slug, the special graph append, and the separate
framework dynamic registry. Existing sessions without the durable key search
again; no compatibility fallback reconstructs results from history.
Connection filtering, auth and approval behavior, partial/all-failure handling, long qualified names, and persisted callback identity remain unchanged.
Default sandbox
Register a public defineSandbox({}) value at sandbox.ts for all local nodes.
An authored sandbox.ts replaces it through normal source composition. The
standard semantics of a selected sandbox definition with no explicit backend
still choose defaultSandbox() for the current environment, but graph
resolution never invents a framework sandbox. Every successfully compiled
local node contains exactly one selected sandbox with a source ID and binding;
disabling the only candidate cannot be repaired by a runtime fallback.
This removes the no-source fallback and gives inspection, hashing, extension scope, and replacement one truthful sandbox source. Sandbox workspace assets remain filesystem resources and are not virtualized.
Framework channels
Register a root-only zero-argument factory returning
eveChannel({ auth: [vercelOidc(), localDev(), placeholderAuth()] }) at
channels/eve.ts. A factory preserves the current per-resolution lifecycle.
The channel value owns the complete /eve/v1 surface as ordinary channel
routes: the session protocol, the connection callbacks (GET and POST plus
their legacy forms), the workflow callback, task input, the public health
protocol at /eve/v1/health, and the agent-info route at /eve/v1/info.
There is exactly one framework identity for that surface; no framework
channel registers at a deeper logical path.
URLs, legacy support, authorization, status codes, and response bodies do not change. Route definitions carry per-route authorization so current behavior survives inside one channel: health stays publicly reachable while info, the callbacks, and the protocol routes keep the resolved channel auth policy. The definitions carry truthful HTTP adapter metadata and use the ordinary channel handler path, eliminating framework-only route construction and fetch dispatch. Data needed by the info handler comes from an eve-owned context provider — the effective compiled graph, binding owners, composition diagnostics, and selected tool descriptors — not a special source kind or build-time native route.
Add a root framework channel module at channels/home.ts using ordinary
defineChannel, GET, and HEAD values for the home page at /. Its
metadata also comes from an eve-owned context provider, and the default
preserves the current response body, status codes, and authentication
behavior.
The eve channel and home compose as two slots. An authored channels/eve.ts
— typically another eveChannel(...) call with different auth — replaces the
complete default surface, and a disable sentinel removes it; health, info,
and the callbacks are not independently replaceable. This is an intentional
pre-1.0 breaking change. A replacement owns its implementation, but the
public payload contracts remain client-enforced: Client.health() validates
successful JSON with HealthResultSchema — a non-success response throws
ClientError and an invalid successful payload throws HealthResponseError
— and Client.info() validates with AgentInfoResultSchema and throws
AgentInfoResponseError for an unusable authorized payload. No client or
host code reaches a hidden native fallback.
One compiled route plan
Source-slot composition runs before concrete route planning. The manifest owns one required plan:
interface CompiledChannelRoutePlan {
readonly effective: readonly CompiledChannelDefinition[];
readonly preflight: readonly CompiledChannelPreflightDefinition[];
readonly shadowed: readonly CompiledShadowedChannelRoute[];
}
Each shadowed route records its concrete method/path identity, the winning
sourceId, and the loser's self-contained source descriptor. Each generated
preflight records normalized CORS options and the selected source IDs that
caused it. The artifact validator rejects duplicate effective identities,
dangling route sources, invalid preflight causes, and overlap with a reserved
host registration at construction and load.
Different compiled channel sources that normalize to the same method and path
pattern use deterministic compiled order and first-wins selection. The loser
is retained, and the compiler emits compile/channel-route-shadowed. The same
source emitting one identity twice is instead
compile/channel-route-duplicate. Nitro and runtime code never perform a
second silent drop.
Route pattern identity ignores parameter names. Method sets must intersect,
ALL intersects every HTTP and WebSocket method, and a static path inside a
parameter pattern's match space overlaps that pattern. A collision with a
closed host registration is compile/reserved-route-collision.
CORS preflights derive only after ordinary route winners are selected.
WEBSOCKET does not produce preflight. An explicit selected OPTIONS route
that collides with a generated preflight fails with
compile/channel-preflight-collision. Selected CORS-enabled methods at one
path must have identical normalized options or fail with
compile/channel-cors-conflict; identical options produce one derived
OPTIONS record.
Replacing or disabling channels/home.ts or channels/eve.ts is source-slot
composition. An unrelated channel that declares the same concrete route
follows route ordering and does not gain source precedence.
Closed native host inventory
Generic dispatch of a selected compiled channel is execution machinery, not a separately registered default. Host behavior outside ordinary sources is limited to four typed categories:
- Workflow transport;
- development control and readiness transport;
- the schedule/cron platform bridge;
- a non-HTTP production process-readiness handshake.
The application and development hosts register or reserve only these inventoried HTTP entries:
ALL EVE_WORKFLOW_FLOW_ROUTE_PATH;GET EVE_DEV_RUNTIME_ARTIFACTS_ROUTE_PATH;GETandPOST EVE_DEV_RUNTIME_ARTIFACTS_REBUILD_ROUTE_PATH;POST EVE_DEV_RUNTIME_ARTIFACTS_SUSPEND_ROUTE_PATH;POST EVE_DEV_RUNTIME_ARTIFACTS_RESUME_ROUTE_PATH;ALL EVE_DEV_WORKFLOW_WORLD_ROUTE_PATH;ALL EVE_DEV_WORKFLOW_STREAM_ROUTE_PATH;POST EVE_DEV_DISPATCH_SCHEDULE_ROUTE_PATTERN;- reservation-only
ALL EVE_PRODUCTION_CRON_ROUTE_PATTERN.
The schedule/cron bridge and process-ready handshake remain typed host capabilities; they are not fabricated as ordinary agent channel sources. Any new host registration requires an inventory entry and collision coverage. HTTP adapter availability derives from the effective compiled channel routes; there is no synthetic adapter source slot or framework route catalog.
Production process startup waits for the non-HTTP ready handshake emitted
after the server listens. Development-server identity and reuse, including
Next.js, Nuxt, and SvelteKit adapter reuse, use the development
control/readiness transport plus recorded server identity. Remote clients, TUI
probes, and evals intentionally continue to observe the replaceable public
health contract through Client.health().
Persisted compiler diagnostics
Discovery, normalization, and route planning append to one persisted compiler
diagnostic union. Every source-specific locator carries its exact compiled
nodeId, plus sourceId, logicalPath, and optional physical sourcePath,
with at least one source locator required.
Once a candidate exists, its diagnostics use both sourceId and
logicalPath. Programmatic sources never fabricate a physical path.
The compiler keeps one diagnostic accumulator open until route planning is
complete. Route warnings therefore contribute to serialized diagnostics,
diagnosticsSummary, source-graph hashes, CLI rendering, and agent-info. A
shadow warning retains enough winner and loser identity to resolve its detailed
CompiledShadowedChannelRoute; the route plan remains authoritative. Compile
errors use the same stable codes and abort instead of being serialized into a
successful artifact. The discovery-diagnostics artifact (version 1 today)
becomes this compiler diagnostic artifact at version 2; loaders reject the
earlier serialized shape rather than repairing it.
Typed tool handling
The durable mechanisms already exist and remain with their current owners. Pending input, approvals, and authorization are harness concerns. Runtime action batches, child dispatch, task control, and Workflow continuation are execution concerns. Provider-tool materialization belongs next to the model call. Phase 2 does not move those implementations behind a universal kernel strategy object.
The missing boundary is the typed handoff between those layers:
selected definition ─> CompiledToolDefinition.behavior
│
├─> prepared source-backed tool
subagent graph ─────────────────┴─> prepared delegation tool
│
├─> availability filter
├─> model-tool materialization
├─> pending input or action request
└─> agent-info projection
Compiler normalization copies the internal declaration from the selected
definition to its CompiledToolDefinition. Runtime resolution carries that
compiled behavior into one uniform prepared-tool descriptor; execute-less
native tools do not need to reload a module merely to recover behavior. Local
and remote subagent graph entries join at that prepared boundary with concrete
dispatch targets; they do not need a fabricated source definition or a global
catalog.
The harness evaluates availability before materialization. It handles
request-input, dispatch, and provider-tool through exhaustive switches on
the descriptor, while an ordinary tool follows its executor. The existing
pending-input and pending-dispatch records remain authoritative for resume. The
broad action-event union remains a protocol projection for clients; the pending
batch narrows to PendingDispatchAction values. Plain tool calls and
load-skill presentation must not enter the durable dispatch planner. Approvals
remain an orthogonal gate on any executable tool and do not become a declared
request-input operation.
Task control requests carry task-update or task-cancel in the typed action
itself. The execution step switches on that action instead of recovering it
from toolName. Background subagent batches carry local or remote dispatch
identity in their prepared definitions, which removes
localSubagentExecutors without changing task admission, fanout, or durable
child ownership.
final_output remains the sole per-turn native tool. It is created only when
a turn has an output schema, and the terminal-output module owns both its
model-visible name and extraction. Other harness and execution modules receive
the typed terminal result; the source graph does not fabricate a static slot.
This boundary is intentionally distributed by ownership. It adds no
kernelPlan artifact, capability registry, strategy table, semantic validator,
or guard rule. The closed TypeScript unions make new handling and dispatch
kinds fail at their actual integration switches. Replacing or disabling a
framework source removes its descriptor because no downstream layer can infer
it from the vacated logical path.
Inspection
Agent-info v3 landed with #2516, and first-class memory advanced the current
payload to v4. Phase 2 does not replace that schema. The /eve/v1/info route
continues to project from the effective compiled graph and remains an ordinary
route of channels/eve.ts.
Agent-info reports:
- the selected default or authored agent config with its binding and owner;
- active static definitions in one list per primitive, each with its logical
path, source ID, and owner (
application,extension, orframework); - active dynamic resolvers separately from their session-specific outputs, including exact subscribed events and explicit source provenance;
- prepared native handling as an inspection projection, including its availability conditions;
- the exact effective ordered channel-route list used by Nitro, including home and health, plus retained route-composition diagnostics;
- every compiled local subagent and its dynamic config resolver with explicit
source ownership and
parentNodeId; - every declared remote agent in a separate
remoteAgentscollection with explicit provenance andparentNodeId, never folded into local subagent counts; - composition diagnostic summaries for shadowed or disabled primitive and subagent candidates, never duplicate resource rows.
A dynamic model uses
routing: { kind: "dynamic", resolver: AgentInfoDynamicResolverEntry }.
A dynamic local subagent exposes
configResolver?: AgentInfoDynamicResolverEntry. These entries carry the
resolver source and exact events instead of adjacent optional fields. The root
agent exposes its compiled nodeId; local and remote collections are flat over
the complete graph, and every entry identifies its owning scope through
parentNodeId. Consumers traverse those opaque identities instead of parsing
them. Local subagents use subagents: { local; total }; remote agents use
remoteAgents: { entries; total } because they do not share local inheritance
or execution semantics.
It removes available, authored, framework, disabledFramework,
replacesFramework*, disabledByAuthor, and the
active | disabled | opt-in | replaced framework status taxonomy. Active
resources can be grouped by owner without duplicating them. Disabled and
shadowed candidates remain compiler composition diagnostics, not runtime
agent state.
Every inspectable source must have explicit ownership. Missing ownership is a
malformed artifact rejected at construction or load; agent-info never falls
back to application ownership, parses a source ID, or reconstructs an origin.
The native-handling projection comes from each selected compiled tool and
prepared graph-derived delegation descriptor. Delete KERNEL_EFFECT_BY_SLOT;
inspection must not parse a logical path or owner to rediscover behavior.
Channel entries continue to come directly from
manifest.channelRoutes.effective.
Normalizers continue recording safe facts that cannot be reconstructed from
serialized JSON, such as execution presence, approval, schemas, model-output
projection, adapter kind, and route identity. They never serialize callbacks.
Tool hasAuth remains false because arbitrary executor calls to getToken() or
requireAuth() cannot be inferred statically.
eve info --json and Vercel summaries keep their narrower contracts but use
the same effective compiled resources. They do not grow session-specific
dynamic outputs or pretend prepared native potential is a concrete model-call
tool set.
In-memory compiler parity
The test-only in-memory compiler declares each programmatic application source
once. That registration derives its candidate, binding, manifest reference,
composition entry, and owner. It supplies complete bindings and composition to
the ordinary constructors, includes the framework default agent.ts, and
proves authored config replacement through the same phase-one composer.
Filesystem and memory fixtures must produce equivalent source owners, config provenance, tool-handling descriptors, and module-map key sets. Cold-start namespace reconstruction and durable callback replay use registry/module bindings only; neither logical paths nor source IDs recover missing backing or provenance.
The app-harness execution helper accepts an installed tool identity or name and
resolves it from the compiled runtime bundle. It never builds a new registry
around a caller-owned ResolvedToolDefinition. Remove createMemorySourceId,
mirrored hand-built AgentSourceManifest references, and every test path that
executes a caller-owned definition instead of the selected installed tool.
Downstream memory integration
The wrapper-namespace path uses derived slot composition: a selected authored
memory slot instantiates a registered template whose virtual
tools/<slot>.ts exports defineDynamic, depends on the selected memory
binding, and carries the slot as serialized parameters. The result is an
ordinary compiled resolver producing qualified provider tools. The memory
implementation must prove cold-start namespace reconstruction and resuming a
parked provider-tool call through this source graph and binding table rather
than a memory-only registry or runtime contributor seam.
Delivery
The source-identity half is complete: #2516 landed the canonical graph, #2539
landed derived slots, #2534 moved memory onto them, and #2531 consolidated the
tool source layout. Issue #2347 was closed when the runtime-contribution seam
was superseded. Phase 2 can now be reviewed independently against current
main.
The original Phase 2 implementation shape is rejected. The discarded
barba/exhaustive-kernel-lifecycle attempt added 4,617 lines while deleting
734, including a separate 476-line kernel-plan semantic validator and 486 lines
of invariant-guard machinery. It reproduced source facts in a capability
strategy table and made no-op lifecycle methods part of the architecture. That
is the inverse of the replacement ratio and self-enforcing boundary required
by this plan.
Phase 2 — typed tool handling
Phase 2 is one focused, behavior-preserving refactor:
- Add closed internal handling and availability declarations to the canonical
framework definitions, and serialize them on the selected
CompiledToolDefinition. - Carry those declarations through runtime resolution into prepared tools; create the same prepared dispatch shape directly from local and remote subagent graph entries.
- Make harness availability, question extraction, provider materialization, runtime-action creation, Workflow host filtering, and agent-info projection consume the prepared descriptor.
- Make task execution consume the typed dispatch action instead of
toolName, and make background subagent fanout consume prepared delegation kind instead of executor function identity. - Delete every superseded marker, name table, owner-and-name branch, logical-slot inspection table, fake native executor, and broad action-event type from the pending dispatch boundary.
If the compiled descriptor changes the serialized manifest, increment the
manifest once from the version current when the PR starts. Do not reserve a
version in this research document. Agent-info remains v4 when its wire shape is
unchanged; kernelEffects may remain the public projection name while its data
comes from selected descriptors. The PR carries a patch changeset when
observable behavior is preserved.
Production deletions must meet or exceed new production machinery. A new
kernel plan, global capability registry, semantic validator, hand-built
artifact factory, or guard-invariants rule fails the design review.
The implemented boundary adds 171 lines across four focused behavior and dispatch modules while deleting 498 lines from existing production modules. It increments the compiled manifest to v46 and the backward-compatible tool extension contract to epoch 23. No kernel plan, global registry, semantic validator, artifact factory, or invariant rule was added.
Phase 2 deletion ledger
The remaining work is incomplete while any listed path or equivalent parallel system remains:
| Requirement group | Required deletion |
|---|---|
| Handling authority | Owner-and-name dispatch in execution/node-step.ts; logical-path or owner reconstruction of native handling; KERNEL_EFFECT_BY_SLOT; any separate kernel plan, registry, strategy table, or semantic validator. |
| Harness shape | frameworkAction, runtimeAction, rootOnly, and workflowCallable as unrelated special fields; ask_question and web_search name gates outside their owning adapters; name-based hidden-tool and Workflow-host filtering. |
| Dispatch | TASK_TOOL_NAMES; task update/cancel selection by toolName; localSubagentExecutors and executor-identity fanout; the broad action-event union as the pending dispatch contract; any source ID, logical path, owner, or model-visible name used to select a dispatch implementation. |
| Definition truth | Framework definitions whose executors can only throw because a downstream name check is expected to intercept them. Native-handled definitions are execute-less; load_skill remains genuinely executable. |
| Presentation | The frameworkAction: "load-skill" marker and name-derived action presentation. If the special load-skill request/result wire projection remains, it derives from selected behavior metadata and stays separate from durable dispatch. |
| Terminal output | final_output name checks spread across the harness. The terminal-output module may own its reserved name, construction, and extraction because no source-backed identity exists. |
Helpers may move behind the typed prepared-tool boundary, but a renamed second source of truth does not satisfy deletion.
Validation
Tests are replaced, not accumulated. Tests that assert name-based dispatch, independent special fields, function-identity classification, or logical-slot inspection are deleted with the code they cover. Replacement coverage proves:
- framework declarations survive compile, serialization, cold loading, and runtime preparation without path inference;
- an authored
defineToolreplacement at a framework slot executes ordinarily, while a selectedwebSearch()sentinel retains provider handling; - request-input and availability semantics remain correct for root, self-delegated, scheduled, named, dynamic, and task-owned sessions;
- task controls and local or remote subagent calls reach the same existing durable dispatch paths from typed actions; and
- Workflow receives only eligible agent-call dispatch tools.
Test helpers obtain compiled artifacts only through the real compiler, from
filesystem fixtures or in-memory source registration; no helper constructs
Compiled* records field-by-field.
Phase 2 adds no new e2e evals or scenario suites. Existing scenario, TUI, and fixture-owned e2e suites are the behavioral regression net and should remain unchanged because the refactor preserves the public tool and agent-info contracts.
Run inexpensive checks throughout implementation:
pnpm fmt
pnpm lint
pnpm typecheck
pnpm guard:invariants
pnpm test:unit
Run the narrowest relevant integration and scenario files while iterating. The complete implementation also runs:
pnpm build
pnpm test:integration
pnpm test:scenario
pnpm test:tui
pnpm docs:check
Fixture-owned e2e suites run in CI. Composition, replacement, and disablement
coverage lives in unit and integration tests against the real compiler, not
in new fixtures. The final required CI includes the deterministic world
suites and the real-model e2e-local aggregate; transport or provider
variance may be rerun, but repeated failures are investigated rather than
waived.
Completion criteria
The implementation is complete only when:
- A compiled artifact with missing binding, owner, composition, or route provenance cannot be constructed or loaded — enforced at construction and after disk or bundled schema parsing, before module-map hydration.
- Every winner is bound before evaluation; losing filesystem exports and programmatic namespace loaders never execute during compilation or cold start.
- Development, generated, materialized, bundled, hydrated, and in-memory
module maps expose the same
(nodeId, sourceId)set and invoke only selected programmatic loaders; no virtual disk path is probed. - Extension projection qualifies each primitive once, preserves package backing and external dependencies, and retains ownership across nested and duplicate extension subagent mounts; no per-primitive merge or origin table remains.
- Removing a selected framework source removes its runtime behavior; no runtime layer recreates it. Default and authored sandboxes and configs each have one truthful selected source, and filesystem and memory compilation report identical provenance.
- Every active ordinary capability appears once by public identity in the compiled graph and once in inspection; named primitives use their runtime name, routes use method plus normalized path pattern. Nitro registers no ordinary route or generated preflight absent from the compiled channel route plan, and route-planning failures use their stable compile error codes.
- Every selected native-handled tool carries one compiled handling descriptor,
every local or remote delegation carries one prepared dispatch descriptor,
and every handling and dispatch union is switched exhaustively at its owning
layer. Root, self-delegated root, named and dynamic task children, non-task
children, provider-dependent calls, Workflow depth, approvals, and
structured output preserve their current behavior. Named task children
receive
task_update. No harness or execution code selects source-backed behavior from a tool name, logical path, owner, source ID, or executor identity. - Connection search preserves filtering, auth, approval, failure, long-name, durable callback, and restart behavior without history scanning; home and eve-channel replacement, client payload validation for health and info, and internal process readiness agree without a native fallback.
- Agent-info explains the owner and replacement history of every config,
primitive, route, local subagent, remote agent, and dynamic resolver
without parsing identifiers;
AgentInfoResultSchemavalidates exact unique sets and counts, and Nitro's ordinary routes equal agent-info channels. - Searches for the distributed deletion ledger return no production match or renamed equivalent.
- Changing a selected filesystem module or programmatic executable revision changes module-map identity, while relocating identical filesystem content without changing its logical extension namespace does not.
- The complete implementation lands with all required local and CI
validation passing, existing higher-level suites behaviorally unchanged,
no new e2e or scenario suites, no new plan artifact, and no new
guard-invariantsrules.
Invariants and rejected alternatives
- Logical paths remain eve's only definition naming grammar. Backing, owner, and source ID never change normalization semantics.
- One candidate composer chooses before definition execution. There is no per-primitive precedence, disable implementation, or separate subagent composer.
- Required compiled bindings are the only authority for namespace loading. Inferred physical paths, diagnostic metadata, and virtual disk fallback cannot affect behavior; derived module dependencies and parameters are required backing data because they do affect behavior.
- Programmatic construction is explicit, immutable, statically reachable, and lazy. There is no global registry, runtime graph mutation, function serialization, generated temporary source, or unselected namespace evaluation. Every binding pins the registered source revision and rejects a different implementation before load.
- Active ownership has one location. Composition entries own only losing or disabled provenance; no winner-owner index or origin table is retained.
- The design is self-enforcing. A boundary that needs a new mechanical guard rule, an AST check over the implementation's own interfaces, or an identifier-name pattern to survive is a design failure to correct, not a guard to add.
defineDynamicremains the sole dynamic-definition lifecycle. Framework and future memory features do not gain parallel validation, durability, replacement, collision, or callback stores.- Native handling is internally declared on canonical definitions, serialized on selected compiled tools, and consumed through prepared descriptors. Public authoring APIs do not expose the declaration, definition modules do not import Workflow, harness, or execution internals, and no source-backed behavior is keyed by tool name.
- Ordinary route dispatch comes only from the compiled route plan. Native
behavior is limited to the closed tool-handling union, the localized
final_outputinterception, and the host inventory; a universal native-tool, route, or adapter escape hatch is rejected. - Pre-1.0 cleanup is a breaking replacement: no legacy manifest reader, agent-info schema, health fallback, history reconstruction, duplicate status state, or compatibility path is retained.