# AI Agent / LLM workflows on Awesomate n8n

Read before designing or editing any agent, chat, or LLM-chain workflow.

**Not sure this is the right kind of agent?** An assistant the user sets up
once and talks to in Slack/Telegram, runs on a schedule, or calls from many
workflows is an **n8n Agent** (the Agents tab, n8n 2.40+), not a workflow with
an AI Agent node: read [n8n-agents.md](n8n-agents.md) and use
`awesomate_n8n_agents`.
Platform basics (task runners, `$vars`, `$json.body`) are in platform-notes.md.

## Before building — always

1. Call `awesomate_n8n_context` — confirm consent, plan, variant, limits.
2. Check model credentials FIRST: `awesomate_n8n_inspect {what:"credentials"}`.
   Look for openrouter / openai / anthropic entries. No model credential =
   stop and ask the user; never invent one.
3. Verify current typeVersions via `awesomate_n8n_node_docs {tool:"get_node"}`
   — `agent`, `chatTrigger`, `httpRequestTool` bump frequently. Point-in-time
   snapshot (re-verify): `@n8n/n8n-nodes-langchain.agent` 3.1,
   `n8n-nodes-base.httpRequestTool` 4.4, `lmChatOpenRouter` 1. On 503
   `node_catalog_unavailable`, fall back to the vendor/ references.

## Architecture

- Chat agent = `@n8n/n8n-nodes-langchain.chatTrigger` →
  `@n8n/n8n-nodes-langchain.agent`, with model (`ai_languageModel`), memory
  (`ai_memory`), and tools (`ai_tool`) attached as sub-node connections.
- Agent v3.x: `systemMessage` lives at `parameters.options.systemMessage`.
  The validator reports it missing — false positive, ignore. Set
  `options.maxIterations` explicitly.
- Chat Hub / streaming requires agent typeVersion >= 3.x. A 1.x agent behind
  a chatTrigger fails at send time with "Agent node version is too old to
  support streaming responses".
- **Reasoning/execution separation**: the AI decides, deterministic nodes
  act. Validate model output before any side effect. An LLM cannot tell its
  input was truncated — detect truncation in the deterministic layer, state
  it in the payload, and forbid destructive recommendations explicitly.

## Tools

- HTTP tool node is `n8n-nodes-base.httpRequestTool` at its CURRENT
  typeVersion (verify via get_node; 4.4 at last check). NEVER
  `@n8n/n8n-nodes-langchain.toolHttpRequest` — deprecated, and under agent
  v3.x it fails every call with `has a "supplyData" method but no "execute"
  method`.
- Every tool needs a descriptive `toolDescription` — it is the only signal
  the agent has for choosing and parameterizing the tool.
- Headers via `headerParameters.parameters` `[{name, value}]`; body via
  `bodyParameters.parameters`. Full response (status/headers/IDs):
  `options.response.response.fullResponse: true`.

### $fromAI rules

- `$fromAI('alias', 'description', 'string')` goes in individual tool
  parameter VALUES only: `"value": "={{ $fromAI('alias', 'desc', 'string') }}"`.
- NEVER inside `JSON.stringify()` or embedded mid-string in `jsonBody` — the
  `$fromAI` scanner cannot extract it and the model passes literal
  placeholder text. (Sole exception: a `jsonBody` that is ONE whole-body
  `$fromAI('JSON', ``, 'json')` expression, optionally wrapped in an
  arrow-IIFE to inject mandatory fields — keep the
  `/*n8n-auto-generated-fromAI-override*/` marker.)
- One `$fromAI` key per node = one parameter. Repeating a key in multiple
  parameters with differing description/type crashes EVERY agent turn with
  `Duplicate key '<k>' found` before any LLM call.
- Type `'json'` does NOT parse a JSON-encoded string argument. For
  array/object params use `JSON.parse($fromAI(..., 'string'))` — the parse
  throw surfaces as a tool error the agent recovers from; it is load-bearing.
- Default emitted type is `'string'`. Numeric/boolean params need a manual
  override: `={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('limit',
  `desc`, 'number') }}` — otherwise real numbers fail schema validation.

### toolWorkflow (sub-workflow tools)

- The sub-workflow must be ACTIVE — an inactive one errors at tool-call
  time, not save time.
- `workflowInputs` needs a populated `schema`, not just `value`. With
  `schema: []`, `$fromAI` mappings silently resolve to empty strings and the
  agent loops to "Max iterations reached".
- Tools run the sub-workflow's PUBLISHED version. Draft edits are dormant
  until the sub-workflow is republished — the classic "my edit did nothing".

## Model credentials

- OpenRouter via `lmChatOpenRouter`: model IDs use DOT notation
  (`anthropic/claude-sonnet-4.5`), never hyphenated version suffixes — the
  wrapper mangles them. Dated slugs (`...-20250929`) are rejected. Verify
  slugs against OpenRouter's live `/api/v1/models`, not memory.
- Dedicated model nodes (lmChatOpenRouter etc.) auto-bind credentials by
  NAME at create time — prefer them over raw HTTP for LLM calls.

## Memory

- Separate memory per user/session: key the memory node's session on the
  chatTrigger's `sessionId`. A static shared key mixes every user's
  conversation into one history.
- Motorhead memory node is deprecated — do not use.

## Structured output

- NEVER attach a structured output parser to the FINAL agent of a chat
  workflow — it breaks streaming/chat responses. Agent v3.x + parser +
  `enableStreaming: true` loops to max iterations: tools fire (side effects
  happen) on iteration 1, then the agent restates a valid answer forever.
- Prefer deriving routing metadata deterministically (e.g.
  `{{ $('Trigger').isExecuted }}`) over asking the LLM to echo it. If
  structured output is genuinely needed, disable streaming or parse in a
  downstream node.
- `retryOnFail` on an agent whose tools have side effects re-runs the whole
  agent after a post-success failure → duplicate posts/sends. Don't combine.

## chainLlm (Basic LLM Chain)

- Emits NO `pairedItem`: any downstream `$('Node').item` throws "Paired item
  data ... is unavailable" on multi-item inputs. `$('Node').first()` works.
  Fixes: positional re-join in a Code node right after the chain (with a
  count-mismatch guard), or route all items through ONE switch output so the
  chain runs exactly once (fan-in from two outputs breaks pairing).
- Its error output (`onError: continueErrorOutput`) DOES carry the input
  item's json fields plus an `error` key.

## Chat widget embed (agent on a website)

n8n's chat widget can embed the agent on any site. chatTrigger config:

- `mode: "webhook"` and `responseMode: "lastNode"` for external embedding.
- CORS `allowedOrigins`: no trailing slashes, no spaces after commas.
- No structured output parser on the final agent (see above).

## Test and deploy

- E2E = activate + production webhook + poll executions: `awesomate_n8n_test`
verify end-to-end: `awesomate_n8n_executions {workflowId}` to find the run,
then `awesomate_n8n_executions {executionId, debug: true}` for node-by-node
detail.
  — `awesomate_n8n_deploy {action:"promote"}` preserves webhookIds.
- `saveExecutionProgress` stays FALSE (hub enforces; quadratic-write
  incident). Agent/tool loops around `splitInBatches` need an
  `executionTimeout`.
- Agent + chat workflows also run their published version — republish after
  edits, and republish referenced sub-workflows too.
