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.
- package/bundled-skills/.antigravity-install-manifest.json +19 -1
- package/bundled-skills/antigravity-maintainer-batch-release/SKILL.md +22 -8
- package/bundled-skills/antigravity-maintainer-batch-release/agents/openai.yaml +4 -0
- package/bundled-skills/anywrite/SKILL.md +18 -8
- package/bundled-skills/cloudflare-security-audit/SKILL.md +5 -3
- package/bundled-skills/cloudflare-security-audit/references/RECONNAISSANCE.md +2 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/aas-agent-first-control-plane-preview-profile.md +2 -2
- package/bundled-skills/docs/maintainers/aas-agent-first-control-plane-v1-worklog.md +1 -1
- package/bundled-skills/docs/maintainers/merge-batch.md +3 -3
- package/bundled-skills/docs/maintainers/merging-prs.md +13 -17
- package/bundled-skills/docs/maintainers/pr-autonomy.md +2 -2
- package/bundled-skills/docs/maintainers/release-process.md +17 -2
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/users/aas-core.md +38 -3
- package/bundled-skills/docs/users/bundles.md +1 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/faq.md +2 -0
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/docs/vietnamese/README.vi.md +4 -4
- package/bundled-skills/hf-cloud-aws-context-discovery/SKILL.md +96 -0
- package/bundled-skills/loki-mode/examples/todo-app-generated/backend/package-lock.json +3 -3
- package/bundled-skills/markstream-angular/SKILL.md +76 -0
- package/bundled-skills/markstream-custom-components/SKILL.md +74 -0
- package/bundled-skills/markstream-custom-components/references/patterns.md +14 -0
- package/bundled-skills/markstream-install/SKILL.md +3 -1
- package/bundled-skills/markstream-migration/SKILL.md +78 -0
- package/bundled-skills/markstream-migration/references/adoption-checklist.md +10 -0
- package/bundled-skills/markstream-nuxt/SKILL.md +71 -0
- package/bundled-skills/markstream-react/SKILL.md +74 -0
- package/bundled-skills/markstream-svelte/SKILL.md +68 -0
- package/bundled-skills/markstream-vue/SKILL.md +71 -0
- package/bundled-skills/markstream-vue2/SKILL.md +70 -0
- package/bundled-skills/markstream-vue2-cli/SKILL.md +70 -0
- package/bundled-skills/markstream-vue2-vite/SKILL.md +69 -0
- package/bundled-skills/n8n-agents/SKILL.md +301 -0
- package/bundled-skills/n8n-agents/references/CHAT_AGENT_PATTERNS.md +228 -0
- package/bundled-skills/n8n-agents/references/EXAMPLES.md +432 -0
- package/bundled-skills/n8n-agents/references/HUMAN_REVIEW.md +180 -0
- package/bundled-skills/n8n-agents/references/MEMORY.md +139 -0
- package/bundled-skills/n8n-agents/references/RAG.md +102 -0
- package/bundled-skills/n8n-agents/references/STRUCTURED_OUTPUT.md +163 -0
- package/bundled-skills/n8n-agents/references/SUBWORKFLOW_AS_TOOL.md +199 -0
- package/bundled-skills/n8n-agents/references/SYSTEM_PROMPT.md +151 -0
- package/bundled-skills/n8n-agents/references/TOOLS.md +199 -0
- package/bundled-skills/n8n-binary-and-data/SKILL.md +269 -0
- package/bundled-skills/n8n-binary-and-data/references/AGENT_TOOL_BINARY.md +227 -0
- package/bundled-skills/n8n-binary-and-data/references/BINARY_BASICS.md +187 -0
- package/bundled-skills/n8n-binary-and-data/references/CDN_REQUIREMENT.md +109 -0
- package/bundled-skills/n8n-binary-and-data/references/MERGE_FOR_CONTEXT.md +130 -0
- package/bundled-skills/n8n-code-tool/SKILL.md +358 -0
- package/bundled-skills/n8n-code-tool/references/ERROR_PATTERNS.md +178 -0
- package/bundled-skills/n8n-code-tool/references/INPUT_SCHEMA.md +132 -0
- package/bundled-skills/n8n-error-handling/SKILL.md +289 -0
- package/bundled-skills/n8n-error-handling/references/API_WORKFLOWS.md +256 -0
- package/bundled-skills/n8n-error-handling/references/ERROR_WORKFLOWS.md +178 -0
- package/bundled-skills/n8n-error-handling/references/NODE_ERROR_OUTPUTS.md +171 -0
- package/bundled-skills/n8n-error-handling/references/RESPONSE_SHAPES.md +220 -0
- package/bundled-skills/n8n-multi-instance/SKILL.md +208 -0
- package/bundled-skills/n8n-node-configuration/references/NODE_FAMILY_GOTCHAS.md +241 -0
- package/bundled-skills/n8n-subworkflows/SKILL.md +271 -0
- package/bundled-skills/n8n-subworkflows/references/NAMING_AND_DISCOVERY.md +130 -0
- package/bundled-skills/n8n-subworkflows/references/SUBWORKFLOW_PATTERNS.md +147 -0
- package/bundled-skills/pptx-deck-creation/SKILL.md +2 -0
- package/bundled-skills/pptx-deck-creation/references/design-profiles.md +2 -0
- package/bundled-skills/sshepherd/SKILL.md +16 -6
- package/bundled-skills/using-n8n-mcp-skills/SKILL.md +193 -0
- package/bundled-skills/weaviate-cookbooks/references/pdf_multimodal_rag.md +12 -6
- package/package.json +1 -1
- package/skills_index.json +596 -13
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Agent memory
|
|
2
|
+
|
|
3
|
+
Memory is a sub-node on the agent, wired via `ai_memory`. Without it, every invocation is stateless. With it, the agent holds a conversation across turns — and across executions, depending on type — keyed by whatever expression you bind to `sessionKey`.
|
|
4
|
+
|
|
5
|
+
Memory node availability shifts between n8n versions, so confirm what's installed with `search_nodes({ query: 'memory' })`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## The two non-negotiables
|
|
10
|
+
|
|
11
|
+
1. **Plumb a stable key through.** Memory buckets by whatever you bind to `sessionKey`. The Chat Trigger fills `sessionId` automatically. For other triggers, derive a stable identifier (Slack `thread_ts`, a webhook conversation ID, a generated UUID, a multi-tenant composite) and forward it to memory and any session-keyed tools. Without consistency across the same conversation, memory never matches.
|
|
12
|
+
2. **Default to `memoryBufferWindow`.** It persists across executions via n8n's internal store, keyed on `sessionKey`, and is the right choice for nearly every chat agent. Reach for Postgres/Redis only when memory must be read **outside** the agent.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## The memory types
|
|
17
|
+
|
|
18
|
+
### `memoryBufferWindow` (the default)
|
|
19
|
+
|
|
20
|
+
In-context memory of the last N exchanges, persisted across executions via n8n's store.
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"parameters": {
|
|
25
|
+
"sessionIdType": "customKey",
|
|
26
|
+
"sessionKey": "={{ $json.sessionId }}",
|
|
27
|
+
"contextWindowLength": 50
|
|
28
|
+
},
|
|
29
|
+
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
|
|
30
|
+
"typeVersion": 1.3,
|
|
31
|
+
"name": "Simple Memory"
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`contextWindowLength` is the number of exchanges retained. **The default is 5 — very low** for modern chat expectations, where users assume a conversation feels close to endless. **50 is a reasonable starting point.** Higher = more context but more tokens per turn.
|
|
36
|
+
|
|
37
|
+
**Messages past the window are removed entirely.** Once the buffer fills, the oldest exchanges are dropped and the agent can't recall, search, or even know they existed. If a user said something 60 turns ago and the window is 50, that's gone from the agent's perspective. For recall beyond the window, raise `contextWindowLength`, or persist key facts in a Data Table that's read and injected into the system prompt.
|
|
38
|
+
|
|
39
|
+
The "window" is a sliding cap on how many messages stay in context — **not** a scope on persistence. With `sessionIdType: 'customKey'` you bind the key to any expression (`{{ $json.sessionId }}`, a Slack `thread_ts`, a multi-tenant composite). Each user/thread/context gets its own bucket.
|
|
40
|
+
|
|
41
|
+
### `memoryPostgresChat` / `memoryRedisChat`
|
|
42
|
+
|
|
43
|
+
Reach for these only when memory must be queried or read **outside** the agent: displaying conversation history in your own UI, analytics on past chats, sharing memory across systems, or migrating instances cleanly.
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"parameters": {
|
|
48
|
+
"sessionIdType": "customKey",
|
|
49
|
+
"sessionKey": "={{ $json.sessionId }}"
|
|
50
|
+
},
|
|
51
|
+
"type": "@n8n/n8n-nodes-langchain.memoryPostgresChat",
|
|
52
|
+
"typeVersion": 1.3,
|
|
53
|
+
"name": "Postgres Memory"
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Wrong for** the default chat case — `memoryBufferWindow` already survives across executions and is the cleaner pick.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Custom patterns (Chat Memory Manager)
|
|
62
|
+
|
|
63
|
+
Most agents don't need this. But when a fixed window isn't enough, the `@n8n/n8n-nodes-langchain.memoryManager` node operates against any wired memory backend and exposes three modes:
|
|
64
|
+
|
|
65
|
+
- **`load`** (default) — read current memory into the workflow (for inspection, branching on size, feeding a summarizer).
|
|
66
|
+
- **`insert`** — append a message. An optional `hideFromUI` flag covers messages that should affect the agent but not show in the chat UI.
|
|
67
|
+
- **`delete`** — remove some or all messages.
|
|
68
|
+
|
|
69
|
+
### Pattern: rolling summarization
|
|
70
|
+
|
|
71
|
+
When a conversation runs long and you want the gist of older turns instead of dropping them:
|
|
72
|
+
|
|
73
|
+
1. After each turn, `load` the buffer.
|
|
74
|
+
2. If it's approaching the cap, route to a summarizer (otherwise no-op).
|
|
75
|
+
3. Summarize the older turns with an LLM.
|
|
76
|
+
4. `delete` the buffer.
|
|
77
|
+
5. `insert` the summary as one message, plus the most recent few turns for continuity.
|
|
78
|
+
|
|
79
|
+
The agent now sees `[summary of turns 1-40] + [recent 5 turns]`, paying far fewer input tokens while keeping long-history context.
|
|
80
|
+
|
|
81
|
+
Other patterns built the same way: **prune by relevance** (`load` → filter → `delete` → `insert` the keepers), **inject runtime facts** (`insert` with `hideFromUI: true`), **reset on command** (`delete` all on `/clear`).
|
|
82
|
+
|
|
83
|
+
The Memory Manager node is more recent than the rest of n8n's memory tooling — verify the modes against your installed version before relying on them in production.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Session ID handling by trigger
|
|
88
|
+
|
|
89
|
+
### Chat Trigger
|
|
90
|
+
Sets `sessionId` automatically. Wire it everywhere consistently:
|
|
91
|
+
- Memory: `sessionKey: ={{ $('Chat Trigger').first().json.sessionId }}`
|
|
92
|
+
- Tools: `sessionId: ={{ $('Chat Trigger').first().json.sessionId }}` (**NOT** through `$fromAI`)
|
|
93
|
+
- Storage keying: derive bucket keys / filenames from `sessionId` for trivial per-session cleanup.
|
|
94
|
+
|
|
95
|
+
### Webhook trigger
|
|
96
|
+
You manage it: the caller passes a header or body field (`body.sessionId`) and you forward it, or you issue one on first call and expect it back. Either way, it must be consistent across the whole conversation, including reconnections.
|
|
97
|
+
|
|
98
|
+
### Manual / scheduled
|
|
99
|
+
Usually no session. Use a stable identifier per "conversation" if one exists (ticket ID, thread ID); otherwise memory adds nothing — omit it.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Memory and tools
|
|
104
|
+
|
|
105
|
+
When a tool is invoked, the tool's sub-workflow does **NOT** see conversation memory — memory is the agent's context, not the tool's input. Pass needed context through `$fromAI` parameters explicitly. For session-keyed state, plumb `sessionId` and have the tool look up state from a Data Table or storage keyed by session.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Memory and binary
|
|
110
|
+
|
|
111
|
+
Memory stores **text turns**. Binary uploaded mid-conversation is NOT in memory — it's in the Chat Trigger's `files[]` for that turn only. The text memory captures that "the user mentioned uploading a file," but to actually use the file in a later tool call it must still be in storage and its key must be in **that** turn's system prompt. In practice, inject the session's file inventory into the system prompt every turn (loaded by `sessionId`). → **n8n-binary-and-data**.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Common mistakes
|
|
116
|
+
|
|
117
|
+
- **Hardcoding `sessionId: 'default'`** — all conversations share one bucket; memory becomes meaningless.
|
|
118
|
+
- **Different `sessionId` on memory vs tools** — memory looks right but tools can't find related state.
|
|
119
|
+
- **Unbounded `memoryBuffer` for chat** — token cost grows until timeout. Use BufferWindow with a sane limit.
|
|
120
|
+
- **Adding memory where there's no session** — a "summarize this article" workflow doesn't need it.
|
|
121
|
+
- **Expecting tools to see memory** — they see only their `$fromAI` parameters and plumbed context.
|
|
122
|
+
- **Drift between the surface and memory** — if anything posts to the conversation outside the agent (a scheduled reply, a human writing directly), the agent operates on an incomplete view and will contradict messages it can't see. Whatever shows on the user-facing surface must also be `insert`ed into memory.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Operational notes
|
|
127
|
+
|
|
128
|
+
- **Memory size drives token cost.** A 15-turn buffer of 200-token messages is 3000 tokens of input every turn before the user even speaks. Plan for it.
|
|
129
|
+
- **Rate limits.** A model that hits a limit fails mid-conversation; memory holds everything until then, and the next turn resumes (assuming session-id continuity).
|
|
130
|
+
- **Concurrent sessions.** Persistent backends key on `sessionId`, so concurrent conversations don't interfere. Verify with two simultaneous tests.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Cross-references
|
|
135
|
+
|
|
136
|
+
- Where the agent fits → parent **SKILL.md**
|
|
137
|
+
- Passing session-keyed state into tools → **SUBWORKFLOW_AS_TOOL.md**
|
|
138
|
+
- Threading-as-session on chat surfaces → **CHAT_AGENT_PATTERNS.md**
|
|
139
|
+
- Session-keyed file storage → **n8n-binary-and-data**
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# RAG (retrieval augmented generation)
|
|
2
|
+
|
|
3
|
+
RAG in n8n is built on the LangChain primitives — document loaders, text splitters, embeddings, vector stores, retrievers, rerankers. They wire onto agents and chains the same way models and memory do (via `ai_*` connections).
|
|
4
|
+
|
|
5
|
+
This reference is intentionally **thin**. The pieces work, but opinionated end-to-end recipes ("which vector store, which chunking, when to rerank") depend heavily on data shape and scale. Verify defaults against current n8n docs and your team's choices.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Before you go vector: rule out cheaper lookups
|
|
10
|
+
|
|
11
|
+
Not every retrieval problem needs a vector store. Three cheaper alternatives to eliminate first:
|
|
12
|
+
|
|
13
|
+
- **Database or Data Table for exact lookups.** "Look up customer X's record", "fetch issue #1234", "get rows where status = 'open'" are NOT RAG problems — use a query directly. → **n8n-node-configuration** for DB nodes.
|
|
14
|
+
- **Live search for freshness.** Information not in anything you've indexed (current news, live API state, anything time-sensitive) wants a search tool (Tavily, etc.), not RAG.
|
|
15
|
+
- **Grep/file-browse tools for small or structured doc sets.** When the documents are few enough to list (a repo, a docs site, a few hundred markdown files), give the agent list/fetch/search tools and let it navigate. As an example, an agent browsing a GitHub repo can use `githubTool` (list files) plus an HTTP Request Tool against the repo contents endpoint to fetch raw text — no ingest, no embeddings, full source paths in citations.
|
|
16
|
+
|
|
17
|
+
Reach for vector RAG when there are too many documents to list, queries are semantic rather than navigational, and you need similarity-based retrieval at low latency.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Quickest start: in-memory vector store
|
|
22
|
+
|
|
23
|
+
The fastest path to a working RAG flow uses `@n8n/n8n-nodes-langchain.vectorStoreInMemory` — no external service, no provisioning, no extra credential beyond whichever embedding / chat-model provider you already use. Data is lost on workflow restart, so it's right for prototypes, learning, and tests, not production.
|
|
24
|
+
|
|
25
|
+
- **Ingest**: any trigger producing documents → Default Data Loader → Vector Store In-Memory (`mode: 'insert'`) with an Embeddings node wired into `ai_embedding`. A Form Trigger with a file-upload field is a quick way to drop in PDFs/CSVs without scripting.
|
|
26
|
+
- **Query**: Chat Trigger → Agent → Vector Store In-Memory (`mode: 'retrieve-as-tool'`), same `memoryKey` and the same embedding model as ingest.
|
|
27
|
+
|
|
28
|
+
When the data must survive restarts or scale beyond one instance, swap the in-memory node for a persistent store — the rest of the wiring stays the same.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Vector RAG: the pieces
|
|
33
|
+
|
|
34
|
+
n8n exposes the LangChain primitives as sub-nodes:
|
|
35
|
+
|
|
36
|
+
- **Document loaders** (`documentDefaultDataLoader`) — pull from sources, optionally with metadata. Wires into a vector store's `ai_document`.
|
|
37
|
+
- **Text splitters** (`textSplitter*`) — chunk into retrievable pieces. The default loader can do this inline for simple cases.
|
|
38
|
+
- **Embeddings** (`embeddingsOpenAi`, `embeddingsCohere`, …) — turn chunks into vectors. Wires into `ai_embedding` on **both** ingest and query.
|
|
39
|
+
- **Vector stores** — `vectorStoreInMemory`, `vectorStoreQdrant`, `vectorStoreSupabase` (Postgres pgvector), `vectorStorePinecone`. Each has modes: `insert` (ingest), `retrieve-as-tool` (the agent's `ai_tool` slot), and others for direct querying.
|
|
40
|
+
|
|
41
|
+
The Default Data Loader's `metadata` field is **load-bearing**: anything you want to filter or display alongside results (source URL, document type, tenant ID) goes there. Without it, results are just chunks with no provenance.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Vector RAG: two workflows
|
|
46
|
+
|
|
47
|
+
### Ingest
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
[Trigger]
|
|
51
|
+
→ [Vector Store, mode: 'insert']
|
|
52
|
+
ai_document <- [Default Data Loader (with metadata)]
|
|
53
|
+
ai_embedding <- [Embeddings]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Ingest does not have to be a tool.** Most often it's a separate scheduled workflow pre-populating the store on a cadence (e.g. nightly), or a webhook-triggered workflow. Wire it as an agent tool only when the documents change dynamically based on conversation (the agent learns something it should remember). For static or system-managed sets, a standalone workflow is simpler.
|
|
57
|
+
|
|
58
|
+
### Query
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
[Chat / webhook trigger]
|
|
62
|
+
→ [Agent]
|
|
63
|
+
ai_tool <- [Vector Store, mode: 'retrieve-as-tool']
|
|
64
|
+
ai_embedding <- [Embeddings (SAME model as ingest)]
|
|
65
|
+
ai_languageModel <- [Chat Model]
|
|
66
|
+
ai_memory <- [Memory]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Wired as `ai_tool`, the vector store becomes a tool the agent calls when it judges retrieval relevant. Wire retrieval directly into the main flow (pre-agent) only when **every** turn requires retrieval — rare in practice.
|
|
70
|
+
|
|
71
|
+
**The embedding model must match.** Whatever embedded the documents on ingest must embed the query. Mismatched models produce garbage retrieval. Change models → re-ingest.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Open decisions (verify per context)
|
|
76
|
+
|
|
77
|
+
### Vector store selection
|
|
78
|
+
|
|
79
|
+
- **In-memory** — zero ops, lost on restart. Prototypes and tests.
|
|
80
|
+
- **Qdrant** — open-source, self-hostable, fast, mature in n8n.
|
|
81
|
+
- **Postgres pgvector / Supabase** — ideal if you already run Postgres; SQL-side metadata filters and relational joins compose nicely.
|
|
82
|
+
- **Pinecone** — fully managed, per-request pricing.
|
|
83
|
+
|
|
84
|
+
### Embedding model
|
|
85
|
+
|
|
86
|
+
OpenAI `text-embedding-3-large`, Cohere `embed-v3`, and open-source models are common. Cost, dimension count, and quality differ — choose carefully upfront to avoid re-embedding.
|
|
87
|
+
|
|
88
|
+
### Retrieval-as-tool vs retrieval-before-agent
|
|
89
|
+
|
|
90
|
+
- **Retrieve-as-tool**: the agent decides when retrieval is relevant AND phrases the query itself (reformulate, decompose, expand vague wording). One extra round trip per retrieval, but fewer wasted retrievals and a better hit rate.
|
|
91
|
+
- **Retrieve-before-agent**: simpler and predictable, but pays the cost every turn AND uses the user's raw input as the query, so vague phrasing ("remind me how that thing works again?") goes straight into the search.
|
|
92
|
+
|
|
93
|
+
Tool-based composes better in multi-capability agents (retrieval is one tool among several). Always-retrieve is fine for narrow Q&A bots where every question is a knowledge-base question.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Cross-references
|
|
98
|
+
|
|
99
|
+
- Agent fundamentals → parent **SKILL.md**
|
|
100
|
+
- Wiring sub-workflows (and agentic retrieval tools) → **SUBWORKFLOW_AS_TOOL.md**
|
|
101
|
+
- Tool naming/descriptions on retrieval tools → **TOOLS.md**
|
|
102
|
+
- Data Tables as an alternative to a vector store for small structured data → **n8n-node-configuration**
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Structured output
|
|
2
|
+
|
|
3
|
+
Non-negotiable: the output parser must **parse AND retry on failure**. Without retry, one malformed model response halts the entire workflow.
|
|
4
|
+
|
|
5
|
+
The parser is the `@n8n/n8n-nodes-langchain.outputParserStructured` node, wired into the agent (or Basic LLM Chain) via the `ai_outputParser` connection.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## The pattern (node objects)
|
|
10
|
+
|
|
11
|
+
The parser, with `autoFix` and its own fixer model:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"parameters": {
|
|
16
|
+
"schemaType": "manual",
|
|
17
|
+
"inputSchema": "{ \"type\": \"object\", \"properties\": { \"score\": { \"type\": \"integer\", \"minimum\": 1, \"maximum\": 5 }, \"reason\": { \"type\": \"string\" } }, \"required\": [\"score\", \"reason\"] }",
|
|
18
|
+
"autoFix": true
|
|
19
|
+
},
|
|
20
|
+
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
|
|
21
|
+
"typeVersion": 1.3,
|
|
22
|
+
"name": "Structured Output Parser"
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Wire the parser to the agent, and a **coding-capable fixer model** to the parser:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
"Structured Output Parser": {
|
|
30
|
+
"ai_outputParser": [[{ "node": "AI Agent", "type": "ai_outputParser", "index": 0 }]]
|
|
31
|
+
},
|
|
32
|
+
"Fixer LLM": {
|
|
33
|
+
"ai_languageModel": [[{ "node": "Structured Output Parser", "type": "ai_languageModel", "index": 0 }]]
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
On the agent, set `hasOutputParser: true` so the slot is active.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Why a schema, not an example
|
|
42
|
+
|
|
43
|
+
`schemaType: 'manual'` with a real JSON Schema is the default. `jsonSchemaExample` (`schemaType: 'fromJson'`) looks easier, but an example **cannot** express:
|
|
44
|
+
|
|
45
|
+
- **Required vs optional fields** — an example is one snapshot; the parser can't tell which keys are mandatory.
|
|
46
|
+
- **Enums** — `"category": "compliance"` doesn't constrain the model to `compliance | history | risk`; it will invent new categories.
|
|
47
|
+
- **Numeric ranges** — `"score": 3` doesn't say `1-5`; the model returns `7` or `0.85` and passes.
|
|
48
|
+
- **Array constraints** — min/max items, item-type uniformity.
|
|
49
|
+
- **String formats** — email, UUID, ISO date, regex.
|
|
50
|
+
|
|
51
|
+
A schema gives the model clearer rules and the parser real validation:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"type": "object",
|
|
56
|
+
"properties": {
|
|
57
|
+
"decision": { "type": "string", "enum": ["approve", "reject", "escalate"] },
|
|
58
|
+
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
59
|
+
"reasons": {
|
|
60
|
+
"type": "array",
|
|
61
|
+
"items": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"properties": {
|
|
64
|
+
"category": { "type": "string", "enum": ["compliance", "history", "risk"] },
|
|
65
|
+
"weight": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
66
|
+
"note": { "type": "string" }
|
|
67
|
+
},
|
|
68
|
+
"required": ["category", "weight"]
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"follow_up_required": { "type": "boolean" }
|
|
72
|
+
},
|
|
73
|
+
"required": ["decision", "confidence", "reasons", "follow_up_required"]
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Reach for `fromJson` + `jsonSchemaExample` only for one-off shapes you're certain will never grow constraints. Once a field needs to be optional, enum-ed, or range-bounded, you're rewriting the parser anyway — start with the schema.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## `autoFix: true` and the fixer model
|
|
82
|
+
|
|
83
|
+
The model can produce almost-but-not-quite-valid JSON: trailing comma, missing field, wrong type, or JSON wrapped in a markdown code block. Without `autoFix`, the workflow halts. With it, the parser sends the bad output to a model with a "fix this" prompt, retries, and continues.
|
|
84
|
+
|
|
85
|
+
The fixer is wired as a **separate** sub-node into the parser's `ai_languageModel` slot. **Use a coding-capable model** (Sonnet-class or better). Reconciling broken JSON against a schema with enums, ranges, and required fields is a structured-output / coding task — a weak or generic model routinely produces another malformed retry, defeating the point and burning tokens.
|
|
86
|
+
|
|
87
|
+
When you want to customize the retry prompt, set `customizeRetryPrompt: true` and provide `prompt`. The placeholders `{instructions}`, `{completion}`, `{error}` are filled at retry time:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
Instructions:
|
|
91
|
+
--------------
|
|
92
|
+
{instructions}
|
|
93
|
+
--------------
|
|
94
|
+
Completion:
|
|
95
|
+
--------------
|
|
96
|
+
{completion}
|
|
97
|
+
--------------
|
|
98
|
+
Above, the Completion did not satisfy the constraints in the Instructions.
|
|
99
|
+
Error:
|
|
100
|
+
--------------
|
|
101
|
+
{error}
|
|
102
|
+
--------------
|
|
103
|
+
Please try again with an answer that satisfies the constraints.
|
|
104
|
+
This is a structured output parser tool in n8n. Ensure the output format is correct to pass parsing.
|
|
105
|
+
DO NOT wrap the output in a markdown code block.
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Generally, leave the retry prompt as default unless you have a specific reason to override it.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## "DO NOT wrap the output in a markdown code block"
|
|
113
|
+
|
|
114
|
+
This line is **load-bearing**. Models default to wrapping JSON in triple-backtick `json` fences, which breaks the parser. If you see parse failures on output that's clearly valid JSON inside a code block, this instruction is the fix — in both the retry prompt and, if the main model wraps aggressively, the **main** system prompt:
|
|
115
|
+
|
|
116
|
+
> When responding with structured output, return raw JSON only. DO NOT wrap in markdown code blocks. DO NOT include any prose before or after the JSON.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## System prompt + parser: belt and suspenders
|
|
121
|
+
|
|
122
|
+
The parser tells the model the schema; the system prompt should ALSO state the shape:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
## Output Format
|
|
126
|
+
Respond with a JSON object matching this exact shape:
|
|
127
|
+
{ "score": 1-5 integer, "reason": "brief explanation" }
|
|
128
|
+
|
|
129
|
+
ONLY output the JSON. No prose, no markdown wrapping.
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
It's repetition, but the model takes the system prompt seriously and reinforcement helps. The parser catches what slips through.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Common parse failures and fixes
|
|
137
|
+
|
|
138
|
+
| Symptom | Likely cause | Fix |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| "Failed to parse output" but the text looks like JSON | Wrapped in a markdown code block | Add "DO NOT wrap in markdown" to retry prompt and system prompt |
|
|
141
|
+
| Empty fields where the schema expects values | Model thinks it can omit unknowns | "Use empty string '' or null for unknown fields, never omit" |
|
|
142
|
+
| Wrong types (number as string) | Schema/example wasn't typed clearly | Use a real number in the schema, not a string |
|
|
143
|
+
| Truncated JSON (unclosed brace) | Hit max tokens mid-response | Increase max tokens, tighten the prompt to produce shorter output |
|
|
144
|
+
| Field names paraphrased ("Score" vs "score") | Schema didn't pin the name | "Field names are exactly as shown" in the system prompt |
|
|
145
|
+
| `autoFix` retries forever | Fixer model too weak for the schema | Swap in a coding-capable (Sonnet-class) fixer; tighten the retry prompt |
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## When NOT to use a parser
|
|
150
|
+
|
|
151
|
+
- **Free-form chat replies to the user** — conversational text doesn't need parsing.
|
|
152
|
+
- **Tool calls only, no final structured output** — if the user-visible output is text, skip it.
|
|
153
|
+
- **Trivial key-value extraction** — a Set node with `JSON.parse($json.output)` covers it.
|
|
154
|
+
|
|
155
|
+
The parser is for when downstream nodes must consume strict JSON.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Cross-references
|
|
160
|
+
|
|
161
|
+
- Why and where to use agents at all → parent **SKILL.md**
|
|
162
|
+
- The system-prompt half of structured output → **SYSTEM_PROMPT.md**
|
|
163
|
+
- Block Kit / adaptive cards need the manual schema even more (union types) → **CHAT_AGENT_PATTERNS.md**
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Sub-workflow as agent tool
|
|
2
|
+
|
|
3
|
+
The default agent-tool shape for anything beyond one node is the Tool Workflow node (`@n8n/n8n-nodes-langchain.toolWorkflow`). Any sub-workflow becomes a tool the agent calls, with typed inputs filled by `$fromAI()`. It composes with everything good about n8n: branching, error handling, sub-workflow reuse, native nodes, custom logic.
|
|
4
|
+
|
|
5
|
+
For the sub-workflow primitive itself (Execute Workflow Trigger inputs/outputs, stateless design, naming, search-before-build), see **n8n-subworkflows** — this reference only covers the *agent-tool* angle.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Why this is the default in n8n
|
|
10
|
+
|
|
11
|
+
In raw LangChain a tool is a function. In n8n a tool can be a whole workflow, so it can:
|
|
12
|
+
|
|
13
|
+
- Branch on input (IF / Switch).
|
|
14
|
+
- Call multiple APIs and aggregate.
|
|
15
|
+
- Have its own retries, fallbacks, error handling.
|
|
16
|
+
- Call other sub-workflows.
|
|
17
|
+
- Read/write Data Tables.
|
|
18
|
+
- Be tested independently with `n8n_test_workflow` and pinned data.
|
|
19
|
+
- Be reused across agents AND non-agent workflows.
|
|
20
|
+
|
|
21
|
+
A function-as-tool can't do most of that without growing into a workflow anyway. n8n gives you the workflow primitive directly.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## The shape: two halves
|
|
26
|
+
|
|
27
|
+
### 1. The sub-workflow side — an Execute Workflow Trigger with typed inputs
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"parameters": {
|
|
32
|
+
"workflowInputs": {
|
|
33
|
+
"values": [
|
|
34
|
+
{ "name": "imagePrompt", "type": "string" },
|
|
35
|
+
{ "name": "imageName", "type": "string" },
|
|
36
|
+
{ "name": "sessionId", "type": "string" }
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
|
41
|
+
"typeVersion": 1.1,
|
|
42
|
+
"name": "When Executed by Another Workflow"
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Each declared input becomes a parameter the caller can fill. **The trigger must be in "Define Below" mode (typed fields), not passthrough** — passthrough has no schema, so the agent has nothing to fill via `$fromAI`. Two exceptions: (a) the sub-workflow needs binary (it can't be an agent tool directly — pre-stage to storage and pass storage keys as typed string fields, see **n8n-binary-and-data**), or (b) the tool takes no inputs at all (passthrough is the only option, and the tool's only decision is whether to invoke).
|
|
47
|
+
|
|
48
|
+
Type enforcement happens on the **agent side** via the `type` argument of `$fromAI`, not at the trigger. Allowed types: `string`, `number`, `boolean`, `json`. Match them.
|
|
49
|
+
|
|
50
|
+
### 2. The Tool Workflow side — points at the sub-workflow, binds params
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"parameters": {
|
|
55
|
+
"description": "Use to create a new image from a prompt OR edit an existing image. Pass imageName as the storage key (e.g. \"abc123.png\") to edit; leave empty to generate from scratch. Returns { imageUrl, imageKey }.",
|
|
56
|
+
"workflowId": { "__rl": true, "value": "<sub-workflow-id>", "mode": "list" },
|
|
57
|
+
"workflowInputs": {
|
|
58
|
+
"mappingMode": "defineBelow",
|
|
59
|
+
"value": {
|
|
60
|
+
"imagePrompt": "={{ $fromAI('imagePrompt', 'Detailed prompt describing the desired image', 'string') }}",
|
|
61
|
+
"imageName": "={{ $fromAI('imageName', 'Storage key of an existing image to edit, or empty for new generation', 'string') }}",
|
|
62
|
+
"sessionId": "={{ $('Chat Trigger').first().json.sessionId }}"
|
|
63
|
+
},
|
|
64
|
+
"schema": [
|
|
65
|
+
{ "id": "imagePrompt", "displayName": "imagePrompt", "type": "string", "display": true },
|
|
66
|
+
{ "id": "imageName", "displayName": "imageName", "type": "string", "display": true },
|
|
67
|
+
{ "id": "sessionId", "displayName": "sessionId", "type": "string", "display": true }
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
|
|
72
|
+
"typeVersion": 2.2,
|
|
73
|
+
"name": "Generate or edit image"
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Wire it into the agent with `ai_tool`:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
"Generate or edit image": {
|
|
81
|
+
"ai_tool": [[{ "node": "AI Agent", "type": "ai_tool", "index": 0 }]]
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The mapping is per-input:
|
|
86
|
+
|
|
87
|
+
- **Agent-filled**: `={{ $fromAI('paramName', 'description', 'string') }}` — the agent decides.
|
|
88
|
+
- **Plumbed**: `={{ $('SourceNode').first().json.field }}` — your workflow fills it.
|
|
89
|
+
|
|
90
|
+
The `sessionId` line is critical: it is **NOT** an agent decision. Plumb it from the trigger so memory and session-keyed work stay consistent. **Never put `sessionId` behind `$fromAI`** — the agent will fabricate a UUID.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## What the agent sees (and doesn't)
|
|
95
|
+
|
|
96
|
+
The agent sees the tool's **name** (the Tool Workflow node's name) and **description** (a parameter on the node) — both follow the **TOOLS.md** rules: specific, API-doc style, treated as prompt.
|
|
97
|
+
|
|
98
|
+
It does **not** see: the sub-workflow internals, the sub-workflow's own name, or plumbed values like `sessionId`. Only `$fromAI` parameters appear in the tool schema. So you can refactor the sub-workflow heavily without changing what the agent sees.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Worked example: one tool, two modes
|
|
103
|
+
|
|
104
|
+
Goal: an agent that can generate or edit images. Both share most logic; they differ only in whether they download an existing image first.
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
[Execute Workflow Trigger: { imagePrompt, imageName, sessionId }]
|
|
108
|
+
↓
|
|
109
|
+
[Crypto: hash for new filename]
|
|
110
|
+
↓
|
|
111
|
+
[IF: imageName empty?]
|
|
112
|
+
├── empty (generate) → [Gemini: generate] ──┐
|
|
113
|
+
└── not empty (edit): │
|
|
114
|
+
[S3: Download by imageName] │
|
|
115
|
+
↓ │
|
|
116
|
+
[Gemini: edit with downloaded binary] ───────┤
|
|
117
|
+
↓
|
|
118
|
+
[S3: Upload result]
|
|
119
|
+
↓
|
|
120
|
+
[Set: { imageUrl, imageKey }]
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The agent picks the mode by what it puts in `imageName`. Two near-identical tools would have made selection harder — collapse them.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Patterns inside the sub-workflow
|
|
128
|
+
|
|
129
|
+
### Return a stable shape (it's a contract)
|
|
130
|
+
|
|
131
|
+
The caller receives whatever the last node outputs. Pick a shape and keep it across modes:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{ "imageUrl": "https://...", "imageKey": "abc123.png" }
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Don't sometimes return `{ url, key }` and other times `{ result: { url, key } }`. The output shape is a contract every caller depends on — agents read it as part of the prompt, deterministic callers wire downstream nodes to specific paths. Drift breaks callers silently.
|
|
138
|
+
|
|
139
|
+
For calls that fail "expectedly" (search with no results), return a branchable shape:
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{ "ok": false, "error": "no_results", "message": "No matches found for query" }
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### When to throw instead: Stop and Error
|
|
146
|
+
|
|
147
|
+
For unexpected-but-handled errors (auth failure, upstream down, unrecoverable input), use a `Stop and Error` node with a detailed message. It propagates as a thrown error: agents see a tool error and can retry/switch/report; deterministic callers catch it via `onError: 'continueErrorOutput'`. Pick this over `{ ok: false }` when the outcome is a true error, not a normal branch. For the full error story (4xx/5xx mapping, retries, error workflows) → **n8n-error-handling**.
|
|
148
|
+
|
|
149
|
+
### Wire `onError: 'continueErrorOutput'` on fallible nodes
|
|
150
|
+
|
|
151
|
+
Inside the sub-workflow, fallible nodes (HTTP, S3, DB) should set `onError: 'continueErrorOutput'` and route to a clean error response, so both agent and deterministic callers receive a structured error instead of a silent halt.
|
|
152
|
+
|
|
153
|
+
### Treat the input contract as an API and document it
|
|
154
|
+
|
|
155
|
+
The Execute Workflow Trigger's declared inputs ARE this tool's API. Document them in the sub-workflow's `description`:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
Generates or edits an image.
|
|
159
|
+
Inputs:
|
|
160
|
+
imagePrompt (string, required): detailed image description.
|
|
161
|
+
imageName (string, optional): storage key of existing image to edit. Empty = new generation.
|
|
162
|
+
sessionId (string, required): chat session ID, used for storage keying.
|
|
163
|
+
Returns:
|
|
164
|
+
{ imageUrl, imageKey }
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Keep tool sub-workflows discoverable
|
|
168
|
+
|
|
169
|
+
Name them with a standard prefix (`Subworkflow:` or domain-specific). The Tool Workflow node references them by ID (stable), but humans browse the UI by name.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Testing the sub-workflow independently
|
|
174
|
+
|
|
175
|
+
A sub-workflow tool can be tested without the agent:
|
|
176
|
+
|
|
177
|
+
1. Pin representative input on the Execute Workflow Trigger.
|
|
178
|
+
2. `n8n_test_workflow` runs it with that pinned data.
|
|
179
|
+
3. Verify the output shape matches what the agent will receive.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## When NOT to use sub-workflow as tool
|
|
184
|
+
|
|
185
|
+
- **Simple one-node wrappers** — "call this endpoint and return" is shorter as an HTTP Request Tool.
|
|
186
|
+
- **One-off code-only logic specific to this agent** — a few lines of pure JS/Python that exist nowhere else work fine as a Custom Code Tool (`.toolCode`, see **n8n-code-tool**). Decision rule: reusable business logic → sub-workflow; one-off agent-specific transform → Code Tool.
|
|
187
|
+
- **Capabilities that already exist as native tool nodes** — don't wrap `slackTool` in a sub-workflow.
|
|
188
|
+
|
|
189
|
+
For everything else, sub-workflow as tool is the default.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Cross-references
|
|
194
|
+
|
|
195
|
+
- The four tool types overview → **TOOLS.md**
|
|
196
|
+
- How `$fromAI` descriptions affect behavior → **TOOLS.md** "`$fromAI()`"
|
|
197
|
+
- The sub-workflow primitive (stateless design, naming, I/O) → **n8n-subworkflows**
|
|
198
|
+
- Passing binary into tools → **n8n-binary-and-data**
|
|
199
|
+
- The Custom Code Tool exception → **n8n-code-tool**
|