opencode-skills-collection 4.0.7 → 4.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/bundled-skills/.antigravity-install-manifest.json +19 -1
  2. package/bundled-skills/antigravity-maintainer-batch-release/SKILL.md +22 -8
  3. package/bundled-skills/antigravity-maintainer-batch-release/agents/openai.yaml +4 -0
  4. package/bundled-skills/anywrite/SKILL.md +18 -8
  5. package/bundled-skills/cloudflare-security-audit/SKILL.md +5 -3
  6. package/bundled-skills/cloudflare-security-audit/references/RECONNAISSANCE.md +2 -0
  7. package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
  8. package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
  9. package/bundled-skills/docs/maintainers/aas-agent-first-control-plane-preview-profile.md +2 -2
  10. package/bundled-skills/docs/maintainers/aas-agent-first-control-plane-v1-worklog.md +1 -1
  11. package/bundled-skills/docs/maintainers/merge-batch.md +3 -3
  12. package/bundled-skills/docs/maintainers/merging-prs.md +13 -17
  13. package/bundled-skills/docs/maintainers/pr-autonomy.md +2 -2
  14. package/bundled-skills/docs/maintainers/release-process.md +17 -2
  15. package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
  16. package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
  17. package/bundled-skills/docs/users/aas-core.md +38 -3
  18. package/bundled-skills/docs/users/bundles.md +1 -1
  19. package/bundled-skills/docs/users/claude-code-skills.md +1 -1
  20. package/bundled-skills/docs/users/faq.md +2 -0
  21. package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
  22. package/bundled-skills/docs/users/kiro-integration.md +1 -1
  23. package/bundled-skills/docs/users/usage.md +4 -4
  24. package/bundled-skills/docs/users/visual-guide.md +4 -4
  25. package/bundled-skills/docs/vietnamese/README.vi.md +4 -4
  26. package/bundled-skills/hf-cloud-aws-context-discovery/SKILL.md +96 -0
  27. package/bundled-skills/loki-mode/examples/todo-app-generated/backend/package-lock.json +3 -3
  28. package/bundled-skills/markstream-angular/SKILL.md +76 -0
  29. package/bundled-skills/markstream-custom-components/SKILL.md +74 -0
  30. package/bundled-skills/markstream-custom-components/references/patterns.md +14 -0
  31. package/bundled-skills/markstream-install/SKILL.md +3 -1
  32. package/bundled-skills/markstream-migration/SKILL.md +78 -0
  33. package/bundled-skills/markstream-migration/references/adoption-checklist.md +10 -0
  34. package/bundled-skills/markstream-nuxt/SKILL.md +71 -0
  35. package/bundled-skills/markstream-react/SKILL.md +74 -0
  36. package/bundled-skills/markstream-svelte/SKILL.md +68 -0
  37. package/bundled-skills/markstream-vue/SKILL.md +71 -0
  38. package/bundled-skills/markstream-vue2/SKILL.md +70 -0
  39. package/bundled-skills/markstream-vue2-cli/SKILL.md +70 -0
  40. package/bundled-skills/markstream-vue2-vite/SKILL.md +69 -0
  41. package/bundled-skills/n8n-agents/SKILL.md +301 -0
  42. package/bundled-skills/n8n-agents/references/CHAT_AGENT_PATTERNS.md +228 -0
  43. package/bundled-skills/n8n-agents/references/EXAMPLES.md +432 -0
  44. package/bundled-skills/n8n-agents/references/HUMAN_REVIEW.md +180 -0
  45. package/bundled-skills/n8n-agents/references/MEMORY.md +139 -0
  46. package/bundled-skills/n8n-agents/references/RAG.md +102 -0
  47. package/bundled-skills/n8n-agents/references/STRUCTURED_OUTPUT.md +163 -0
  48. package/bundled-skills/n8n-agents/references/SUBWORKFLOW_AS_TOOL.md +199 -0
  49. package/bundled-skills/n8n-agents/references/SYSTEM_PROMPT.md +151 -0
  50. package/bundled-skills/n8n-agents/references/TOOLS.md +199 -0
  51. package/bundled-skills/n8n-binary-and-data/SKILL.md +269 -0
  52. package/bundled-skills/n8n-binary-and-data/references/AGENT_TOOL_BINARY.md +227 -0
  53. package/bundled-skills/n8n-binary-and-data/references/BINARY_BASICS.md +187 -0
  54. package/bundled-skills/n8n-binary-and-data/references/CDN_REQUIREMENT.md +109 -0
  55. package/bundled-skills/n8n-binary-and-data/references/MERGE_FOR_CONTEXT.md +130 -0
  56. package/bundled-skills/n8n-code-tool/SKILL.md +358 -0
  57. package/bundled-skills/n8n-code-tool/references/ERROR_PATTERNS.md +178 -0
  58. package/bundled-skills/n8n-code-tool/references/INPUT_SCHEMA.md +132 -0
  59. package/bundled-skills/n8n-error-handling/SKILL.md +289 -0
  60. package/bundled-skills/n8n-error-handling/references/API_WORKFLOWS.md +256 -0
  61. package/bundled-skills/n8n-error-handling/references/ERROR_WORKFLOWS.md +178 -0
  62. package/bundled-skills/n8n-error-handling/references/NODE_ERROR_OUTPUTS.md +171 -0
  63. package/bundled-skills/n8n-error-handling/references/RESPONSE_SHAPES.md +220 -0
  64. package/bundled-skills/n8n-multi-instance/SKILL.md +208 -0
  65. package/bundled-skills/n8n-node-configuration/references/NODE_FAMILY_GOTCHAS.md +241 -0
  66. package/bundled-skills/n8n-subworkflows/SKILL.md +271 -0
  67. package/bundled-skills/n8n-subworkflows/references/NAMING_AND_DISCOVERY.md +130 -0
  68. package/bundled-skills/n8n-subworkflows/references/SUBWORKFLOW_PATTERNS.md +147 -0
  69. package/bundled-skills/pptx-deck-creation/SKILL.md +2 -0
  70. package/bundled-skills/pptx-deck-creation/references/design-profiles.md +2 -0
  71. package/bundled-skills/sshepherd/SKILL.md +16 -6
  72. package/bundled-skills/using-n8n-mcp-skills/SKILL.md +193 -0
  73. package/bundled-skills/weaviate-cookbooks/references/pdf_multimodal_rag.md +12 -6
  74. package/package.json +1 -1
  75. package/skills_index.json +596 -13
