Agent builder switches from code to JSON configuration

The n8n agent builder now writes agent configurations as JSON instead of TypeScript code, with a new sandboxed execution environment for custom tools.
The agent builder's internal workflow has been completely reimagined. Previously, the builder LLM generated TypeScript code that defined agents using the SDK's fluent API. Now, the builder writes and patches agent configurations directly as JSON strings.
This shift eliminates a fragile code-generation step. The builder LLM receives two primary tools: write_config for complete configuration replacement, and patch_config for incremental updates using RFC 6902 JSON Patch operations. Both tools validate against a Zod schema before persisting, with structured error messages returned when validation fails so the LLM can self-correct.
Custom tools remain TypeScript, but their execution now happens inside V8 isolates. A new AgentSecureRuntime uses esbuild to bundle the necessary libraries (@n8n/agents and zod) into a single string that gets injected into sandbox contexts. This allows untrusted tool code to run safely without access to the filesystem, network, or other Node.js built-ins.
The agents database table loses its code column and gains a tools column that stores custom tool code alongside lightweight descriptors. The frontend's code editor tab is replaced with a JSON editor that syncs changes back to the API.
This is part of a broader initiative to make the agent builder more reliable and easier to maintain.
View Original GitHub Description
Summary
Introduces a JSON-based builder mode for the agent builder. The builder LLM now writes and patches the agent configuration as raw JSON strings rather than through writing typescript code. Custom tools are still written using typescript.
The builder prompt includes examples of using different features and simplified json schema of the agent config. There's a custom json schema serializer that reduces token size of schema by around ~40%
<details> <summary>Serialized agent schema</summary> name: string [1..128 chars] (required)
description?: string [max 512 chars]
model: string [min 1 chars, pattern: ^[a-z0-9-]+\\/[a-z0-9._-]+$] (required)
credential: string [min 1 chars] (required)
instructions: string (required)
memory?: object
enabled: boolean (required)
storage: \"n8n\" | \"sqlite\" | \"postgres\" (required)
connection?: Record<string, unknown>
lastMessages?: integer [1..200]
semanticRecall?: object
topK: integer [1..100] (required)
scope?: \"thread\" | \"resource\"
messageRange?: object
before: integer [min 0] (required)
after: integer [min 0] (required)
embedder?: string
tools?: array with items any of:
| (type = \"custom\")
id: string [min 1 chars, pattern: ^[a-z0-9_-]+$] (required)
requireApproval?: boolean
| (type = \"workflow\")
workflow: string [min 1 chars] (required)
name?: string
description?: string
requireApproval?: boolean
allOutputs?: boolean
| (type = \"node\")
name: string [min 1 chars] (required)
description?: string
node: object (required)
nodeType: string [min 1 chars] (required)
nodeTypeVersion: number (required)
nodeParameters?: Record<string, unknown>
credentials?: Record<string, { id: string, name: string }>
inputSchema: object (required)
type?: \"object\"
properties?: Record<string, unknown>
required?: array of <string>
requireApproval?: boolean
providerTools?: Record<string, Record<string, unknown>>
config?: object
thinking?: object
provider: \"anthropic\" | \"openai\" (required)
budgetTokens?: integer
reasoningEffort?: string
toolCallConcurrency?: integer [1..20]
requireToolApproval?: boolean
</details>
The builder has access to two groups of tools:
Config tools (JSON mode)
| Tool | What it does |
|---|---|
write_config | Full replace — accepts a complete agent config as a JSON string, validates it, and persists it |
patch_config | Incremental update — accepts RFC 6902 JSON Patch operations, applies them to the current config via fast-json-patch, validates the result, and persists it |
Both tools return { ok: true } on success or { ok: false, errors } with structured path/message/expected/received fields on failure. patch_config additionally returns a stage ("parse", "patch", or "schema") indicating where the failure occurred.
Shared tools (always present)
| Tool | What it does |
|---|---|
build_custom_tool | Writes or updates a custom TypeScript tool using the Tool builder API; validates the code in a sandbox before saving |
list_credentials | Lists credentials available in the project — call this before generating config |
list_workflows | Lists project workflows with compatible trigger types (manual, chat, schedule, etc.) |
search_nodes | Searches for n8n integration nodes by name or service |
get_node_types | Returns the full parameter schema for specific node IDs found via search_nodes |
Validation pipeline
write_config and patch_config pass every config through the same pipeline:
tryParseConfigJson— catchesJSON.parsefailures with position infoAgentJsonConfigSchema.safeParse(Zod) — validates schema shape and field constraintsagentsService.updateConfig— persists to the database
Zod errors are normalized by formatZodErrors into { path, message, expected, received } objects so the LLM can self-correct.
Related Linear tickets, Github issues, and Community forum posts
<!-- Include links to **Linear ticket** or Github issue or Community forum post. Important in order to close *automatically* and provide context to reviewers. https://linear.app/n8n/issue/[TICKET-ID] --> <!-- Use "closes #<issue-number>", "fixes #<issue-number>", or "resolves #<issue-number>" to automatically close issues when the PR is merged. -->Review / Merge checklist
- I have seen this code, I have run this code, and I take responsibility for this code.
- PR title and summary are descriptive. (conventions) <!-- **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** -->
- Docs updated or follow-up ticket created.
- Tests included. <!-- A bug is not considered fixed, unless a test is added to prevent it from happening again. A feature is not complete without tests. -->
- PR Labeled with
Backport to Beta,Backport to Stable, orBackport to v1(if the PR is an urgent fix that needs to be backported)