feat(cli): ephemeral node-tool executor for agents (AGENT-26 1/4)#28882
feat(cli): ephemeral node-tool executor for agents (AGENT-26 1/4)#28882DeveloperTheExplorer wants to merge 6 commits inton8n-agentsfrom
Conversation
Foundation for letting agents invoke node tools without persisting a workflow or execution. Adds: - `EphemeralNodeExecutor` — runs a single node's `execute` or `supplyData` path against an in-memory Workflow + ExecuteContext / SupplyDataContext, with credential resolution scoped to the agent's project. - `resolveNodeTool` in `node-tool-factory` — converts a persisted node-tool ref into a runnable `BuiltTool` that routes through the ephemeral executor, including at-registration-time schema introspection for native LangChain tools so the LLM-facing schema matches the schema the tool actually validates against. - Shared `SUPPORTED_WORKFLOW_TOOL_TRIGGERS` in `@n8n/api-types` — one source of truth for the backend compatibility check (`workflow-tool-factory.ts`) and the upcoming frontend Available-list pre-filter. Part of AGENT-26 scope breakdown. No user-visible UI in this PR.
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
… part 1/4)
- `@n8n/api-types/src/index.ts`: the existing `export type * from './agents'`
re-exported types, and the follow-up `export { SUPPORTED_WORKFLOW_TOOL_TRIGGERS }`
re-exported the value — but TypeScript treats the const's type as exported twice
(once via the type-only re-export, once via the value re-export). Collapse to a
single `export * from './agents'` now that the file carries both types and a
value export.
- `@n8n/agents/src/runtime/title-generation.ts:144`: `||` → `??`. The downstream
use is `emoji && { emoji }` (line 200), so collapsing `''` to `undefined` vs
keeping `''` is indistinguishable, and `??` is preferred by the lint rule.
There was a problem hiding this comment.
1 issue found across 9 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/cli/src/modules/agents/tools/node-tool-factory.ts">
<violation number="1" location="packages/cli/src/modules/agents/tools/node-tool-factory.ts:89">
P1: Normalize introspected Zod schemas to a top-level object before returning them. Returning raw `zodToJsonSchema()` can produce top-level `oneOf`, which breaks Anthropic tool registration.</violation>
</file>
Architecture diagram
sequenceDiagram
participant Agent as Agent Runtime
participant Service as AgentsService
participant Factory as node-tool-factory
participant Executor as EphemeralNodeExecutor
participant Registry as NodeTypes Registry
participant DB as Credentials / Shared DB
Note over Agent,DB: Tool Resolution & Introspection (Registration Time)
Agent->>Service: loadTools()
Service->>Factory: NEW: resolveNodeTool(ref, projectId)
opt NEW: Schema Introspection (for supplyData nodes)
Factory->>Executor: introspectSupplyDataToolSchema(nodeConfig)
Executor->>Registry: getByNameAndVersion()
Registry-->>Executor: nodeType
Executor->>Executor: buildEphemeralContextParts()
Note right of Executor: Creates in-memory Workflow<br/>& SupplyDataContext
Executor->>Executor: nodeType.supplyData()
Executor-->>Factory: Zod/JSON Schema
end
Factory-->>Agent: BuiltTool (with sanitized name)
Note over Agent,DB: Tool Invocation (Execution Time)
Agent->>Factory: tool.handler(input)
Factory->>Executor: NEW: executeInline(request)
Executor->>Executor: isUsableAsAgentTool() validation
alt NEW: Resolve & Verify Credentials
Executor->>DB: findCredentialsForProject(projectId)
DB-->>Executor: credentials
Executor->>Executor: Check access & type compatibility
Note right of Executor: Throws if missing or unauthorized
end
Executor->>Registry: getByNameAndVersion()
Registry-->>Executor: nodeType
alt Path A: Native Tool (supplyData)
Executor->>Executor: invokeSupplyDataTool()
Executor->>Executor: call supplyData() via SupplyDataContext
Executor->>Executor: langchainTool.invoke(input)
Note right of Executor: In-memory execution (no DB records)
else Path B: Standard Node (execute)
Executor->>Executor: executeNodeDirectly()
Executor->>Executor: call execute() via ExecuteContext
end
alt Success
Executor-->>Factory: NodeExecutionResult (success)
Factory-->>Agent: Data
else NEW: Important Unhappy Path (Error/Blacklist)
Executor-->>Factory: NodeExecutionResult (error)
Note left of Executor: e.g. "sendAndWait" not supported
Factory-->>Agent: Error Response
end
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
zodToJsonSchema() emits top-level anyOf/allOf or non-object roots for
z.union, z.discriminatedUnion, z.intersection, z.array and primitive
Zod types. Anthropic's tool input_schema requires { type: "object" }
at the root — any other shape breaks tool registration.
Add normalizeToObjectSchema() in node-tool-factory: pass through object
schemas as-is, merge allOf-of-objects losslessly, and fall back to an
empty object for roots that can't be expressed as an object.
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/cli/src/modules/agents/tools/node-tool-factory.ts">
<violation number="1" location="packages/cli/src/modules/agents/tools/node-tool-factory.ts:88">
P2: Union/discriminated-union tool schemas are flattened to an empty object, which loses all input-field guidance for the LLM.
(Based on your team's feedback about Anthropic tool schemas requiring a top-level object without discarding union shape.) [FEEDBACK_USED]</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
…GENT-26 part 1/4)
The previous normalizer lifted `type: 'object'` into place for z.intersection
(merged allOf of objects) but collapsed every other non-object root — most
importantly `z.union` / `z.discriminatedUnion` — to `{ type: 'object',
properties: {} }`. For a tool whose runtime schema is an action-keyed
discriminated union (common in MCP tools), the LLM was handed an empty
object and lost every per-branch field hint.
Rework:
- `anyOf` / `oneOf` of objects: hoist `type: 'object'` to the root but keep
the union. JSON Schema permits both simultaneously and Anthropic accepts
the shape, so per-branch field lists stay intact.
- Nested non-object members inside a union are normalized recursively —
primitive branches get wrapped in `{ input: <schema> }` so they survive
as valid object members rather than being dropped.
- `type: 'array'` or a primitive root: wrap in `{ input: <schema> }` with
`required: ['input']`, matching the base LangChain `Tool` convention the
handler already uses.
- Existing allOf-merge path and the last-resort empty-object fallback
(for allOf-of-primitives and unknown roots) are preserved.
Tests updated: the old assertions pinned the lossy behaviour (anyOf/oneOf
"falls back to empty object"). They now assert union preservation, nested
normalization, and primitive/array wrapping; allOf merging + dedup
coverage is retained.
Performance ComparisonComparing current → latest master → 14-day baseline Memory consumption baseline with starter plan resources
docker-stats
Idle baseline with Instance AI module loaded
How to read this table
|
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/cli/src/modules/agents/tools/node-tool-factory.ts">
<violation number="1" location="packages/cli/src/modules/agents/tools/node-tool-factory.ts:111">
P2: `normalizeToObjectSchema` misses schemas where `type` is an array (`string[]`), causing valid multi-type roots to degrade to an empty object schema.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
…ation (AGENT-26 part 1/4)
JSON Schema's `type` can be a single string or an array of strings — e.g.
`{ type: ['string', 'null'] }`, which `zodToJsonSchema` emits for
`z.string().nullable()` / `z.union([z.string(), z.null()])` in its
nullable-as-type-union mode. The previous check (`typeof schema.type ===
'string'`) missed that shape, so nullable primitive roots degraded to an
empty `{ type: 'object', properties: {} }` instead of being wrapped in
`{ input: <schema> }`.
- Accept both `typeof schema.type === 'string'` and `Array.isArray(schema.type)`
in the primitive/array wrap path.
- Also drop the redundant `schema.type === 'array'` disjunct — `typeof
'array' === 'string'` already subsumes it.
- New test covers `{ type: ['string', 'null'] }`.
Summary
Part 1 of 4 of the AGENT-26 "manual tool management UI" scope. Backend-only foundation — no user-visible UI in this PR. Merges into
n8n-agents.EphemeralNodeExecutor— runs a single node'sexecuteorsupplyDatapath against an in-memory Workflow + ExecuteContext / SupplyDataContext, with credential resolution scoped to the agent's project.resolveNodeToolinnode-tool-factory— converts a persisted node-tool ref into a runnableBuiltToolthat routes through the ephemeral executor, including at-registration-time schema introspection for native LangChain tools so the LLM-facing schema matches whattool.invoke(args)zod-parses against.SUPPORTED_WORKFLOW_TOOL_TRIGGERSin@n8n/api-types— one source of truth for the backend compatibility check (workflow-tool-factory.ts) and the PR 4 frontend Available-list pre-filter. A compile-time assertion keeps the map in lockstep.PR stack
Merge order: 1 → 2 → 3 → 4. Each PR retargets to
n8n-agentsonce its parent lands.Related Linear tickets
https://linear.app/n8n/issue/AGENT-26
Test plan
ephemeral-node-executor.test.ts— 22 tests covering credential resolution, verification, validation, direct-execute routing, and supplyData routing (invocation + schema introspection).node-tool-factory.test.ts— 3 tests for tool-name sanitisation.pnpm typecheckclean for files touched by this PR.