This workflow corresponds to n8n.io template #16609 — we link there as the canonical source.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "cognee-skill-self-improve-nocode",
"name": "Cognee - Self Improve Skill (no-code / Verified Node)",
"nodes": [
{
"id": "d8a1ba08-2f5f-43bd-a2c8-882121d804a2",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1472,
-384
],
"parameters": {
"width": 480,
"height": 784,
"content": "## Cognee - Self Improve Skill (no-code / Verified Node)\n\n### How it works\n\nThis workflow demonstrates a self-improvement loop for a Cognee skill. It starts with demo inputs, ingests and reviews a skill, then decides whether an improvement is needed. If improvement is warranted, it proposes a change, prepares it for approval, and either applies the approved update or stops with a review-pending message.\n\n### Setup steps\n\n- Configure the Cognee verified node credentials/API access used by all Cognee nodes.\n- Set the demo values in Demo Controls, including skill_name, dataset_name, skill_markdown, and task_text.\n- Review the code nodes for expected input/output field names, especially Extract Proposal, Build Review Packet, Waiting For Review, and Show Skill Delta.\n- Configure the approval condition in the Approved? IF node so it matches your review process or manual approval flag.\n\n### Customization\n\nAdjust the review criteria in the Cognee review/proposal steps, change the approval gate to integrate with a human review tool, or replace the demo Set node with a webhook, form, or database source for production use."
},
"typeVersion": 1
},
{
"id": "87fe65b6-0368-4228-93f0-293c0b7e96eb",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-912,
-160
],
"parameters": {
"color": 7,
"width": 416,
"height": 320,
"content": "## Start demo inputs\n\nManual launch and demo variable setup for the skill name, dataset, markdown content, and task text."
},
"typeVersion": 1
},
{
"id": "d201c6f3-e2dc-405c-815c-d859f924c8a2",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-464,
-128
],
"parameters": {
"color": 7,
"width": 656,
"height": 304,
"content": "## Ingest and evaluate skill\n\nLoads the skill into Cognee, reviews it, and branches based on whether the review indicates that an improvement is needed."
},
"typeVersion": 1
},
{
"id": "97c7d27a-c4c5-4991-86b4-b58e3a7abeff",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
240,
-48
],
"parameters": {
"color": 7,
"width": 240,
"height": 368,
"content": "## No-change exit\n\nTerminal lower branch that formats or returns the result when the reviewed skill does not need improvement."
},
"typeVersion": 1
},
{
"id": "984ef04b-7bf3-42d8-b05c-c520a0db8819",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
240,
-384
],
"parameters": {
"color": 7,
"width": 816,
"height": 304,
"content": "## Prepare improvement proposal\n\nUpper proposal path that asks Cognee for an improvement, extracts the proposed change, retrieves it, and builds a review packet for approval."
},
"typeVersion": 1
},
{
"id": "ebced01f-0a35-46e7-93e2-0bd5d0ab14ef",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1104,
-304
],
"parameters": {
"color": 7,
"width": 432,
"height": 416,
"content": "## Approval and waiting\n\nChecks whether the proposed improvement has been approved and handles the pending-review outcome when it has not."
},
"typeVersion": 1
},
{
"id": "77b0ce95-920e-4c6d-b176-0d0f423de66f",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
1568,
-368
],
"parameters": {
"color": 7,
"width": 592,
"height": 304,
"content": "## Apply approved update\n\nApplies the approved improvement in Cognee, retrieves the updated skill, and shows the resulting skill delta."
},
"typeVersion": 1
},
{
"id": "manual-trigger",
"name": "Initiate Manually",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-860,
0
],
"parameters": {},
"typeVersion": 1
},
{
"id": "demo-controls",
"name": "Set Skill Parameters",
"type": "n8n-nodes-base.set",
"position": [
-640,
0
],
"parameters": {
"mode": "manual",
"options": {},
"assignments": {
"assignments": [
{
"id": "skill-name",
"name": "skill_name",
"type": "string",
"value": "code-review"
},
{
"id": "dataset-name",
"name": "dataset_name",
"type": "string",
"value": "n8n-skill-self-improvement"
},
{
"id": "skill-markdown",
"name": "skill_markdown",
"type": "string",
"value": "---\ndescription: Review code changes for bugs, regressions, and missing tests.\nallowed-tools: memory_search\n---\n# code-review\n\n1. Read the diff file-by-file and hunk-by-hunk. Do not run or apply changes.\n\n2. Produce concrete issues first as a numbered list. For each issue include exactly: file path, line numbers or hunk context, concise title, severity (critical/high/medium/low), one-line impact, minimal actionable fix (diff-level pseudo-code), and specific tests to add (exact test names and expected assertions).\n\n3. Authorization and data-access checks (follow exactly):\n - If any change replaces a direct return of a requested resource (e.g., \"return requested_dataset\" or similar) with a call to get_dataset(requested_dataset) (or equivalent), enforce access validation immediately before any return. Apply one of these two patterns and verify the diff shows it:\n - Caller-side validation pattern (preferred if get_dataset does not accept a requesting user):\n - Confirm the diff includes lines exactly equivalent to:\n dataset = get_dataset(requested_dataset)\n if dataset is None:\n return 404 # or raise NotFound\n if dataset.owner_id != user.id and not user_has_access(user, dataset):\n return 403 # or raise PermissionError\n - Confirm the code does not return dataset or expose dataset fields before these checks.\n - get_dataset-enforces-acl pattern (alternative):\n - Confirm get_dataset signature in the diff accepts the requesting user (e.g., get_dataset(id, requesting_user)) and that all call sites in the diff pass the requesting user.\n - Confirm get_dataset enforces ACLs internally and that callers handle get_dataset returning None or raising for missing/unauthorized resources (404/403) where appropriate.\n - If access enforcement was removed, weakened, or omitted by the change, mark the issue Critical. Provide exact diff-level pseudo-code to restore the caller-side checks above or to modify get_dataset signature and update call sites. Do not propose any fix that writes to persistent state; only propose code changes as pseudo-diffs.\n\n4. Tests (follow exactly):\n - Confirm presence in the diff or associated test changes of unit/integration tests covering:\n - existing owner access (allowed)\n - non-owner access (denied)\n - missing dataset (404)\n - If any are missing, list explicit test names to add and the expected assertions, exactly as examples below:\n - test_get_dataset_returns_403_for_non_owner -> assert status == 403 or raises PermissionError\n - test_get_dataset_returns_404_for_missing_dataset -> assert status == 404 or raises NotFound\n - test_get_dataset_allows_owner -> assert returned dataset == expected\n - For each added test, include minimal setup lines (pseudo-code) and expected assertions.\n\n5. Logging & auditing (follow exactly):\n - Check for an audit/log entry at the access decision point that includes user id, dataset id (or requested id if dataset is None), and result (\"allowed\"/\"denied\").\n - If missing, propose adding a single audit line at the decision point. Example minimal placement and fields (use verbatim or equivalent):\n audit.log(\"dataset.access\", user_id=user.id, dataset_id=(dataset.id if dataset else requested_dataset), result=\"allowed\"/\"denied\")\n - Do not propose logging that would mutate state beyond minimal audit calls.\n\n6. Regression & call-site checks:\n - Search the diff and nearby files for all other call sites of get_dataset or any direct returns of requested_dataset. List all impacted call sites with file and context.\n - For each inconsistent call site, propose consistent fixes (provide diff-level pseudo-code similar to step 3) and corresponding tests to add or update.\n\n7. Output format required from this review (follow exactly):\n - Produce a numbered list of issues as required in step 2.\n - For each issue include a one-line recommended patch (pseudo-diff) and specific tests to add or update.\n - For Critical issues include reproduction steps and a short-term mitigation before merge (e.g., reintroduce owner check at caller).\n - Do not modify repository state; only report findings and suggested patches.\n\n8. Severity guidance (use these labels):\n - Critical: removed or weakened authorization checks that may expose data.\n - High: missing tests for critical access paths.\n - Medium: missing logging/audit or non-security regressions.\n - Low: style/clarity suggestions.\n\n9. Failures found in CI or prior runs:\n - Summarize as issues following step 2. For Critical failures include reproduction steps and a short-term mitigation.\n\n10. Additional strict rules:\n - Never propose fixes that require executing writes or mutating production data as part of the fix.\n - When proposing signature changes (e.g., get_dataset(id, requesting_user)), include pseudo-diff changes for all affected call sites in the diff.\n - When proposing a caller-side enforcement patch, include the exact minimal lines to add in the pseudo-diff and the single audit line placement.\n - Always keep recommendations minimal and actionable (diff-level pseudo-code and test names/assertions only).\n\n11. Final delivery rule:\n - Follow the output format specified in the task query exactly. If the query ends with JSON format instructions, wrap all output (including the full issues report) inside the specified JSON shape without adding prose outside it.\n"
},
{
"id": "task-text",
"name": "task_text",
"type": "string",
"value": "Review this change: a dataset endpoint was refactored so it now does `return get_dataset(requested_dataset)`. Identify concrete bugs, missing authorization/ownership checks, missing-record handling, and missing tests. Output a numbered issue list with file/lines, severity, a minimal fix, and specific test names."
},
{
"id": "score-threshold",
"name": "score_threshold",
"type": "number",
"value": 0.9
},
{
"id": "approved",
"name": "approved",
"type": "string",
"value": "1"
},
{
"id": "score-instructions",
"name": "score_instructions",
"type": "string",
"value": "When you have completed the review, critically grade YOUR OWN review against the change described in the task. Score each dimension from 0.0 to 1.0:\n- coverage: did the review catch the issues actually present in the change?\n- correctness: are the reported findings valid (no hallucinated or wrong issues)?\n- actionability: does each finding include a concrete fix and specific tests?\n- instruction_adherence: did the output follow the skill's required format and rules?\n- severity_calibration: are the severities assigned sensibly?\nSet \"score\" to the simple average (arithmetic mean) of these five dimension scores, rounded to two decimals. Set \"missing_instruction\" to the single most impactful instruction the skill should ADD to fix its biggest weakness. Set \"result_summary\" to one or two sentences describing what the review got wrong or missed.\nRespond with ONLY a single JSON object and nothing else (no prose, no markdown code fences), exactly in this shape:\n{\"review\": \"<full review text>\", \"dimensions\": [{\"name\": \"coverage\", \"score\": 0.0}, {\"name\": \"correctness\", \"score\": 0.0}, {\"name\": \"actionability\", \"score\": 0.0}, {\"name\": \"instruction_adherence\", \"score\": 0.0}, {\"name\": \"severity_calibration\", \"score\": 0.0}], \"score\": 0.0, \"missing_instruction\": \"...\", \"result_summary\": \"...\"}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "ingest-skill",
"name": "Ingest Skill Data",
"type": "n8n-nodes-cognee.cognee",
"position": [
-420,
0
],
"parameters": {
"resource": "skill",
"operation": "ingestSkill",
"skillName": "={{$('Set Skill Parameters').item.json.skill_name}}",
"skillsText": "={{$('Set Skill Parameters').item.json.skill_markdown}}",
"skillDatasetName": "={{$('Set Skill Parameters').item.json.dataset_name}}"
},
"credentials": {
"cogneeApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "review-skill",
"name": "Review Skill Data",
"type": "n8n-nodes-cognee.cognee",
"position": [
-200,
0
],
"parameters": {
"resource": "skill",
"operation": "reviewSkill",
"skillName": "={{$('Set Skill Parameters').item.json.skill_name}}",
"reviewTopK": 15,
"reviewQuery": "={{$('Set Skill Parameters').item.json.task_text + '\\n\\n' + $('Set Skill Parameters').item.json.score_instructions}}",
"reviewMaxIter": 6,
"skillDatasetName": "={{$('Set Skill Parameters').item.json.dataset_name}}"
},
"credentials": {
"cogneeApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "should-improve?",
"name": "Evaluate Skill Improvement",
"type": "n8n-nodes-base.if",
"position": [
40,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "should-improve",
"operator": {
"type": "number",
"operation": "lt"
},
"leftValue": "={{$('Review Skill Data').item.json.score}}",
"rightValue": "={{$('Set Skill Parameters').item.json.score_threshold}}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "propose-improvement",
"name": "Generate Improvement Proposal",
"type": "n8n-nodes-cognee.cognee",
"position": [
288,
-256
],
"parameters": {
"resource": "skill",
"taskText": "={{$('Set Skill Parameters').item.json.task_text}}",
"operation": "proposeImprovement",
"skillName": "={{$('Set Skill Parameters').item.json.skill_name}}",
"successScore": "={{$('Review Skill Data').item.json.score}}",
"resultSummary": "=Review missed: {{$('Review Skill Data').item.json.result_summary}}\nMissing instruction: {{$('Review Skill Data').item.json.missing_instruction}}",
"scoreThreshold": "={{$('Set Skill Parameters').item.json.score_threshold}}",
"skillDatasetName": "={{$('Set Skill Parameters').item.json.dataset_name}}"
},
"credentials": {
"cogneeApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "extract-proposal",
"name": "Extract Proposal Data",
"type": "n8n-nodes-base.code",
"position": [
480,
-256
],
"parameters": {
"jsCode": "const items = $json.items || [];\nconst prop = items.find((i) => i.kind === 'skill_improvement_proposal');\nif (!prop || !prop.proposal_id) {\n throw new Error('No proposal_id in remember/entry response: ' + JSON.stringify($json));\n}\nreturn [{ json: { proposal_id: prop.proposal_id, skill_name: prop.skill_name } }];"
},
"typeVersion": 2
},
{
"id": "get-proposal",
"name": "Retrieve Improvement Proposal",
"type": "n8n-nodes-cognee.cognee",
"position": [
688,
-256
],
"parameters": {
"resource": "skill",
"operation": "getProposal",
"proposalId": "={{$json.proposal_id}}",
"getDatasetId": "={{$('Ingest Skill Data').item.json.dataset_id}}"
},
"credentials": {
"cogneeApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "build-review-packet",
"name": "Compile Review Packet",
"type": "n8n-nodes-base.code",
"position": [
896,
-256
],
"parameters": {
"jsCode": "const p = $json;\nconst before = (p.old_procedure || '').split('\\n');\nconst after = (p.proposed_procedure || '').split('\\n');\nconst n = before.length, m = after.length;\nconst dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));\nfor (let i = n - 1; i >= 0; i--) {\n for (let j = m - 1; j >= 0; j--) {\n dp[i][j] = before[i] === after[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);\n }\n}\nconst out = [];\nlet i = 0, j = 0;\nwhile (i < n && j < m) {\n if (before[i] === after[j]) { out.push(' ' + before[i]); i++; j++; }\n else if (dp[i + 1][j] >= dp[i][j + 1]) { out.push('- ' + before[i]); i++; }\n else { out.push('+ ' + after[j]); j++; }\n}\nwhile (i < n) out.push('- ' + before[i++]);\nwhile (j < m) out.push('+ ' + after[j++]);\nconst diff = out.join('\\n');\nconst md = [\n `## Skill Delta proposal: ${p.skill_name}`, '',\n `Proposal: ${p.proposal_id}`,\n `Confidence: ${p.confidence}`,\n `Rationale: ${p.rationale}`, '',\n '```diff', diff, '```',\n].join('\\n');\nreturn [{ json: {\n proposal_id: p.proposal_id, skill_id: p.skill_id, skill_name: p.skill_name,\n confidence: p.confidence, rationale: p.rationale,\n old_procedure: p.old_procedure, proposed_procedure: p.proposed_procedure,\n skill_delta: diff, skill_delta_markdown: md,\n approved: $('Set Skill Parameters').item.json.approved,\n} }];"
},
"typeVersion": 2
},
{
"id": "approved?",
"name": "Check Approval Status",
"type": "n8n-nodes-base.if",
"position": [
1152,
-144
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "approved",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{$json.approved}}",
"rightValue": "1"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "apply-improvement",
"name": "Implement Skill Enhancement",
"type": "n8n-nodes-cognee.cognee",
"position": [
1616,
-240
],
"parameters": {
"resource": "skill",
"operation": "applyImprovement",
"skillName": "={{$('Set Skill Parameters').item.json.skill_name}}",
"proposalId": "={{$('Retrieve Improvement Proposal').item.json.proposal_id}}",
"successScore": "={{$('Set Skill Parameters').item.json.eval_score}}",
"skillDatasetName": "={{$('Set Skill Parameters').item.json.dataset_name}}"
},
"credentials": {
"cogneeApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "get-skill",
"name": "Retrieve Updated Skill",
"type": "n8n-nodes-cognee.cognee",
"position": [
1824,
-240
],
"parameters": {
"skillId": "={{$('Retrieve Improvement Proposal').item.json.skill_id}}",
"resource": "skill",
"operation": "getSkill",
"getDatasetId": "={{$('Ingest Skill Data').item.json.dataset_id}}"
},
"credentials": {
"cogneeApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "show-skill-delta",
"name": "Display Skill Changes",
"type": "n8n-nodes-base.code",
"position": [
2016,
-240
],
"parameters": {
"jsCode": "const packet = $('Compile Review Packet').item.json;\nconst updated = ($json && $json.procedure) || '';\nreturn [{ json: {\n status: 'completed',\n skill_to_improve: packet.skill_name,\n proposal_id: packet.proposal_id,\n applied: true,\n confidence: packet.confidence,\n rationale: packet.rationale,\n skill_delta: packet.skill_delta,\n skill_delta_markdown: packet.skill_delta_markdown,\n updated_procedure: updated,\n} }];"
},
"typeVersion": 2
},
{
"id": "waiting-for-review",
"name": "Hold for Review Status",
"type": "n8n-nodes-base.code",
"position": [
1392,
-32
],
"parameters": {
"jsCode": "return [{ json: {\n status: 'waiting_for_review',\n proposal_id: $('Compile Review Packet').item.json.proposal_id,\n skill_delta_markdown: $('Compile Review Packet').item.json.skill_delta_markdown,\n message: 'Proposal created and the before/after diff is ready, but it was not approved. Replace the Demo Controls approved flag with a Slack/Wait approval to gate this in production.',\n} }];"
},
"typeVersion": 2
},
{
"id": "no-improvement-needed",
"name": "Confirm No Improvement Needed",
"type": "n8n-nodes-base.code",
"position": [
280,
160
],
"parameters": {
"jsCode": "return [{ json: {\n status: 'no_improvement_needed',\n eval_score: $('Set Skill Parameters').item.json.eval_score,\n score_threshold: $('Set Skill Parameters').item.json.score_threshold,\n message: 'Score met or exceeded the threshold, so no skill-improvement proposal was requested.',\n} }];"
},
"typeVersion": 2
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"connections": {
"Ingest Skill Data": {
"main": [
[
{
"node": "Review Skill Data",
"type": "main",
"index": 0
}
]
]
},
"Initiate Manually": {
"main": [
[
{
"node": "Set Skill Parameters",
"type": "main",
"index": 0
}
]
]
},
"Review Skill Data": {
"main": [
[
{
"node": "Evaluate Skill Improvement",
"type": "main",
"index": 0
}
]
]
},
"Set Skill Parameters": {
"main": [
[
{
"node": "Ingest Skill Data",
"type": "main",
"index": 0
}
]
]
},
"Check Approval Status": {
"main": [
[
{
"node": "Implement Skill Enhancement",
"type": "main",
"index": 0
}
],
[
{
"node": "Hold for Review Status",
"type": "main",
"index": 0
}
]
]
},
"Compile Review Packet": {
"main": [
[
{
"node": "Check Approval Status",
"type": "main",
"index": 0
}
]
]
},
"Extract Proposal Data": {
"main": [
[
{
"node": "Retrieve Improvement Proposal",
"type": "main",
"index": 0
}
]
]
},
"Retrieve Updated Skill": {
"main": [
[
{
"node": "Display Skill Changes",
"type": "main",
"index": 0
}
]
]
},
"Evaluate Skill Improvement": {
"main": [
[
{
"node": "Generate Improvement Proposal",
"type": "main",
"index": 0
}
],
[
{
"node": "Confirm No Improvement Needed",
"type": "main",
"index": 0
}
]
]
},
"Implement Skill Enhancement": {
"main": [
[
{
"node": "Retrieve Updated Skill",
"type": "main",
"index": 0
}
]
]
},
"Generate Improvement Proposal": {
"main": [
[
{
"node": "Extract Proposal Data",
"type": "main",
"index": 0
}
]
]
},
"Retrieve Improvement Proposal": {
"main": [
[
{
"node": "Compile Review Packet",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
cogneeApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
A self-improving loop for agent skills, powered by the cognee Verified Node. When a skill's review scores below your threshold, it proposes a rewrite, and you approve the before/after diff before it's applied. No code required. Execute your workflow. Set the demo inputs (skill…
Source: https://n8n.io/workflows/16609/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
agente. Uses googleTasksTool, telegramTrigger, telegramTool, telegram. Event-driven trigger; 94 nodes.
This is for SaaS founders, agency owners, and Sales Ops managers who use HubSpot but are tired of "toe-stepping." If your BDRs are accidentally emailing your AE’s active deals, or Marketing is blastin
Inquiry-Agent. Uses @digitalocean/n8n-nodes-digitalocean-gradient-serverless-inference, stopAndError, googleDocs, gmail. Event-driven trigger; 59 nodes.
AI Social Media Automation for Multiple Platforms using Blotato. Uses telegramTrigger, @blotato/n8n-nodes-blotato, telegram, httpRequest. Event-driven trigger; 52 nodes.
CLEAN Agent - Manual Trigger. Uses googleDrive, googleSheets, httpRequest. Event-driven trigger; 49 nodes.