@@ -0,0 +1,301 @@
1
+ ---
2
+ name: n8n-agents
3
+ description: Design n8n AI agents, chains, classifiers, extractors, tool calling, memory, RAG, structured output, and human-review flows.
4
+ risk: critical
5
+ source: https://github.com/czlonkowski/n8n-skills/tree/main/skills/n8n-agents
6
+ source_repo: czlonkowski/n8n-skills
7
+ source_type: community
8
+ date_added: "2026-07-21"
9
+ author: Romuald Czlonkowski
10
+ license: MIT
11
+ license_source: https://github.com/czlonkowski/n8n-skills/blob/main/LICENSE
12
+ ---
13
+
14
+ # n8n Agents
15
+
16
+ ## When to Use
17
+
18
+ Use this skill for n8n AI Agent, LangChain, classifier, extractor, memory, RAG, tool-calling, structured-output, or human-review design. Confirm the target n8n instance and inspect the live node schema before applying version-sensitive configuration.
19
+
20
+ Before activating or testing a workflow that can send messages, write data, make purchases, change accounts, or call external services, show the user the exact effects and obtain approval. Store provider keys and tokens only in n8n credentials; never place them in prompts, Set nodes, workflow JSON, examples, or logs.
21
+
22
+ The n8n AI Agent node (`@n8n/n8n-nodes-langchain.agent`) is a multi-turn LLM driver with sub-nodes for the model, memory, tools, and an optional output parser. This skill is the **deep** guide to designing agents and the LangChain family around them. For the high-level "where an agent fits in a workflow" picture, see the **n8n-workflow-patterns** skill — this skill goes one level down into *how to build it well*.
23
+
24
+ For node-type formats: in workflow JSON the LangChain nodes use the long `@n8n/n8n-nodes-langchain.*` form (`.agent`, `.lmChatOpenAi`, `.memoryBufferWindow`, `.outputParserStructured`, `.toolWorkflow`, `.toolHttpRequest`, `.toolCode`). When you call `get_node` / `validate_node`, use the **short** form (`nodes-langchain.agent`). See **n8n-mcp-tools-expert** for the format rules.
25
+
26
+ ---
27
+
28
+ ## Pick the right node first
29
+
30
+ Reaching for an Agent when the task is one-shot classification or extraction is the most common over-build. Decide before you wire anything:
31
+
32
+ | You need to… | Use | Why |
33
+ |---|---|---|
34
+ | Call tools, reason over multiple turns, or hold memory | **AI Agent** (`.agent`) | The full loop: model + tools + memory + optional parser. Also a fine default when you'd rather standardize. |
35
+ | One-shot text in → text out, no tools | **Basic LLM Chain** (`.chainLlm`) | No agent loop, easier to debug. Still accepts an `outputParserStructured` sub-node. |
36
+ | Route a natural-language input to one of **N branches** | **Text Classifier** (`.textClassifier`) | ONE node, N output handles, downstream wires directly into each. Not Agent + Switch. |
37
+ | Pull structured fields out of free text | **Information Extractor** (`.informationExtractor`) | Purpose-built field extraction with a schema. |
38
+ | 3-way positive/neutral/negative split | **Sentiment Analysis** (`.sentimentAnalysis`) | Built-in branch outputs. |
39
+ | Condense a long document | **Summarization Chain** (`.chainSummarization`) | Map-reduce summarization built in. |
40
+ | Generate an image / audio / video | **The provider's native single-call node** (OpenAI, Gemini, ElevenLabs…) | NEVER wrap media generation in an Agent — see "Binary and the agent boundary". |
41
+
42
+ **Text Classifier detail (the Agent + Switch anti-pattern):** every category needs both a **name AND a description**. The model routes against the *description*, not the name — a category with no description gets picked by coin-flip. Set `options.enableAutoFixing: true` for robustness on edge inputs. One node, N branches, done. Reaching for an Agent that "decides" then a Switch that "routes" is two nodes plus prompt boilerplate for what Text Classifier does natively.
43
+
44
+ Chat-model nodes (`.lmChatOpenAi`, `.lmChatAnthropic`, `.lmChatOpenRouter`, …) are **sub-nodes** — they don't run standalone. They wire into a chain, agent, classifier, or extractor via the `ai_languageModel` connection.
45
+
46
+ ---
47
+
48
+ ## The sub-node pattern
49
+
50
+ The Agent has a **main input** (the prompt / user message) and up to four **sub-node slots**, each wired by its own `ai_*` connection type:
51
+
52
+ | Slot | Connection type | Required? | Node example |
53
+ |---|---|---|---|
54
+ | **model** | `ai_languageModel` | Yes | `.lmChatOpenAi`, `.lmChatAnthropic`, `.lmChatOpenRouter` |
55
+ | **memory** | `ai_memory` | Optional | `.memoryBufferWindow`, `.memoryPostgresChat` |
56
+ | **tools** | `ai_tool` | Optional (but the point of an agent) | `slackTool`, `.toolWorkflow`, `.toolHttpRequest`, `.toolCode` |
57
+ | **outputParser** | `ai_outputParser` | Optional | `.outputParserStructured` |
58
+
59
+ A sub-node connects FROM itself TO the agent. In workflow JSON the connection lives on the **sub-node**, keyed by the `ai_*` type:
60
+
61
+ ```json
62
+ "Main LLM": {
63
+ "ai_languageModel": [[{ "node": "AI Agent", "type": "ai_languageModel", "index": 0 }]]
64
+ },
65
+ "Simple Memory": {
66
+ "ai_memory": [[{ "node": "AI Agent", "type": "ai_memory", "index": 0 }]]
67
+ },
68
+ "Search customer DB": {
69
+ "ai_tool": [[{ "node": "AI Agent", "type": "ai_tool", "index": 0 }]]
70
+ }
71
+ ```
72
+
73
+ Multiple tools all connect into the same `ai_tool` index 0 — they stack, they don't fan into separate indices. With `n8n_update_partial_workflow` you wire each with an `addConnection` op using `sourceOutput: "ai_tool"`. The agent puts its final answer in **`$json.output`** (not `.text`, not `.response`) — downstream nodes read `{{ $json.output }}`.
74
+
75
+ See **references/EXAMPLES.md** for a complete stateless agent-core node-object snippet.
76
+
77
+ ---
78
+
79
+ ## Two non-negotiables
80
+
81
+ 1. **Tool names and descriptions ARE part of the prompt.** The model picks a tool by reading its name and description — nothing else. A tool named `tool1` with an empty description is invisible to the model: it skips it, mis-selects it, or hallucinates parameters. There's usually no error — just an agent that "won't use my tool". Treat both like API design. → **references/TOOLS.md**
82
+ 2. **Structured output must parse AND autoFix.** An `outputParserStructured` with `autoFix: true` and a **coding-capable fixer model** is the production pattern. Without autoFix, one malformed JSON response halts the whole workflow. → **references/STRUCTURED_OUTPUT.md**
83
+
84
+ ---
85
+
86
+ ## Strong defaults
87
+
88
+ - **Per-tool usage goes in the tool description, not the system prompt.** Anything about *how to call this specific tool* belongs with the tool, so it travels across agents and keeps the system prompt focused. → **references/SYSTEM_PROMPT.md**
89
+ - **Sub-workflow tools (`.toolWorkflow`) for anything multi-step.** Any workflow becomes a tool with typed `$fromAI()` inputs, and composes with branching, error handling, and reuse. Default here when in doubt. → **references/SUBWORKFLOW_AS_TOOL.md** and **n8n-subworkflows**.
90
+ - **Wrap tools with user-visible side effects in human review.** Sends, payments, refunds, account changes get gated behind an approval node so a human signs off before the tool fires. → **references/HUMAN_REVIEW.md**
91
+ - **Raise `maxIterations`.** The default tool-call cap is **low** (single digits on most versions) — fine for a one-tool agent, far too low for a multi-tool agent that chains several calls per turn. It surfaces as "max iterations reached" or empty output. Set `options.maxIterations` to a realistic ceiling (15 for a focused sub-agent, 50-200 for a broad orchestrator).
92
+ - **Put the current date in the system prompt** via `{{ $now }}` (or `{{ $now.format('DDDD') }}`). A hardcoded date is stale immediately.
93
+
94
+ ---
95
+
96
+ ## The four tool types
97
+
98
+ Pick the lightest option that covers the job:
99
+
100
+ | Tool type | Node | Use when |
101
+ |---|---|---|
102
+ | **Native tool node** | `slackTool`, `gmailTool`, `toolCalculator`, … | The capability maps to one existing node + one operation. Lowest overhead. |
103
+ | **Sub-workflow as tool** | `.toolWorkflow` | More than one node, reusable logic, or you want independent testability. The canonical n8n way — **default when in doubt**. |
104
+ | **HTTP Request Tool** | `.toolHttpRequest` | A single external HTTP API the agent should orchestrate directly. Reuse the service's predefined credential to cover operations a native node doesn't expose. |
105
+ | **MCP Client Tool** | `.mcpClientTool` | A maintained MCP server already covers it, or you want one published workflow to serve many agents. |
106
+
107
+ There is also a **Custom Code Tool** (`.toolCode`) for pure inline computation — but its runtime contract (string in / string out, no `$fromAI`, no `$helpers`) is owned by the **n8n-code-tool** skill. Read that before writing one. Rule of thumb: if you find yourself reaching for `$fromAI()` inside the code, you want `.toolWorkflow` instead.
108
+
109
+ ### `$fromAI()`: how the agent fills tool parameters
110
+
111
+ Tool parameters the agent should decide are wrapped in `$fromAI()`. It is a **real n8n expression helper**, used inside a tool node's parameter expressions:
112
+
113
+ ```
114
+ ={{ $fromAI('paramName', 'what to put here — be specific: format, range, example', 'string') }}
115
+ ```
116
+
117
+ - **paramName** — the name the model uses internally (snake_case or camelCase, be consistent).
118
+ - **description** — tells the model what value to produce. **It is part of the prompt** — write it like JSDoc.
119
+ - **type** (optional) — `'string'` (default), `'number'`, `'boolean'`, `'json'`. A wrong-typed value fails the call.
120
+ - **defaultValue** (optional) — used when the model omits it.
121
+
122
+ `$fromAI()` carries JSON only — it **cannot carry binary** (no base64, no file bytes). And not every parameter has to be `$fromAI`: plumb identity, authority limits, and correlation IDs (`userId`, refund caps, `sessionId`) deterministically from workflow context so the agent can't get them wrong or even see them. → **references/TOOLS.md** for the full anatomy and the "give the agent a button, not a steering wheel" pattern.
123
+
124
+ ---
125
+
126
+ ## System prompt vs tool description
127
+
128
+ | Belongs in the **system prompt** | Belongs in the **tool's description** |
129
+ |---|---|
130
+ | Persona, role, voice | What this specific tool does |
131
+ | Global output/format rules ("respond in markdown") | When to use it vs other tools |
132
+ | Refusal / safety behavior | What each parameter means and its shape |
133
+ | Display protocols (`![]()` for images) | Examples of good vs bad invocations |
134
+ | Universal context (current date via `$now`, user role) | Tool-specific gotchas (rate limits, edge cases) |
135
+ | Inter-tool flow ("after generating, always display") | Tool-specific input transformations |
136
+
137
+ Why split it: a well-described tool works in **any** agent that drops it in, tool details only "load" when the model considers that tool (token efficiency), and you update one tool description instead of a paragraph buried in a 5000-token prompt. → **references/SYSTEM_PROMPT.md**
138
+
139
+ ---
140
+
141
+ ## Structured output: when and how
142
+
143
+ Add an `outputParserStructured` sub-node (wired `ai_outputParser`) when downstream needs strict JSON, not free-form text. Two rules:
144
+
145
+ 1. **Use `schemaType: 'manual'` with a real JSON Schema, not `jsonSchemaExample`.** An example can't express required-vs-optional, enums, numeric ranges, or array constraints — you outgrow it the first time the shape gets non-trivial. Reach for `fromJson` + an example only for throwaway shapes.
146
+ 2. **`autoFix: true` with a coding-capable fixer model.** Wire a *second* model into the parser's `ai_languageModel` slot. Reconciling broken JSON against a schema is a coding task — a weak fixer just produces another malformed retry and burns tokens.
147
+
148
+ → **references/STRUCTURED_OUTPUT.md** for the schema patterns, the load-bearing "DO NOT wrap in markdown" retry line, and the parse-failure cookbook.
149
+
150
+ ---
151
+
152
+ ## Memory: brief mental model
153
+
154
+ Memory is a sub-node (`ai_memory`). Without it, every call is stateless — correct for one-shot tasks (classify, summarize). With it, the agent holds a conversation, keyed by whatever expression you bind to `sessionKey`.
155
+
156
+ - **`memoryBufferWindow`** — keeps the last N exchanges per key and persists across executions via n8n's store. The default for chat. **`contextWindowLength` defaults to 5, which is very low** — 50 is a saner starting point. Messages past the window are gone entirely.
157
+ - **`memoryPostgresChat` / `memoryRedisChat`** — only when memory must be read *outside* the agent (your own UI, analytics, cross-system). Not needed just to survive restarts; BufferWindow already does that.
158
+
159
+ **Plumb a stable key from the trigger to memory consistently.** Chat triggers fill `sessionId` automatically; for other surfaces derive one (Slack `thread_ts`, a webhook conversation ID). Never hardcode `sessionId: 'default'` and never put `sessionId` behind `$fromAI` (the model will fabricate a UUID). → **references/MEMORY.md**
160
+
161
+ ---
162
+
163
+ ## Binary and the agent boundary
164
+
165
+ This is the seam that trips people up:
166
+
167
+ - **The model CAN see uploaded images** (vision) via `options.passthroughBinaryImages: true` on the agent.
168
+ - **Tools CANNOT receive binary.** `$fromAI()` is JSON-only — no base64, no bytes, even through non-AI bindings.
169
+ - **The agent's output is text-shaped** (or structured-text with a parser). When a model returns image/audio/video bytes, the Agent doesn't surface them at all — there's nothing to recover downstream.
170
+
171
+ **Workaround:** pre-stage uploads to storage before the agent runs, inject the storage keys into the system prompt, and let tools accept the key as a string parameter and re-fetch internally. For one-shot media generation, skip the agent and call the provider's native single-call node directly.
172
+
173
+ The binary mechanics (which storage, how to stage, how to re-fetch) are owned by **n8n-binary-and-data** — see its agent-tool binary reference. This skill only marks the boundary; don't re-derive the mechanics here.
174
+
175
+ ---
176
+
177
+ ## Human review (gate destructive tools)
178
+
179
+ When a tool's effect needs human sign-off before execution (sends, payments, refunds, account changes), wrap it with a review tool node — `slackHitlTool`, `discordHitlTool`, `telegramHitlTool`, `gmailHitlTool`, etc. (n8n names these "Hitl" / human-in-the-loop). The review node sits **between** the wrapped tool and the agent on the `ai_tool` connection: wrapped tool → review node → Agent.
180
+
181
+ Whether sign-off is needed is a product/policy call — **surface the question to the user**, recommend based on blast radius, and let them decide.
182
+
183
+ **The critical rule: show the actual parameters the wrapped tool will receive.** Use the literal `{{ $tool.parameters.<name> }}` in the approval message, never a `$fromAI()` paraphrase — otherwise the human approves text the model made up, not the call about to fire. → **references/HUMAN_REVIEW.md**
184
+
185
+ ---
186
+
187
+ ## Chat agents (Slack, Discord, Teams, Telegram)
188
+
189
+ **The one non-negotiable, regardless of complexity:** any chat-triggered workflow that posts a reply MUST **filter out the bot's own user ID**, or its own replies re-trigger it in an infinite loop that burns runs and tokens. Prefer trigger-level filtering when available (Slack Trigger's `options.userIds` is an **exclusion list** — put the bot ID there); otherwise filter `$json.user !== '<BOT_USER_ID>'` in the first node after the trigger.
190
+
191
+ Beyond the filter, a simple bot (trigger → agent → reply) lives fine in one workflow. Split into **shell + core + sub-agents** only once you need loading UX, sub-agents, multi-surface reuse, or robust error handling:
192
+
193
+ - **Shell** — trigger, anti-loop filter, event-type Switch, loading/error UX, renders the reply. No LLM.
194
+ - **Core** — stateless agent, `chatInput` + `threadId` inputs, memory keyed on `threadId`, tools and sub-agents.
195
+ - **Sub-agents** — one narrow domain each, called via `.toolWorkflow`, **stateless** (full context in `chatInput`).
196
+
197
+ → **references/CHAT_AGENT_PATTERNS.md** for per-surface semantics, threading-as-session, and the full topology.
198
+
199
+ ---
200
+
201
+ ## RAG (retrieval augmented generation)
202
+
203
+ n8n ships the LangChain RAG primitives (document loaders, splitters, embeddings, vector stores, retrievers). Two opinions worth stating up front:
204
+
205
+ 1. **Rule out cheaper lookups first.** Exact lookups → a database or Data Table query, not RAG. Freshness → a live search tool. A small/structured doc set → give the agent list/fetch tools. Reach for a vector store only when there are too many docs to list and queries are semantic.
206
+ 2. **Wire the vector store as a retrieval tool** (`mode: 'retrieve-as-tool'`, `ai_tool`) so the agent decides when retrieval is relevant and can phrase the query itself. Embed query and documents with the **same** model.
207
+
208
+ → **references/RAG.md** (intentionally thin — defaults depend on data shape and scale).
209
+
210
+ ---
211
+
212
+ ## Reference files
213
+
214
+ | File | Read when |
215
+ |---|---|
216
+ | **references/TOOLS.md** | Adding tools, choosing among the four types, writing names/descriptions, `$fromAI` anatomy |
217
+ | **references/SUBWORKFLOW_AS_TOOL.md** | Wiring a sub-workflow as a tool via `.toolWorkflow`, mapping agent-filled vs plumbed params |
218
+ | **references/SYSTEM_PROMPT.md** | Writing/refactoring a system prompt, the system-prompt-vs-tool-description split |
219
+ | **references/STRUCTURED_OUTPUT.md** | Forcing JSON output, configuring autoFix, the fixer model, parse-failure fixes |
220
+ | **references/MEMORY.md** | Choosing a memory type, persistence, sessionId handling |
221
+ | **references/HUMAN_REVIEW.md** | Adding human approval, approval-message content, multi-channel approver |
222
+ | **references/CHAT_AGENT_PATTERNS.md** | Building a Slack/Discord/Teams/Telegram bot, shell + core + sub-agents topology |
223
+ | **references/RAG.md** | Retrieval-augmented agents (thin by design) |
224
+ | **references/EXAMPLES.md** | Concrete node-object snippets: stateless agent core, Slack router shell, domain sub-agent |
225
+
226
+ ---
227
+
228
+ ## Anti-patterns
229
+
230
+ | Anti-pattern | What goes wrong | Fix |
231
+ |---|---|---|
232
+ | Generic tool names (`tool1`, `doStuff`, `runQuery`) | Model can't tell which tool to pick — skips them or hallucinates params | Verb-first specific names: `Search customer database`, `Generate image with Veo` |
233
+ | Empty or one-line tool descriptions | Model has no idea when to invoke; bad selection, no error | Write a real description: what it does, when to use, what each param means |
234
+ | Cramming per-tool instructions into the system prompt | Bloated prompt, no reuse, per-tool guidance buried | Move tool-specific instructions into tool descriptions |
235
+ | Agent + Switch to route on natural language | Two nodes + prompt boilerplate where Text Classifier is one node | Use Text Classifier — each category gets its own output handle (name **and** description) |
236
+ | Wrapping image/audio/video generation in an Agent | Binary doesn't flow through tools or out of the agent output | Use the provider's native single-call node directly |
237
+ | `outputParserStructured` without `autoFix` | One malformed response halts the workflow | `autoFix: true` + a coding-capable fixer model |
238
+ | Passing binary directly to a tool | Doesn't work — binary can't cross the tool boundary | Pre-stage to storage, pass keys; see **n8n-binary-and-data** |
239
+ | Hardcoded `sessionId` / no sessionId / `sessionId` behind `$fromAI` | Conversations cross, or the model fabricates a UUID | Plumb a stable key from the trigger to memory and tools |
240
+ | Two near-identical tools | Selection is non-deterministic, model gets confused | One tool with internal branching driven by a parameter |
241
+ | Chat bot with no bot-user filter | Its own replies re-trigger it → infinite loop | Exclude the bot user ID at the trigger or first node |
242
+ | `maxIterations` left at the low default on a multi-tool agent | "Max iterations reached" / empty output | Raise `options.maxIterations` |
243
+ | Filling the human-review message via `$fromAI()` | Approver signs off on a paraphrase, not the real call | Use literal `{{ $tool.parameters.<name> }}` |
244
+
245
+ ---
246
+
247
+ ## What's NOT available via the community MCP
248
+
249
+ | Want to do | Reality |
250
+ |---|---|
251
+ | Run / chat-test the agent end-to-end with live tokens | `n8n_test_workflow` runs the workflow, but a true multi-turn chat session is a UI activity (canvas chat tester). |
252
+ | Set credentials' actual secret values | `n8n_manage_credentials` creates/updates credential records, but the agent provider keys themselves are entered/verified in the UI. |
253
+ | Assign a workflow's Error Workflow | UI only — see **n8n-error-handling**. Build the catch-all, then hand the user the UI step. |
254
+ | Pin the exact model availability per instance | Model lists shift between versions — `search_nodes`/`get_node` reflect what's installed. Verify on the target instance. |
255
+
256
+ What the MCP **can** do: search and inspect every LangChain node (`search_nodes`, `get_node`), validate node config and the whole graph (`validate_node`, `validate_workflow`), build and patch the agent and its sub-nodes (`n8n_update_partial_workflow` with `addConnection` on `ai_*` outputs), test (`n8n_test_workflow`), and pull the saved JSON to verify wiring (`n8n_get_workflow`). The deep AI-agent guide also lives in `tools_documentation({topic: "ai_agents_guide", depth: "full"})`.
257
+
258
+ ---
259
+
260
+ ## Integration with other skills
261
+
262
+ - **n8n-workflow-patterns** — the high-level "agent in a workflow" shape. This skill is the deep dive; start there for architecture.
263
+ - **n8n-mcp-tools-expert** — node-type formats (short form for `get_node`, long form in JSON) and tool-selection guidance. Consult before any MCP call.
264
+ - **n8n-node-configuration** — `displayOptions`-driven fields on the agent and sub-nodes; Slack/Block Kit message shapes (`NODE_FAMILY_GOTCHAS.md`, Slack section).
265
+ - **n8n-expression-syntax** — `{{ }}`, `$json.output`, `$now`, and `$fromAI`/`$tool.parameters` all rely on correct expression syntax.
266
+ - **n8n-code-tool** — the Custom Code Tool's runtime contract (string in/out, no `$fromAI`). Read it before writing a `.toolCode`.
267
+ - **n8n-subworkflows** — the sub-workflow primitive that `.toolWorkflow` builds on (Execute Workflow Trigger inputs/outputs, naming, search-before-build).
268
+ - **n8n-binary-and-data** — owns the agent-tool binary boundary mechanics (staging uploads, returning generated files).
269
+ - **n8n-validation-expert** — interpreting `validate_workflow` results, including AI-connection issues (a tool wired into `main` instead of `ai_tool` flags as disconnected).
270
+ - **n8n-error-handling** — `onError: 'continueErrorOutput'` on tool sub-workflows and the agent-core call; error UX on chat shells.
271
+ - **n8n-code-javascript / n8n-code-python** — for Code-node logic *inside* a tool sub-workflow (different sandbox from the Code Tool).
272
+
273
+ ---
274
+
275
+ ## Quick reference checklist
276
+
277
+ Before shipping an agent:
278
+
279
+ - [ ] **Right node**: Agent for tools/memory/multi-turn; Text Classifier for routing; Information Extractor for fields; native node for media
280
+ - [ ] **Model** wired via `ai_languageModel`
281
+ - [ ] **Every tool** has a verb-first specific name AND a real description
282
+ - [ ] **`$fromAI()` descriptions** are specific (format, range, example); identity/limits/sessionId plumbed deterministically, not via `$fromAI`
283
+ - [ ] **Per-tool guidance** lives in tool descriptions, not the system prompt
284
+ - [ ] **`$now`** in the system prompt (no hardcoded date)
285
+ - [ ] **`maxIterations`** raised for multi-tool agents
286
+ - [ ] **Memory** keyed on a stable `sessionKey` from the trigger (not `'default'`, not `$fromAI`); `contextWindowLength` raised from 5
287
+ - [ ] **Structured output**: `schemaType: 'manual'` + `autoFix: true` + a coding-capable fixer model
288
+ - [ ] **Destructive tools** wrapped in human review; approval message uses `$tool.parameters`, not `$fromAI`
289
+ - [ ] **Chat bots** filter the bot's own user ID (trigger-level or first node)
290
+ - [ ] **Binary**: model vision via `passthroughBinaryImages`; tools get storage keys, never bytes
291
+ - [ ] **Validated** with `validate_workflow` and verified with `n8n_get_workflow` (sub-nodes on `ai_*`, not `main`)
292
+
293
+ ---
294
+
295
+ **Remember**: an agent is only as good as its tool names, descriptions, and system-prompt discipline. The model can't see your wiring — it sees a system prompt and a list of named, described tools. Design those like an API and most "the agent won't behave" problems disappear.
296
+
297
+ ## Limitations
298
+
299
+ - Node types, parameters, model availability, and defaults vary by n8n version; verify them against the target instance.
300
+ - This guidance cannot set provider secret values or prove a live multi-turn agent works without an authorized execution.
301
+ - Validation does not prove tool selection quality, correct wiring, idempotency, or safe side effects; inspect and test those separately.
@@ -0,0 +1,228 @@
1
+ # Chat agent patterns: shell + core + sub-agents
2
+
3
+ For external chat surfaces — Slack, Discord, Microsoft Teams, Telegram, embedded webhook chats. The building blocks (memory, tools, sub-workflow-as-tool, structured output) live in their own references; this file covers the **multi-workflow composition** production chat agents grow into, plus chat-surface gotchas the other refs don't.
4
+
5
+ ---
6
+
7
+ ## The one non-negotiable: anti-loop filtering
8
+
9
+ **Any chat-triggered workflow that posts a reply MUST filter out the bot's own user ID right after the trigger, or it triggers itself forever** — every reply fires another run, until rate limits or n8n concurrency stop it (and it can take n8n down with it). That's the minimum bar for **every** bot, simple or complex.
10
+
11
+ **Prefer trigger-level filtering when the trigger supports it** — the loop then breaks before any downstream node runs. Semantics differ per surface; verify against your version:
12
+
13
+ - **Slack** (`n8n-nodes-base.slackTrigger`): `options.userIds` is an **exclusion list** — listed users are dropped before the workflow runs. Put the bot's user ID here. (Verified in the trigger source: it returns early `if (userIds.includes(event.user))`.)
14
+ - **Telegram** (`n8n-nodes-base.telegramTrigger`): `additionalFields.userIds` is an **inclusion / allowlist** (only listed users fire). NOT a bot-exclusion filter — and Telegram bots don't see their own messages by default, so anti-loop usually isn't needed. Use the allowlist to restrict a private bot to specific humans.
15
+ - **Discord, Teams**: no native user-level trigger filter — use the downstream Filter node.
16
+
17
+ Slack trigger-level example:
18
+
19
+ ```json
20
+ {
21
+ "parameters": {
22
+ "trigger": ["message"],
23
+ "channelId": { "__rl": true, "mode": "list", "value": "<CHANNEL_ID>" },
24
+ "options": { "userIds": "={{ [\"<BOT_USER_ID>\"] }}" }
25
+ },
26
+ "type": "n8n-nodes-base.slackTrigger"
27
+ }
28
+ ```
29
+
30
+ When the trigger doesn't expose a usable exclusion filter, the first node after the trigger must drop the bot's own ID:
31
+
32
+ ```json
33
+ {
34
+ "parameters": {
35
+ "conditions": {
36
+ "conditions": [
37
+ {
38
+ "leftValue": "={{ $json.user }}",
39
+ "rightValue": "<BOT_USER_ID>",
40
+ "operator": { "type": "string", "operation": "notEquals" }
41
+ }
42
+ ]
43
+ }
44
+ },
45
+ "type": "n8n-nodes-base.filter"
46
+ }
47
+ ```
48
+
49
+ The bot user ID is the API ID from your bot's auth (Slack `bot_user_id`, Discord application ID, Teams `botId`).
50
+
51
+ ---
52
+
53
+ ## When to split into shell + core + sub-agents
54
+
55
+ Beyond the anti-loop filter, a **simple bot (one trigger → one agent → one reply, with the filter)** lives fine in a single workflow. The shell + core + sub-agents split is for production robustness — it earns its keep once any of these is true:
56
+
57
+ - The bot needs loading-state UX (typing indicator, reaction, placeholder) and graceful error handling beyond a single message.
58
+ - It's invoked from more than one surface (Slack AND Discord).
59
+ - There are specialist domains the agent shouldn't carry inline (Notion DB schema, CRM custom fields, Linear labels).
60
+ - The agent or its tools will be reused across workflows.
61
+
62
+ If none apply, keep it in one workflow (filter still in place). The shape when you do split:
63
+
64
+ ```
65
+ [chat-surface workflow] ──► [agent core workflow] ──► [sub-agent workflows]
66
+ ("the shell") ("the brain") ("specialists")
67
+
68
+ - Trigger from the surface - Stateless - One narrow domain each
69
+ - Anti-loop filter - chatInput + threadId - chatInput only
70
+ - Routing / event types - Memory keyed on threadId - Their own tools + model
71
+ - Loading + error UX - Tools, sub-agents
72
+ - Render the reply - No surface concerns
73
+ ```
74
+
75
+ See **EXAMPLES.md** for a Slack router shell and a domain sub-agent snippet.
76
+
77
+ ---
78
+
79
+ ## The shell
80
+
81
+ Receives chat events, decides whether to respond, manages UX, calls the core, renders the reply. No reasoning, no LLM.
82
+
83
+ ### Switch on event type
84
+
85
+ The same trigger fires for messages, reactions, mentions, slash commands, button clicks. One Switch right after the anti-loop filter routes each to the right handler:
86
+
87
+ ```
88
+ "owner message" → Execute Workflow: agent-core
89
+ "owner reaction" → no-op (or a reaction handler)
90
+ "unknown user" → canned reply
91
+ "slash command: /summary" → Execute Workflow: summary-command
92
+ "button click" → Execute Workflow: interaction-handler
93
+ ```
94
+
95
+ Each case is its own sub-workflow because the routing decision and the work are different concerns (different models, timeouts, memory shapes). Adding a slash command means one Switch output + one sub-workflow, not a new top-level trigger.
96
+
97
+ Slack-specific notes (payload shapes evolve — verify against a live event before hardcoding paths): reactions/mentions flow through the Slack Trigger as Events API events; **slash commands and Block Kit button clicks generally don't** (Slack delivers those to separate Request URLs). Bring them in via a second Webhook node feeding the same Switch, or a community Socket Mode node. Slash commands expose a `command` field; Block Kit interactions arrive with `type === 'block_actions'` and an `actions` array.
98
+
99
+ ### Loading-state UX
100
+
101
+ Users assume nothing is happening without acknowledgement. Pattern: **add a loading indicator before the agent call, remove it on every exit path — including error.**
102
+
103
+ ```
104
+ [Trigger] → [Filter bot] → [Switch]
105
+ → (owner message)
106
+ → [Add loading reaction] (:spinner:, etc.)
107
+ → [Execute Workflow: Agent core] onError: 'continueErrorOutput'
108
+ ├── (success) → [Remove reaction] → [Send reply]
109
+ └── (error) → [Remove reaction] → [Send error message with link]
110
+ ```
111
+
112
+ The error path is the easy one to forget — without it the indicator sits forever and the user thinks the bot is still working. `onError: 'continueErrorOutput'` on the Execute Workflow node enables the second branch (→ **n8n-error-handling**). For Discord/Telegram, typing indicators are time-bounded; for long agents send a placeholder message and edit it.
113
+
114
+ ### Threading as session continuity
115
+
116
+ Use the surface's thread primitive as the memory `sessionKey`:
117
+
118
+ ```json
119
+ "workflowInputs": {
120
+ "value": {
121
+ "chatInput": "={{ $('Filter bot').item.json.text }}",
122
+ "threadId": "={{ $('Filter bot').item.json.thread_ts || $('Filter bot').item.json.ts }}"
123
+ }
124
+ }
125
+ ```
126
+
127
+ `thread_ts || ts` is the canonical Slack idiom: replies in a thread carry `thread_ts` (referencing the parent), the parent itself only has `ts`. Falling back to `ts` makes the parent message the session key for its thread, so each thread is a fresh conversation and memory doesn't leak across threads. **User ID, channel ID, or workspace ID alone are wrong — they cross conversations.** When sending the reply, target the same thread (`otherOptions.thread_ts.replyValues.thread_ts` = the same `thread_ts || ts`).
128
+
129
+ ### Error UX: surface, don't hang
130
+
131
+ The error branch sends a short message with a link to the failed execution:
132
+
133
+ ```
134
+ There was a workflow error. https://<n8n-host>/workflow/<id>/executions/{{ $execution.id }}
135
+ ```
136
+
137
+ `$execution.id` is the live execution ID at the time the error fires. Parameterize the host across environments.
138
+
139
+ ---
140
+
141
+ ## The agent core
142
+
143
+ A sub-workflow with two declared inputs: `chatInput` (the user's message) and `threadId` (the surface's thread/session ID). Returns the agent's final output — a string, a structured object, or a surface-specific envelope (Block Kit, adaptive card).
144
+
145
+ The only chat-specific wiring beyond **MEMORY.md** is plumbing `threadId` straight to `sessionKey`:
146
+
147
+ ```json
148
+ "sessionIdType": "customKey",
149
+ "sessionKey": "={{ $json.threadId }}"
150
+ ```
151
+
152
+ `threadId` flows trigger → (pass-through nodes) → memory. Don't put it behind `$fromAI`.
153
+
154
+ Per-execution context (user identity, attached files) goes in a Set node before the agent and gets templated into the system prompt (→ **SYSTEM_PROMPT.md** "file-handling injection" and "piecing"). Don't add a Set node speculatively — inline in `systemMessage` is fine until reuse is real.
155
+
156
+ **Block Kit / adaptive cards: pair the agent with `outputParserStructured`** (→ **STRUCTURED_OUTPUT.md**). The "use `schemaType: 'manual'` with a real JSON Schema" guidance applies even harder here: Block Kit and adaptive cards lean on `oneOf` union types across block kinds plus per-block enums (`style`, etc.) — `jsonSchemaExample` can't express any of it, and will produce confidently-wrong block trees the surface rejects.
157
+
158
+ ### Block Kit envelope gotcha (Slack)
159
+
160
+ When the agent returns Block Kit and you post it via the Slack node's `blocksUi`, the value must be an object shaped `{ "blocks": [...] }` where the value is a **real array**, not the array alone and not a stringified one:
161
+
162
+ ```
163
+ ✅ ={{ { "blocks": $('Call Agent core').item.json.output.blocks } }}
164
+ ❌ ={{ $('Call Agent core').item.json.output.blocks }}
165
+ ```
166
+
167
+ Passing only the array fails **silently** — the Slack node accepts the input, the message posts with no rich content, and there's no error or warning. → **n8n-node-configuration** `NODE_FAMILY_GOTCHAS.md` (Slack section).
168
+
169
+ ---
170
+
171
+ ## Sub-agents (an agent as a tool)
172
+
173
+ A sub-agent is its own workflow with its own Agent node, called from the router agent via `.toolWorkflow`. Reach for one when:
174
+
175
+ - The domain has a schema/enum set the router shouldn't carry (Notion DB properties, Linear labels, CRM fields).
176
+ - The domain has 5+ tools that would clutter the router's tool list.
177
+ - The capability is reused across more than one router.
178
+ - The domain warrants a different (cheaper, faster) model than the router.
179
+
180
+ **The contract is stateless.** The router sends the full request in `chatInput` — no shared memory, no implicit context. Reinforce it in both the tool description (router-side) AND the sub-agent's system prompt (callee-side):
181
+
182
+ > IMPORTANT: This tool is stateless. Send all relevant context in a single message. If you need to create an entry, include ALL required fields upfront.
183
+
184
+ Without that, the router assumes implicit context and the sub-agent guesses. Everything else about wiring sub-workflows as tools → **SUBWORKFLOW_AS_TOOL.md**.
185
+
186
+ ### Fresh schema injection
187
+
188
+ When the domain schema can change at runtime (Notion DB options evolve, Linear teams add labels), refetch it on every sub-agent call instead of hardcoding it:
189
+
190
+ ```
191
+ [Execute Workflow Trigger]
192
+
193
+ [Notion: Get Database] # fetches the live schema
194
+
195
+ [Agent] system prompt template includes:
196
+ ## Database Schema
197
+ {{ $('Get a database').first().json.properties.toJsonString() }}
198
+ ```
199
+
200
+ One extra API call per invocation; in exchange the sub-agent never returns "that property doesn't exist" because the prompt is stale. Worth it for low-volume chat assistants. For high-volume hot paths, cache the schema in a Data Table with a TTL.
201
+
202
+ ---
203
+
204
+ ## Anti-patterns
205
+
206
+ | Anti-pattern | What goes wrong | Fix |
207
+ |---|---|---|
208
+ | No bot-user-ID filter at the top of the shell | Bot's own messages re-trigger the workflow — infinite loop | Trigger-level exclusion (Slack `options.userIds`) or a Filter on `$json.user !== '<BOT_USER_ID>'` first |
209
+ | Bot ID in Telegram's `userIds` expecting exclusion | It's an **allowlist** — only the bot would fire, so no human gets through; looks "fixed" but is silent | Telegram bots don't see their own messages; use `userIds` only to allowlist humans |
210
+ | Loading indicator removed only on success | User sees the bot stuck "thinking" forever after any error | `onError: 'continueErrorOutput'` + remove on both branches |
211
+ | User/channel/workspace ID as the session key | Conversations cross threads in the same channel | Use the thread primitive (Slack `thread_ts || ts`) |
212
+ | One workflow when multi-surface/sub-agent/reuse is already needed | Can't reuse, UX leaks into reasoning, hard to test in isolation | Split into shell + core + sub-agents (only once a need is real) |
213
+ | Sub-agent that reads/writes shared memory | Caller can't reason about behavior, not safely retryable | Sub-agents are stateless — full context in `chatInput` |
214
+ | Hardcoded domain schema in a sub-agent's prompt | Schema rots, sub-agent picks invalid options later | Re-fetch and template it at runtime |
215
+ | Passing the bare blocks array to `blocksUi` | Slack posts an empty message, no error | Wrap as `{ "blocks": [...] }` with a real array |
216
+
217
+ ---
218
+
219
+ ## Cross-references
220
+
221
+ - Tool naming, descriptions, `$fromAI` → **TOOLS.md**
222
+ - The `.toolWorkflow` shape and parameter mapping → **SUBWORKFLOW_AS_TOOL.md**
223
+ - Per-execution context, file injection, prompt storage → **SYSTEM_PROMPT.md**
224
+ - Parser config, autoFix, fixer model → **STRUCTURED_OUTPUT.md**
225
+ - Memory types, `sessionKey` persistence → **MEMORY.md**
226
+ - `onError: 'continueErrorOutput'` and error UX → **n8n-error-handling**
227
+ - Slack node parameter shapes (Block Kit) → **n8n-node-configuration** `NODE_FAMILY_GOTCHAS.md` (Slack section)
228
+ - Receiving uploaded files / returning generated files per surface → **n8n-binary-and-data**