This workflow follows the HTTP Request → Slack recipe pattern — see all workflows that pair these two integrations.
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 →
{
"name": "Obsidian \u2192 Aragora \u2192 Linear Pipeline",
"description": "Complete workflow: Watch Obsidian vault for #ready tagged notes, run multi-agent debate via Aragora, create Linear issues from decisions, write receipts back to Obsidian.",
"version": "1.0.0",
"nodes": [
{
"id": "1",
"name": "Obsidian Vault Watcher",
"type": "n8n-nodes-base.localFileTrigger",
"position": [
100,
300
],
"parameters": {
"path": "={{ $env.OBSIDIAN_VAULT_PATH }}",
"events": [
"change"
],
"options": {
"recursive": true,
"ignored": "**/.obsidian/**,**/templates/**"
}
},
"notes": "Watches the Obsidian vault for file changes. Configure OBSIDIAN_VAULT_PATH environment variable."
},
{
"id": "2",
"name": "Filter Ready Notes",
"type": "n8n-nodes-base.filter",
"position": [
300,
300
],
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json.path }}",
"operation": "endsWith",
"value2": ".md"
}
]
}
}
},
{
"id": "3",
"name": "Read Note Content",
"type": "n8n-nodes-base.readBinaryFiles",
"position": [
500,
300
],
"parameters": {
"fileSelector": "={{ $json.path }}"
}
},
{
"id": "4",
"name": "Parse Frontmatter",
"type": "n8n-nodes-base.code",
"position": [
700,
300
],
"parameters": {
"jsCode": "// Parse YAML frontmatter from markdown\nconst content = $input.first().binary.data.toString('utf-8');\n\nlet frontmatter = {};\nlet body = content;\n\nif (content.startsWith('---')) {\n const parts = content.split('---');\n if (parts.length >= 3) {\n const yaml = parts[1].trim();\n body = parts.slice(2).join('---').trim();\n \n // Simple YAML parsing\n yaml.split('\\n').forEach(line => {\n const [key, ...valueParts] = line.split(':');\n if (key && valueParts.length) {\n const value = valueParts.join(':').trim();\n frontmatter[key.trim()] = value.replace(/^[\"']|[\"']$/g, '');\n }\n });\n }\n}\n\n// Check for #ready tag\nconst hasReadyTag = body.includes('#ready') || \n (frontmatter.tags && frontmatter.tags.includes('ready'));\n\nreturn {\n json: {\n path: $input.first().json.path,\n filename: $input.first().json.path.split('/').pop(),\n frontmatter,\n body,\n hasReadyTag,\n title: frontmatter.title || $input.first().json.path.split('/').pop().replace('.md', '')\n }\n};"
}
},
{
"id": "5",
"name": "Check Ready Tag",
"type": "n8n-nodes-base.if",
"position": [
900,
300
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $json.hasReadyTag }}",
"value2": true
}
]
}
}
},
{
"id": "6",
"name": "Launch Aragora Debate",
"type": "n8n-nodes-base.httpRequest",
"position": [
1100,
200
],
"parameters": {
"method": "POST",
"url": "={{ $env.ARAGORA_API_URL }}/api/v2/debates",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer {{ $env.ARAGORA_API_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "task",
"value": "={{ $json.title }}: {{ $json.body.substring(0, 500) }}"
},
{
"name": "agents",
"value": "[\"claude\", \"gpt-4\", \"gemini\"]"
},
{
"name": "rounds",
"value": "3"
},
{
"name": "evidence_sources",
"value": "[\"knowledge_mound\"]"
},
{
"name": "metadata",
"value": "={{ JSON.stringify({ obsidian_path: $json.path, obsidian_title: $json.title }) }}"
}
]
},
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"notes": "Launches multi-agent debate via Aragora API. Configure ARAGORA_API_URL and ARAGORA_API_KEY."
},
{
"id": "7",
"name": "Wait for Debate Completion",
"type": "n8n-nodes-base.httpRequest",
"position": [
1300,
200
],
"parameters": {
"method": "POST",
"url": "={{ $env.ARAGORA_API_URL }}/api/v1/debates/{{ $json.debate_id }}/decision-integrity",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer {{ $env.ARAGORA_API_KEY }}"
}
]
},
"options": {
"response": {
"response": {
"responseFormat": "json"
}
},
"retry": {
"maxRetries": 10,
"retryInterval": 5000,
"retryOnStatus": [
202
]
}
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"include_receipt\": true,\n \"include_plan\": true,\n \"include_context\": false,\n \"plan_strategy\": \"single_task\",\n \"notify_origin\": false\n}"
},
"notes": "Builds a decision integrity package. Retries until available."
},
{
"id": "8",
"name": "Create Linear Issue",
"type": "n8n-nodes-base.httpRequest",
"position": [
1500,
200
],
"parameters": {
"method": "POST",
"url": "https://api.linear.app/graphql",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "{{ $env.LINEAR_API_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"query\": \"mutation CreateIssue($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id identifier url title } } }\",\n \"variables\": {\n \"input\": {\n \"teamId\": \"{{ $env.LINEAR_TEAM_ID }}\",\n \"title\": \"[Decision] {{ $node['Parse Frontmatter'].json.title }}\",\n \"description\": \"## Decision Summary\n\n{{ $json.receipt?.verdict_reasoning || $json.receipt?.input_summary || 'Decision reached' }}\n\n### Consensus\n- **Reached**: {{ $json.receipt?.consensus_proof?.reached ? 'Yes' : 'No' }}\n- **Confidence**: {{ Math.round(($json.receipt?.consensus_proof?.confidence ?? $json.receipt?.confidence ?? 0) * 100) }}%\n\n### Source\n- Obsidian Note: `{{ $node['Parse Frontmatter'].json.path }}`\n- Debate ID: `{{ $json.debate_id || $json.receipt?.gauntlet_id || 'unknown' }}`\n\n---\n*Generated by Aragora Decision Engine*\",\n \"priority\": {{ ($json.receipt?.consensus_proof?.confidence ?? $json.receipt?.confidence ?? 0) > 0.8 ? 2 : 3 }},\n \"labelIds\": [\"{{ $env.LINEAR_DECISION_LABEL_ID }}\"]\n }\n }\n}",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"notes": "Creates Linear issue from debate decision. Configure LINEAR_API_KEY, LINEAR_TEAM_ID, LINEAR_DECISION_LABEL_ID."
},
{
"id": "9",
"name": "Write Receipt to Obsidian",
"type": "n8n-nodes-base.code",
"position": [
1700,
200
],
"parameters": {
"jsCode": "const fs = require('fs');\nconst path = require('path');\n\nconst packageData = $input.first().json || {};\nconst receipt = packageData.receipt || {};\nconst plan = packageData.plan || {};\nconst originalNote = $node['Parse Frontmatter'].json;\nconst linearIssue = $node['Create Linear Issue'].json.data?.issueCreate?.issue;\n\nconst debateId = packageData.debate_id || receipt.gauntlet_id || receipt.debate_id || 'unknown';\nconst consensusReached = (receipt.consensus_proof && receipt.consensus_proof.reached !== undefined)\n ? receipt.consensus_proof.reached\n : ['PASS', 'CONDITIONAL'].includes(String(receipt.verdict || '').toUpperCase());\nconst confidence = (receipt.consensus_proof && receipt.consensus_proof.confidence !== undefined)\n ? receipt.consensus_proof.confidence\n : (receipt.confidence || 0);\nconst decisionSummary = receipt.verdict_reasoning || receipt.input_summary || 'Decision reached through multi-agent deliberation.';\nconst planTasks = Array.isArray(plan.tasks) ? plan.tasks : [];\n\nconst planSection = planTasks.length > 0\n ? `## Implementation Plan\n\n${planTasks.map(t => `- [ ] ${t.id || ''}: ${t.description || ''}${t.complexity ? ` (${t.complexity})` : ''}${t.files && t.files.length ? ` \u2014 Files: ${t.files.join(', ')}` : ''}`).join('\\n')}`\n : '## Implementation Plan\n\n_No implementation tasks generated_';\n\nconst receiptContent = `---\ntitle: \"Decision Integrity - ${originalNote.title}\"\ndate: ${new Date().toISOString().split('T')[0]}\ntags:\n - decision\n - aragora\n - integrity\naragora_id: ${debateId}\ndebate_id: ${debateId}\nconsensus: ${consensusReached}\nconfidence: ${confidence}\nreceipt_id: ${receipt.receipt_id || 'N/A'}\nplan_task_count: ${planTasks.length}\nlinear_issue: ${linearIssue?.identifier || 'N/A'}\nrelated_notes:\n - \"[[${originalNote.filename.replace('.md', '')}]]\"\n---\n\n# Decision Integrity: ${originalNote.title}\n\n## Summary\n\n${decisionSummary}\n\n## Outcome\n\n| Metric | Value |\n|--------|-------|\n| Consensus Reached | ${consensusReached ? 'Yes \u2713' : 'No \u2717'} |\n| Confidence Score | ${Math.round(confidence * 100)}% |\n| Participating Agents | ${receipt.consensus_proof?.supporting_agents?.length || 0} |\n| Debate Rounds | ${receipt.probes_run || 0} |\n\n## Linear Issue\n\n${linearIssue ? `[${linearIssue.identifier}](${linearIssue.url}) - ${linearIssue.title}` : 'No issue created'}\n\n## Dissent Trail\n\n${receipt.dissenting_views?.length > 0\n ? receipt.dissenting_views.map((d, i) => `${i + 1}. ${d}`).join('\\n')\n : '_No dissent recorded_'}\n\n${planSection}\n\n## Verification\n\n- **Debate ID**: \\`${debateId}\\`\n- **Receipt ID**: \\`${receipt.receipt_id || 'N/A'}\\`\n- **Signature**: \\`${receipt.signature?.substring(0, 20) || 'N/A'}...\\`\n- **Timestamp**: ${receipt.timestamp || new Date().toISOString()}\n\n## Original Note\n\n![[${originalNote.filename.replace('.md', '')}]]\n\n---\n\n*This decision integrity package was automatically generated by Aragora.*\n*Verify at: ${process.env.ARAGORA_API_URL}/verify/${debateId}*\n`;\n\nconst vaultPath = process.env.OBSIDIAN_VAULT_PATH;\nconst decisionsFolder = path.join(vaultPath, 'decisions');\nconst safeTitle = originalNote.title.replace(/[^a-zA-Z0-9\\s-]/g, '').substring(0, 50);\nconst receiptFilename = `${new Date().toISOString().split('T')[0]}-${safeTitle.replace(/\\s+/g, '-')}-integrity.md`;\nconst receiptPath = path.join(decisionsFolder, receiptFilename);\n\nif (!fs.existsSync(decisionsFolder)) {\n fs.mkdirSync(decisionsFolder, { recursive: true });\n}\n\nfs.writeFileSync(receiptPath, receiptContent, 'utf-8');\n\nconst originalPath = originalNote.path;\nlet originalContent = fs.readFileSync(originalPath, 'utf-8');\noriginalContent = originalContent.replace(/#ready/g, '#processed');\n\nif (originalContent.startsWith('---')) {\n const parts = originalContent.split('---');\n if (parts.length >= 3) {\n let fm = parts[1];\n if (!fm.includes('aragora_id:')) {\n fm += `\\naragora_id: ${debateId}`;\n fm += `\\nlinear_issue: ${linearIssue?.identifier || 'N/A'}`;\n fm += `\\ndecision_integrity: true`;\n }\n originalContent = `---${fm}---${parts.slice(2).join('---')}`;\n }\n}\n\nfs.writeFileSync(originalPath, originalContent, 'utf-8');\n\nreturn {\n json: {\n success: true,\n receiptPath,\n originalPath: originalNote.path,\n debateId,\n linearIssue: linearIssue?.identifier,\n linearUrl: linearIssue?.url,\n }\n};"
},
"notes": "Writes decision receipt to Obsidian vault and updates original note."
},
{
"id": "10",
"name": "Send Slack Notification",
"type": "n8n-nodes-base.slack",
"position": [
1900,
200
],
"parameters": {
"operation": "post",
"channel": "={{ $env.SLACK_CHANNEL }}",
"text": "\ud83c\udfaf *Decision Made*\n\n*Topic:* {{ $node['Parse Frontmatter'].json.title }}\n*Consensus:* {{ $node['Wait for Debate Completion'].json.receipt?.consensus_proof?.reached ? '\u2713 Reached' : '\u2717 Not reached' }}\n*Confidence:* {{ Math.round(($node['Wait for Debate Completion'].json.receipt?.consensus_proof?.confidence ?? $node['Wait for Debate Completion'].json.receipt?.confidence ?? 0) * 100) }}%\n\n\ud83d\udccb Linear Issue: {{ $node['Create Linear Issue'].json.data?.issueCreate?.issue?.url || 'N/A' }}\n\ud83d\udcdd Receipt: `decisions/{{ $json.receiptPath.split('/').pop() }}`",
"otherOptions": {
"includeLinkToWorkflow": false
}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"notes": "Optional: Send notification to Slack. Configure SLACK_CHANNEL and Slack credentials."
}
],
"connections": {
"Obsidian Vault Watcher": {
"main": [
[
{
"node": "Filter Ready Notes",
"type": "main",
"index": 0
}
]
]
},
"Filter Ready Notes": {
"main": [
[
{
"node": "Read Note Content",
"type": "main",
"index": 0
}
]
]
},
"Read Note Content": {
"main": [
[
{
"node": "Parse Frontmatter",
"type": "main",
"index": 0
}
]
]
},
"Parse Frontmatter": {
"main": [
[
{
"node": "Check Ready Tag",
"type": "main",
"index": 0
}
]
]
},
"Check Ready Tag": {
"main": [
[
{
"node": "Launch Aragora Debate",
"type": "main",
"index": 0
}
],
[]
]
},
"Launch Aragora Debate": {
"main": [
[
{
"node": "Wait for Debate Completion",
"type": "main",
"index": 0
}
]
]
},
"Wait for Debate Completion": {
"main": [
[
{
"node": "Create Linear Issue",
"type": "main",
"index": 0
}
]
]
},
"Create Linear Issue": {
"main": [
[
{
"node": "Write Receipt to Obsidian",
"type": "main",
"index": 0
}
]
]
},
"Write Receipt to Obsidian": {
"main": [
[
{
"node": "Send Slack Notification",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"tags": [
"obsidian",
"aragora",
"linear",
"decision-making",
"automation"
],
"versionId": "1.0.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.
slackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Obsidian → Aragora → Linear Pipeline. Uses localFileTrigger, readBinaryFiles, httpRequest, slack. Event-driven trigger; 10 nodes.
Source: https://github.com/synaptent/aragora/blob/d5c506cf176a72f72fe9c90cc631a8501dee23d2/templates/n8n/obsidian-aragora-linear.json — 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.
Monitor Google Drive folder, parsing PDF, DOCX and image file into a destination folder, ready for further processing (e.g. RAG ingestion, translation, etc.) Keep processing log in Google Sheet and se
13195 Search Slack For N8N Templates With Openai Tips Google Sheets Cache And Weekly Analytics. Uses slackTrigger, googleSheets, httpRequest, slack. Event-driven trigger; 31 nodes.
Legal, Procurement, and Compliance teams at mid-size companies. ESN and agencies selling AI-powered contract review as a service.
This workflow takes a Loom link, extracts the video ID, uses the Loom API to download the video, then sends it to Gemini along with your question. Finally, it sends the output to Slack.
This workflow empowers marketing teams, agencies and solopreneurs to instantly generate on-brand, platform-optimized social media ads — without designers or complex setup. Running performance marketin