| name | workflow-json-architect |
| description | n8n workflow JSON structure management for Vigil Guard v2.0.0 (24-node 3-branch parallel pipeline). Use for node manipulation, connections, Code node JavaScript, 3-Branch Executor logic, Arbiter v2 integration, and critical reminder to import workflow to n8n GUI. |
| version | 2.0.0 |
| allowed-tools | Read, Write, Edit, Bash, Grep, Glob |
n8n Workflow JSON Architecture Management (v2.0.0)
Overview
Expert guidance for managing n8n workflow JSON files (24 nodes, 3-branch parallel detection) in Vigil Guard v2.0.0. This skill focuses exclusively on the JSON structure, node manipulation, connections, and workflow lifecycle - NOT on detection patterns or business logic (see n8n-vigil-workflow for patterns).
When to Use This Skill
- Modifying workflow JSON structure (
Vigil Guard v2.0.0.json) - Adding/removing/reordering nodes
- Editing Code node embedded JavaScript
- Managing node connections and data flow
- Working with 3-Branch Executor node
- Configuring Arbiter v2 weighted fusion
- Migrating workflows between versions
- Debugging workflow execution errors
- Understanding n8n JSON schema
- Explaining to user: "Import workflow to n8n GUI NOW!"
v2.0.0 Architecture Change
From 40-node Sequential → 24-node 3-Branch Parallel
OLD (v1.x): 40+ nodes, sequential processing, rules.config.json
NEW (v2.0.0): 24 nodes, 3-branch parallel detection
- 3-Branch Executor (parallel HTTP calls)
- Arbiter v2 (weighted fusion)
- No rules.config.json (patterns in heuristics-service)
CRITICAL: Workflow Lifecycle Understanding
The #1 Most Important Concept
n8n workflows are stored in SQLite database, NOT in JSON files!
TRUTH:
- Workflow execution reads from: SQLite database (/home/node/.n8n/database.sqlite)
- JSON file in repo is: Backup/version control ONLY
- Changes to JSON have NO EFFECT until user imports to n8n GUI
WORKFLOW:
1. Edit JSON file in repo
2. IMMEDIATELY tell user: " IMPORT THIS FILE TO n8n GUI NOW!"
3. User imports via: n8n GUI → Import from File
4. n8n copies JSON → SQLite database
5. Workflow executes from SQLite (NOT from file)
CROWN RULE: WORKFLOW IMPORT STATUS - OPERATOR TRUST AXIOM
ABSOLUTELY FORBIDDEN DEBUGGING HYPOTHESES:
NEVER CHECK:
- "Was the workflow imported?"
- "Is the workflow in n8n SQL database?"
- "Is the workflow active?"
- "Did you activate the workflow after import?"
INDISPUTABLE FACT:
- If operator confirmed import: workflow IS imported
- Workflow IS in SQLite database
- Workflow IS active
- All Code nodes have LATEST JavaScript from JSON file
- This is 100% TRUTH without verification
CORRECT DEBUGGING APPROACH:
- Operator confirms import → END OF STORY
- Problem is NOT in import
- Problem IS in workflow logic
- Debug: analyze node code, tests, configuration
- DO NOT waste tokens verifying import
User Communication Template
Workflow JSON updated successfully!
**CRITICAL NEXT STEP - DO THIS NOW:**
1. Open n8n GUI: http://localhost:5678
2. Click menu (≡) → **Import from File**
3. Select: `services/workflow/workflows/Vigil Guard v2.0.0.json`
4. Confirm import
5. Activate workflow (toggle switch)
**Your changes will NOT work until you complete this import!**
After importing, test with: n8n GUI → Test workflow → Chat tab
Workflow JSON Structure (v2.0.0)
Top-Level Schema
{
"name": "Vigil Guard v2.0.0",
"nodes": [
{
"parameters": {},
"id": "unique-uuid",
"name": "Node_Name",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [x, y]
}
],
"connections": {
"Source_Node": {
"main": [
[
{
"node": "Target_Node",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"updatedAt": "2025-01-30T10:00:00.000Z",
"versionId": "uuid"
}
24-Node Architecture (v2.0.0)
1. When chat message received # Chat trigger
2. Webhook v2 # HTTP webhook trigger
3. Extract Input # Normalize input format
4. Load allowlist.schema.json # Load allowlist config
5. Load pii.conf # Load PII patterns (361 lines)
6. Load unified_config.json # Load main config (303 lines, v5.0.0)
7. Extract allowlist # Parse allowlist
8. Extract pii.conf # Parse PII config
9. Extract unified_config # Parse main config
10. Merge Config # Combine all configs
11. Config Loader v2 # Final config object
12. Input Validator v2 # Validate input (length, format)
13. Validation Check # Route based on validation
14. 3-Branch Executor # PARALLEL branch execution (CRITICAL)
15. Arbiter v2 # Weighted score fusion (CRITICAL)
16. Arbiter Decision # Route based on score
17. PII_Redactor_v2 # Dual-language PII detection
18. Block Response v2 # Generate block response
19. Merge Final # Merge all paths
20. Build NDJSON v2 # Format for logging
21. Log to ClickHouse v2 # Send to analytics DB
22. Clean Output v2 # User-facing result
23. Early Block v2 # Fast-path for validation failures
24. output to plugin # Browser extension response
Node Types
Core Processing Nodes:
- Code - Custom JavaScript logic (most common)
- HTTP Request - API calls (3-branch services, Presidio, ClickHouse)
- Merge - Combine data streams (config merge, path merge)
- Switch - Conditional routing (thresholds)
- Set - Variable assignment
- Webhook - Trigger endpoint
Data Flow (3-Branch Parallel)
Webhook Trigger
→ Extract Input
→ Config Loader v2 (merge configs)
→ Input Validator v2
→ 3-Branch Executor (PARALLEL):
├─ HTTP: heuristics-service:5005 (Branch A)
├─ HTTP: semantic-service:5006 (Branch B)
└─ HTTP: prompt-guard-api:8000 (Branch C)
→ Arbiter v2 (weighted fusion: A*0.30 + B*0.35 + C*0.35)
→ Arbiter Decision (Switch)
├─ ALLOW → Clean Output
├─ SANITIZE → PII_Redactor_v2 → Clean Output
└─ BLOCK → Block Response
→ Build NDJSON v2
→ Log to ClickHouse v2
→ Clean Output v2
Common Tasks
Task 1: Modify 3-Branch Executor
Example: Add timeout to branch calls
// Find 3-Branch Executor node
const branchNode = workflow.nodes.find(n => n.name === "3-Branch Executor");
// Modify HTTP parameters for each branch
branchNode.parameters = {
jsCode: `
const items = $input.all();
const text = items[0].json.sanitized_input || items[0].json.chatInput;
const requestId = items[0].json.request_id || 'default';
// Branch timeouts (v2.0.0)
const TIMEOUTS = {
A: 1000, // Heuristics: 1 second
B: 2000, // Semantic: 2 seconds
C: 3000 // LLM Guard: 3 seconds
};
// Parallel branch execution
const [branchA, branchB, branchC] = await Promise.allSettled([
// Branch A: Heuristics Service
fetch('http://heuristics-service:5005/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, request_id: requestId }),
signal: AbortSignal.timeout(TIMEOUTS.A)
}).then(r => r.json()),
// Branch B: Semantic Service
fetch('http://semantic-service:5006/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, request_id: requestId }),
signal: AbortSignal.timeout(TIMEOUTS.B)
}).then(r => r.json()),
// Branch C: LLM Guard
fetch('http://prompt-guard-api:8000/detect', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text }),
signal: AbortSignal.timeout(TIMEOUTS.C)
}).then(r => r.json())
]);
return [{
json: {
...items[0].json,
branch_results: {
A: branchA.status === 'fulfilled' ? branchA.value : { score: 0, degraded: true },
B: branchB.status === 'fulfilled' ? branchB.value : { score: 0, degraded: true },
C: branchC.status === 'fulfilled' ? branchC.value : { score: 0, degraded: true }
}
}
}];
`
};
Task 2: Modify Arbiter v2 Weights
Example: Change branch weights
// Find Arbiter v2 node
const arbiterNode = workflow.nodes.find(n => n.name === "Arbiter v2");
// Modify weighted fusion logic
arbiterNode.parameters.jsCode = `
const items = $input.all();
const branches = items[0].json.branch_results;
// v2.0.0 Weighted fusion
const WEIGHTS = {
A: 0.30, // Heuristics
B: 0.35, // Semantic
C: 0.35 // LLM Guard
};
// Calculate weighted score
const weightedScore =
(branches.A.score || 0) * WEIGHTS.A +
(branches.B.score || 0) * WEIGHTS.B +
(branches.C.score || 0) * WEIGHTS.C;
// Critical signal override
const criticalOverride =
branches.A.critical_signals?.obfuscation_heavy ||
branches.C.critical_signals?.llm_attack;
// Decision matrix
let decision;
if (criticalOverride || weightedScore >= 85) {
decision = 'BLOCK';
} else if (weightedScore >= 30) {
decision = 'SANITIZE';
} else {
decision = 'ALLOW';
}
return [{
json: {
...items[0].json,
threat_score: weightedScore,
arbiter_decision: decision,
critical_override: criticalOverride
}
}];
`;
Task 3: Add New Node
Example: Add post-processing step
// 1. Create new node
const newNode = {
"parameters": {
"jsCode": `
const items = $input.all();
// Your processing logic
return items.map((item, index) => ({
json: {
...item.json,
processed: true
},
pairedItem: { item: index }
}));
`
},
"id": crypto.randomUUID(),
"name": "Post_Processing",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1500, 300]
};
// 2. Insert into nodes array
workflow.nodes.push(newNode);
// 3. Update connections
// Find where to insert (after Arbiter Decision, before Clean Output)
workflow.connections["Arbiter Decision"].main[0][0].node = "Post_Processing";
workflow.connections["Post_Processing"] = {
"main": [[{ "node": "Clean Output v2", "type": "main", "index": 0 }]]
};
// 4. Save and tell user to import
Task 4: Migrate from v1.x to v2.0.0
Migration Checklist:
// 1. Update workflow name
workflow.name = "Vigil Guard v2.0.0";
// 2. Replace 40-node sequential with 24-node parallel
// - Remove all sequential pattern matching nodes
// - Add 3-Branch Executor
// - Add Arbiter v2
// 3. Remove rules.config.json references
// - Pattern matching now in heuristics-service
// - Categories in unified_config.json
// 4. Update ClickHouse logging
const loggingNode = workflow.nodes.find(n => n.name.includes("NDJSON"));
loggingNode.parameters.jsCode = loggingNode.parameters.jsCode
.replace(/"pipeline_version": "1\.\d+\.\d+"/, '"pipeline_version": "2.0.0"')
+ `
// v2.0.0: Add branch columns
result.branch_a_score = items[0].json.branch_results?.A?.score || 0;
result.branch_b_score = items[0].json.branch_results?.B?.score || 0;
result.branch_c_score = items[0].json.branch_results?.C?.score || 0;
result.arbiter_decision = items[0].json.arbiter_decision;
result.branch_a_timing_ms = items[0].json.timing?.branch_a_ms || 0;
result.branch_b_timing_ms = items[0].json.timing?.branch_b_ms || 0;
result.branch_c_timing_ms = items[0].json.timing?.branch_c_ms || 0;
`;
// 5. Update workflow metadata
workflow.updatedAt = new Date().toISOString();
workflow.versionId = crypto.randomUUID();
Code Node Patterns (v2.0.0)
Pattern 1: Data Access
// Get all input items
const items = $input.all();
// Access first item's JSON
const data = items[0].json;
// Access branch results (v2.0.0)
const branchA = data.branch_results?.A;
const branchB = data.branch_results?.B;
const branchC = data.branch_results?.C;
Pattern 2: Returning Modified Data
// Return modified items (preserves pairedItem for tracing)
return items.map((item, index) => ({
json: {
...item.json,
new_field: "value"
},
pairedItem: {
item: index
}
}));
Pattern 3: Accessing Config Data
// Config loaded via Merge node, available in all downstream Code nodes
const items = $input.all();
const contextItem = items[0];
// Access config sections (v2.0.0: unified_config.json)
const unified = contextItem.json.config; // unified_config.json (303 lines)
const piiConfig = contextItem.json.pii; // pii.conf (361 lines)
// Use config values
const allowMax = unified.thresholds?.allow_max || 29;
const sanitizeLight = unified.thresholds?.sanitize_light || 64;
Pattern 4: Branch Result Handling
// Handle degraded branches (timeout/error)
const branches = items[0].json.branch_results;
for (const [id, result] of Object.entries(branches)) {
if (result.degraded) {
console.log(`Branch ${id} degraded, using score 0`);
result.score = 0;
}
}
// Calculate weighted score
const score =
branches.A.score * 0.30 +
branches.B.score * 0.35 +
branches.C.score * 0.35;
Pattern 5: HTTP Request Error Handling
try {
const response = await fetch('http://heuristics-service:5005/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, request_id: requestId }),
signal: AbortSignal.timeout(1000) // 1 second timeout
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Branch A error:', error.message);
return { score: 0, degraded: true, error: error.message };
}
Node Positioning (Visual Layout)
Grid System
- X-axis: 0 (left) → 2000+ (right)
- Y-axis: 0 (top) → 1200+ (bottom)
- Spacing: 220 pixels between nodes horizontally
- Vertical offset: 100-150 pixels for parallel branches
Example Layout (v2.0.0)
// Sequential flow (left to right)
nodes[0].position = [240, 300]; // Webhook Trigger
nodes[1].position = [460, 300]; // Extract Input
nodes[2].position = [680, 300]; // Config Loader
nodes[3].position = [900, 300]; // Input Validator
// 3-Branch Executor (single node, handles parallel internally)
nodes[4].position = [1120, 300]; // 3-Branch Executor
// Post-branch processing
nodes[5].position = [1340, 300]; // Arbiter v2
nodes[6].position = [1560, 300]; // Arbiter Decision
Connection Syntax
Simple Connection (A → B)
{
"connections": {
"Node_A": {
"main": [
[{ "node": "Node_B", "type": "main", "index": 0 }]
]
}
}
}
Switch Node (Conditional - Arbiter Decision)
{
"connections": {
"Arbiter Decision": {
"main": [
[{ "node": "Clean Output v2", "type": "main", "index": 0 }],
[{ "node": "PII_Redactor_v2", "type": "main", "index": 0 }],
[{ "node": "Block Response v2", "type": "main", "index": 0 }]
]
}
}
}
Debugging Workflow Execution
Common Error Patterns
Error: Branch timeout
// Problem: Branch service takes too long
// Solution: Increase timeout or check service health
// Check service health
curl http://localhost:5005/health // Heuristics
curl http://localhost:5006/health // Semantic
curl http://localhost:8000/health // LLM Guard
Error: "Cannot read property 'branch_results' of undefined"
// Problem: 3-Branch Executor didn't return results
// Solution: Check branch node output and add fallback
const branches = items[0].json.branch_results || {
A: { score: 0, degraded: true },
B: { score: 0, degraded: true },
C: { score: 0, degraded: true }
};
Enable Debug Logging
// Add to Code nodes for debugging
console.log('=== Node Name Debug ===');
console.log('Input:', JSON.stringify($input.all(), null, 2));
console.log('Branch Results:', JSON.stringify(items[0].json.branch_results, null, 2));
console.log('Arbiter Decision:', items[0].json.arbiter_decision);
View Logs:
docker logs vigil-n8n | grep "Debug"
Performance Optimization
Parallel Branch Execution
// 3-Branch Executor uses Promise.allSettled for parallel execution
// Each branch runs independently, no blocking
const [branchA, branchB, branchC] = await Promise.allSettled([
fetchBranchA(text),
fetchBranchB(text),
fetchBranchC(text)
]);
// All branches complete in max(timeoutA, timeoutB, timeoutC)
// Not sequential: timeoutA + timeoutB + timeoutC
Cache Config Data
// Config loaded once at start, reused in all nodes
const config = $('Config_Loader_v2').all()[0].json.config;
// Don't reload in every node
Integration with Other Skills
When to Use Other Skills
n8n-vigil-workflow:
- Understanding 3-branch detection logic
- Modifying decision thresholds
- PII detection configuration
- Business logic changes
pattern-library-manager:
- Adding detection patterns (heuristics-service)
- ReDoS validation
vigil-testing-e2e:
- Writing tests for modified workflow
- Validating arbiter decisions
- Testing branch behavior
clickhouse-grafana-monitoring:
- Verifying workflow logs branch columns correctly
- Analyzing branch metrics
Reference Files
Workflow
- Current:
services/workflow/workflows/Vigil Guard v2.0.0.json - Backups:
services/workflow/workflows/backups/
Configuration
services/workflow/config/unified_config.json(303 lines, v5.0.0)services/workflow/config/pii.conf(361 lines)
Documentation
- Architecture:
docs/ARCHITECTURE.md - n8n docs: https://docs.n8n.io/
Quick Reference
Essential Commands
# View workflow structure
cat services/workflow/workflows/Vigil*.json | jq .
# Count nodes (expected: 24)
jq '.nodes | length' services/workflow/workflows/Vigil*.json
# List node names
jq -r '.nodes[].name' services/workflow/workflows/Vigil*.json
# Find 3-Branch Executor
jq '.nodes[] | select(.name | contains("Branch"))' services/workflow/workflows/Vigil*.json
# Extract Arbiter v2 code
jq -r '.nodes[] | select(.name == "Arbiter v2") | .parameters.jsCode' services/workflow/workflows/Vigil*.json
Node Checklist (Before Commit)
- Node has unique
id(UUID) - Node has valid
type(n8n-nodes-base.*) - Node has
position[x, y] - Node has incoming connection (or is trigger)
- Node has outgoing connection (or is terminal)
- Code node JavaScript is syntactically valid
- pairedItem preserved in return statements
- Error handling implemented (try/catch)
- Branch degradation handled (score=0 on timeout)
- Workflow
updatedAttimestamp updated - User instructed: "Import to n8n NOW!"
Last Updated: 2025-12-09 Workflow Version: v2.0.0 (24 nodes, 3-branch parallel) Maintained By: Vigil Guard Development Team
Version History
- v2.0.0 (Current): 24 nodes, 3-branch parallel, Arbiter v2
- v1.7.0: 41 nodes, sequential pipeline
- v1.6.11: 40 nodes, rules.config.json (DEPRECATED)