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,151 @@
1
+ # System prompts
2
+
3
+ The system prompt is the load-bearing config of an agent. Most "the agent isn't doing what I want" problems trace back to a system prompt that's too long, too vague, or mixing concerns.
4
+
5
+ This file is opinionated: keep system prompts on **persona and global behavior**, push tool-specific instructions into tool descriptions, and iterate. The system prompt goes in `options.systemMessage` on the agent node.
6
+
7
+ ---
8
+
9
+ ## What the system prompt is for
10
+
11
+ 1. **Persona / role.** Who, scope, tone.
12
+ 2. **Global output rules.** Format conventions, display protocols (e.g. "show images via `![]()` markdown"), language.
13
+ 3. **Refusal and safety behavior.** What the agent should NOT do — prefer specific bounds over generic boilerplate.
14
+ 4. **Universal context.** Current date, user's name/role, company/product context.
15
+ 5. **Inter-tool flow rules.** "After generating, always show via the display protocol", "confirm before destructive operations" — things that touch multiple tools.
16
+ 6. **File-handling injection.** When chat includes uploaded files, inject the storage keys so the agent can reference them in tool calls (mechanics → **n8n-binary-and-data**).
17
+
18
+ What it is NOT for: per-tool usage instructions. Those go in the tool's description.
19
+
20
+ ---
21
+
22
+ ## Always include the current date
23
+
24
+ A hardcoded date is stale immediately. Inject it at runtime:
25
+
26
+ ```
27
+ Current date: {{ $now }}
28
+ ```
29
+
30
+ or formatted:
31
+
32
+ ```
33
+ The current time is {{ $now.format('DDDD TTTT') }}
34
+ ```
35
+
36
+ ---
37
+
38
+ ## The modular split
39
+
40
+ ```
41
+ System prompt → Persona, global behavior, format rules, file handling
42
+ Tool description → How to use THIS tool, its parameters, when to pick it over others
43
+ $fromAI desc. → What value to put in this specific parameter
44
+ ```
45
+
46
+ Why this split:
47
+
48
+ - **Reuse.** A well-described tool works in any agent; the system prompt doesn't re-teach it.
49
+ - **Token efficiency.** Tool details only "load" when the model considers that tool. Per-tool text in the system prompt burns tokens every turn.
50
+ - **Maintainability.** Update one tool description, not a paragraph buried in a 5000-token prompt.
51
+
52
+ ### What to move where
53
+
54
+ | Was in the system prompt | Better location |
55
+ |---|---|
56
+ | "When using Generate Image, prefer realistic photography over `8k cinematic`" | `Generate Image` tool description |
57
+ | "When the user uploads an image and asks for background changes, edit it, don't generate new" | `Edit Image` tool description (and a "do not use" boundary on `Generate Image`) |
58
+ | "Use 9:16 aspect ratio for video tools" | `Generate Video` tool description |
59
+ | "Respond with markdown image embeds: `![alt](url)`" | **System prompt** (global display rule) |
60
+ | "Refuse to generate images of real people without consent" | **System prompt** (global safety) |
61
+ | "Today is 2026-04-25" | **System prompt** as `{{ $now }}` (universal context, computed) |
62
+
63
+ The first three move out; the last three stay in.
64
+
65
+ ---
66
+
67
+ ## Storing the prompt
68
+
69
+ Inline (typed directly into `systemMessage`) is fine for a first agent or any prompt that lives in one place. A 1500-token inline prompt is a normal shape — don't push first-time builders toward externalization.
70
+
71
+ The real reason to externalize is **piecing**, not length. Reusable chunks of context — `COMPANY_DESCRIPTION`, `BRAND_VOICE`, `CURRENT_PROMOTION` — each get one canonical home, and every prompt that needs them references that home. Suggest this when you see one of:
72
+
73
+ - Multiple agents share the same context (same product description, same compliance language).
74
+ - Pieces drift on their own cadence (`COMPANY_DESCRIPTION` quarterly, `CURRENT_PROMOTION` weekly).
75
+ - A non-engineer owns part of the prompt (marketing owns brand voice, legal owns disclosures).
76
+ - You want to A/B test one chunk without touching the rest.
77
+
78
+ If none apply, stay inline. Mid-prompt restructures cost more than they save with no second consumer to pay them back.
79
+
80
+ ### How piecing works
81
+
82
+ Load each chunk at workflow start (one node per chunk — a Data Table `Get Row`, an HTTP fetch, a Set node), then reference them inline in `systemMessage` where they should appear:
83
+
84
+ ```
85
+ =You are the assistant for {{ $('Company Description').first().json.value }}.
86
+
87
+ ## Market positioning
88
+ {{ $('Market Fit').first().json.value }}
89
+
90
+ ## Brand voice
91
+ {{ $('Brand Voice').first().json.value }}
92
+
93
+ Current date: {{ $now }}
94
+ User: {{ $('Lookup').first().json.name }}
95
+ ```
96
+
97
+ Mix sources: a **Data Table** (default for shared chunks, editable in UI), **n8n Variables** (`$vars.X`, paid plans — short shared values like a brand name), or **computed at run time** (`$now`, current user, available files).
98
+
99
+ ---
100
+
101
+ ## Common patterns
102
+
103
+ ### Include
104
+
105
+ - **Display protocols** for output needing specific formatting (markdown image syntax, link format, code-block conventions).
106
+ - **Conversational style cues** for user-facing agents ("ask one clarifying question before destructive actions").
107
+ - **Boundaries** unique to this agent ("only answer questions about domain X, otherwise redirect").
108
+ - **Universal context** that changes per execution (date, user identity, files).
109
+
110
+ ### Exclude
111
+
112
+ - **Per-tool usage docs** — move to tool descriptions.
113
+ - **Generic safety language** — built in; reinforcing adds tokens without changing behavior. Reserve for specific risks.
114
+ - **"You are a helpful assistant" preamble** — replace with a specific role.
115
+ - **Lengthy examples that aren't earning their tokens** — one sharp example beats five mediocre ones.
116
+
117
+ ---
118
+
119
+ ## Iteration loop
120
+
121
+ Treat the system prompt like code:
122
+
123
+ 1. Run the agent on representative inputs.
124
+ 2. Note where it does the wrong thing.
125
+ 3. Decide: system-prompt fix, tool-description fix, or downstream-validation fix?
126
+ 4. Make the smallest change that addresses it.
127
+ 5. Re-test on the same inputs PLUS one or two new ones.
128
+ 6. Watch for regressions on previously-working inputs.
129
+
130
+ Most "the agent doesn't follow my instructions" issues are conflicts between the system prompt, tool descriptions, and model defaults. Resolve those conflicts first.
131
+
132
+ ---
133
+
134
+ ## Anti-patterns
135
+
136
+ | Anti-pattern | Symptom | Fix |
137
+ |---|---|---|
138
+ | "You are a helpful assistant" + no specifics | Generic responses, no identity | Replace with a specific role and scope |
139
+ | 5000-token prompt with a section per tool | Token cost, slow responses, hard to edit | Move tool sections to tool descriptions |
140
+ | Hardcoded date / "current year" | Stale immediately | Inject `{{ $now }}` at runtime |
141
+ | A stack of `DON'T` rules | Model gets defensive, refuses too eagerly | Frame as positive instructions where possible |
142
+ | Multiple pasted "examples" | Cargo-cult, rarely earns its tokens | One sharp example, or none |
143
+ | Per-execution context hardcoded | Hard to update | Build the prompt from a template + variables |
144
+
145
+ ---
146
+
147
+ ## Cross-references
148
+
149
+ - Tool descriptions as the other half of the split → **TOOLS.md**
150
+ - The system-prompt half of structured output → **STRUCTURED_OUTPUT.md**
151
+ - File-handling injection mechanics → **n8n-binary-and-data**
@@ -0,0 +1,199 @@
1
+ # Agent tools
2
+
3
+ The agent picks tools by reading their **name** and **description** — nothing else. Both are part of the prompt. Treat tool design like API design: what it does, when to use it, what each parameter means, and how it fails.
4
+
5
+ ---
6
+
7
+ ## The four tool types
8
+
9
+ ### 1. Native tool node
10
+
11
+ Pre-built tool versions of regular nodes: `slackTool`, `gmailTool`, `googleSheetsTool`, `toolCalculator`, `notionTool`, `httpRequestTool`, and so on. Identical to their non-tool counterparts except parameters can be agent-filled via `$fromAI()`.
12
+
13
+ - **Pros**: minimal config, well-tested, native feel.
14
+ - **Cons**: one node = one operation. Multi-step logic doesn't fit.
15
+ - **Use when**: the capability maps cleanly to one node and one operation.
16
+
17
+ When a native node is missing an operation or needs a non-standard param shape, point an **HTTP Request Tool** at the service's API with the service's *predefined credential type* — you reuse the existing OAuth/API-key credential and get the full API.
18
+
19
+ ### 2. Sub-workflow as tool (`@n8n/n8n-nodes-langchain.toolWorkflow`)
20
+
21
+ The default for anything beyond one node. Any workflow becomes a tool with typed `$fromAI()` inputs.
22
+
23
+ - **Pros**: full power of n8n inside the tool — branching, error handling, sub-sub-workflows, native nodes, custom logic. Reusable across agents. Independently testable.
24
+ - **Cons**: one extra workflow boundary, slight latency.
25
+ - **Use when**: more than one node, logic that might be reused, or you want testability.
26
+
27
+ The canonical n8n way to build agent capabilities. → **SUBWORKFLOW_AS_TOOL.md**
28
+
29
+ ### 3. HTTP Request Tool (`@n8n/n8n-nodes-langchain.toolHttpRequest`)
30
+
31
+ A wrapper around the HTTP Request node exposing its parameters to the agent.
32
+
33
+ - **Pros**: any HTTP API becomes a tool with one node.
34
+ - **Cons**: HTTP only. Auth/retry/error handling are yours to wire.
35
+ - **Use when**: calling a single external API the agent should orchestrate directly.
36
+
37
+ One thing to know: HTTP Request has its own HTTP-level timeout (default 5 minutes) — bump `options.timeout` for slow endpoints. The agent tool itself has no timeout; the agent waits as long as the tool takes. Pointing it at, say, the Notion API (with the Notion predefined credential) lets the agent compose path, method, and body itself — covering operations the native node doesn't expose. Trade-off: the agent is now writing API requests, which is more error-prone and needs a capable model plus clear endpoint guidance in the description. That widens the blast radius — make sure the user understands.
38
+
39
+ ### 4. MCP Client Tool (`@n8n/n8n-nodes-langchain.mcpClientTool`)
40
+
41
+ Connects the agent to any MCP server. Two flavors:
42
+
43
+ - **External MCP servers** — any third-party or self-hosted MCP (GitHub, Linear, Notion, custom internal). One node exposes every tool that server offers.
44
+ - **n8n-hosted MCP** — a workflow on the same instance published with MCP access enabled. Same client node, pointed at an n8n MCP trigger URL. Lets one workflow serve many agents.
45
+
46
+ - **Cons**: tool descriptions and shapes come from the server, so quality varies and you can't easily tune them. Auth and reachability are yours.
47
+ - **Use when**: a maintained MCP server already covers the capability, or you want one published workflow to serve many agents.
48
+
49
+ ### Plus: Custom Code Tool (`@n8n/n8n-nodes-langchain.toolCode`)
50
+
51
+ Pure inline computation (math, parsing, formatting). Its runtime contract is **string in / string out, no `$fromAI`, no `$helpers`** and is owned by the **n8n-code-tool** skill — read it before writing one. Rule of thumb: if you want `$fromAI()` in the code, you want `.toolWorkflow` instead.
52
+
53
+ ---
54
+
55
+ ## Decision: which tool type?
56
+
57
+ ```
58
+ Capability the agent needs?
59
+ ├── One native node + one operation does it
60
+ │ → native tool node
61
+ ├── Native node missing an op / needs custom params for ONE API
62
+ │ → HTTP Request Tool (with the service's predefined credential)
63
+ ├── More than one node, or logic that might be reused
64
+ │ → Sub-workflow as tool (.toolWorkflow) ← default when in doubt
65
+ ├── Pure deterministic computation, one-off, inline
66
+ │ → Custom Code Tool (.toolCode) ← see n8n-code-tool
67
+ └── A maintained MCP server covers it / publish n8n logic to many agents
68
+ → MCP Client Tool
69
+ ```
70
+
71
+ ---
72
+
73
+ ## `$fromAI()`: how the agent fills tool parameters
74
+
75
+ `$fromAI()` is a **real n8n expression helper**, written inside a tool node's parameter expressions. Parameters the agent should decide get wrapped in it:
76
+
77
+ ```
78
+ sendTo: ={{ $fromAI('recipient', 'Email address of the recipient', 'string') }}
79
+ subject: ={{ $fromAI('subject', 'Email subject line, concise and informative', 'string') }}
80
+ body: ={{ $fromAI('body', 'Email body in plain text, professional tone', 'string') }}
81
+ ```
82
+
83
+ Shape: `$fromAI(paramName, description, type?, defaultValue?)`
84
+
85
+ - **paramName** — the name the model uses internally. snake_case or camelCase, be consistent.
86
+ - **description** — what value to produce. **Part of the prompt.** Be specific: format, range, example.
87
+ - **type** — `'string'` (default), `'number'`, `'boolean'`, `'json'`. Enforced — a wrong-typed value fails the call.
88
+ - **defaultValue** — used when the model omits the parameter.
89
+
90
+ It carries **JSON only** — it cannot carry binary (no base64, no file bytes), even through a non-AI binding. For binary, pass a storage key as a string and have the tool re-fetch (→ **n8n-binary-and-data**).
91
+
92
+ A good description vs a useless one:
93
+
94
+ ```
95
+ ✅ ={{ $fromAI('imageName', 'Storage key for an existing image to edit, or empty for a new generation. Use the exact key shown in the system prompt; do not reconstruct or guess.', 'string') }}
96
+
97
+ ❌ ={{ $fromAI('imageName', 'image name', 'string') }} // useless to the model
98
+ ```
99
+
100
+ Treat `$fromAI` descriptions like JSDoc — the model reads them to figure out what to pass.
101
+
102
+ ---
103
+
104
+ ## Plumbed params: hide what the agent shouldn't decide
105
+
106
+ Not every parameter has to be `$fromAI`. Any parameter can be filled deterministically from workflow context, and **plumbed values are invisible to the agent** — not in the tool schema, not influenceable by anything the model produces:
107
+
108
+ ```
109
+ reason: ={{ $fromAI('reason', 'Why the user is requesting a refund', 'string') }} // agent-filled
110
+ customerId: ={{ $('Chat Trigger').first().json.user.id }} // hidden
111
+ maxRefund: ={{ $('Get user tier').first().json.refundLimit }} // hidden
112
+ idempotencyKey:={{ $('Chat Trigger').first().json.sessionId }} // hidden
113
+ ```
114
+
115
+ Plumb anything the agent shouldn't get wrong or see:
116
+
117
+ - **Identity** — `userId`, `customerId`, authenticated actor, tenant scope.
118
+ - **Authority limits** — refund caps, tier flags, allowed regions.
119
+ - **Correlation IDs** — `sessionId`, idempotency keys, trace IDs.
120
+
121
+ **Give the agent a button to push, not a steering wheel.** The strongest version is a sensitive tool with **zero `$fromAI` parameters**: a "Refund order" tool takes `orderId` from the trigger, `amount` from the fetched order record, `actor` from the session — all plumbed. The agent literally cannot refund the wrong order; it only chooses whether to fire. Pair with **HUMAN_REVIEW.md** for actions needing both deterministic params and sign-off.
122
+
123
+ ---
124
+
125
+ ## Tool name and description as prompt
126
+
127
+ Selection process the model runs every turn:
128
+
129
+ 1. It gets the system prompt, conversation, and the list of tools.
130
+ 2. For each tool it reads name + description + parameter schema (with `$fromAI` descriptions).
131
+ 3. It picks the tool whose description best matches what it needs to do.
132
+
133
+ **Bad names and descriptions cause bad selection — usually silently.** The model just doesn't call your tool, or calls a different one with garbage parameters. No error.
134
+
135
+ ### Names: verb-first and specific
136
+
137
+ | Good | Bad | Why |
138
+ |---|---|---|
139
+ | `Search customer database` | `query` / `tool1` | Generic names say nothing |
140
+ | `Generate image with Veo` | `imageGen` | Which generator? |
141
+ | `Edit existing image` | `edit` | Edit what? |
142
+ | `Send Slack message to channel` | `slack` | Name the action, not just the surface |
143
+ | `Lookup user by email` | `getUser` | Lookup how? |
144
+
145
+ ### Descriptions: three parts
146
+
147
+ 1. **What it does** (one sentence).
148
+ 2. **When to use it** (one or two sentences, with boundaries / examples).
149
+ 3. **Parameter notes** (only if not already covered in `$fromAI` descriptions).
150
+
151
+ ```
152
+ Edit existing image: Modifies an image the user already uploaded, based on a prompt.
153
+ Use when the user uploaded an image and asks for changes (color, style, composition, content).
154
+ Do NOT use for generating new images from scratch — use Generate Image for that.
155
+ The imageName parameter must be the storage key of the existing image as listed in your
156
+ available files; do not pass the original filename or a URL.
157
+ ```
158
+
159
+ That description does work that would otherwise bloat the system prompt — which is exactly the point.
160
+
161
+ ---
162
+
163
+ ## Tool descriptions as modular prompts
164
+
165
+ Anything specific to *how to call this tool* belongs in the tool's description, not the system prompt:
166
+
167
+ | In the system prompt (move out) | Better in the tool description |
168
+ |---|---|
169
+ | "When generating images, prefer realistic photography over `8k cinematic`" | `Generate Image`: "Default to realistic photography aesthetics…" |
170
+ | "If the search tool returns nothing, summarize politely" | `Search`: "Returns up to 10 results; if empty, report 'no matches' rather than retrying broader" |
171
+ | "Use 9:16 for video tools" | `Generate Video`: "Defaults to 9:16; pass `aspectRatio: '16:9'` for landscape" |
172
+
173
+ Three reasons: **reusability** (the tool teaches each new agent how to use it), **token efficiency** (per-tool guidance only loads when the model considers that tool, not every turn), **maintainability** (one description, not a buried paragraph).
174
+
175
+ ---
176
+
177
+ ## Granularity: one tool with branching, not two near-identical tools
178
+
179
+ The model gets confused choosing between near-identical tools. If two are ~80% the same internally:
180
+
181
+ - **One tool with a branching parameter.** `Generate Image` vs `Edit Image` share most logic → collapse to one with an `imageName` parameter (empty = generate, populated = edit).
182
+ - **Two tools only when genuinely distinct AND the descriptions clearly differentiate.** `Send DM` vs `Send Channel Message` are distinct.
183
+
184
+ ---
185
+
186
+ ## Operational notes
187
+
188
+ - **maxIterations.** Agents have a configurable tool-call cap (`options.maxIterations`), and the default is **low**. A multi-tool agent that chains calls hits it and surfaces "max iterations reached" or empty output. Raise it. Build a fallback — don't trust graceful recovery.
189
+ - **Tool-call cost.** Each call is at minimum one extra model round-trip. Frequently-called tools should return **concise** results — bloated returns burn input tokens fast.
190
+ - **Tool failure handling.** Set `onError: 'continueErrorOutput'` on tool sub-workflows where you want the agent to receive an error string instead of halting; the agent can retry, switch tools, or report. → **n8n-error-handling**.
191
+
192
+ ---
193
+
194
+ ## Cross-references
195
+
196
+ - The sub-workflow tool pattern in detail → **SUBWORKFLOW_AS_TOOL.md**
197
+ - System-prompt-vs-tool-description split → **SYSTEM_PROMPT.md**
198
+ - Passing binary into tools → **n8n-binary-and-data**
199
+ - The Custom Code Tool contract → **n8n-code-tool**
@@ -0,0 +1,269 @@
1
+ ---
2
+ name: n8n-binary-and-data
3
+ description: Handle n8n files and binary data across uploads, downloads, transforms, multimodal inputs, agent tools, and chat surfaces.
4
+ risk: unknown
5
+ source: https://github.com/czlonkowski/n8n-skills/tree/main/skills/n8n-binary-and-data
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 Binary and Data
15
+
16
+ ## When to Use
17
+
18
+ Use this skill when an n8n workflow reads, transforms, stores, uploads, downloads, or transmits files and binary fields, including multimodal agent inputs and chat attachments.
19
+
20
+ Treat uploaded files and generated URLs as potentially sensitive. Obtain approval before sending data to a new external host, use the narrowest retention and access scope available, avoid logging bytes or base64 payloads, and do not embed credentials in URLs or workflow fields.
21
+
22
+ Every n8n item carries two independent slots: `$json` for structured data and `$binary` for file bytes. They travel side by side through the workflow. File contents — the actual PDF, image, or zip — live in `$binary`, never in `$json`. Get that split wrong and you read an empty field, lose a file mid-flow, or hand an AI agent a tool input it can't use.
23
+
24
+ This skill covers where binary lives, how to read and write it, how to keep it from being silently stripped, the hard wall between binary and the AI-agent tool boundary, and why chat surfaces need a URL instead of raw bytes.
25
+
26
+ ---
27
+
28
+ ## The three rules that prevent 90% of binary bugs
29
+
30
+ 1. **File contents are in `$binary`, not `$json`.** After an HTTP download, a "Read Files", or an email-attachment trigger, the bytes sit in `$binary.<key>`. `$json` holds metadata at most. Reading `$json.data` for file contents gives you nothing.
31
+
32
+ 2. **Binary cannot cross the AI-agent tool boundary — in either direction.** Tool arguments and tool return values are JSON only. An uploaded image can't be passed into a tool as a file, and a tool can't return raw bytes. Pre-stage to storage and pass a key or URL through JSON instead. See `references/AGENT_TOOL_BINARY.md`.
33
+
34
+ 3. **Chat surfaces render images by URL, not by `$binary`.** Slack, Discord, Teams, Telegram, embedded webhook chat — none of them read the binary slot. The image has to live somewhere a URL can fetch it. See `references/CDN_REQUIREMENT.md`.
35
+
36
+ ---
37
+
38
+ ## The two slots
39
+
40
+ Each item is shaped like this:
41
+
42
+ ```json
43
+ {
44
+ "json": { "customerId": 42, "status": "sent" },
45
+ "binary": {
46
+ "invoice": {
47
+ "data": "<base64-encoded bytes>",
48
+ "mimeType": "application/pdf",
49
+ "fileName": "invoice-42.pdf",
50
+ "fileExtension": "pdf"
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ The key inside `binary` (`invoice` here) is the **binary property name**. Most file-handling nodes have a `binaryPropertyName` parameter that points at it — the producer names the slot, the consumer references it by that name. The default key across most nodes is `data`, so when nothing tells you otherwise, assume `$binary.data`.
57
+
58
+ `$json` and `$binary` are separate namespaces. An expression like `{{ $binary.invoice.fileName }}` reads file metadata; `{{ $json.customerId }}` reads data. They never mix.
59
+
60
+ This split also explains a webhook gotcha: a Webhook trigger receiving `multipart/form-data` puts the uploaded file in `$binary` and the accompanying form fields in `$json.body` — so an uploaded file is not somewhere under `$json` at all. (The `$json.body` nesting for webhooks is **n8n-expression-syntax** territory.)
61
+
62
+ See `references/BINARY_BASICS.md` for the full slot anatomy, mime types, and size limits.
63
+
64
+ ---
65
+
66
+ ## Producing binary
67
+
68
+ You rarely build a `$binary` slot by hand — nodes populate it for you:
69
+
70
+ | Source | How binary appears |
71
+ |---|---|
72
+ | HTTP Request with `responseFormat: "file"` | Response body lands in `$binary.data` (or the name you set) |
73
+ | Read/Write Files from Disk | File contents read into `$binary` |
74
+ | Storage downloads (S3, Google Drive, Dropbox, etc.) | Downloaded file in `$binary.<key>` |
75
+ | Email triggers with attachments | Each attachment arrives in `$binary` |
76
+ | Provider AI media nodes (image/audio gen) | Set `options.binaryPropertyOutput` so the bytes land where the next node looks |
77
+
78
+ For an HTTP download, the one field that matters is `responseFormat`. Confirm it with `get_node` on `nodes-base.httpRequest` — leaving it as the default JSON/string format is the classic reason a downloaded file ends up as garbled text in `$json` instead of clean bytes in `$binary`.
79
+
80
+ ---
81
+
82
+ ## Reading and writing binary in a Code node
83
+
84
+ Most workflows never need to crack open the bytes — they just pass binary through to a consumer (email attachment, file upload, Slack file). When you do need the raw bytes, do it in a Code node.
85
+
86
+ **Read** with `getBinaryDataBuffer` — do not try to base64-decode `$binary.<key>.data` by hand:
87
+
88
+ ```javascript
89
+ // Code node, "Run Once for Each Item"
90
+ const buffer = await this.helpers.getBinaryDataBuffer(0, 'data'); // (itemIndex, propertyName)
91
+ const text = buffer.toString('utf-8');
92
+ const length = buffer.length;
93
+
94
+ return [{
95
+ json: { ...$json, length },
96
+ binary: $input.item.binary, // pass the binary through, or it's gone
97
+ }];
98
+ ```
99
+
100
+ **Write** by building the slot yourself — base64 the bytes plus a mime type and file name:
101
+
102
+ ```javascript
103
+ const text = 'Hello, world!';
104
+ return [{
105
+ json: { ok: true },
106
+ binary: {
107
+ report: {
108
+ data: Buffer.from(text).toString('base64'),
109
+ mimeType: 'text/plain',
110
+ fileName: 'report.txt',
111
+ fileExtension: 'txt',
112
+ },
113
+ },
114
+ }];
115
+ ```
116
+
117
+ The Code-node sandbox, helpers, and execution modes are the domain of **n8n-code-javascript** (and **n8n-code-python**) — use those for the language-level detail. The one binary-specific thing to remember here: a Code node that returns `[{ json: {...} }]` without re-attaching `binary` **silently drops the file**. See `references/BINARY_BASICS.md`.
118
+
119
+ ---
120
+
121
+ ## Keeping binary alive across transforms
122
+
123
+ JSON-only nodes — Edit Fields (Set), Code, IF, and others — can drop the `$binary` slot from their output. The workflow validates clean and runs without error; the file just isn't there downstream when the email node goes to attach it.
124
+
125
+ Two ways to keep it:
126
+
127
+ - **Pass-through option on the transforming node.** Edit Fields has `includeOtherFields`; a Code node can return `binary: $input.item.binary` explicitly. Cheapest fix when it's available.
128
+ - **Fan out and Merge by position.** Route the source into both the transform and a bypass branch, then recombine with a Merge in `combineByPosition` mode. The JSON comes from the transform side, the binary survives on the bypass side.
129
+
130
+ ```
131
+ [Source with binary] ─┬─→ [Edit Fields: change JSON] ─┐
132
+ │ (binary stripped here) ├─→ [Merge: combineByPosition] ─→ [Email: attach]
133
+ └──────────────────────────────────┘
134
+ (bypass — binary passes through untouched)
135
+ ```
136
+
137
+ `combineByPosition` pairs item N from each input, so the field counts must line up. The connection wiring and the alternatives for many-strip-point chains (upload-early, sub-workflow) are in `references/MERGE_FOR_CONTEXT.md`.
138
+
139
+ ---
140
+
141
+ ## The agent-tool binary boundary
142
+
143
+ This is the sharpest edge. An AI Agent talks to its tools (Custom Code Tool, Call n8n Workflow Tool, HTTP Request Tool, MCP tools) over JSON. Binary does not fit through that pipe in either direction. The fix is the same shape both ways: **stage the bytes in storage, pass a key/URL through JSON, fetch on the other side.**
144
+
145
+ **Inbound — a user uploads a file the agent's tool must operate on:**
146
+
147
+ 1. The chat trigger gives you a `files[]` array. Split it out and upload each file to private storage under a hashed key.
148
+ 2. Re-merge that branch before the agent runs (it's a synchronization barrier, not decoration), and set `executeOnce: true` on the agent so N files don't trigger N agent runs.
149
+ 3. Inject the keys into the agent's system prompt, listing both the original name (human context) and the storage key (what the tool needs), with an explicit "use EXACTLY this key".
150
+ 4. The tool receives the key as a string argument and downloads the file from storage itself.
151
+
152
+ **Outbound — a tool generates a file the agent must return:**
153
+
154
+ 1. The tool sub-workflow generates the binary, uploads it to storage, and returns JSON like `{ "ok": true, "key": "...", "url": "https://...", "mimeType": "image/png" }`.
155
+ 2. The agent embeds the URL in its reply (or passes the key to another tool).
156
+
157
+ `passthroughBinaryImages: true` on the agent only changes what the **LLM sees** for vision — it does **not** let tools receive the file, and it's image-only (no PDFs, audio, or video). You still need the upload-and-pass-key pattern for any tool. Full patterns, hash strategy, storage choices, and the long-running-tool variant are in `references/AGENT_TOOL_BINARY.md`.
158
+
159
+ > Building the tool itself? See **n8n-code-tool** for the Custom Code Tool contract and **n8n-workflow-patterns** for the AI-Agent-with-tools shape.
160
+
161
+ ---
162
+
163
+ ## The CDN requirement for chat surfaces
164
+
165
+ When a workflow generates an image and the user wants it shown inside a chat message:
166
+
167
+ - **Binary on the item isn't enough.** The chat client renders messages that reference images by URL (or pushes bytes through the platform's own file-upload API). It never reads `$binary`.
168
+ - **The bytes have to live somewhere a URL can fetch over HTTPS.** Upload to an object store or drive first, then embed the returned URL.
169
+ - **n8n has no built-in CDN.** The user provides the storage.
170
+
171
+ Ask which storage they already use rather than defaulting to S3 — object storage (S3, R2, GCS, Azure Blob, Backblaze B2, Supabase Storage) and drive-style services (Dropbox, Google Drive, OneDrive, Box) all work and all change the URL shape. Cloudflare R2 is the lowest-friction starting point if they have nothing. For sensitive content, use a signed URL with an expiry rather than a permanently public one. See `references/CDN_REQUIREMENT.md`.
172
+
173
+ ---
174
+
175
+ ## What's NOT available
176
+
177
+ - **`$fromAI()` cannot carry binary.** It fills tool parameters with strings, numbers, booleans, and objects — never file bytes. Pass a storage key instead.
178
+ - **Tool arguments and returns are JSON only.** There is no "binary parameter" on an agent tool, in or out.
179
+ - **n8n ships no CDN or public file host.** Serving a file over a URL is always something the user's storage does, not n8n.
180
+ - **`getBinaryDataBuffer` is a Code-node helper.** It isn't available in the Custom Code Tool sandbox (see **n8n-code-tool**).
181
+
182
+ ---
183
+
184
+ ## Where Data Tables live
185
+
186
+ For persistent tabular storage — reference-counting staged files, tracking which keys are live, dedup — that's the `n8n_manage_datatable` surface, owned by **n8n-mcp-tools-expert**. This skill does not cover Data Tables.
187
+
188
+ ---
189
+
190
+ ## Anti-patterns
191
+
192
+ | Anti-pattern | What goes wrong | Fix |
193
+ |---|---|---|
194
+ | Reading file contents from `$json` | Bytes live in `$binary`; `$json` is empty or metadata only | Read `$binary.<key>`, or `getBinaryDataBuffer` in a Code node |
195
+ | HTTP download without `responseFormat: "file"` | Bytes arrive as mangled text in `$json`, not clean binary | Set `responseFormat: "file"` on the HTTP Request node |
196
+ | Code node returns `[{json:{...}}]`, no `binary` | The file is silently dropped downstream | Re-attach `binary: $input.item.binary` in the return |
197
+ | JSON transform (Edit Fields/IF) eats the binary | Email/upload node finds nothing to attach | Pass-through option, or fan out + Merge by position |
198
+ | Passing an uploaded file into a tool via `$fromAI` | `$fromAI` can't carry binary; the tool gets nothing | Pre-stage to storage, inject the key in the system prompt, tool fetches by key |
199
+ | Assuming `passthroughBinaryImages` lets tools see the file | It only affects what the LLM sees, and only for images | Still need the upload-and-pass-key pattern for tools |
200
+ | Tool returns raw binary to the agent | Tool output is JSON; bytes don't survive (and bloat context) | Upload, return `{ key, url }` in JSON |
201
+ | Posting `$binary` to a chat surface and expecting an image | Chat clients render by URL, not raw bytes | Upload to storage/CDN, embed the URL or use the platform file API |
202
+ | Hardcoding base64 in a Code node | Huge workflow JSON, slow, leaky | Reference via `$binary`, or upload and reference by URL |
203
+
204
+ ---
205
+
206
+ ## Reference files
207
+
208
+ | File | Read when |
209
+ |---|---|
210
+ | `references/BINARY_BASICS.md` | First time handling binary, or reading/writing the `$binary` slot, mime types, size limits |
211
+ | `references/AGENT_TOOL_BINARY.md` | An agent tool needs an uploaded file, or produces one — the boundary in either direction |
212
+ | `references/MERGE_FOR_CONTEXT.md` | Binary disappears after a JSON transform and you need to re-attach it |
213
+ | `references/CDN_REQUIREMENT.md` | Showing images in a chat surface or anywhere that needs URL-referenced images |
214
+
215
+ ---
216
+
217
+ ## Integration with Other Skills
218
+
219
+ **n8n-code-javascript / n8n-code-python**: the Code node is where you read/write raw bytes (`getBinaryDataBuffer`, `Buffer.from(...).toString('base64')`). Those skills own the sandbox, helpers, and execution-mode detail — this skill owns the rule that binary must be re-attached on return.
220
+
221
+ **n8n-code-tool**: the Custom Code Tool sandbox is narrower — no `$binary`, no `getBinaryDataBuffer`, no `$fromAI`. When a tool needs a file, this skill's storage-key pattern is how it gets one.
222
+
223
+ **n8n-workflow-patterns**: the agent-tool binary boundary sits inside the AI-Agent-with-tools pattern; the CDN flow is a generate → upload → reply chain.
224
+
225
+ **n8n-node-configuration**: `responseFormat`, `binaryPropertyName`, `includeOtherFields`, `binaryPropertyOutput` are all conditional fields — use `get_node` to confirm the exact names on the user's version.
226
+
227
+ **n8n-expression-syntax**: addressing `$binary.<key>.fileName` vs `$json.body` (webhook uploads in particular) is expression territory.
228
+
229
+ **n8n-validation-expert**: a dropped binary slot is a silent failure — `validate_workflow` won't flag it. Confirm presence by inspecting the execution.
230
+
231
+ **n8n-mcp-tools-expert**: owns `n8n_manage_datatable` (Data Tables) and `n8n_executions` — use the latter to confirm a `binary` slot actually survived a given node.
232
+
233
+ **n8n-error-handling**: storage uploads and downloads fail; the inbound/outbound staging steps need error branches so a missing key doesn't 404 silently.
234
+
235
+ **using-n8n-mcp-skills**: the index of how these skills fit together.
236
+
237
+ ---
238
+
239
+ ## Verifying binary survived
240
+
241
+ Validation won't catch a stripped binary slot — it's a silent failure. Confirm it ran correctly:
242
+
243
+ 1. `n8n_test_workflow` (or trigger a real run) to produce an execution.
244
+ 2. `n8n_executions` to pull that execution, and inspect per-node output for the `binary` slot — it shows presence and metadata even if the base64 is too large to render.
245
+ 3. The node where `binary` last appears is the node before the strip. That's where the pass-through or Merge goes.
246
+
247
+ ---
248
+
249
+ ## Quick Reference Checklist
250
+
251
+ - [ ] File contents read from `$binary.<key>` — never `$json`
252
+ - [ ] HTTP downloads use `responseFormat: "file"`
253
+ - [ ] Code nodes re-attach `binary` on return when the file must continue
254
+ - [ ] JSON transforms either pass binary through or Merge it back (`combineByPosition`)
255
+ - [ ] No attempt to pass binary into/out of an agent tool — keys/URLs through JSON instead
256
+ - [ ] `passthroughBinaryImages` used only for LLM vision, not as a tool channel
257
+ - [ ] Chat-surface images uploaded to storage; the URL is embedded, not the bytes
258
+ - [ ] Storage backend chosen with the user (not defaulted to S3); signed URLs for sensitive content
259
+ - [ ] Binary presence confirmed by inspecting the execution, not by validation
260
+
261
+ ---
262
+
263
+ **Remember**: two slots, side by side. Data rides in `$json`, files ride in `$binary` — and the moment a file has to cross an agent tool or reach a chat surface, it travels as a URL, not as bytes.
264
+
265
+ ## Limitations
266
+
267
+ - Storage limits, binary modes, and node-specific field names vary across n8n versions and hosting configurations.
268
+ - An n8n validation pass cannot prove that file bytes survived a live execution; inspect execution data with a safe sample.
269
+ - This skill does not choose a storage provider or authorize uploading sensitive data to one.