{"id":"2453c5de-7dd7-4fd6-95dd-4a1842a1a452","entityType":"agent","slug":"clawhub-mwamedacen-n8n-evol-i","name":"N8N EVOL I","canonicalUrl":"https://www.xpersona.co/agent/clawhub-mwamedacen-n8n-evol-i","canonicalPath":"/agent/clawhub-mwamedacen-n8n-evol-i","generatedAt":"2026-10-12T08:36:33.919Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-12T01:16:28.792Z","emptyReason":null},"description":"A harness to help coding agents build, deploy, maintain, and debug multi-workflow n8n-powered automation systems. No lock-in — work from the agent, continue... Skill: N8N EVOL I Owner: mwamedacen Summary: A harness to help coding agents build, deploy, maintain, and debug multi-workflow n8n-powered automation systems. No lock-in — work from the agent, continue... Tags: latest:2.0.0 Version history: v2.0.0 | 2026-05-25T22:16:50.188Z | user Queue primitives and lifecycle support added + breaking change on interpolation syntax - Introduced primitives and helper scripts to suppo","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 962 downloads reported by the source. Last updated 10/12/2026.","installCommand":"clawhub skill install s1796g7dypaz4std6t3senhtn5860gse:n8n-evol-i","sourceUrl":"https://clawhub.ai/mwamedacen/n8n-evol-i","homepage":"https://clawhub.ai/mwamedacen/skills/n8n-evol-i","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/mwamedacen/n8n-evol-i","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/mwamedacen/skills/n8n-evol-i","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":60,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"A harness to help coding agents build, deploy, maintain, and debug multi-workflow n8n-powered automation systems. No lock-in — work from the agent, continue... "},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-12T01:16:28.792Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-12T01:16:28.792Z","emptyReason":null},"stars":null,"forks":null,"downloads":962,"likes":null,"task":null,"library":null,"packageName":null,"latestVersion":"2.0.0","tractionLabel":"962 downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-12T01:16:28.697Z","emptyReason":null},"lastUpdatedAt":"2026-10-12T01:16:28.792Z","lastCrawledAt":"2026-10-12T01:16:28.697Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-13T01:16:28.697Z","lastVerifiedAt":null,"highlights":[{"version":"2.0.0","createdAt":"2026-05-25T22:16:50.188Z","changelog":"Queue primitives and lifecycle support added + breaking change on interpolation syntax - Introduced primitives and helper scripts to support queue-based workflows (using Redis Streams). - Added lifecycle skills for queue creation, publishing, and consumption: `create-queue`, `add-queue-publish-to-workflow`, `add-queue-consumer-to-workflow`. - Added reference docs: patterns/queues and integrations/redis/queue-pattern. - Included primitive workflow templates for queue ack, publish, pop, sample producer/consumer, and error handler queue cleanup. - New lifecycle skill: `manage-variables` for n8n runtime variables. - Updated main skill index to list and describe new queue-related features.","fileCount":138,"zipByteSize":250588},{"version":"1.0.1","createdAt":"2026-05-03T11:10:20.894Z","changelog":"- Updated the skill description to clarify its role as a general harness for agents to build, deploy, and maintain multi-workflow n8n automation systems, emphasizing flexibility and no lock-in. - No changes to features or functionality; documentation improvement only.","fileCount":121,"zipByteSize":199981},{"version":"1.0.0","createdAt":"2026-05-03T10:56:53.106Z","changelog":"Initial release of n8n-evol-I: a read-only skill package to author, deploy, and operate n8n workflows from code. - Routes user requests to a library of lifecycle sub-skills (init, bootstrap-env, deploy, run, resync, and more). - Supports a rich set of helpers for composing, validating, deploying, and maintaining n8n workflows programmatically. - Provides reference docs for best practices, workflow patterns, and common n8n integrations. - Uses a modular design: sub-skills handle specific authoring and operational tasks. - Comprehensive skill and integration documentation included for n8n assistants.","fileCount":121,"zipByteSize":199951}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s1796g7dypaz4std6t3senhtn5860gse:n8n-evol-i","setupComplexity":"low","setupSteps":["Setup complexity is LOW. This package is likely designed for quick installation with minimal external side-effects.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-mwamedacen-n8n-evol-i/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-mwamedacen-n8n-evol-i/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-mwamedacen-n8n-evol-i/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-mwamedacen-n8n-evol-i/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-mwamedacen-n8n-evol-i/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-mwamedacen-n8n-evol-i/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-12T08:36:33.909Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-mwamedacen-n8n-evol-i/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-mwamedacen-n8n-evol-i/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-mwamedacen-n8n-evol-i/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-mwamedacen-n8n-evol-i/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-12T01:16:28.792Z","emptyReason":null},"readme":"Skill: N8N EVOL I\n\nOwner: mwamedacen\n\nSummary: A harness to help coding agents build, deploy, maintain, and debug multi-workflow n8n-powered automation systems. No lock-in — work from the agent, continue...\n\nTags: latest:2.0.0\n\nVersion history:\n\nv2.0.0 | 2026-05-25T22:16:50.188Z | user\n\nQueue primitives and lifecycle support added + breaking change on interpolation syntax \n\n- Introduced primitives and helper scripts to support queue-based workflows (using Redis Streams).\n- Added lifecycle skills for queue creation, publishing, and consumption: `create-queue`, `add-queue-publish-to-workflow`, `add-queue-consumer-to-workflow`.\n- Added reference docs: patterns/queues and integrations/redis/queue-pattern.\n- Included primitive workflow templates for queue ack, publish, pop, sample producer/consumer, and error handler queue cleanup.\n- New lifecycle skill: `manage-variables` for n8n runtime variables.\n- Updated main skill index to list and describe new queue-related features.\n\nv1.0.1 | 2026-05-03T11:10:20.894Z | user\n\n- Updated the skill description to clarify its role as a general harness for agents to build, deploy, and maintain multi-workflow n8n automation systems, emphasizing flexibility and no lock-in.\n- No changes to features or functionality; documentation improvement only.\n\nv1.0.0 | 2026-05-03T10:56:53.106Z | user\n\nInitial release of n8n-evol-I: a read-only skill package to author, deploy, and operate n8n workflows from code.\n\n- Routes user requests to a library of lifecycle sub-skills (init, bootstrap-env, deploy, run, resync, and more).\n- Supports a rich set of helpers for composing, validating, deploying, and maintaining n8n workflows programmatically.\n- Provides reference docs for best practices, workflow patterns, and common n8n integrations.\n- Uses a modular design: sub-skills handle specific authoring and operational tasks.\n- Comprehensive skill and integration documentation included for n8n assistants.\n\nArchive index:\n\nArchive v2.0.0: 138 files, 250588 bytes\n\nFiles: helpers/__init__.py (0b), helpers/_dspy_config.py (2010b), helpers/activate.py (1441b), helpers/add_cloud_function.py (4779b), helpers/add_lock_to_workflow.py (14167b), helpers/add_queue_consumer_to_workflow.py (17115b), helpers/add_queue_publish_to_workflow.py (13515b), helpers/add_rate_limit_to_workflow.py (9774b), helpers/archive.py (1492b), helpers/bootstrap_env.py (7183b), helpers/cleanup_queue_state.py (4684b), helpers/config.py (2828b), helpers/copy_primitive.py (3725b), helpers/create_lock.py (3865b), helpers/create_queue.py (4600b), helpers/create_workflow.py (6036b), helpers/deactivate.py (1330b), helpers/dehydrate.py (5139b), helpers/dependency_graph.py (8424b), helpers/deploy_all.py (5268b), helpers/deploy_run_assert.py (2376b), helpers/deploy.py (6322b), helpers/diff.py (3282b), helpers/doctor.py (13558b), helpers/find_skills.py (4229b), helpers/hydrate.py (2647b), helpers/init.py (7549b), helpers/inspect_execution.py (3679b), helpers/iterate_prompt.py (5871b), helpers/list_executions.py (6710b), helpers/manage_credentials.py (7939b), helpers/manage_variables.py (5229b), helpers/n8n_client.py (2942b), helpers/package-lock.json (42779b), helpers/package.json (90b), helpers/placeholder/__init__.py (0b), helpers/placeholder/env_resolver.py (2450b), helpers/placeholder/file_resolver.py (1139b), helpers/placeholder/js_resolver.py (2482b), helpers/placeholder/py_resolver.py (2772b), helpers/placeholder/uuid_resolver.py (743b), helpers/placeholder/validator.py (1823b), helpers/register_error_handler.py (2765b), helpers/resync_all.py (1211b), helpers/resync.py (1496b), helpers/run.py (6322b), helpers/stop_executions.py (4719b), helpers/test_functions.py (3764b), helpers/tidy_shim.mjs (652b), helpers/tidy_workflow.py (8833b), helpers/unarchive.py (1517b), helpers/validate.py (12527b), helpers/workspace.py (2098b), install.md (3569b), primitives/cloud-functions/app.py (963b), primitives/cloud-functions/functions/__init__.py (0b), primitives/cloud-functions/functions/hello_world.py (226b), primitives/cloud-functions/railpack.json (116b), primitives/cloud-functions/railway.toml (108b), primitives/cloud-functions/registry.py (196b), primitives/cloud-functions/requirements.txt (39b), primitives/prompts/example_summary_prompt.txt (237b), primitives/prompts/example_summary_schema.json (320b), primitives/workflows/_minimal.template.json (1156b), primitives/workflows/error_handler_lock_cleanup.template.json (8914b), primitives/workflows/error_handler_queue_cleanup.template.json (12862b), primitives/workflows/lock_acquisition.template.json (13909b), primitives/workflows/lock_release.template.json (7643b), primitives/workflows/queue_ack.template.json (10017b), primitives/workflows/queue_pop.template.json (25872b), primitives/workflows/queue_publish.template.json (3527b), primitives/workflows/queue_sample_consumer.template.json (4975b), primitives/workflows/queue_sample_producer.template.json (3250b), primitives/workflows/rate_limit_check.template.json (2557b), pyproject.toml (704b), README.md (10318b), requirements.txt (12b), skill-card.md (2636b), SKILL.md (7909b), skills/activate-single-workflow-in-env.md (433b)\n\nFile v2.0.0:SKILL.md\n\n---\nname: n8n-evol-I\ndescription: A harness to help coding agents build, deploy, maintain, and debug multi-workflow n8n-powered automation systems. No lock-in — work from the agent, continue from the n8n UI, hand back to the agent at any time.\n---\n\n# n8n-evol-I skill router\n\nThis is the entry point. When the user asks anything n8n-related, route to the matching sub-skill.\n\n## Mental model\n\n- The skill package (this directory) is **read-only**. You never edit files here.\n- The user's project state lives in `${PWD}/n8n-evol-I-workspace/` (created by `init.md`).\n- Helpers are Python scripts under `helpers/`. Invoke them with `python3 ${CLAUDE_PLUGIN_ROOT}/helpers/<name>.py [args]` (plugin mode) or `python3 <path-to-harness>/helpers/<name>.py [args]` (skill mode).\n- All helpers default to `--workspace ${PWD}/n8n-evol-I-workspace`. Pass `--workspace <path>` if the user runs from elsewhere.\n- **Agent memory**: read `N8N-WORKSPACE-MEMORY.md` in the workspace at the start of every session; append a dated entry whenever you learn something durable about this project. Full guidance in `AGENTS.md` (also in the workspace root).\n\n## Lifecycle skills (use when the user wants to do X)\n\n| Skill | When |\n|---|---|\n| [init.md](skills/init.md) | First-time setup. Creates the workspace at `${PWD}/n8n-evol-I-workspace/`. |\n| [bootstrap-env.md](skills/bootstrap-env.md) | Configure an environment (`dev` / `staging` / `prod`). Creates env YAML + `.env`, validates, mints placeholder workflow IDs. |\n| [doctor.md](skills/doctor.md) | Health check. Run before/after major changes. |\n| [create-new-workflow.md](skills/create-new-workflow.md) | Author a brand-new workflow. |\n| [register-workflow-to-error-handler.md](skills/register-workflow-to-error-handler.md) | Wire `settings.errorWorkflow`. |\n| [create-lock.md](skills/create-lock.md) | First-time setup for distributed locking (Redis-backed primitives). |\n| [copy-primitive.md](skills/copy-primitive.md) | Copy a single primitive (any) into the workspace. General-purpose; doesn't register. |\n| [add-lock-to-workflow.md](skills/add-lock-to-workflow.md) | Wrap a workflow's main flow in lock acquire/release. |\n| [add-rate-limit-to-workflow.md](skills/add-rate-limit-to-workflow.md) | Gate a workflow's main flow with a Redis-backed fixed-window rate-limit check. |\n| [create-queue.md](skills/create-queue.md) | First-time setup for queue primitive (Redis Streams + atomic-INCR semaphore). |\n| [add-queue-publish-to-workflow.md](skills/add-queue-publish-to-workflow.md) | Wrap a workflow with a producer-side XADD call. |\n| [add-queue-consumer-to-workflow.md](skills/add-queue-consumer-to-workflow.md) | Turn a workflow into a schedule-polled queue consumer with bounded concurrency. |\n| [tidy-workflow.md](skills/tidy-workflow.md) | Apply n8n's canvas-layout algorithm to a workflow template to clean up node positions. |\n| [deploy.md](skills/deploy.md) | Deploy one workflow to one env. |\n| [activate-single-workflow-in-env.md](skills/activate-single-workflow-in-env.md) | Activate after deploy. |\n| [deactivate-single-workflow-in-env.md](skills/deactivate-single-workflow-in-env.md) | Pause triggers (commonly during dev). |\n| [archive-workflow.md](skills/archive-workflow.md) | Retire a deployed workflow (hidden + read-only on the live instance). |\n| [unarchive-workflow.md](skills/unarchive-workflow.md) | Restore a previously-archived workflow so it accepts updates again. |\n| [deploy_all.md](skills/deploy_all.md) | Roll out an entire env in tier order. |\n| [resync.md](skills/resync.md) | Pull live state of one workflow back into its template. |\n| [resync_all.md](skills/resync_all.md) | Snapshot a full env back to templates. |\n| [dehydrate-workflow.md](skills/dehydrate-workflow.md) | Convert raw exported JSON into a template. |\n| [validate.md](skills/validate.md) | Structural REST validation before deploy. |\n| [run.md](skills/run.md) | Fire a webhook + assert terminal status. |\n| [debug.md](skills/debug.md) | Investigate a failing or missing execution — from vague symptom to root-cause with evidence. |\n| [deploy-run-assert.md](skills/deploy-run-assert.md) | One-shot validate → deploy → run verify. |\n| [find-skills.md](skills/find-skills.md) | While authoring, find applicable patterns/integrations. |\n| [manage-credentials.md](skills/manage-credentials.md) | Create or link n8n credentials (Path A from `.env.<env>` / Path B from existing UI credential). |\n| [manage-variables.md](skills/manage-variables.md) | Lifecycle for n8n Variables (`$vars.*` — non-credential runtime values, or secret fallback when `$env` is blocked). |\n| [add-cloud-function.md](skills/add-cloud-function.md) | Scaffold a Python serverless function / cloud function / serverless API under `<workspace>/cloud-functions/`. |\n| [iterate-prompt.md](skills/iterate-prompt.md) | Optimize a prompt against a paired schema + dataset using DSPy. |\n| [test.md](skills/test.md) | Run unit tests over n8n Code-node JS and / or cloud-function Python. |\n\n## Pattern skills (read-only knowledge)\n\nThese are reference docs, not action triggers. Read them while authoring.\n\n- [skills/patterns/subworkflows.md](skills/patterns/subworkflows.md)\n- [skills/patterns/error-handling.md](skills/patterns/error-handling.md)\n- [skills/patterns/credential-refs.md](skills/patterns/credential-refs.md)\n- [skills/patterns/multi-env-uuid-collision.md](skills/patterns/multi-env-uuid-collision.md)\n- [skills/patterns/validate-deploy.md](skills/patterns/validate-deploy.md)\n- [skills/patterns/code-node-discipline.md](skills/patterns/code-node-discipline.md)\n- [skills/patterns/llm-providers.md](skills/patterns/llm-providers.md)\n- [skills/patterns/locking.md](skills/patterns/locking.md)\n- [skills/patterns/queues.md](skills/patterns/queues.md)\n- [skills/patterns/pindata-hygiene.md](skills/patterns/pindata-hygiene.md)\n- [skills/patterns/position-recalculation.md](skills/patterns/position-recalculation.md)\n- [skills/patterns/prompt-and-schema-conventions.md](skills/patterns/prompt-and-schema-conventions.md)\n- [skills/patterns/agent-api-discipline.md](skills/patterns/agent-api-discipline.md)\n- [skills/patterns/investigation-discipline.md](skills/patterns/investigation-discipline.md)\n\n## Integration skills (per-service quirks)\n\n- [skills/integrations/microsoft-365/excel-and-sharepoint.md](skills/integrations/microsoft-365/excel-and-sharepoint.md)\n- [skills/integrations/gmail/sending-email.md](skills/integrations/gmail/sending-email.md)\n- [skills/integrations/redis/lock-pattern.md](skills/integrations/redis/lock-pattern.md)\n- [skills/integrations/sentry/README.md](skills/integrations/sentry/README.md)\n- [skills/integrations/datadog/README.md](skills/integrations/datadog/README.md)\n- [skills/integrations/slack/README.md](skills/integrations/slack/README.md)\n- [skills/integrations/google-drive/README.md](skills/integrations/google-drive/README.md)\n- [skills/integrations/notion/README.md](skills/integrations/notion/README.md)\n- [skills/integrations/airtable/README.md](skills/integrations/airtable/README.md)\n- [skills/integrations/webhooks/README.md](skills/integrations/webhooks/README.md)\n\n## Placeholder syntax (workflow templates)\n\nTemplates use `{{@type:path}}` (preferred form) or the canonical long form `{{INTERPOLATE_type:path}}`. The two are equivalent — `@` is an alias for `INTERPOLATE_`. Examples below use the `@` form.\n\n| Type | Syntax | Source |\n|---|---|---|\n| `env` | `{{@env:key.path}}` | YAML config value (dot notation) |\n| `txt` | `{{@txt:relative/path.txt}}` | Text file in workspace |\n| `json` | `{{@json:relative/path.json}}` | JSON file (stringified) |\n| `html` | `{{@html:relative/path.html}}` | HTML file |\n| `js` | `{{@js:relative/path.js}}` | JavaScript file |\n| `py` | `{{@py:relative/path.py}}` | Python file (Code-node `language: python`) |\n| `uuid` | `{{@uuid:identifier}}` | Fresh UUID v4 (consistent within one hydration) |\n\nFile v2.0.0:README.md\n\n<img width=\"1376\" height=\"768\" alt=\"n8n_evol_I_hero\" src=\"https://github.com/user-attachments/assets/0a28f1f6-106f-4f84-9ce1-cf7eebcf4cd6\" />\n\n# N8N EVOL I\n\nA harness to help coding agents build, deploy, maintain, and debug multi-workflow n8n-powered automation systems.\n\nNo lock-in. Build with your agent and push upstream to n8n; tweak directly in the n8n UI and pull downstream back into your workspace. Round-trips are template-stable, so humans and agents can work the same project in parallel without stepping on each other's edits.\n\n```mermaid\nflowchart LR\n    Agent <-->|edits / resync| Workspace\n    Workspace <-->|deploy / resync| n8n[(n8n instance)]\n    n8n <-->|UI edits| Human\n```\n\n## Features\n\n- **Multi-environment workflows.** Per-env YAML in `n8n-config/<env>.yml` (instance URL, workflow IDs, credential refs); every helper accepts `--env`. See [`bootstrap-env.md`](skills/bootstrap-env.md).\n\n  ```yaml\n  # n8n-config/dev.yml\n  instance: acme.dev.n8n.cloud\n  envSuffix: \" (dev)\"          # appended to every workflow name\n  workflows:\n    lead_enrichment:\n      id: wf_def456\n      displayName: \"Lead Enrichment\"\n    lead_persister:\n      id: wf_abc123\n      displayName: \"Lead Persister\"\n  ```\n\n- **Build-time substitution.** Code, prompts, schemas, templates, and env values live in workspace files; `{{@js|py|txt|json|html|env|uuid:...}}` placeholders are substituted at deploy time. Template-stable round-trips. See [`code-node-discipline.md`](skills/patterns/code-node-discipline.md).\n\n  ```jsonc\n  // n8n-workflows-template/lead_enrichment.template.json\n  {\n    \"name\": \"{{@env:workflows.lead_enrichment.displayName}}{{@env:envSuffix}}\",\n    \"nodes\": [\n      {\n        \"name\": \"Webhook\",\n        \"type\": \"n8n-nodes-base.webhook\",\n        \"parameters\": { \"path\": \"lead\", \"httpMethod\": \"POST\" }\n      },\n      {\n        \"name\": \"Score Lead\",\n        \"type\": \"@n8n/n8n-nodes-langchain.openAi\",\n        \"parameters\": {\n          \"messages\": [\n            { \"role\": \"system\", \"content\": \"{{@txt:n8n-prompts/prompts/score_lead.txt}}\" }\n          ]\n        }\n      },\n      {\n        \"name\": \"Normalize Fields\",\n        \"type\": \"n8n-nodes-base.code\",\n        \"parameters\": {\n          \"jsCode\": \"{{@js:n8n-functions/js/normalize_lead.js}}\\n\\nreturn normalize(items);\"\n        }\n      },\n      {\n        \"name\": \"Persist via Sub-workflow\",\n        \"type\": \"n8n-nodes-base.executeWorkflow\",\n        \"parameters\": {\n          \"workflowId\": \"{{@env:workflows.lead_persister.id}}\"\n        }\n      }\n    ]\n  }\n  ```\n\n- **Dependency-ordered deployment.** `deploy_all.py` rolls out an env in tier order (set per-workflow in `deployment_order.yml`) so callees deploy before callers. See [`deploy_all.md`](skills/deploy_all.md).\n\n  ```mermaid\n  graph LR\n    A[tier 0: leaves] --> B[tier 1: handlers] --> C[tier 2: callers]\n  ```\n\n- **Execution debugging.** Structured symptom → root-cause flow (dependency graph, candidate pre-screen, causal-linkage, sub-agent cross-check), backed by `list_executions.py`, `inspect_execution.py`, `dependency_graph.py`. See [`investigation-discipline.md`](skills/patterns/investigation-discipline.md).\n\n  ```bash\n  python3 $HARNESS/helpers/inspect_execution.py --env dev --id 91234\n  ```\n\n- **Distributed locking.** Redis-backed `lock_acquisition` / `lock_release` primitives with an identity-payload sidecar; crashes either self-heal via Redis TTL or are cleaned by the registered `error_handler_lock_cleanup` workflow. See [`locking.md`](skills/patterns/locking.md).\n\n  ```\n  ┌──────────┐   ┌──────┐   ┌──────────┐\n  │ acquire  │ → │ work │ → │ release  │\n  │ (scope)  │   │      │   │ (scope)  │\n  └──────────┘   └──────┘   └──────────┘\n  ```\n\n- **Rate limiting.** Fixed-window Redis `INCR` primitive; configurable limit, window, and denied-branch behavior (passthrough / stop / error). See [`locking.md`](skills/patterns/locking.md).\n\n  ```\n                            ┌─→ work       (within N per window)\n  request → check(scope) ───┤\n                            └─→ denied     (over limit)\n  ```\n\n- **Error handling + observability.** Capture (Error Trigger) → log (sinks fan out in parallel) → process (lock cleanup, DB invalidation, compensating workflows). See [`error-handling.md`](skills/patterns/error-handling.md), [`datadog/`](skills/integrations/datadog/README.md).\n\n  ```\n                    ┌─ Sentry  ─┐\n  Error Trigger ──→ ├─ Datadog ─┤ ──→ lock cleanup / invalidate / compensate\n                    └─ Slack   ─┘\n  ```\n\n- **Serverless functions (a.k.a. cloud functions / serverless APIs).** Scaffolds a Python function into a FastAPI service in `cloud-functions/`, auto-registers it in the router, ships with Railway config, callable from n8n via HTTP Request nodes. See [`add-cloud-function.md`](skills/add-cloud-function.md).\n\n  ```bash\n  python3 $HARNESS/helpers/add_cloud_function.py --name parse_invoice\n  ```\n\n- **DSPy prompt optimization.** Optimize a workspace prompt against its paired schema + dataset (BootstrapFewShot or MIPROv2), eval on structural correctness, optionally write the result back to disk. See [`iterate-prompt.md`](skills/iterate-prompt.md).\n\n  ```bash\n  python3 $HARNESS/helpers/iterate_prompt.py --prompt extract_invoice --optimizer mipro_v2\n  ```\n\n## Install\n\n| | Skill mode | Plugin mode |\n|---|---|---|\n| Slash commands | No | Yes (`/n8n-evol-I:*`) |\n| Auto-tidy hook | Manual opt-in | Automatic |\n| Discovery | Read `SKILL.md` | Claude Code `/help` |\n| Other agent runtimes | Yes | Claude Code only |\n\n**Skill mode** (any agent runtime):\n\n```bash\ngit clone https://github.com/mwamedacen/n8n-evol-I.git ~/.claude/skills/n8n-evol-I\npip install pyyaml requests python-dotenv\n```\n\n**Plugin mode** (Claude Code) — adds 10 namespaced slash commands (`/n8n-evol-I:deploy`, `:deploy_all`, `:resync`, `:resync_all`, `:tidyup`, `:debug`, `:run`, `:doctor`, `:validate`, `:test`) plus an auto-tidy hook.\n\n```bash\nclaude plugin install https://github.com/mwamedacen/n8n-evol-I\n```\n\n## Quick start\n\n```bash\nHARNESS=~/.claude/skills/n8n-evol-I   # or $CLAUDE_PLUGIN_ROOT in plugin mode\npython3 $HARNESS/helpers/init.py\npython3 $HARNESS/helpers/bootstrap_env.py --env dev --instance acme.app.n8n.cloud --api-key <key>\npython3 $HARNESS/helpers/doctor.py --env dev\n```\n\nSee [`install.md`](install.md) for prerequisites, optional extras, and update flow.\n\n## Why\n\nOperating n8n past the toy stage exposes the same gaps on every project. The UI editor is fine for a demo and tedious by the third workflow — every code change is click-paste-save, every refactor is manual, every diff is a 200KB JSON blob with code, prompts, schemas, and HTML inlined. A coding agent burns half its context window parsing one file. Two scheduled runs can quietly race on the same record. When something fails at 2am, the execution log tells you *what* broke, not *why*.\n\nN8N EVOL I is the structure that takes those problems off the table.\n\n## How it works\n\nThe harness directory is read-only at runtime. The harness operates against your existing project directory — referred to here as `{YOUR_N8N_PROJECT_DIR}` — which becomes the workspace; the agent reads and writes the opinionated subtree (`n8n-config/`, `n8n-workflows-template/`, …) directly inside it, and you version-control it however you want. The agent reads [`SKILL.md`](SKILL.md) to route any n8n request to the right sub-skill, which invokes a standalone Python helper in `helpers/`. No master CLI, no daemon. `validate.py` enforces segmentation discipline before any deploy.\n\n```mermaid\ngraph LR\n  A[agent] --> B[SKILL.md router]\n  B --> C[skills/*.md]\n  C --> D[helpers/*.py]\n  D --> E[workspace files]\n  D --> F[n8n REST API]\n```\n\n## Workspace layout\n\n`init.py` scaffolds the opinionated default below. If your project already has its own layout, document the mapping in `N8N-WORKSPACE-MEMORY.md` — the agent reads that journal each session and reconciles, so most non-default shapes work fine as long as it can see where each thing actually lives:\n\n```\n{YOUR_N8N_PROJECT_DIR}/\n├── AGENTS.md                # workspace orientation (read first every session)\n├── N8N-WORKSPACE-MEMORY.md  # rolling journal — agent appends as it learns\n├── n8n-config/              # env YAML (dev.yml, prod.yml, …) + .env.<env> secrets\n├── n8n-workflows-template/  # *.template.json — canonical, version-controlled\n├── n8n-build/               # hydrated outputs — gitignored, regenerated on deploy\n├── n8n-functions/\n│   ├── js/                  # pure JS injected via {{@js:...}}\n│   └── py/                  # pure Python injected via {{@py:...}}\n├── n8n-functions-tests/     # *.test.js / test_*.py — paired tests, validator-required\n├── n8n-prompts/\n│   ├── prompts/             # *_prompt.txt + *_schema.json\n│   ├── datasets/            # *.json for iterate-prompt\n│   └── evals/\n├── n8n-assets/\n│   ├── email-templates/     # *.html injected via {{@html:...}}\n│   ├── images/\n│   └── misc/\n├── cloud-functions/         # FastAPI service scaffolded by add-cloud-function\n│   └── functions/\n└── cloud-functions-tests/\n```\n\n## Repository layout\n\n| Path | Contents |\n|---|---|\n| [`SKILL.md`](SKILL.md) | Router — lists all lifecycle, pattern, and integration skills. |\n| [`install.md`](install.md) | Prerequisites, install, smoke test, update flow. |\n| `skills/` | 27 lifecycle + 13 pattern + 10 integration skills (50 total). |\n| `helpers/` | 35 top-level Python helpers + 6 `placeholder/` resolvers. |\n| `primitives/workflows/` | Seed templates: `_minimal`, `lock_acquisition`, `lock_release`, `error_handler_lock_cleanup`, `rate_limit_check`. |\n| `primitives/cloud-functions/` | FastAPI app seed + Railway config (`app.py`, `registry.py`, `railpack.json`). |\n| `primitives/prompts/` | Example prompt + schema for `iterate-prompt`. |\n| `tests/` | 200+ offline tests (HTTP mocked) covering every helper, primitive, and pattern. |\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md).\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n\nFile v2.0.0:skills/integrations/airtable/README.md\n\n---\nname: integration-airtable\ndescription: Airtable nodes — PAT credential, base / table IDs.\nuser-invocable: false\n---\n\n# Airtable\n\n## Node type\n\n`n8n-nodes-base.airtable`.\n\n## Credential type\n\n`airtableTokenApi` — Personal Access Token (PAT) from `airtable.com/create/tokens`.\n\nFor setup, see [`skills/manage-credentials.md`](../../manage-credentials.md). Scopes minimum: `data.records:read` and (if writing) `data.records:write`. Each token can be limited to specific bases — granular tokens are best practice.\n\n## Base + table IDs\n\nAirtable URLs: `https://airtable.com/<baseId>/<tableId>/<viewId>`. The n8n Airtable node takes `base` (= `baseId`) and `table` (= `tableId` or table name).\n\nStore in env YAML:\n\n```yaml\nairtable:\n  ordersBase: \"appabc...\"\n  ordersTable: \"tblxyz...\"\n```\n\n## Field-name vs field-id\n\nRecords returned by the API use field NAMES by default. The Airtable node also supports `returnFieldsByFieldId: true` if your field names change frequently — this returns IDs instead and is more stable.\n\nFile v2.0.0:skills/integrations/datadog/README.md\n\n---\nname: integration-datadog\ndescription: Datadog as the audit destination for n8n error handlers — events POST via HTTP Request, region selection, tag conventions for aggregation.\nuser-invocable: false\n---\n\n# Datadog\n\n## Node type\n\n`n8n-nodes-base.httpRequest`. There is no dedicated `n8n-nodes-base.datadog` node — Datadog is HTTP-shaped.\n\n## Credential type\n\n`httpHeaderAuth` with header `DD-API-KEY: <api_key>`. Get the key from Datadog → Organization Settings → API Keys.\n\nFor most error-handling use cases the API key alone is sufficient. The Application Key (`DD-APPLICATION-KEY`) is only required for endpoints that read data on your behalf (querying logs, dashboards, etc.) — POSTing events doesn't need it.\n\nStore as `.env.<env>`:\n\n```bash\n# .env.<env>\nDD_API_KEY=<your-key>\n```\n\nResolve via `{{@env:credentials.datadog.id}}` / `{{@env:credentials.datadog.name}}` after running `manage-credentials` to register the n8n credential. See [`skills/manage-credentials.md`](../../manage-credentials.md).\n\n## Endpoint\n\nErrors as **events** (recommended for human-readable incident streams):\n```\nPOST https://api.datadoghq.com/api/v1/events\n```\n\nErrors as **logs** (alternative — better when you want full-text search across high-volume errors):\n```\nPOST https://api.datadoghq.com/api/v2/logs\n```\n\nThe events endpoint shows up in Datadog's Event Stream and can drive monitor alerts directly. The logs endpoint shows up in Log Explorer and indexes the body for full-text search. Pick events if your error volume is low (incidents, not noise); pick logs if you want every error retained and queryable.\n\n### Region selection\n\nDatadog hosts per region. The endpoint changes accordingly:\n\n| Region | Base URL |\n|---|---|\n| US1 (default) | `https://api.datadoghq.com` |\n| US3 | `https://api.us3.datadoghq.com` |\n| US5 | `https://api.us5.datadoghq.com` |\n| EU1 | `https://api.datadoghq.eu` |\n| AP1 | `https://api.ap1.datadoghq.com` |\n| US-Gov | `https://api.ddog-gov.com` |\n\nMake region env-configurable so a single template runs in EU staging + US prod:\n\n```yaml\n# n8n-config/<env>.yml\ndatadog:\n  endpoint: \"https://api.datadoghq.com\"\n  apiKeyEnv: \"DD_API_KEY\"\n```\n\nThen `={{ $env.DD_ENDPOINT || 'https://api.datadoghq.com' }}` in the URL field, or use a `{{@env:datadog.endpoint}}` placeholder.\n\n## Worked example: HTTP Request POST event\n\nInside an n8n error handler, fed by the `Build Context` Code node from [`patterns/error-handling.md`](../../patterns/error-handling.md):\n\n```json\n{\n  \"type\": \"n8n-nodes-base.httpRequest\",\n  \"typeVersion\": 4.2,\n  \"parameters\": {\n    \"method\": \"POST\",\n    \"url\": \"={{ ($env.DD_ENDPOINT || 'https://api.datadoghq.com') + '/api/v1/events' }}\",\n    \"authentication\": \"genericCredentialType\",\n    \"genericAuthType\": \"httpHeaderAuth\",\n    \"sendHeaders\": true,\n    \"headerParameters\": {\n      \"parameters\": [\n        { \"name\": \"Accept\", \"value\": \"application/json\" },\n        { \"name\": \"Content-Type\", \"value\": \"application/json\" }\n      ]\n    },\n    \"sendBody\": true,\n    \"specifyBody\": \"json\",\n    \"jsonBody\": \"={{ JSON.stringify({ title: 'n8n workflow failed: ' + $json.workflow_name, text: '%%%\\\\n**Workflow:** ' + $json.workflow_name + '\\\\n**Last node:** ' + $json.last_node + '\\\\n**Execution:** [' + $json.execution_id + '](' + ($json.execution_url || '') + ')\\\\n\\\\n**Error:**\\\\n```\\\\n' + $json.message + '\\\\n```\\\\n%%%', tags: ['workflow:' + $json.workflow_name, 'workflow_id:' + $json.workflow_id, 'execution:' + $json.execution_id, 'env:' + ($env.N8N_ENV || 'unknown'), 'last_node:' + $json.last_node, 'alert_type:error'], priority: 'normal', alert_type: 'error', source_type_name: 'n8n', aggregation_key: 'n8n-workflow-' + $json.workflow_id }) }}\",\n    \"options\": { \"response\": { \"response\": { \"neverError\": true } } }\n  },\n  \"credentials\": {\n    \"httpHeaderAuth\": {\n      \"id\": \"{{@env:credentials.datadog.id}}\",\n      \"name\": \"{{@env:credentials.datadog.name}}\"\n    }\n  }\n}\n```\n\nTwo things worth knowing:\n\n- **`%%%` markers wrap markdown.** Datadog's events API treats text between `%%%` lines as markdown; outside, plain text. Without the markers, the code block won't render.\n- **`response.neverError: true`** prevents a Datadog 5xx from breaking the handler. Same reasoning as Sentry — log destinations must not crash the handler.\n\n## Event body shape\n\n```json\n{\n  \"title\": \"<one-line summary>\",\n  \"text\": \"%%%\\n<markdown body>\\n%%%\",\n  \"tags\": [\n    \"workflow:<name>\",\n    \"workflow_id:<id>\",\n    \"execution:<id>\",\n    \"env:<dev|staging|prod>\",\n    \"last_node:<name>\",\n    \"alert_type:error\"\n  ],\n  \"priority\": \"normal\",\n  \"alert_type\": \"error\",\n  \"source_type_name\": \"n8n\",\n  \"aggregation_key\": \"n8n-workflow-<workflow_id>\"\n}\n```\n\n| Field | Why |\n|---|---|\n| `title` | Shows in event stream + alerts. Keep short, lead with the workflow name. |\n| `text` | The detail. Use `%%%`-wrapped markdown so code blocks + links render. |\n| `tags` | Datadog tags are `key:value` strings (note the colon, not `key=value`). |\n| `priority` | `normal` or `low`. Errors are normal. |\n| `alert_type` | `error`, `warning`, `info`, `success`, `user_update`. Drives icon + color. |\n| `source_type_name` | Free-form source identifier. Use `\"n8n\"` so monitor filters can target n8n events. |\n| `aggregation_key` | Events with the same key cluster in the stream. Use the workflow id so all errors from the same workflow group together; switch to execution id if you want per-execution clustering instead. |\n\n## Tag conventions\n\nDatadog tags are powerful but quirky:\n\n- **Use `key:value`, not `key=value`** — the colon is the separator.\n- **Lowercase keys.** Datadog is case-insensitive on tag keys but normalizes to lowercase; using lowercase explicitly avoids surprises in dashboard queries.\n- **Don't put PII in tags.** Tags are indexed and persist; emails, user names, etc. should go in the `text` field instead.\n\nThe standard tags from [`patterns/error-handling.md`](../../patterns/error-handling.md):\n\n| Tag | Source expression |\n|---|---|\n| `workflow:` | `={{ $workflow.name }}` |\n| `workflow_id:` | `={{ $workflow.id }}` |\n| `execution:` | `={{ $workflow.errorData?.execution?.id || $execution.id }}` |\n| `env:` | `{{@env:name}}` |\n| `last_node:` | `={{ $workflow.errorData?.lastNodeExecuted }}` |\n\nAdd business-relevant tags (`scope:`, `user_id:`, `tenant_id:`) for triage filtering.\n\n## Logs endpoint alternative\n\nIf you'd rather ship to Datadog Logs (full-text search, longer retention):\n\n```json\n{\n  \"type\": \"n8n-nodes-base.httpRequest\",\n  \"typeVersion\": 4.2,\n  \"parameters\": {\n    \"method\": \"POST\",\n    \"url\": \"={{ ($env.DD_ENDPOINT || 'https://api.datadoghq.com') + '/api/v2/logs' }}\",\n    \"authentication\": \"genericCredentialType\",\n    \"genericAuthType\": \"httpHeaderAuth\",\n    \"sendBody\": true,\n    \"specifyBody\": \"json\",\n    \"jsonBody\": \"={{ JSON.stringify([{ ddsource: 'n8n', ddtags: 'workflow:' + $json.workflow_name + ',env:' + ($env.N8N_ENV || 'unknown') + ',workflow_id:' + $json.workflow_id, hostname: 'n8n', service: 'n8n-workflow', message: $json.message, status: 'error', workflow_id: $json.workflow_id, workflow_name: $json.workflow_name, execution_id: $json.execution_id, last_node: $json.last_node, execution_url: $json.execution_url }]) }}\"\n  },\n  \"credentials\": {\n    \"httpHeaderAuth\": {\n      \"id\": \"{{@env:credentials.datadog.id}}\",\n      \"name\": \"{{@env:credentials.datadog.name}}\"\n    }\n  }\n}\n```\n\nNote: `ddtags` is a single comma-separated string, NOT an array (logs API quirk). The body is an array of log entries (single-element array for one entry).\n\n## See also\n\n- [`patterns/error-handling.md`](../../patterns/error-handling.md) — three-step paradigm + worked handler example.\n- [`manage-credentials.md`](../../manage-credentials.md) — registering the DD-API-KEY credential.\n\nFile v2.0.0:skills/integrations/google-drive/README.md\n\n---\nname: integration-google-drive\ndescription: Google Drive + Sheets nodes. OAuth scopes, folder-vs-file IDs.\nuser-invocable: false\n---\n\n# Google Drive (and Sheets)\n\n## Node types\n\n- `n8n-nodes-base.googleDrive` — file ops on Drive.\n- `n8n-nodes-base.googleSheets` — Sheets read/write.\n\n## Credential type\n\n`googleApi` (single credential covers Drive + Sheets if scopes include both).\n\nFor setup, see [`skills/manage-credentials.md`](../../manage-credentials.md). OAuth scopes minimum:\n\n- Drive: `https://www.googleapis.com/auth/drive`\n- Sheets: `https://www.googleapis.com/auth/spreadsheets`\n\n## File / folder IDs\n\nA Drive URL `https://drive.google.com/drive/folders/<ID>` gives the folder ID. A Sheets URL `https://docs.google.com/spreadsheets/d/<ID>/edit` gives the spreadsheet ID.\n\nStore IDs in env YAML:\n\n```yaml\ngdrive:\n  reportsFolder: \"1abc...\"\n  weeklyReport: \"1xyz...\"  # spreadsheet\n```\n\n## App verification\n\nUsing sensitive scopes (full Drive access, etc.) requires Google's app verification for production-tier apps. For internal-team-only projects, configure the OAuth client as \"internal\" within a Workspace org to bypass verification.\n\nFile v2.0.0:skills/integrations/notion/README.md\n\n---\nname: integration-notion\ndescription: Notion nodes — internal integration tokens, database / page IDs.\nuser-invocable: false\n---\n\n# Notion\n\n## Node type\n\n`n8n-nodes-base.notion`.\n\n## Credential type\n\n`notionApi` — Internal Integration token from `notion.so/profile/integrations`.\n\nFor setup, see [`skills/manage-credentials.md`](../../manage-credentials.md). Path A: paste the `secret_...` token into `.env.<env>` and run.\n\n## Database / page IDs\n\nNotion URLs encode IDs without dashes; the API expects dashes. Example:\n\n- URL: `https://www.notion.so/Workspace/MyDB-abc123def456789012345678901234`\n- API: `abc123de-f456-7890-1234-5678901234`\n\nThe n8n Notion node accepts either form. Store the with-dashes version in env YAML.\n\n## Sharing the integration\n\nDatabase / page operations only work if the page is **shared with the integration** (top-right \"Share\" → invite the integration). This is a common foot-gun — the API returns 404 for unshared pages.\n\n## Per-property field shape\n\nEach property type (title, rich_text, multi_select, …) has a different JSON shape. The n8n Notion node abstracts this for common types but for complex inserts you may need a Code node that builds the body manually. See Notion's API docs for the exact field shapes.\n\nFile v2.0.0:skills/integrations/sentry/README.md\n\n---\nname: integration-sentry\ndescription: Sentry as the audit destination for n8n error handlers — envelope POST via HTTP Request, DSN auth, tag conventions for grouping.\nuser-invocable: false\n---\n\n# Sentry\n\n## Node type\n\n`n8n-nodes-base.httpRequest`. There is no dedicated `n8n-nodes-base.sentry` node — Sentry is HTTP-shaped end-to-end.\n\n## Credential type\n\nTwo ways to authenticate to Sentry's ingest endpoint:\n\n1. **Embedded DSN auth (recommended for the envelope endpoint)** — the DSN string contains both the project ID and the public key. No credential needed; the DSN goes in the URL.\n2. **`httpHeaderAuth` with a Bearer token** — for the legacy `/store/` endpoint or for Sentry's REST management API. Get a token from Sentry → Settings → Account → API → Auth Tokens.\n\nThe envelope endpoint with DSN-in-URL is the simpler, current-recommended path. Treat the DSN as a secret and store it in `.env.<env>`:\n\n```bash\n# .env.<env>\nSENTRY_DSN=https://<public_key>@o<org_id>.ingest.sentry.io/<project_id>\n```\n\nResolve via `{{@env:sentry.dsn}}` (after wiring `sentry.dsn: \"${SENTRY_DSN}\"` in your env YAML). See [`skills/manage-credentials.md`](../../manage-credentials.md) for the env-secret pattern.\n\n## Endpoint\n\n**Current (envelope):**\n```\nPOST https://o<org_id>.ingest.sentry.io/api/<project_id>/envelope/\n```\n\nThe envelope endpoint accepts events wrapped in a multi-line newline-delimited envelope structure. For the simpler \"send one event\" case, the envelope body is three lines: header, item-header, item-payload.\n\n**Legacy (deprecated, still works):**\n```\nPOST https://sentry.io/api/<project_id>/store/\nHeader: X-Sentry-Auth: Sentry sentry_version=7, sentry_key=<public_key>, sentry_client=n8n/1.0\nBody: <event JSON>\n```\n\nThe legacy `/store/` endpoint is documented as deprecated by Sentry but is widely used in custom integrations and will keep working for the foreseeable future. If your Sentry environment supports the envelope endpoint, prefer it.\n\n## Worked example: HTTP Request node sending an envelope\n\nThis is the node body inside an n8n error handler, fed by the `Build Context` Code node from [`patterns/error-handling.md`](../../patterns/error-handling.md). The DSN can be baked into the URL via expression — DSN-in-URL skips the X-Sentry-Auth header entirely.\n\n```json\n{\n  \"type\": \"n8n-nodes-base.httpRequest\",\n  \"typeVersion\": 4.2,\n  \"parameters\": {\n    \"method\": \"POST\",\n    \"url\": \"={{ 'https://o' + $env.SENTRY_ORG_ID + '.ingest.sentry.io/api/' + $env.SENTRY_PROJECT_ID + '/envelope/' }}\",\n    \"sendHeaders\": true,\n    \"headerParameters\": {\n      \"parameters\": [\n        { \"name\": \"Content-Type\", \"value\": \"application/x-sentry-envelope\" },\n        { \"name\": \"X-Sentry-Auth\", \"value\": \"={{ 'Sentry sentry_version=7, sentry_key=' + $env.SENTRY_PUBLIC_KEY + ', sentry_client=n8n/1.0' }}\" }\n      ]\n    },\n    \"sendBody\": true,\n    \"contentType\": \"raw\",\n    \"rawContentType\": \"application/x-sentry-envelope\",\n    \"body\": \"={{ JSON.stringify({ event_id: $json.execution_id.replace(/-/g,''), sent_at: $json.timestamp, dsn: $env.SENTRY_DSN }) + '\\\\n' + JSON.stringify({ type: 'event', content_type: 'application/json' }) + '\\\\n' + JSON.stringify({ event_id: $json.execution_id.replace(/-/g,''), timestamp: Math.floor(new Date($json.timestamp).getTime() / 1000), platform: 'javascript', logger: 'n8n', level: 'error', logentry: { message: $json.message }, exception: { values: [{ type: 'WorkflowError', value: $json.message, stacktrace: { frames: [{ function: $json.last_node, module: $json.workflow_name, in_app: true }] } }] }, tags: { workflow_id: $json.workflow_id, workflow_name: $json.workflow_name, execution_id: $json.execution_id, env: $env.N8N_ENV, last_node: $json.last_node }, extra: { execution_url: $json.execution_url } }) }}\",\n    \"options\": { \"response\": { \"response\": { \"neverError\": true } } }\n  }\n}\n```\n\nThe body is the three-line envelope: envelope header (with DSN), item header (declaring the event type), then the event JSON payload.\n\nFor most cases the simpler legacy `/store/` POST is more readable:\n\n```json\n{\n  \"type\": \"n8n-nodes-base.httpRequest\",\n  \"typeVersion\": 4.2,\n  \"parameters\": {\n    \"method\": \"POST\",\n    \"url\": \"={{ 'https://sentry.io/api/' + $env.SENTRY_PROJECT_ID + '/store/' }}\",\n    \"sendHeaders\": true,\n    \"headerParameters\": {\n      \"parameters\": [\n        { \"name\": \"Content-Type\", \"value\": \"application/json\" },\n        { \"name\": \"X-Sentry-Auth\", \"value\": \"={{ 'Sentry sentry_version=7, sentry_key=' + $env.SENTRY_PUBLIC_KEY + ', sentry_client=n8n/1.0' }}\" }\n      ]\n    },\n    \"sendBody\": true,\n    \"specifyBody\": \"json\",\n    \"jsonBody\": \"={{ JSON.stringify({ event_id: $json.execution_id.replace(/-/g,''), timestamp: Math.floor(new Date($json.timestamp).getTime() / 1000), platform: 'javascript', logger: 'n8n', level: 'error', logentry: { message: $json.message }, exception: { values: [{ type: 'WorkflowError', value: $json.message }] }, tags: { workflow_id: $json.workflow_id, workflow_name: $json.workflow_name, execution_id: $json.execution_id, env: $env.N8N_ENV, last_node: $json.last_node }, extra: { execution_url: $json.execution_url } }) }}\",\n    \"options\": { \"response\": { \"response\": { \"neverError\": true } } }\n  }\n}\n```\n\n`response.neverError: true` is critical: a Sentry-side rate limit or 5xx must NOT break the error handler. Log silently and continue.\n\n## Event JSON shape (legacy /store/ form)\n\n```json\n{\n  \"event_id\": \"<32-char hex>\",\n  \"timestamp\": <unix-seconds>,\n  \"platform\": \"javascript\",\n  \"logger\": \"n8n\",\n  \"level\": \"error\",\n  \"logentry\": { \"message\": \"<human-readable>\" },\n  \"exception\": {\n    \"values\": [\n      {\n        \"type\": \"WorkflowError\",\n        \"value\": \"<error message>\",\n        \"stacktrace\": {\n          \"frames\": [\n            { \"function\": \"<last_node_name>\", \"module\": \"<workflow_name>\", \"in_app\": true }\n          ]\n        }\n      }\n    ]\n  },\n  \"tags\": {\n    \"workflow_id\": \"...\",\n    \"workflow_name\": \"...\",\n    \"execution_id\": \"...\",\n    \"env\": \"...\",\n    \"last_node\": \"...\"\n  },\n  \"extra\": {\n    \"execution_url\": \"...\"\n  }\n}\n```\n\n## Tag conventions\n\nSentry's grouping algorithm is driven by the `exception.values[].type` and the top frame's `function` + `module`. To make grouping behave well:\n\n- Set `exception.values[0].type` to a stable string like `\"WorkflowError\"` (not the dynamic error message).\n- Put the failing node name in the top frame's `function`. This makes \"all errors at the Foo node\" cluster together regardless of message.\n- Put `workflow_name` in the top frame's `module`. Sentry will then cluster per workflow as the next layer.\n\nAlways include the standard tags from [`patterns/error-handling.md`](../../patterns/error-handling.md):\n\n| Tag | Source expression |\n|---|---|\n| `workflow_id` | `={{ $workflow.id }}` |\n| `workflow_name` | `={{ $workflow.name }}` |\n| `execution_id` | `={{ $workflow.errorData?.execution?.id || $execution.id }}` |\n| `env` | `{{@env:name}}` |\n| `last_node` | `={{ $workflow.errorData?.lastNodeExecuted }}` |\n\nAdd business-relevant tags (`scope`, `user_id`, `tenant_id`) so Sentry filtering reflects what your on-call actually needs.\n\n## See also\n\n- [`patterns/error-handling.md`](../../patterns/error-handling.md) — three-step paradigm + worked handler example.\n- [`manage-credentials.md`](../../manage-credentials.md) — DSN as env-secret.\n\nFile v2.0.0:skills/integrations/slack/README.md\n\n---\nname: integration-slack\ndescription: Slack nodes — bot/user OAuth, posting messages, threads, error-notification Block Kit pattern.\nuser-invocable: false\n---\n\n# Slack\n\n## Node type\n\n`n8n-nodes-base.slack`.\n\n## Credential type\n\n`slackApi` (bot token) or `slackOAuth2Api` (full OAuth flow).\n\nFor setup, see [`skills/manage-credentials.md`](../../manage-credentials.md). Bot tokens (`slackApi`) are simpler — paste `xoxb-...` into `.env.<env>` and run Path A.\n\n## Common operations\n\n- **Post message:** `resource: message`, `operation: post`. Set `channel` to a channel ID (preferred) or `#name` (sometimes resolves slowly).\n- **Reply in thread:** include `thread_ts` from a previous message.\n- **Update message:** `operation: update` with `ts` of the original message.\n\n## Channel ID gotcha\n\nSlack's UI shows `#channel-name` but the API takes channel IDs (e.g. `C0123456789`). Find the ID by right-clicking the channel → Copy Link → the URL contains the ID. Store the ID in your env YAML, not the name:\n\n```yaml\nslack:\n  alertsChannel: \"C0123456789\"\n```\n\n```json\n{\n  \"type\": \"n8n-nodes-base.slack\",\n  \"parameters\": {\n    \"channel\": \"{{@env:slack.alertsChannel}}\"\n  }\n}\n```\n\n## Error-notification worked example\n\nUsed inside an n8n error handler, fed by the `Build Context` Code node from [`patterns/error-handling.md`](../../patterns/error-handling.md). Posts a Block Kit message to the alerts channel + captures the response so a downstream node can thread updates onto it.\n\n```json\n{\n  \"type\": \"n8n-nodes-base.slack\",\n  \"typeVersion\": 2.3,\n  \"parameters\": {\n    \"resource\": \"message\",\n    \"operation\": \"post\",\n    \"select\": \"channel\",\n    \"channelId\": {\n      \"__rl\": true,\n      \"value\": \"{{@env:slack.alertsChannel}}\",\n      \"mode\": \"id\"\n    },\n    \"messageType\": \"block\",\n    \"blocksUi\": \"={{ JSON.stringify({ blocks: [\\n  { type: 'header', text: { type: 'plain_text', text: ':rotating_light: Workflow failed: ' + $json.workflow_name } },\\n  { type: 'section', fields: [\\n    { type: 'mrkdwn', text: '*Workflow:*\\\\n' + $json.workflow_name },\\n    { type: 'mrkdwn', text: '*Last node:*\\\\n`' + ($json.last_node || 'unknown') + '`' },\\n    { type: 'mrkdwn', text: '*Env:*\\\\n' + ($env.N8N_ENV || 'unknown') },\\n    { type: 'mrkdwn', text: '*When:*\\\\n' + $json.timestamp }\\n  ]},\\n  { type: 'section', text: { type: 'mrkdwn', text: '*Error message:*\\\\n```\\\\n' + $json.message + '\\\\n```' } },\\n  { type: 'actions', elements: [\\n    { type: 'button', text: { type: 'plain_text', text: 'Open in n8n' }, url: $json.execution_url || 'https://n8n.example.com', style: 'primary' }\\n  ]}\\n]}) }}\",\n    \"otherOptions\": {}\n  },\n  \"credentials\": {\n    \"slackApi\": {\n      \"id\": \"{{@env:credentials.slack.id}}\",\n      \"name\": \"{{@env:credentials.slack.name}}\"\n    }\n  }\n}\n```\n\nThe `messageType: block` mode tells the Slack node to send Block Kit JSON in the `blocksUi` field. The Block Kit structure shipped above is:\n\n- A `header` block with the rotating-light emoji + workflow name (large, can't-miss-it).\n- A `section` with `fields` showing workflow / last-node / env / timestamp in a 2-column grid.\n- A second `section` with the error message in a fenced code block.\n- An `actions` row with a primary button linking to the n8n execution URL.\n\nSlack's response includes `ts` (the message timestamp) — capture it via `$json.ts` for thread updates:\n\n```javascript\n// In a downstream node referencing the Slack post output:\nconst threadTs = $('Slack Post').first().json.ts;\n```\n\n## Threading updates onto the original alert\n\nWhen the cleanup step finishes (lock released, DB invalidated, etc.), reply in-thread on the original alert so on-call sees the resolution without a second top-level message:\n\n```json\n{\n  \"type\": \"n8n-nodes-base.slack\",\n  \"typeVersion\": 2.3,\n  \"parameters\": {\n    \"resource\": \"message\",\n    \"operation\": \"post\",\n    \"select\": \"channel\",\n    \"channelId\": {\n      \"__rl\": true,\n      \"value\": \"{{@env:slack.alertsChannel}}\",\n      \"mode\": \"id\"\n    },\n    \"text\": \"={{ ':white_check_mark: Cleanup complete: ' + $json.cleanup_summary }}\",\n    \"otherOptions\": {\n      \"thread_ts\": \"={{ $('Slack Post').first().json.ts }}\"\n    }\n  }\n}\n```\n\nThis keeps the alerts channel readable: one top-level :rotating_light: per incident, replies-in-thread for status updates.\n\n## See also\n\n- [`patterns/error-handling.md`](../../patterns/error-handling.md) — three-step paradigm; this Slack post is the \"log to humans\" step.\n- [`manage-credentials.md`](../../manage-credentials.md) — registering the slackApi credential.\n\nFile v2.0.0:skills/integrations/webhooks/README.md\n\n---\nname: integration-webhooks\ndescription: Webhook node patterns — paths, response modes, test vs production URLs.\nuser-invocable: false\n---\n\n# Webhooks\n\n## Node types\n\n- `n8n-nodes-base.webhook` — incoming webhook trigger.\n- `n8n-nodes-base.respondToWebhook` — explicit response (when not using `responseMode: lastNode`).\n\n## Path conventions\n\nWebhook paths are env-agnostic but share a base URL per env. Use a stable, descriptive path:\n\n```json\n{\n  \"type\": \"n8n-nodes-base.webhook\",\n  \"parameters\": {\n    \"httpMethod\": \"POST\",\n    \"path\": \"report/daily\",\n    \"responseMode\": \"lastNode\"\n  }\n}\n```\n\nn8n exposes the webhook at `<instance>/webhook/<path>` (production) and `<instance>/webhook-test/<path>` (test mode in the UI). `run.py` tries production first, then falls back to test.\n\n## Response modes\n\n- `responseMode: lastNode` — n8n returns the last node's output as the response. Simplest case.\n- `responseMode: responseNode` — terminate flow with `respondToWebhook` to control status/headers/body explicitly.\n\n## Authentication\n\n- **None** — public webhook. Fine for triggers from trusted sources (or with HMAC-verifying middleware in the workflow).\n- **Header Auth** — n8n verifies a header against a credential. Credential type: `httpHeaderAuth`.\n- **Basic Auth** — credential type: `httpBasicAuth`.\n\nFor credential setup, see [`skills/manage-credentials.md`](../../manage-credentials.md).\n\n## webhookId placeholder\n\nn8n's webhook node has a `webhookId` field that contains a UUID identifying the public webhook URL. Use `{{@uuid:webhook-public}}` so each env gets its own (the path is shared but the public webhook URL varies).\n\nFile v2.0.0:_meta.json\n\n{\n  \"ownerId\": \"kn744p2vzxwdnxfjhek7pxsz4n860kr1\",\n  \"slug\": \"n8n-evol-i\",\n  \"version\": \"2.0.0\",\n  \"publishedAt\": 1779747410188\n}\n\nFile v2.0.0:install.md\n\n---\nname: install\ndescription: How to install n8n-evol-I as a skill package and what it expects on the host system.\n---\n\n# install\n\n## Prerequisites\n\n- Python ≥ 3.11 on PATH\n- Pip-installable: `pyyaml`, `requests`, `python-dotenv`\n- An n8n instance the agent can reach via REST + an API key with workflow + credential scopes\n\n## Install (one-time)\n\n### Skill mode (any agent runtime)\n\nClone into your agent's skills directory:\n\n```bash\ncd ~/.claude/skills   # or wherever your agent runtime reads skills from\ngit clone https://github.com/mwamedacen/n8n-evol-I.git\n```\n\nInstall Python deps:\n\n```bash\npip install pyyaml requests python-dotenv\n```\n\n(Optional, only if you'll use `iterate-prompt`:)\n\n```bash\npip install dspy litellm\n```\n\n### Plugin mode (Claude Code only)\n\nCLI form (run in your terminal):\n\n```bash\nclaude plugin install https://github.com/mwamedacen/n8n-evol-I\n```\n\nIn-session form (inside a Claude Code session):\n\n```\n/plugin install https://github.com/mwamedacen/n8n-evol-I\n```\n\nLocal dev (loads from a local checkout):\n\n```bash\nclaude --plugin-dir ./n8n-evol-I\n```\n\n#### Plugin extras\n\nWhen installed as a plugin, n8n-evol-I ships two additional behaviors:\n\n- **Slash commands** — 10 user-facing commands available as `/n8n-evol-I:deploy`, `/n8n-evol-I:tidyup`, etc. Hidden lifecycle skills remain agent-loadable via `SKILL.md` routing but do not appear in `/help`.\n\n- **Auto-tidy hook** — a `PostToolUse` hook fires after every Write/Edit/MultiEdit tool call; the hook script filters to `*.template.json` files and runs `tidy_workflow.py --in-place` on matching files. This keeps node positions clean without any manual step.\n\n  The hook runs asynchronously (`async: true`), so Claude Code does not wait for it before continuing. If the agent reads the template file immediately after writing it (e.g. to feed it into a deploy step), it may observe the un-tidied version. In that case, run `tidy-workflow` manually before deploying, or add a short pause between the write and the read.\n\n  To disable the auto-tidy hook: remove or rename `hooks/hooks.json` in the plugin directory, or disable the plugin in Claude Code settings. Standalone-skill-mode users who want auto-tidy can configure the hook manually in `~/.claude/settings.json`.\n\n## What the harness expects on disk\n\nThe skill package is a directory containing:\n\n- `SKILL.md` — entry point\n- `skills/*.md` and `skills/{patterns,integrations}/...md` — sub-skills\n- `helpers/*.py` — the executable surface\n- `primitives/` — seed templates copied into workspaces on demand\n\nHelpers are invoked by absolute path (no console script, no PATH pollution):\n\n```bash\npython3 <harness>/helpers/<name>.py [args]\n```\n\n## Per-project setup\n\nPer project, the agent runs `init.md` once to scaffold a workspace at `${PWD}/n8n-evol-I-workspace/`. From there, `bootstrap-env.md` configures envs, `create-new-workflow.md` authors workflows, etc. See [`SKILL.md`](SKILL.md) for the full skill catalogue.\n\n## Updating\n\nThe harness's \"version\" is its git SHA. To upgrade:\n\n```bash\ncd ~/.claude/skills/n8n-evol-I\ngit pull\n```\n\nBreaking changes between versions are documented in [`CHANGELOG.md`](CHANGELOG.md). For the migration from the legacy single-repo shape (pre-`d6848fd`), see [`docs/migration-from-d6848fd.md`](docs/migration-from-d6848fd.md).\n\n## Verifying the install\n\n```bash\npython3 <harness>/helpers/init.py --workspace /tmp/n8n-evol-I-smoke\nls /tmp/n8n-evol-I-smoke/n8n-config /tmp/n8n-evol-I-smoke/n8n-workflows-template\n```\n\nIf both directories exist after the run, the install is good.\n\nFile v2.0.0:skill-card.md\n\n## Description:\n\nA harness to help coding agents build, deploy, maintain, and debug multi-workflow n8n-powered automation systems.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[mwamedacen](https://clawhub.ai/user/mwamedacen)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and automation engineers use N8N EVOL I to author, validate, deploy, resync, and debug multi-environment n8n workflows while keeping workflow templates, credential references, primitives, and tests organized in a local workspace.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill can change live n8n workflows and credentials.\n\nMitigation: Install only for trusted publishers, use narrowly scoped n8n API keys, and run validation, deployment checks, and review before activating workflows.\n\nRisk: Transport, persistence, or placeholder behavior may expose workflow or credential authority if configured loosely.\n\nMitigation: Use HTTPS-only n8n and Upstash endpoints, pin to a reviewed release, and review workflow templates for ../ placeholder paths before deploying.\n\nRisk: Plugin auto-hooks can rewrite workflow templates automatically and asynchronously.\n\nMitigation: Avoid plugin auto-hooks unless automatic template rewrites are intended; run tidy-workflow manually before deployment when deterministic template state matters.\n\n## Reference(s):\n\n- [ClawHub skill page](https://clawhub.ai/mwamedacen/skills/n8n-evol-i)\n- [README](artifact/README.md)\n- [Installation guide](artifact/install.md)\n- [Credential management](artifact/skills/manage-credentials.md)\n- [Validate and deploy pattern](artifact/skills/patterns/validate-deploy.md)\n- [Redis queue pattern](artifact/skills/patterns/queues.md)\n- [Redis locking pattern](artifact/skills/patterns/locking.md)\n\n## Skill Output:\n\n**Output Type(s):** [text, markdown, code, shell commands, configuration, guidance]\n\n**Output Format:** [Markdown guidance with inline shell commands plus generated workspace files, workflow templates, configuration, and code snippets.]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [May invoke Python helpers that write workspace files and call n8n REST APIs when the selected workflow operation requires it.]\n\n## Skill Version(s):\n\n2.0.0 (source: ClawHub release metadata, target metadata, pyproject.toml)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.0.1: 121 files, 199981 bytes\n\nFiles: helpers/__init__.py (0b), helpers/_dspy_config.py (2010b), helpers/activate.py (1441b), helpers/add_cloud_function.py (4779b), helpers/add_lock_to_workflow.py (14170b), helpers/add_rate_limit_to_workflow.py (9784b), helpers/archive.py (1492b), helpers/bootstrap_env.py (7183b), helpers/config.py (2828b), helpers/copy_primitive.py (3725b), helpers/create_lock.py (3865b), helpers/create_workflow.py (5543b), helpers/deactivate.py (1330b), helpers/dehydrate.py (5149b), helpers/dependency_graph.py (8432b), helpers/deploy_all.py (5268b), helpers/deploy_run_assert.py (2376b), helpers/deploy.py (6322b), helpers/diff.py (3282b), helpers/doctor.py (13558b), helpers/find_skills.py (4229b), helpers/hydrate.py (2648b), helpers/init.py (7552b), helpers/inspect_execution.py (3679b), helpers/iterate_prompt.py (5871b), helpers/list_executions.py (6710b), helpers/manage_credentials.py (7939b), helpers/manage_variables.py (5229b), helpers/n8n_client.py (2942b), helpers/package-lock.json (42779b), helpers/package.json (90b), helpers/placeholder/__init__.py (0b), helpers/placeholder/env_resolver.py (2454b), helpers/placeholder/file_resolver.py (1143b), helpers/placeholder/js_resolver.py (2487b), helpers/placeholder/py_resolver.py (2777b), helpers/placeholder/uuid_resolver.py (745b), helpers/placeholder/validator.py (1434b), helpers/register_error_handler.py (2766b), helpers/resync_all.py (1211b), helpers/resync.py (1496b), helpers/run.py (6322b), helpers/stop_executions.py (4719b), helpers/test_functions.py (3764b), helpers/tidy_shim.mjs (652b), helpers/tidy_workflow.py (8833b), helpers/unarchive.py (1517b), helpers/validate.py (12528b), helpers/workspace.py (2098b), install.md (3569b), primitives/cloud-functions/app.py (963b), primitives/cloud-functions/functions/__init__.py (0b), primitives/cloud-functions/functions/hello_world.py (226b), primitives/cloud-functions/railpack.json (116b), primitives/cloud-functions/railway.toml (108b), primitives/cloud-functions/registry.py (196b), primitives/cloud-functions/requirements.txt (39b), primitives/prompts/example_summary_prompt.txt (237b), primitives/prompts/example_summary_schema.json (320b), primitives/workflows/_minimal.template.json (1162b), primitives/workflows/error_handler_lock_cleanup.template.json (8934b), primitives/workflows/lock_acquisition.template.json (13937b), primitives/workflows/lock_release.template.json (7661b), primitives/workflows/rate_limit_check.template.json (2565b), pyproject.toml (704b), README.md (10327b), requirements.txt (12b), SKILL.md (7265b), skills/activate-single-workflow-in-env.md (433b), skills/add-cloud-function.md (1257b), skills/add-lock-to-workflow.md (8125b), skills/add-rate-limit-to-workflow.md (4911b), skills/archive-workflow.md (1620b), skills/bootstrap-env.md (1673b), skills/copy-primitive.md (2693b), skills/create-lock.md (5552b), skills/create-new-workflow.md (1692b), skills/deactivate-single-workflow-in-env.md (429b), skills/debug.md (2924b), skills/dehydrate-workflow.md (589b)\n\nFile v1.0.1:SKILL.md\n\n---\nname: n8n-evol-I\ndescription: A harness to help coding agents build, deploy, maintain, and debug multi-workflow n8n-powered automation systems. No lock-in — work from the agent, continue from the n8n UI, hand back to the agent at any time.\n---\n\n# n8n-evol-I skill router\n\nThis is the entry point. When the user asks anything n8n-related, route to the matching sub-skill.\n\n## Mental model\n\n- The skill package (this directory) is **read-only**. You never edit files here.\n- The user's project state lives in `${PWD}/n8n-evol-I-workspace/` (created by `init.md`).\n- Helpers are Python scripts under `helpers/`. Invoke them with `python3 ${CLAUDE_PLUGIN_ROOT}/helpers/<name>.py [args]` (plugin mode) or `python3 <path-to-harness>/helpers/<name>.py [args]` (skill mode).\n- All helpers default to `--workspace ${PWD}/n8n-evol-I-workspace`. Pass `--workspace <path>` if the user runs from elsewhere.\n- **Agent memory**: read `N8N-WORKSPACE-MEMORY.md` in the workspace at the start of every session; append a dated entry whenever you learn something durable about this project. Full guidance in `AGENTS.md` (also in the workspace root).\n\n## Lifecycle skills (use when the user wants to do X)\n\n| Skill | When |\n|---|---|\n| [init.md](skills/init.md) | First-time setup. Creates the workspace at `${PWD}/n8n-evol-I-workspace/`. |\n| [bootstrap-env.md](skills/bootstrap-env.md) | Configure an environment (`dev` / `staging` / `prod`). Creates env YAML + `.env`, validates, mints placeholder workflow IDs. |\n| [doctor.md](skills/doctor.md) | Health check. Run before/after major changes. |\n| [create-new-workflow.md](skills/create-new-workflow.md) | Author a brand-new workflow. |\n| [register-workflow-to-error-handler.md](skills/register-workflow-to-error-handler.md) | Wire `settings.errorWorkflow`. |\n| [create-lock.md](skills/create-lock.md) | First-time setup for distributed locking (Redis-backed primitives). |\n| [copy-primitive.md](skills/copy-primitive.md) | Copy a single primitive (any) into the workspace. General-purpose; doesn't register. |\n| [add-lock-to-workflow.md](skills/add-lock-to-workflow.md) | Wrap a workflow's main flow in lock acquire/release. |\n| [add-rate-limit-to-workflow.md](skills/add-rate-limit-to-workflow.md) | Gate a workflow's main flow with a Redis-backed fixed-window rate-limit check. |\n| [tidy-workflow.md](skills/tidy-workflow.md) | Apply n8n's canvas-layout algorithm to a workflow template to clean up node positions. |\n| [deploy.md](skills/deploy.md) | Deploy one workflow to one env. |\n| [activate-single-workflow-in-env.md](skills/activate-single-workflow-in-env.md) | Activate after deploy. |\n| [deactivate-single-workflow-in-env.md](skills/deactivate-single-workflow-in-env.md) | Pause triggers (commonly during dev). |\n| [archive-workflow.md](skills/archive-workflow.md) | Retire a deployed workflow (hidden + read-only on the live instance). |\n| [unarchive-workflow.md](skills/unarchive-workflow.md) | Restore a previously-archived workflow so it accepts updates again. |\n| [deploy_all.md](skills/deploy_all.md) | Roll out an entire env in tier order. |\n| [resync.md](skills/resync.md) | Pull live state of one workflow back into its template. |\n| [resync_all.md](skills/resync_all.md) | Snapshot a full env back to templates. |\n| [dehydrate-workflow.md](skills/dehydrate-workflow.md) | Convert raw exported JSON into a template. |\n| [validate.md](skills/validate.md) | Structural REST validation before deploy. |\n| [run.md](skills/run.md) | Fire a webhook + assert terminal status. |\n| [debug.md](skills/debug.md) | Investigate a failing or missing execution — from vague symptom to root-cause with evidence. |\n| [deploy-run-assert.md](skills/deploy-run-assert.md) | One-shot validate → deploy → run verify. |\n| [find-skills.md](skills/find-skills.md) | While authoring, find applicable patterns/integrations. |\n| [manage-credentials.md](skills/manage-credentials.md) | Create or link n8n credentials (Path A from `.env.<env>` / Path B from existing UI credential). |\n| [add-cloud-function.md](skills/add-cloud-function.md) | Scaffold a Python serverless function / cloud function / serverless API under `<workspace>/cloud-functions/`. |\n| [iterate-prompt.md](skills/iterate-prompt.md) | Optimize a prompt against a paired schema + dataset using DSPy. |\n| [test.md](skills/test.md) | Run unit tests over n8n Code-node JS and / or cloud-function Python. |\n\n## Pattern skills (read-only knowledge)\n\nThese are reference docs, not action triggers. Read them while authoring.\n\n- [skills/patterns/subworkflows.md](skills/patterns/subworkflows.md)\n- [skills/patterns/error-handling.md](skills/patterns/error-handling.md)\n- [skills/patterns/credential-refs.md](skills/patterns/credential-refs.md)\n- [skills/patterns/multi-env-uuid-collision.md](skills/patterns/multi-env-uuid-collision.md)\n- [skills/patterns/validate-deploy.md](skills/patterns/validate-deploy.md)\n- [skills/patterns/code-node-discipline.md](skills/patterns/code-node-discipline.md)\n- [skills/patterns/llm-providers.md](skills/patterns/llm-providers.md)\n- [skills/patterns/locking.md](skills/patterns/locking.md)\n- [skills/patterns/pindata-hygiene.md](skills/patterns/pindata-hygiene.md)\n- [skills/patterns/position-recalculation.md](skills/patterns/position-recalculation.md)\n- [skills/patterns/prompt-and-schema-conventions.md](skills/patterns/prompt-and-schema-conventions.md)\n- [skills/patterns/agent-api-discipline.md](skills/patterns/agent-api-discipline.md)\n- [skills/patterns/investigation-discipline.md](skills/patterns/investigation-discipline.md)\n\n## Integration skills (per-service quirks)\n\n- [skills/integrations/microsoft-365/excel-and-sharepoint.md](skills/integrations/microsoft-365/excel-and-sharepoint.md)\n- [skills/integrations/gmail/sending-email.md](skills/integrations/gmail/sending-email.md)\n- [skills/integrations/redis/lock-pattern.md](skills/integrations/redis/lock-pattern.md)\n- [skills/integrations/sentry/README.md](skills/integrations/sentry/README.md)\n- [skills/integrations/datadog/README.md](skills/integrations/datadog/README.md)\n- [skills/integrations/slack/README.md](skills/integrations/slack/README.md)\n- [skills/integrations/google-drive/README.md](skills/integrations/google-drive/README.md)\n- [skills/integrations/notion/README.md](skills/integrations/notion/README.md)\n- [skills/integrations/airtable/README.md](skills/integrations/airtable/README.md)\n- [skills/integrations/webhooks/README.md](skills/integrations/webhooks/README.md)\n\n## Placeholder syntax (workflow templates)\n\nTemplates use `{{@:type:path}}` (preferred form) or the canonical long form `{{INTERPOLATE:type:path}}`. The two are equivalent — `@` is an alias for `INTERPOLATE`. Examples below use the `@` form.\n\n| Type | Syntax | Source |\n|---|---|---|\n| `env` | `{{@:env:key.path}}` | YAML config value (dot notation) |\n| `txt` | `{{@:txt:relative/path.txt}}` | Text file in workspace |\n| `json` | `{{@:json:relative/path.json}}` | JSON file (stringified) |\n| `html` | `{{@:html:relative/path.html}}` | HTML file |\n| `js` | `{{@:js:relative/path.js}}` | JavaScript file |\n| `py` | `{{@:py:relative/path.py}}` | Python file (Code-node `language: python`) |\n| `uuid` | `{{@:uuid:identifier}}` | Fresh UUID v4 (consistent within one hydration) |\n\nFile v1.0.1:README.md\n\n<img width=\"1376\" height=\"768\" alt=\"n8n_evol_I_hero\" src=\"https://github.com/user-attachments/assets/0a28f1f6-106f-4f84-9ce1-cf7eebcf4cd6\" />\n\n# N8N EVOL I\n\nA harness to help coding agents build, deploy, maintain, and debug multi-workflow n8n-powered automation systems.\n\nNo lock-in. Build with your agent and push upstream to n8n; tweak directly in the n8n UI and pull downstream back into your workspace. Round-trips are template-stable, so humans and agents can work the same project in parallel without stepping on each other's edits.\n\n```mermaid\nflowchart LR\n    Agent <-->|edits / resync| Workspace\n    Workspace <-->|deploy / resync| n8n[(n8n instance)]\n    n8n <-->|UI edits| Human\n```\n\n## Features\n\n- **Multi-environment workflows.** Per-env YAML in `n8n-config/<env>.yml` (instance URL, workflow IDs, credential refs); every helper accepts `--env`. See [`bootstrap-env.md`](skills/bootstrap-env.md).\n\n  ```yaml\n  # n8n-config/dev.yml\n  instance: acme.dev.n8n.cloud\n  envSuffix: \" (dev)\"          # appended to every workflow name\n  workflows:\n    lead_enrichment:\n      id: wf_def456\n      displayName: \"Lead Enrichment\"\n    lead_persister:\n      id: wf_abc123\n      displayName: \"Lead Persister\"\n  ```\n\n- **Build-time substitution.** Code, prompts, schemas, templates, and env values live in workspace files; `{{@:js|py|txt|json|html|env|uuid:...}}` placeholders are substituted at deploy time. Template-stable round-trips. See [`code-node-discipline.md`](skills/patterns/code-node-discipline.md).\n\n  ```jsonc\n  // n8n-workflows-template/lead_enrichment.template.json\n  {\n    \"name\": \"{{@:env:workflows.lead_enrichment.displayName}}{{@:env:envSuffix}}\",\n    \"nodes\": [\n      {\n        \"name\": \"Webhook\",\n        \"type\": \"n8n-nodes-base.webhook\",\n        \"parameters\": { \"path\": \"lead\", \"httpMethod\": \"POST\" }\n      },\n      {\n        \"name\": \"Score Lead\",\n        \"type\": \"@n8n/n8n-nodes-langchain.openAi\",\n        \"parameters\": {\n          \"messages\": [\n            { \"role\": \"system\", \"content\": \"{{@:txt:n8n-prompts/prompts/score_lead.txt}}\" }\n          ]\n        }\n      },\n      {\n        \"name\": \"Normalize Fields\",\n        \"type\": \"n8n-nodes-base.code\",\n        \"parameters\": {\n          \"jsCode\": \"{{@:js:n8n-functions/js/normalize_lead.js}}\\n\\nreturn normalize(items);\"\n        }\n      },\n      {\n        \"name\": \"Persist via Sub-workflow\",\n        \"type\": \"n8n-nodes-base.executeWorkflow\",\n        \"parameters\": {\n          \"workflowId\": \"{{@:env:workflows.lead_persister.id}}\"\n        }\n      }\n    ]\n  }\n  ```\n\n- **Dependency-ordered deployment.** `deploy_all.py` rolls out an env in tier order (set per-workflow in `deployment_order.yml`) so callees deploy before callers. See [`deploy_all.md`](skills/deploy_all.md).\n\n  ```mermaid\n  graph LR\n    A[tier 0: leaves] --> B[tier 1: handlers] --> C[tier 2: callers]\n  ```\n\n- **Execution debugging.** Structured symptom → root-cause flow (dependency graph, candidate pre-screen, causal-linkage, sub-agent cross-check), backed by `list_executions.py`, `inspect_execution.py`, `dependency_graph.py`. See [`investigation-discipline.md`](skills/patterns/investigation-discipline.md).\n\n  ```bash\n  python3 $HARNESS/helpers/inspect_execution.py --env dev --id 91234\n  ```\n\n- **Distributed locking.** Redis-backed `lock_acquisition` / `lock_release` primitives with an identity-payload sidecar; crashes either self-heal via Redis TTL or are cleaned by the registered `error_handler_lock_cleanup` workflow. See [`locking.md`](skills/patterns/locking.md).\n\n  ```\n  ┌──────────┐   ┌──────┐   ┌──────────┐\n  │ acquire  │ → │ work │ → │ release  │\n  │ (scope)  │   │      │   │ (scope)  │\n  └──────────┘   └──────┘   └──────────┘\n  ```\n\n- **Rate limiting.** Fixed-window Redis `INCR` primitive; configurable limit, window, and denied-branch behavior (passthrough / stop / error). See [`locking.md`](skills/patterns/locking.md).\n\n  ```\n                            ┌─→ work       (within N per window)\n  request → check(scope) ───┤\n                            └─→ denied     (over limit)\n  ```\n\n- **Error handling + observability.** Capture (Error Trigger) → log (sinks fan out in parallel) → process (lock cleanup, DB invalidation, compensating workflows). See [`error-handling.md`](skills/patterns/error-handling.md), [`datadog/`](skills/integrations/datadog/README.md).\n\n  ```\n                    ┌─ Sentry  ─┐\n  Error Trigger ──→ ├─ Datadog ─┤ ──→ lock cleanup / invalidate / compensate\n                    └─ Slack   ─┘\n  ```\n\n- **Serverless functions (a.k.a. cloud functions / serverless APIs).** Scaffolds a Python function into a FastAPI service in `cloud-functions/`, auto-registers it in the router, ships with Railway config, callable from n8n via HTTP Request nodes. See [`add-cloud-function.md`](skills/add-cloud-function.md).\n\n  ```bash\n  python3 $HARNESS/helpers/add_cloud_function.py --name parse_invoice\n  ```\n\n- **DSPy prompt optimization.** Optimize a workspace prompt against its paired schema + dataset (BootstrapFewShot or MIPROv2), eval on structural correctness, optionally write the result back to disk. See [`iterate-prompt.md`](skills/iterate-prompt.md).\n\n  ```bash\n  python3 $HARNESS/helpers/iterate_prompt.py --prompt extract_invoice --optimizer mipro_v2\n  ```\n\n## Install\n\n| | Skill mode | Plugin mode |\n|---|---|---|\n| Slash commands | No | Yes (`/n8n-evol-I:*`) |\n| Auto-tidy hook | Manual opt-in | Automatic |\n| Discovery | Read `SKILL.md` | Claude Code `/help` |\n| Other agent runtimes | Yes | Claude Code only |\n\n**Skill mode** (any agent runtime):\n\n```bash\ngit clone https://github.com/mwamedacen/n8n-evol-I.git ~/.claude/skills/n8n-evol-I\npip install pyyaml requests python-dotenv\n```\n\n**Plugin mode** (Claude Code) — adds 10 namespaced slash commands (`/n8n-evol-I:deploy`, `:deploy_all`, `:resync`, `:resync_all`, `:tidyup`, `:debug`, `:run`, `:doctor`, `:validate`, `:test`) plus an auto-tidy hook.\n\n```bash\nclaude plugin install https://github.com/mwamedacen/n8n-evol-I\n```\n\n## Quick start\n\n```bash\nHARNESS=~/.claude/skills/n8n-evol-I   # or $CLAUDE_PLUGIN_ROOT in plugin mode\npython3 $HARNESS/helpers/init.py\npython3 $HARNESS/helpers/bootstrap_env.py --env dev --instance acme.app.n8n.cloud --api-key <key>\npython3 $HARNESS/helpers/doctor.py --env dev\n```\n\nSee [`install.md`](install.md) for prerequisites, optional extras, and update flow.\n\n## Why\n\nOperating n8n past the toy stage exposes the same gaps on every project. The UI editor is fine for a demo and tedious by the third workflow — every code change is click-paste-save, every refactor is manual, every diff is a 200KB JSON blob with code, prompts, schemas, and HTML inlined. A coding agent burns half its context window parsing one file. Two scheduled runs can quietly race on the same record. When something fails at 2am, the execution log tells you *what* broke, not *why*.\n\nN8N EVOL I is the structure that takes those problems off the table.\n\n## How it works\n\nThe harness directory is read-only at runtime. The harness operates against your existing project directory — referred to here as `{YOUR_N8N_PROJECT_DIR}` — which becomes the workspace; the agent reads and writes the opinionated subtree (`n8n-config/`, `n8n-workflows-template/`, …) directly inside it, and you version-control it however you want. The agent reads [`SKILL.md`](SKILL.md) to route any n8n request to the right sub-skill, which invokes a standalone Python helper in `helpers/`. No master CLI, no daemon. `validate.py` enforces segmentation discipline before any deploy.\n\n```mermaid\ngraph LR\n  A[agent] --> B[SKILL.md router]\n  B --> C[skills/*.md]\n  C --> D[helpers/*.py]\n  D --> E[workspace files]\n  D --> F[n8n REST API]\n```\n\n## Workspace layout\n\n`init.py` scaffolds the opinionated default below. If your project already has its own layout, document the mapping in `N8N-WORKSPACE-MEMORY.md` — the agent reads that journal each session and reconciles, so most non-default shapes work fine as long as it can see where each thing actually lives:\n\n```\n{YOUR_N8N_PROJECT_DIR}/\n├── AGENTS.md                # workspace orientation (read first every session)\n├── N8N-WORKSPACE-MEMORY.md  # rolling journal — agent appends as it learns\n├── n8n-config/              # env YAML (dev.yml, prod.yml, …) + .env.<env> secrets\n├── n8n-workflows-template/  # *.template.json — canonical, version-controlled\n├── n8n-build/               # hydrated outputs — gitignored, regenerated on deploy\n├── n8n-functions/\n│   ├── js/                  # pure JS injected via {{@:js:...}}\n│   └── py/                  # pure Python injected via {{@:py:...}}\n├── n8n-functions-tests/     # *.test.js / test_*.py — paired tests, validator-required\n├── n8n-prompts/\n│   ├── prompts/             # *_prompt.txt + *_schema.json\n│   ├── datasets/            # *.json for iterate-prompt\n│   └── evals/\n├── n8n-assets/\n│   ├── email-templates/     # *.html injected via {{@:html:...}}\n│   ├── images/\n│   └── misc/\n├── cloud-functions/         # FastAPI service scaffolded by add-cloud-function\n│   └── functions/\n└── cloud-functions-tests/\n```\n\n## Repository layout\n\n| Path | Contents |\n|---|---|\n| [`SKILL.md`](SKILL.md) | Router — lists all lifecycle, pattern, and integration skills. |\n| [`install.md`](install.md) | Prerequisites, install, smoke test, update flow. |\n| `skills/` | 27 lifecycle + 13 pattern + 10 integration skills (50 total). |\n| `helpers/` | 35 top-level Python helpers + 6 `placeholder/` resolvers. |\n| `primitives/workflows/` | Seed templates: `_minimal`, `lock_acquisition`, `lock_release`, `error_handler_lock_cleanup`, `rate_limit_check`. |\n| `primitives/cloud-functions/` | FastAPI app seed + Railway config (`app.py`, `registry.py`, `railpack.json`). |\n| `primitives/prompts/` | Example prompt + schema for `iterate-prompt`. |\n| `tests/` | 200+ offline tests (HTTP mocked) covering every helper, primitive, and pattern. |\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md).\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n\nFile v1.0.1:skills/integrations/airtable/README.md\n\n---\nname: integration-airtable\ndescription: Airtable nodes — PAT credential, base / table IDs.\nuser-invocable: false\n---\n\n# Airtable\n\n## Node type\n\n`n8n-nodes-base.airtable`.\n\n## Credential type\n\n`airtableTokenApi` — Personal Access Token (PAT) from `airtable.com/create/tokens`.\n\nFor setup, see [`skills/manage-credentials.md`](../../manage-credentials.md). Scopes minimum: `data.records:read` and (if writing) `data.records:write`. Each token can be limited to specific bases — granular tokens are best practice.\n\n## Base + table IDs\n\nAirtable URLs: `https://airtable.com/<baseId>/<tableId>/<viewId>`. The n8n Airtable node takes `base` (= `baseId`) and `table` (= `tableId` or table name).\n\nStore in env YAML:\n\n```yaml\nairtable:\n  ordersBase: \"appabc...\"\n  ordersTable: \"tblxyz...\"\n```\n\n## Field-name vs field-id\n\nRecords returned by the API use field NAMES by default. The Airtable node also supports `returnFieldsByFieldId: true` if your field names change frequently — this returns IDs instead and is more stable.\n\nFile v1.0.1:skills/integrations/datadog/README.md\n\n---\nname: integration-datadog\ndescription: Datadog as the audit destination for n8n error handlers — events POST via HTTP Request, region selection, tag conventions for aggregation.\nuser-invocable: false\n---\n\n# Datadog\n\n## Node type\n\n`n8n-nodes-base.httpRequest`. There is no dedicated `n8n-nodes-base.datadog` node — Datadog is HTTP-shaped.\n\n## Credential type\n\n`httpHeaderAuth` with header `DD-API-KEY: <api_key>`. Get the key from Datadog → Organization Settings → API Keys.\n\nFor most error-handling use cases the API key alone is sufficient. The Application Key (`DD-APPLICATION-KEY`) is only required for endpoints that read data on your behalf (querying logs, dashboards, etc.) — POSTing events doesn't need it.\n\nStore as `.env.<env>`:\n\n```bash\n# .env.<env>\nDD_API_KEY=<your-key>\n```\n\nResolve via `{{@:env:credentials.datadog.id}}` / `{{@:env:credentials.datadog.name}}` after running `manage-credentials` to register the n8n credential. See [`skills/manage-credentials.md`](../../manage-credentials.md).\n\n## Endpoint\n\nErrors as **events** (recommended for human-readable incident streams):\n```\nPOST https://api.datadoghq.com/api/v1/events\n```\n\nErrors as **logs** (alternative — better when you want full-text search across high-volume errors):\n```\nPOST https://api.datadoghq.com/api/v2/logs\n```\n\nThe events endpoint shows up in Datadog's Event Stream and can drive monitor alerts directly. The logs endpoint shows up in Log Explorer and indexes the body for full-text search. Pick events if your error volume is low (incidents, not noise); pick logs if you want every error retained and queryable.\n\n### Region selection\n\nDatadog hosts per region. The endpoint changes accordingly:\n\n| Region | Base URL |\n|---|---|\n| US1 (default) | `https://api.datadoghq.com` |\n| US3 | `https://api.us3.datadoghq.com` |\n| US5 | `https://api.us5.datadoghq.com` |\n| EU1 | `https://api.datadoghq.eu` |\n| AP1 | `https://api.ap1.datadoghq.com` |\n| US-Gov | `https://api.ddog-gov.com` |\n\nMake region env-configurable so a single template runs in EU staging + US prod:\n\n```yaml\n# n8n-config/<env>.yml\ndatadog:\n  endpoint: \"https://api.datadoghq.com\"\n  apiKeyEnv: \"DD_API_KEY\"\n```\n\nThen `={{ $env.DD_ENDPOINT || 'https://api.datadoghq.com' }}` in the URL field, or use a `{{@:env:datadog.endpoint}}` placeholder.\n\n## Worked example: HTTP Request POST event\n\nInside an n8n error handler, fed by the `Build Context` Code node from [`patterns/error-handling.md`](../../patterns/error-handling.md):\n\n```json\n{\n  \"type\": \"n8n-nodes-base.httpRequest\",\n  \"typeVersion\": 4.2,\n  \"parameters\": {\n    \"method\": \"POST\",\n    \"url\": \"={{ ($env.DD_ENDPOINT || 'https://api.datadoghq.com') + '/api/v1/events' }}\",\n    \"authentication\": \"genericCredentialType\",\n    \"genericAuthType\": \"httpHeaderAuth\",\n    \"sendHeaders\": true,\n    \"headerParameters\": {\n      \"parameters\": [\n        { \"name\": \"Accept\", \"value\": \"application/json\" },\n        { \"name\": \"Content-Type\", \"value\": \"application/json\" }\n      ]\n    },\n    \"sendBody\": true,\n    \"specifyBody\": \"json\",\n    \"jsonBody\": \"={{ JSON.stringify({ title: 'n8n workflow failed: ' + $json.workflow_name, text: '%%%\\\\n**Workflow:** ' + $json.workflow_name + '\\\\n**Last node:** ' + $json.last_node + '\\\\n**Execution:** [' + $json.execution_id + '](' + ($json.execution_url || '') + ')\\\\n\\\\n**Error:**\\\\n```\\\\n' + $json.message + '\\\\n```\\\\n%%%', tags: ['workflow:' + $json.workflow_name, 'workflow_id:' + $json.workflow_id, 'execution:' + $json.execution_id, 'env:' + ($env.N8N_ENV || 'unknown'), 'last_node:' + $json.last_node, 'alert_type:error'], priority: 'normal', alert_type: 'error', source_type_name: 'n8n', aggregation_key: 'n8n-workflow-' + $json.workflow_id }) }}\",\n    \"options\": { \"response\": { \"response\": { \"neverError\": true } } }\n  },\n  \"credentials\": {\n    \"httpHeaderAuth\": {\n      \"id\": \"{{@:env:credentials.datadog.id}}\",\n      \"name\": \"{{@:env:credentials.datadog.name}}\"\n    }\n  }\n}\n```\n\nTwo things worth knowing:\n\n- **`%%%` markers wrap markdown.** Datadog's events API treats text between `%%%` lines as markdown; outside, plain text. Without the markers, the code block won't render.\n- **`response.neverError: true`** prevents a Datadog 5xx from breaking the handler. Same reasoning as Sentry — log destinations must not crash the handler.\n\n## Event body shape\n\n```json\n{\n  \"title\": \"<one-line summary>\",\n  \"text\": \"%%%\\n<markdown body>\\n%%%\",\n  \"tags\": [\n    \"workflow:<name>\",\n    \"workflow_id:<id>\",\n    \"execution:<id>\",\n    \"env:<dev|staging|prod>\",\n    \"last_node:<name>\",\n    \"alert_type:error\"\n  ],\n  \"priority\": \"normal\",\n  \"alert_type\": \"error\",\n  \"source_type_name\": \"n8n\",\n  \"aggregation_key\": \"n8n-workflow-<workflow_id>\"\n}\n```\n\n| Field | Why |\n|---|---|\n| `title` | Shows in event stream + alerts. Keep short, lead with the workflow name. |\n| `text` | The detail. Use `%%%`-wrapped markdown so code blocks + links render. |\n| `tags` | Datadog tags are `key:value` strings (note the colon, not `key=value`). |\n| `priority` | `normal` or `low`. Errors are normal. |\n| `alert_type` | `error`, `warning`, `info`, `success`, `user_update`. Drives icon + color. |\n| `source_type_name` | Free-form source identifier. Use `\"n8n\"` so monitor filters can target n8n events. |\n| `aggregation_key` | Events with the same key cluster in the stream. Use the workflow id so all errors from the same workflow group together; switch to execution id if you want per-execution clustering instead. |\n\n## Tag conventions\n\nDatadog tags are powerful but quirky:\n\n- **Use `key:value`, not `key=value`** — the colon is the separator.\n- **Lowercase keys.** Datadog is case-insensitive on tag keys but normalizes to lowercase; using lowercase explicitly avoids surprises in dashboard queries.\n- **Don't put PII in tags.** Tags are indexed and persist; emails, user names, etc. should go in the `text` field instead.\n\nThe standard tags from [`patterns/error-handling.md`](../../patterns/error-handling.md):\n\n| Tag | Source expression |\n|---|---|\n| `workflow:` | `={{ $workflow.name }}` |\n| `workflow_id:` | `={{ $workflow.id }}` |\n| `execution:` | `={{ $workflow.errorData?.execution?.id || $execution.id }}` |\n| `env:` | `{{@:env:name}}` |\n| `last_node:` | `={{ $workflow.errorData?.lastNodeExecuted }}` |\n\nAdd business-relevant tags (`scope:`, `user_id:`, `tenant_id:`) for triage filtering.\n\n## Logs endpoint alternative\n\nIf you'd rather ship to Datadog Logs (full-text search, longer retention):\n\n```json\n{\n  \"type\": \"n8n-nodes-base.httpRequest\",\n  \"typeVersion\": 4.2,\n  \"parameters\": {\n    \"method\": \"POST\",\n    \"url\": \"={{ ($env.DD_ENDPOINT || 'https://api.datadoghq.com') + '/api/v2/logs' }}\",\n    \"authentication\": \"genericCredentialType\",\n    \"genericAuthType\": \"httpHeaderAuth\",\n    \"sendBody\": true,\n    \"specifyBody\": \"json\",\n    \"jsonBody\": \"={{ JSON.stringify([{ ddsource: 'n8n', ddtags: 'workflow:' + $json.workflow_name + ',env:' + ($env.N8N_ENV || 'unknown') + ',workflow_id:' + $json.workflow_id, hostname: 'n8n', service: 'n8n-workflow', message: $json.message, status: 'error', workflow_id: $json.workflow_id, workflow_name: $json.workflow_name, execution_id: $json.execution_id, last_node: $json.last_node, execution_url: $json.execution_url }]) }}\"\n  },\n  \"credentials\": {\n    \"httpHeaderAuth\": {\n      \"id\": \"{{@:env:credentials.datadog.id}}\",\n      \"name\": \"{{@:env:credentials.datadog.name}}\"\n    }\n  }\n}\n```\n\nNote: `ddtags` is a single comma-separated string, NOT an array (logs API quirk). The body is an array of log entries (single-element array for one entry).\n\n## See also\n\n- [`patterns/error-handling.md`](../../patterns/error-handling.md) — three-step paradigm + worked handler example.\n- [`manage-credentials.md`](../../manage-credentials.md) — registering the DD-API-KEY credential.\n\nFile v1.0.1:skills/integrations/google-drive/README.md\n\n---\nname: integration-google-drive\ndescription: Google Drive + Sheets nodes. OAuth scopes, folder-vs-file IDs.\nuser-invocable: false\n---\n\n# Google Drive (and Sheets)\n\n## Node types\n\n- `n8n-nodes-base.googleDrive` — file ops on Drive.\n- `n8n-nodes-base.googleSheets` — Sheets read/write.\n\n## Credential type\n\n`googleApi` (single credential covers Drive + Sheets if scopes include both).\n\nFor setup, see [`skills/manage-credentials.md`](../../manage-credentials.md). OAuth scopes minimum:\n\n- Drive: `https://www.googleapis.com/auth/drive`\n- Sheets: `https://www.googleapis.com/auth/spreadsheets`\n\n## File / folder IDs\n\nA Drive URL `https://drive.google.com/drive/folders/<ID>` gives the folder ID. A Sheets URL `https://docs.google.com/spreadsheets/d/<ID>/edit` gives the spreadsheet ID.\n\nStore IDs in env YAML:\n\n```yaml\ngdrive:\n  reportsFolder: \"1abc...\"\n  weeklyReport: \"1xyz...\"  # spreadsheet\n```\n\n## App verification\n\nUsing sensitive scopes (full Drive access, etc.) requires Google's app verification for production-tier apps. For internal-team-only projects, configure the OAuth client as \"internal\" within a Workspace org to bypass verification.\n\nFile v1.0.1:skills/integrations/notion/README.md\n\n---\nname: integration-notion\ndescription: Notion nodes — internal integration tokens, database / page IDs.\nuser-invocable: false\n---\n\n# Notion\n\n## Node type\n\n`n8n-nodes-base.notion`.\n\n## Credential type\n\n`notionApi` — Internal Integration token from `notion.so/profile/integrations`.\n\nFor setup, see [`skills/manage-credentials.md`](../../manage-credentials.md). Path A: paste the `secret_...` token into `.env.<env>` and run.\n\n## Database / page IDs\n\nNotion URLs encode IDs without dashes; the API expects dashes. Example:\n\n- URL: `https://www.notion.so/Workspace/MyDB-abc123def456789012345678901234`\n- API: `abc123de-f456-7890-1234-5678901234`\n\nThe n8n Notion node accepts either form. Store the with-dashes version in env YAML.\n\n## Sharing the integration\n\nDatabase / page operations only work if the page is **shared with the integration** (top-right \"Share\" → invite the integration). This is a common foot-gun — the API returns 404 for unshared pages.\n\n## Per-property field shape\n\nEach property type (title, rich_text, multi_select, …) has a different JSON shape. The n8n Notion node abstracts this for common types but for complex inserts you may need a Code node that builds the body manually. See Notion's API docs for the exact field shapes.\n\nFile v1.0.1:skills/integrations/sentry/README.md\n\n---\nname: integration-sentry\ndescription: Sentry as the audit destination for n8n error handlers — envelope POST via HTTP Request, DSN auth, tag conventions for grouping.\nuser-invocable: false\n---\n\n# Sentry\n\n## Node type\n\n`n8n-nodes-base.httpRequest`. There is no dedicated `n8n-nodes-base.sentry` node — Sentry is HTTP-shaped end-to-end.\n\n## Credential type\n\nTwo ways to authenticate to Sentry's ingest endpoint:\n\n1. **Embedded DSN auth (recommended for the envelope endpoint)** — the DSN string contains both the project ID and the public key. No credential needed; the DSN goes in the URL.\n2. **`httpHeaderAuth` with a Bearer token** — for the legacy `/store/` endpoint or for Sentry's REST management API. Get a token from Sentry → Settings → Account → API → Auth Tokens.\n\nThe envelope endpoint with DSN-in-URL is the simpler, current-recommended path. Treat the DSN as a secret and store it in `.env.<env>`:\n\n```bash\n# .env.<env>\nSENTRY_DSN=https://<public_key>@o<org_id>.ingest.sentry.io/<project_id>\n```\n\nResolve via `{{@:env:sentry.dsn}}` (after wiring `sentry.dsn: \"${SENTRY_DSN}\"` in your env YAML). See [`skills/manage-credentials.md`](../../manage-credentials.md) for the env-secret pattern.\n\n## Endpoint\n\n**Current (envelope):**\n```\nPOST https://o<org_id>.ingest.sentry.io/api/<project_id>/envelope/\n```\n\nThe envelope endpoint accepts events wrapped in a multi-line newline-delimited envelope structure. For the simpler \"send one event\" case, the envelope body is three lines: header, item-header, item-payload.\n\n**Legacy (deprecated, still works):**\n```\nPOST https://sentry.io/api/<project_id>/store/\nHeader: X-Sentry-Auth: Sentry sentry_version=7, sentry_key=<public_key>, sentry_client=n8n/1.0\nBody: <event JSON>\n```\n\nThe legacy `/store/` endpoint is documented as deprecated by Sentry but is widely used in custom integrations and will keep working for the foreseeable future. If your Sentry environment supports the envelope endpoint, prefer it.\n\n## Worked example: HTTP Request node sending an envelope\n\nThis is the node body inside an n8n error handler, fed by the `Build Context` Code node from [`patterns/error-handling.md`](../../patterns/error-handling.md). The DSN can be baked into the URL via expression — DSN-in-URL skips the X-Sentry-Auth header entirely.\n\n```json\n{\n  \"type\": \"n8n-nodes-base.httpRequest\",\n  \"typeVersion\": 4.2,\n  \"parameters\": {\n    \"method\": \"POST\",\n    \"url\": \"={{ 'https://o' + $env.SENTRY_ORG_ID + '.ingest.sentry.io/api/' + $env.SENTRY_PROJECT_ID + '/envelope/' }}\",\n    \"sendHeaders\": true,\n    \"headerParameters\": {\n      \"parameters\": [\n        { \"name\": \"Content-Type\", \"value\": \"application/x-sentry-envelope\" },\n        { \"name\": \"X-Sentry-Auth\", \"value\": \"={{ 'Sentry sentry_version=7, sentry_key=' + $env.SENTRY_PUBLIC_KEY + ', sentry_client=n8n/1.0' }}\" }\n      ]\n    },\n    \"sendBody\": true,\n    \"contentType\": \"raw\",\n    \"rawContentType\": \"application/x-sentry-envelope\",\n    \"body\": \"={{ JSON.stringify({ event_id: $json.execution_id.replace(/-/g,''), sent_at: $json.timestamp, dsn: $env.SENTRY_DSN }) + '\\\\n' + JSON.stringify({ type: 'event', content_type: 'application/json' }) + '\\\\n' + JSON.stringify({ event_id: $json.execution_id.replace(/-/g,''), timestamp: Math.floor(new Date($json.timestamp).getTime() / 1000), platform: 'javascript', logger: 'n8n', level: 'error', logentry: { message: $json.message }, exception: { values: [{ type: 'WorkflowError', value: $json.message, stacktrace: { frames: [{ function: $json.last_node, module: $json.workflow_name, in_app: true }] } }] }, tags: { workflow_id: $json.workflow_id, workflow_name: $json.workflow_name, execution_id: $json.execution_id, env: $env.N8N_ENV, last_node: $json.last_node }, extra: { execution_url: $json.execution_url } }) }}\",\n    \"options\": { \"response\": { \"response\": { \"neverError\": true } } }\n  }\n}\n```\n\nThe body is the three-line envelope: envelope header (with DSN), item header (declaring the event type), then the event JSON payload.\n\nFor most cases the simpler legacy `/store/` POST is more readable:\n\n```json\n{\n  \"type\": \"n8n-nodes-base.httpRequest\",\n  \"typeVersion\": 4.2,\n  \"parameters\": {\n    \"method\": \"POST\",\n    \"url\": \"={{ 'https://sentry.io/api/' + $env.SENTRY_PROJECT_ID + '/store/' }}\",\n    \"sendHeaders\": true,\n    \"headerParameters\": {\n      \"parameters\": [\n        { \"name\": \"Content-Type\", \"value\": \"application/json\" },\n        { \"name\": \"X-Sentry-Auth\", \"value\": \"={{ 'Sentry sentry_version=7, sentry_key=' + $env.SENTRY_PUBLIC_KEY + ', sentry_client=n8n/1.0' }}\" }\n      ]\n    },\n    \"sendBody\": true,\n    \"specifyBody\": \"json\",\n    \"jsonBody\": \"={{ JSON.stringify({ event_id: $json.execution_id.replace(/-/g,''), timestamp: Math.floor(new Date($json.timestamp).getTime() / 1000), platform: 'javascript', logger: 'n8n', level: 'error', logentry: { message: $json.message }, exception: { values: [{ type: 'WorkflowError', value: $json.message }] }, tags: { workflow_id: $json.workflow_id, workflow_name: $json.workflow_name, execution_id: $json.execution_id, env: $env.N8N_ENV, last_node: $json.last_node }, extra: { execution_url: $json.execution_url } }) }}\",\n    \"options\": { \"response\": { \"response\": { \"neverError\": true } } }\n  }\n}\n```\n\n`response.neverError: true` is critical: a Sentry-side rate limit or 5xx must NOT break the error handler. Log silently and continue.\n\n## Event JSON shape (legacy /store/ form)\n\n```json\n{\n  \"event_id\": \"<32-char hex>\",\n  \"timestamp\": <unix-seconds>,\n  \"platform\": \"javascript\",\n  \"logger\": \"n8n\",\n  \"level\": \"error\",\n  \"logentry\": { \"message\": \"<human-readable>\" },\n  \"exception\": {\n    \"values\": [\n      {\n        \"type\": \"WorkflowError\",\n        \"value\": \"<error message>\",\n        \"stacktrace\": {\n          \"frames\": [\n            { \"function\": \"<last_node_name>\", \"module\": \"<workflow_name>\", \"in_app\": true }\n          ]\n        }\n      }\n    ]\n  },\n  \"tags\": {\n    \"workflow_id\": \"...\",\n    \"workflow_name\": \"...\",\n    \"execution_id\": \"...\",\n    \"env\": \"...\",\n    \"last_node\": \"...\"\n  },\n  \"extra\": {\n    \"execution_url\": \"...\"\n  }\n}\n```\n\n## Tag conventions\n\nSentry's grouping algorithm is driven by the `exception.values[].type` and the top frame's `function` + `module`. To make grouping behave well:\n\n- Set `exception.values[0].type` to a stable string like `\"WorkflowError\"` (not the dynamic error message).\n- Put the failing node name in the top frame's `function`. This makes \"all errors at the Foo node\" cluster together regardless of message.\n- Put `workflow_name` in the top frame's `module`. Sentry will then cluster per workflow as the next layer.\n\nAlways include the standard tags from [`patterns/error-handling.md`](../../patterns/error-handling.md):\n\n| Tag | Source expression |\n|---|---|\n| `workflow_id` | `={{ $workflow.id }}` |\n| `workflow_name` | `={{ $workflow.name }}` |\n| `execution_id` | `={{ $workflow.errorData?.execution?.id || $execution.id }}` |\n| `env` | `{{@:env:name}}` |\n| `last_node` | `={{ $workflow.errorData?.lastNodeExecuted }}` |\n\nAdd business-relevant tags (`scope`, `user_id`, `tenant_id`) so Sentry filtering reflects what your on-call actually needs.\n\n## See also\n\n- [`patterns/error-handling.md`](../../patterns/error-handling.md) — three-step paradigm + worked handler example.\n- [`manage-credentials.md`](../../manage-credentials.md) — DSN as env-secret.\n\nFile v1.0.1:skills/integrations/slack/README.md\n\n---\nname: integration-slack\ndescription: Slack nodes — bot/user OAuth, posting messages, threads, error-notification Block Kit pattern.\nuser-invocable: false\n---\n\n# Slack\n\n## Node type\n\n`n8n-nodes-base.slack`.\n\n## Credential type\n\n`slackApi` (bot token) or `slackOAuth2Api` (full OAuth flow).\n\nFor setup, see [`skills/manage-credentials.md`](../../manage-credentials.md). Bot tokens (`slackApi`) are simpler — paste `xoxb-...` into `.env.<env>` and run Path A.\n\n## Common operations\n\n- **Post message:** `resource: message`, `operation: post`. Set `channel` to a channel ID (preferred) or `#name` (sometimes resolves slowly).\n- **Reply in thread:** include `thread_ts` from a previous message.\n- **Update message:** `operation: update` with `ts` of the original message.\n\n## Channel ID gotcha\n\nSlack's UI shows `#channel-name` but the API takes channel IDs (e.g. `C0123456789`). Find the ID by right-clicking the channel → Copy Link → the URL contains the ID. Store the ID in your env YAML, not the name:\n\n```yaml\nslack:\n  alertsChannel: \"C0123456789\"\n```\n\n```json\n{\n  \"type\": \"n8n-nodes-base.slack\",\n  \"parameters\": {\n    \"channel\": \"{{@:env:slack.alertsChannel}}\"\n  }\n}\n```\n\n## Error-notification worked example\n\nUsed inside an n8n error handler, fed by the `Build Context` Code node from [`patterns/error-handling.md`](../../patterns/error-handling.md). Posts a Block Kit message to the alerts channel + captures the response so a downstream node can thread updates onto it.\n\n```json\n{\n  \"type\": \"n8n-nodes-base.slack\",\n  \"typeVersion\": 2.3,\n  \"parameters\": {\n    \"resource\": \"message\",\n    \"operation\": \"post\",\n    \"select\": \"channel\",\n    \"channelId\": {\n      \"__rl\": true,\n      \"value\": \"{{@:env:slack.alertsChannel}}\",\n      \"mode\": \"id\"\n    },\n    \"messageType\": \"block\",\n    \"blocksUi\": \"={{ JSON.stringify({ blocks: [\\n  { type: 'header', text: { type: 'plain_text', text: ':rotating_light: Workflow failed: ' + $json.workflow_name } },\\n  { type: 'section', fields: [\\n    { type: 'mrkdwn', text: '*Workflow:*\\\\n' + $json.workflow_name },\\n    { type: 'mrkdwn', text: '*Last node:*\\\\n`' + ($json.last_node || 'unknown') + '`' },\\n    { type: 'mrkdwn', text: '*Env:*\\\\n' + ($env.N8N_ENV || 'unknown') },\\n    { type: 'mrkdwn', text: '*When:*\\\\n' + $json.timestamp }\\n  ]},\\n  { type: 'section', text: { type: 'mrkdwn', text: '*Error message:*\\\\n```\\\\n' + $json.message + '\\\\n```' } },\\n  { type: 'actions', elements: [\\n    { type: 'button', text: { type: 'plain_text', text: 'Open in n8n' }, url: $json.execution_url || 'https://n8n.example.com', style: 'primary' }\\n  ]}\\n]}) }}\",\n    \"otherOptions\": {}\n  },\n  \"credentials\": {\n    \"slackApi\": {\n      \"id\": \"{{@:env:credentials.slack.id}}\",\n      \"name\": \"{{@:env:credentials.slack.name}}\"\n    }\n  }\n}\n```\n\nThe `messageType: block` mode tells the Slack node to send Block Kit JSON in the `blocksUi` field. The Block Kit structure shipped above is:\n\n- A `header` block with the rotating-light emoji + workflow name (large, can't-miss-it).\n- A `section` with `fields` showing workflow / last-node / env / timestamp in a 2-column grid.\n- A second `section` with the error message in a fenced code block.\n- An `actions` row with a primary button linking to the n8n execution URL.\n\nSlack's response includes `ts` (the message timestamp) — capture it via `$json.ts` for thread updates:\n\n```javascript\n// In a downstream node referencing the Slack post output:\nconst threadTs = $('Slack Post').first().json.ts;\n```\n\n## Threading updates onto the original alert\n\nWhen the cleanup step finishes (lock released, DB invalidated, etc.), reply in-thread on the original alert so on-call sees the resolution without a second top-level message:\n\n```json\n{\n  \"type\": \"n8n-nodes-base.slack\",\n  \"typeVersion\": 2.3,\n  \"parameters\": {\n    \"resource\": \"message\",\n    \"operation\": \"post\",\n    \"select\": \"channel\",\n    \"channelId\": {\n      \"__rl\": true,\n      \"value\": \"{{@:env:slack.alertsChannel}}\",\n      \"mode\": \"id\"\n    },\n    \"text\": \"={{ ':white_check_mark: Cleanup complete: ' + $json.cleanup_summary }}\",\n    \"otherOptions\": {\n      \"thread_ts\": \"={{ $('Slack Post').first().json.ts }}\"\n    }\n  }\n}\n```\n\nThis keeps the alerts channel readable: one top-level :rotating_light: per incident, replies-in-thread for status updates.\n\n## See also\n\n- [`patterns/error-handling.md`](../../patterns/error-handling.md) — three-step paradigm; this Slack post is the \"log to humans\" step.\n- [`manage-credentials.md`](../../manage-credentials.md) — registering the slackApi credential.\n\nFile v1.0.1:skills/integrations/webhooks/README.md\n\n---\nname: integration-webhooks\ndescription: Webhook node patterns — paths, response modes, test vs production URLs.\nuser-invocable: false\n---\n\n# Webhooks\n\n## Node types\n\n- `n8n-nodes-base.webhook` — incoming webhook trigger.\n- `n8n-nodes-base.respondToWebhook` — explicit response (when not using `responseMode: lastNode`).\n\n## Path conventions\n\nWebhook paths are env-agnostic but share a base URL per env. Use a stable, descriptive path:\n\n```json\n{\n  \"type\": \"n8n-nodes-base.webhook\",\n  \"parameters\": {\n    \"httpMethod\": \"POST\",\n    \"path\": \"report/daily\",\n    \"responseMode\": \"lastNode\"\n  }\n}\n```\n\nn8n exposes the webhook at `<instance>/webhook/<path>` (production) and `<instance>/webhook-test/<path>` (test mode in the UI). `run.py` tries production first, then falls back to test.\n\n## Response modes\n\n- `responseMode: lastNode` — n8n returns the last node's output as the response. Simplest case.\n- `responseMode: responseNode` — terminate flow with `respondToWebhook` to control status/headers/body explicitly.\n\n## Authentication\n\n- **None** — public webhook. Fine for triggers from trusted sources (or with HMAC-verifying middleware in the workflow).\n- **Header Auth** — n8n verifies a header against a credential. Credential type: `httpHeaderAuth`.\n- **Basic Auth** — credential type: `httpBasicAuth`.\n\nFor credential setup, see [`skills/manage-credentials.md`](../../manage-credentials.md).\n\n## webhookId placeholder\n\nn8n's webhook node has a `webhookId` field that contains a UUID identifying the public webhook URL. Use `{{@:uuid:webhook-public}}` so each env gets its own (the path is shared but the public webhook URL varies).\n\nFile v1.0.1:_meta.json\n\n{\n  \"ownerId\": \"kn744p2vzxwdnxfjhek7pxsz4n860kr1\",\n  \"slug\": \"n8n-evol-i\",\n  \"version\": \"1.0.1\",\n  \"publishedAt\": 1777806620894\n}\n\nFile v1.0.1:install.md\n\n---\nname: install\ndescription: How to install n8n-evol-I as a skill package and what it expects on the host system.\n---\n\n# install\n\n## Prerequisites\n\n- Python ≥ 3.11 on PATH\n- Pip-installable: `pyyaml`, `requests`, `python-dotenv`\n- An n8n instance the agent can reach via REST + an API key with workflow + credential scopes\n\n## Install (one-time)\n\n### Skill mode (any agent runtime)\n\nClone into your agent's skills directory:\n\n```bash\ncd ~/.claude/skills   # or wherever your agent runtime reads skills from\ngit clone https://github.com/mwamedacen/n8n-evol-I.git\n```\n\nInstall Python deps:\n\n```bash\npip install pyyaml requests python-dotenv\n```\n\n(Optional, only if you'll use `iterate-prompt`:)\n\n```bash\npip install dspy litellm\n```\n\n### Plugin mode (Claude Code only)\n\nCLI form (run in your terminal):\n\n```bash\nclaude plugin install https://github.com/mwamedacen/n8n-evol-I\n```\n\nIn-session form (inside a Claude Code session):\n\n```\n/plugin install https://github.com/mwamedacen/n8n-evol-I\n```\n\nLocal dev (loads from a local checkout):\n\n```bash\nclaude --plugin-dir ./n8n-evol-I\n```\n\n#### Plugin extras\n\nWhen installed as a plugin, n8n-evol-I ships two additional behaviors:\n\n- **Slash commands** — 10 user-facing commands available as `/n8n-evol-I:deploy`, `/n8n-evol-I:tidyup`, etc. Hidden lifecycle skills remain agent-loadable via `SKILL.md` routing but do not appear in `/help`.\n\n- **Auto-tidy hook** — a `PostToolUse` hook fires after every Write/Edit/MultiEdit tool call; the hook script filters to `*.template.json` files and runs `tidy_workflow.py --in-place` on matching files. This keeps node positions clean without any manual step.\n\n  The hook runs asynchronously (`async: true`), so Claude Code does not wait for it before continuing. If the agent reads the template file immediately after writing it (e.g. to feed it into a deploy step), it may observe the un-tidied version. In that case, run `tidy-workflow` manually before deploying, or add a short pause between the write and the read.\n\n  To disable the auto-tidy hook: remove or rename `hooks/hooks.json` in the plugin directory, or disable the plugin in Claude Code settings. Standalone-skill-mode users who want auto-tidy can configure the hook manually in `~/.claude/settings.json`.\n\n## What the harness expects on disk\n\nThe skill package is a directory containing:\n\n- `SKILL.md` — entry point\n- `skills/*.md` and `skills/{patterns,integrations}/...md` — sub-skills\n- `helpers/*.py` — the executable surface\n- `primitives/` — seed templates copied into workspaces on demand\n\nHelpers are invoked by absolute path (no console script, no PATH pollution):\n\n```bash\npython3 <harness>/helpers/<name>.py [args]\n```\n\n## Per-project setup\n\nPer project, the agent runs `init.md` once to scaffold a workspace at `${PWD}/n8n-evol-I-workspace/`. From there, `bootstrap-env.md` configures envs, `create-new-workflow.md` authors workflows, etc. See [`SKILL.md`](SKILL.md) for the full skill catalogue.\n\n## Updating\n\nThe harness's \"version\" is its git SHA. To upgrade:\n\n```bash\ncd ~/.claude/skills/n8n-evol-I\ngit pull\n```\n\nBreaking changes between versions are documented in [`CHANGELOG.md`](CHANGELOG.md). For the migration from the legacy single-repo shape (pre-`d6848fd`), see [`docs/migration-from-d6848fd.md`](docs/migration-from-d6848fd.md).\n\n## Verifying the install\n\n```bash\npython3 <harness>/helpers/init.py --workspace /tmp/n8n-evol-I-smoke\nls /tmp/n8n-evol-I-smoke/n8n-config /tmp/n8n-evol-I-smoke/n8n-workflows-template\n```\n\nIf both directories exist after the run, the install is good.\n\nFile v1.0.1:skills/activate-single-workflow-in-env.md\n\n---\nname: activate-single-workflow-in-env\ndescription: Activate an already-deployed workflow.\nuser-invocable: false\n---\n\n# activate-single-workflow-in-env\n\n## When\n\nActivate a workflow that was deployed with `--no-activate`, or re-activate after a deactivate.\n\n## How\n\n```bash\npython3 ${CLAUDE_PLUGIN_ROOT}/helpers/activate.py --env <env> --workflow-key <key>\n```\n\nPOST `/api/v1/workflows/<id>/activate`. Idempotent on the n8n side.\n\nArchive v1.0.0: 121 files, 199951 bytes\n\nFiles: helpers/__init__.py (0b), helpers/_dspy_config.py (2010b), helpers/activate.py (1441b), helpers/add_cloud_function.py (4779b), helpers/add_lock_to_workflow.py (14170b), helpers/add_rate_limit_to_workflow.py (9784b), helpers/archive.py (1492b), helpers/bootstrap_env.py (7183b), helpers/config.py (2828b), helpers/copy_primitive.py (3725b), helpers/create_lock.py (3865b), helpers/create_workflow.py (5543b), helpers/deactivate.py (1330b), helpers/dehydrate.py (5149b), helpers/dependency_graph.py (8432b), helpers/deploy_all.py (5268b), helpers/deploy_run_assert.py (2376b), helpers/deploy.py (6322b), helpers/diff.py (3282b), helpers/doctor.py (13558b), helpers/find_skills.py (4229b), helpers/hydrate.py (2648b), helpers/init.py (7552b), helpers/inspect_execution.py (3679b), helpers/iterate_prompt.py (5871b), helpers/list_executions.py (6710b), helpers/manage_credentials.py (7939b), helpers/manage_variables.py (5229b), helpers/n8n_client.py (2942b), helpers/package-lock.json (42779b), helpers/package.json (90b), helpers/placeholder/__init__.py (0b), helpers/placeholder/env_resolver.py (2454b), helpers/placeholder/file_resolver.py (1143b), helpers/placeholder/js_resolver.py (2487b), helpers/placeholder/py_resolver.py (2777b), helpers/placeholder/uuid_resolver.py (745b), helpers/placeholder/validator.py (1434b), helpers/register_error_handler.py (2766b), helpers/resync_all.py (1211b), helpers/resync.py (1496b), helpers/run.py (6322b), helpers/stop_executions.py (4719b), helpers/test_functions.py (3764b), helpers/tidy_shim.mjs (652b), helpers/tidy_workflow.py (8833b), helpers/unarchive.py (1517b), helpers/validate.py (12528b), helpers/workspace.py (2098b), install.md (3569b), primitives/cloud-functions/app.py (963b), primitives/cloud-functions/functions/__init__.py (0b), primitives/cloud-functions/functions/hello_world.py (226b), primitives/cloud-functions/railpack.json (116b), primitives/cloud-functions/railway.toml (108b), primitives/cloud-functions/registry.py (196b), primitives/cloud-functions/requirements.txt (39b), primitives/prompts/example_summary_prompt.txt (237b), primitives/prompts/example_summary_schema.json (320b), primitives/workflows/_minimal.template.json (1162b), primitives/workflows/error_handler_lock_cleanup.template.json (8934b), primitives/workflows/lock_acquisition.template.json (13937b), primitives/workflows/lock_release.template.json (7661b), primitives/workflows/rate_limit_check.template.json (2565b), pyproject.toml (704b), README.md (10327b), requirements.txt (12b), SKILL.md (7254b), skills/activate-single-workflow-in-env.md (433b), skills/add-cloud-function.md (1257b), skills/add-lock-to-workflow.md (8125b), skills/add-rate-limit-to-workflow.md (4911b), skills/archive-workflow.md (1620b), skills/bootstrap-env.md (1673b), skills/copy-primitive.md (2693b), skills/create-lock.md (5552b), skills/create-new-workflow.md (1692b), skills/deactivate-single-workflow-in-env.md (429b), skills/debug.md (2924b), skills/dehydrate-workflow.md (589b)\n\nFile v1.0.0:SKILL.md\n\n---\nname: n8n-evol-I\ndescription: Read-only skill package for authoring, deploying, and operating n8n workflows from code. Routes user requests to lifecycle skills (init, bootstrap-env, create-new-workflow, deploy, run, resync, etc.).\n---\n\n# n8n-evol-I skill router\n\nThis is the entry point. When the user asks anything n8n-related, route to the matching sub-skill.\n\n## Mental model\n\n- The skill package (this directory) is **read-only**. You never edit files here.\n- The user's project state lives in `${PWD}/n8n-evol-I-workspace/` (created by `init.md`).\n- Helpers are Python scripts under `helpers/`. Invoke them with `python3 ${CLAUDE_PLUGIN_ROOT}/helpers/<name>.py [args]` (plugin mode) or `python3 <path-to-harness>/helpers/<name>.py [args]` (skill mode).\n- All helpers default to `--workspace ${PWD}/n8n-evol-I-workspace`. Pass `--workspace <path>` if the user runs from elsewhere.\n- **Agent memory**: read `N8N-WORKSPACE-MEMORY.md` in the workspace at the start of every session; append a dated entry whenever you learn something durable about this project. Full guidance in `AGENTS.md` (also in the workspace root).\n\n## Lifecycle skills (use when the user wants to do X)\n\n| Skill | When |\n|---|---|\n| [init.md](skills/init.md) | First-time setup. Creates the workspace at `${PWD}/n8n-evol-I-workspace/`. |\n| [bootstrap-env.md](skills/bootstrap-env.md) | Configure an environment (`dev` / `staging` / `prod`). Creates env YAML + `.env`, validates, mints placeholder workflow IDs. |\n| [doctor.md](skills/doctor.md) | Health check. Run before/after major changes. |\n| [create-new-workflow.md](skills/create-new-workflow.md) | Author a brand-new workflow. |\n| [register-workflow-to-error-handler.md](skills/register-workflow-to-error-handler.md) | Wire `settings.errorWorkflow`. |\n| [create-lock.md](skills/create-lock.md) | First-time setup for distributed locking (Redis-backed primitives). |\n| [copy-primitive.md](skills/copy-primitive.md) | Copy a single primitive (any) into the workspace. General-purpose; doesn't register. |\n| [add-lock-to-workflow.md](skills/add-lock-to-workflow.md) | Wrap a workflow's main flow in lock acquire/release. |\n| [add-rate-limit-to-workflow.md](skills/add-rate-limit-to-workflow.md) | Gate a workflow's main flow with a Redis-backed fixed-window rate-limit check. |\n| [tidy-workflow.md](skills/tidy-workflow.md) | Apply n8n's canvas-layout algorithm to a workflow template to clean up node positions. |\n| [deploy.md](skills/deploy.md) | Deploy one workflow to one env. |\n| [activate-single-workflow-in-env.md](skills/activate-single-workflow-in-env.md) | Activate after deploy. |\n| [deactivate-single-workflow-in-env.md](skills/deactivate-single-workflow-in-env.md) | Pause triggers (commonly during dev). |\n| [archive-workflow.md](skills/archive-workflow.md) | Retire a deployed workflow (hidden + read-only on the live instance). |\n| [unarchive-workflow.md](skills/unarchive-workflow.md) | Restore a previously-archived workflow so it accepts updates again. |\n| [deploy_all.md](skills/deploy_all.md) | Roll out an entire env in tier order. |\n| [resync.md](skills/resync.md) | Pull live state of one workflow back into its template. |\n| [resync_all.md](skills/resync_all.md) | Snapshot a full env back to templates. |\n| [dehydrate-workflow.md](skills/dehydrate-workflow.md) | Convert raw exported JSON into a template. |\n| [validate.md](skills/validate.md) | Structural REST validation before deploy. |\n| [run.md](skills/run.md) | Fire a webhook + assert terminal status. |\n| [debug.md](skills/debug.md) | Investigate a failing or missing execution — from vague symptom to root-cause with evidence. |\n| [deploy-run-assert.md](skills/deploy-run-assert.md) | One-shot validate → deploy → run verify. |\n| [find-skills.md](skills/find-skills.md) | While authoring, find applicable patterns/integrations. |\n| [manage-credentials.md](skills/manage-credentials.md) | Create or link n8n credentials (Path A from `.env.<env>` / Path B from existing UI credential). |\n| [add-cloud-function.md](skills/add-cloud-function.md) | Scaffold a Python serverless function / cloud function / serverless API under `<workspace>/cloud-functions/`. |\n| [iterate-prompt.md](skills/iterate-prompt.md) | Optimize a prompt against a paired schema + dataset using DSPy. |\n| [test.md](skills/test.md) | Run unit tests over n8n Code-node JS and / or cloud-function Python. |\n\n## Pattern skills (read-only knowledge)\n\nThese are reference docs, not action triggers. Read them while authoring.\n\n- [skills/patterns/subworkflows.md](skills/patterns/subworkflows.md)\n- [skills/patterns/error-handling.md](skills/patterns/error-handling.md)\n- [skills/patterns/credential-refs.md](skills/patterns/credential-refs.md)\n- [skills/patterns/multi-env-uuid-collision.md](skills/patterns/multi-env-uuid-collision.md)\n- [skills/patterns/validate-deploy.md](skills/patterns/validate-deploy.md)\n- [skills/patterns/code-node-discipline.md](skills/patterns/code-node-discipline.md)\n- [skills/patterns/llm-providers.md](skills/patterns/llm-providers.md)\n- [skills/patterns/locking.md](skills/patterns/locking.md)\n- [skills/patterns/pindata-hygiene.md](skills/patterns/pindata-hygiene.md)\n- [skills/patterns/position-recalculation.md](skills/patterns/position-recalculation.md)\n- [skills/patterns/prompt-and-schema-conventions.md](skills/patterns/prompt-and-schema-conventions.md)\n- [skills/patterns/agent-api-discipline.md](skills/patterns/agent-api-discipline.md)\n- [skills/patterns/investigation-discipline.md](skills/patterns/investigation-discipline.md)\n\n## Integration skills (per-service quirks)\n\n- [skills/integrations/microsoft-365/excel-and-sharepoint.md](skills/integrations/microsoft-365/excel-and-sharepoint.md)\n- [skills/integrations/gmail/sending-email.md](skills/integrations/gmail/sending-email.md)\n- [skills/integrations/redis/lock-pattern.md](skills/integrations/redis/lock-pattern.md)\n- [skills/integrations/sentry/README.md](skills/integrations/sentry/README.md)\n- [skills/integrations/datadog/README.md](skills/integrations/datadog/README.md)\n- [skills/integrations/slack/README.md](skills/integrations/slack/README.md)\n- [skills/integrations/google-drive/README.md](skills/integrations/google-drive/README.md)\n- [skills/integrations/notion/README.md](skills/integrations/notion/README.md)\n- [skills/integrations/airtable/README.md](skills/integrations/airtable/README.md)\n- [skills/integrations/webhooks/README.md](skills/integrations/webhooks/README.md)\n\n## Placeholder syntax (workflow templates)\n\nTemplates use `{{@:type:path}}` (preferred form) or the canonical long form `{{INTERPOLATE:type:path}}`. The two are equivalent — `@` is an alias for `INTERPOLATE`. Examples below use the `@` form.\n\n| Type | Syntax | Source |\n|---|---|---|\n| `env` | `{{@:env:key.path}}` | YAML config value (dot notation) |\n| `txt` | `{{@:txt:relative/path.txt}}` | Text file in workspace |\n| `json` | `{{@:json:relative/path.json}}` | JSON file (stringified) |\n| `html` | `{{@:html:relative/path.html}}` | HTML file |\n| `js` | `{{@:js:relative/path.js}}` | JavaScript file |\n| `py` | `{{@:py:relative/path.py}}` | Python file (Code-node `language: python`) |\n| `uuid` | `{{@:uuid:identifier}}` | Fresh UUID v4 (consistent within one hydration) |\n\nFile v1.0.0:README.md\n\n<img width=\"1376\" height=\"768\" alt=\"n8n_evol_I_hero\" src=\"https://github.com/user-attachments/assets/0a28f1f6-106f-4f84-9ce1-cf7eebcf4cd6\" />\n\n# N8N EVOL I\n\nA harness to help coding agents build, deploy, maintain, and debug multi-workflow n8n-powered automation systems.\n\nNo lock-in. Build with your agent and push upstream to n8n; tweak directly in the n8n UI and pull downstream back into your workspace. Round-trips are template-stable, so humans and agents can work the same project in parallel without stepping on each other's edits.\n\n```mermaid\nflowchart LR\n    Agent <-->|edits / resync| Workspace\n    Workspace <-->|deploy / resync| n8n[(n8n instance)]\n    n8n <-->|UI edits| Human\n```\n\n## Features\n\n- **Multi-environment workflows.** Per-env YAML in `n8n-config/<env>.yml` (instance URL, workflow IDs, credential refs); every helper accepts `--env`. See [`bootstrap-env.md`](skills/bootstrap-env.md).\n\n  ```yaml\n  # n8n-config/dev.yml\n  instance: acme.dev.n8n.cloud\n  envSuffix: \" (dev)\"          # appended to every workflow name\n  workflows:\n    lead_enrichment:\n      id: wf_def456\n      displayName: \"Lead Enrichment\"\n    lead_persister:\n      id: wf_abc123\n      displayName: \"Lead Persister\"\n  ```\n\n- **Build-time substitution.** Code, prompts, schemas, templates, and env values live in workspace files; `{{@:js|py|txt|json|html|env|uuid:...}}` placeholders are substituted at deploy time. Template-stable round-trips. See [`code-node-discipline.md`](skills/patterns/code-node-discipline.md).\n\n  ```jsonc\n  // n8n-workflows-template/lead_enrichment.template.json\n  {\n    \"name\": \"{{@:env:workflows.lead_enrichment.displayName}}{{@:env:envSuffix}}\",\n    \"nodes\": [\n      {\n        \"name\": \"Webhook\",\n        \"type\": \"n8n-nodes-base.webhook\",\n        \"parameters\": { \"path\": \"lead\", \"httpMethod\": \"POST\" }\n      },\n      {\n        \"name\": \"Score Lead\",\n        \"type\": \"@n8n/n8n-nodes-langchain.openAi\",\n        \"parameters\": {\n          \"messages\": [\n            { \"role\": \"system\", \"content\": \"{{@:txt:n8n-prompts/prompts/score_lead.txt}}\" }\n          ]\n        }\n      },\n      {\n        \"name\": \"Normalize Fields\",\n        \"type\": \"n8n-nodes-base.code\",\n        \"parameters\": {\n          \"jsCode\": \"{{@:js:n8n-functions/js/normalize_lead.js}}\\n\\nreturn normalize(items);\"\n        }\n      },\n      {\n        \"name\": \"Persist via Sub-workflow\",\n        \"type\": \"n8n-nodes-base.executeWorkflow\",\n        \"parameters\": {\n          \"workflowId\": \"{{@:env:workflows.lead_persister.id}}\"\n        }\n      }\n    ]\n  }\n  ```\n\n- **Dependency-ordered deployment.** `deploy_all.py` rolls out an env in tier order (set per-workflow in `deployment_order.yml`) so callees deploy before callers. See [`deploy_all.md`](skills/deploy_all.md).\n\n  ```mermaid\n  graph LR\n    A[tier 0: leaves] --> B[tier 1: handlers] --> C[tier 2: callers]\n  ```\n\n- **Execution debugging.** Structured symptom → root-cause flow (dependency graph, candidate pre-screen, causal-linkage, sub-agent cross-check), backed by `list_executions.py`, `inspect_execution.py`, `dependency_graph.py`. See [`investigation-discipline.md`](skills/patterns/investigation-discipline.md).\n\n  ```bash\n  python3 $HARNESS/helpers/inspect_execution.py --env dev --id 91234\n  ```\n\n- **Distributed locking.** Redis-backed `lock_acquisition` / `lock_release` primitives with an identity-payload sidecar; crashes either self-heal via Redis TTL or are cleaned by the registered `error_handler_lock_cleanup` workflow. See [`locking.md`](skills/patterns/locking.md).\n\n  ```\n  ┌──────────┐   ┌──────┐   ┌──────────┐\n  │ acquire  │ → │ work │ → │ release  │\n  │ (scope)  │   │      │   │ (scope)  │\n  └──────────┘   └──────┘   └──────────┘\n  ```\n\n- **Rate limiting.** Fixed-window Redis `INCR` primitive; configurable limit, window, and denied-branch behavior (passthrough / stop / error). See [`locking.md`](skills/patterns/locking.md).\n\n  ```\n                            ┌─→ work       (within N per window)\n  request → check(scope) ───┤\n                            └─→ denied     (over limit)\n  ```\n\n- **Error handling + observability.** Capture (Error Trigger) → log (sinks fan out in parallel) → process (lock cleanup, DB invalidation, compensating workflows). See [`error-handling.md`](skills/patterns/error-handling.md), [`datadog/`](skills/integrations/datadog/README.md).\n\n  ```\n                    ┌─ Sentry  ─┐\n  Error Trigger ──→ ├─ Datadog ─┤ ──→ lock cleanup / invalidate / compensate\n                    └─ Slack   ─┘\n  ```\n\n- **Serverless functions (a.k.a. cloud functions / serverless APIs).** Scaffolds a Python function into a FastAPI service in `cloud-functions/`, auto-registers it in the router, ships with Railway config, callable from n8n via HTTP Request nodes. See [`add-cloud-function.md`](skills/add-cloud-function.md).\n\n  ```bash\n  python3 $HARNESS/helpers/add_cloud_function.py --name parse_invoice\n  ```\n\n- **DSPy prompt optimization.** Optimize a workspace prompt against its paired schema + dataset (BootstrapFewShot or MIPROv2), eval on structural correctness, optionally write the result back to disk. See [`iterate-prompt.md`](skills/iterate-prompt.md).\n\n  ```bash\n  python3 $HARNESS/helpers/iterate_prompt.py --prompt extract_invoice --optimizer mipro_v2\n  ```\n\n## Install\n\n| | Skill mode | Plugin mode |\n|---|---|---|\n| Slash commands | No | Yes (`/n8n-evol-I:*`) |\n| Auto-tidy hook | Manual opt-in | Automatic |\n| Discovery | Read `SKILL.md` | Claude Code `/help` |\n| Other agent runtimes | Yes | Claude Code only |\n\n**Skill mode** (any agent runtime):\n\n```bash\ngit clone https://github.com/mwamedacen/n8n-evol-I.git ~/.claude/skills/n8n-evol-I\npip install pyyaml requests python-dotenv\n```\n\n**Plugin mode** (Claude Code) — adds 10 namespaced slash commands (`/n8n-evol-I:deploy`, `:deploy_all`, `:resync`, `:resync_all`, `:tidyup`, `:debug`, `:run`, `:doctor`, `:validate`, `:test`) plus an auto-tidy hook.\n\n```bash\nclaude plugin install https://github.com/mwamedacen/n8n-evol-I\n```\n\n## Quick start\n\n```bash\nHARNESS=~/.claude/skills/n8n-evol-I   # or $CLAUDE_PLUGIN_ROOT in plugin mode\npython3 $HARNESS/helpers/init.py\npython3 $HARNESS/helpers/bootstrap_env.py --env dev --instance acme.app.n8n.cloud --api-key <key>\npython3 $HARNESS/helpers/doctor.py --env dev\n```\n\nSee [`install.md`](install.md) for prerequisites, optional extras, and update flow.\n\n## Why\n\nOperating n8n past the toy stage exposes the same gaps on every project. The UI editor is fine for a demo and tedious by the third workflow — every code change is click-paste-save, every refactor is manual, every diff is a 200KB JSON blob with code, prompts, schemas, and HTML inlined. A coding agent burns half its context window parsing one file. Two scheduled runs can quietly race on the same record. When something fails at 2am, the execution log tells you *what* broke, not *why*.\n\nN8N EVOL I is the structure that takes those problems off the table.\n\n## How it works\n\nThe harness directory is read-only at runtime. The harness operates against your existing project directory — referred to here as `{YOUR_N8N_PROJECT_DIR}` — which becomes the workspace; the agent reads and writes the opinionated subtree (`n8n-config/`, `n8n-workflows-template/`, …) directly inside it, and you version-control it however you want. The agent reads [`SKILL.md`](SKILL.md) to route any n8n request to the right sub-skill, which invokes a standalone Python helper in `helpers/`. No master CLI, no daemon. `validate.py` enforces segmentation discipline before any deploy.\n\n```mermaid\ngraph LR\n  A[agent] --> B[SKILL.md router]\n  B --> C[skills/*.md]\n  C --> D[helpers/*.py]\n  D --> E[workspace files]\n  D --> F[n8n REST API]\n```\n\n## Workspace layout\n\n`init.py` scaffolds the opinionated default below. If your project already has its own layout, document the mapping in `N8N-WORKSPACE-MEMORY.md` — the agent reads that journal each session and reconciles, so most non-default shapes work fine as long as it can see where each thing actually lives:\n\n```\n{YOUR_N8N_PROJECT_DIR}/\n├── AGENTS.md                # workspace orientation (read first every session)\n├── N8N-WORKSPACE-MEMORY.md  # rolling journal — agent appends as it learns\n├── n8n-config/              # env YAML (dev.yml, prod.yml, …) + .env.<env> secrets\n├── n8n-workflows-template/  # *.template.json — canonical, version-controlled\n├── n8n-build/               # hydrated outputs — gitignored, regenerated on deploy\n├── n8n-functions/\n│   ├── js/                  # pure JS injected via {{@:js:...}}\n│   └── py/                  # pure Python injected via {{@:py:...}}\n├── n8n-functions-tests/     # *.test.js / test_*.py — paired tests, validator-required\n├── n8n-prompts/\n│   ├── prompts/             # *_prompt.txt + *_schema.json\n│   ├── datasets/            # *.json for iterate-prompt\n│   └── evals/\n├── n8n-assets/\n│   ├── email-templates/     # *.html injected via {{@:html:...}}\n│   ├── images/\n│   └── misc/\n├── cloud-functions/         # FastAPI service scaffolded by add-cloud-function\n│   └── functions/\n└── cloud-functions-tests/\n```\n\n## Repository layout\n\n| Path | Contents |\n|---|---|\n| [`SKILL.md`](SKILL.md) | Router — lists all lifecycle, pattern, and integration skills. |\n| [`install.md`](install.md) | Prerequisites, install, smoke test, update flow. |\n| `skills/` | 27 lifecycle + 13 pattern + 10 integration skills (50 total). |\n| `helpers/` | 35 top-level Python helpers + 6 `placeholder/` resolvers. |\n| `primitives/workflows/` | Seed templates: `_minimal`, `lock_acquisition`, `lock_release`, `error_handler_lock_cleanup`, `rate_limit_check`. |\n| `primitives/cloud-functions/` | FastAPI app seed + Railway config (`app.py`, `registry.py`, `railpack.json`). |\n| `primitives/prompts/` | Example prompt + schema for `iterate-prompt`. |\n| `tests/` | 200+ offline tests (HTTP mocked) covering every helper, primitive, and pattern. |\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md).\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n\nFile v1.0.0:skills/integrations/airtable/README.md\n\n---\nname: integration-airtable\ndescription: Airtable nodes — PAT credential, base / table IDs.\nuser-invocable: false\n---\n\n# Airtable\n\n## Node type\n\n`n8n-nodes-base.airtable`.\n\n## Credential type\n\n`airtableTokenApi` — Personal Access Token (PAT) from `airtable.com/create/tokens`.\n\nFor setup, see [`skills/manage-credentials.md`](../../manage-credentials.md). Scopes minimum: `data.records:read` and (if writing) `data.records:write`. Each token can be limited to specific bases — granular tokens are best practice.\n\n## Base + table IDs\n\nAirtable URLs: `https://airtable.com/<baseId>/<tableId>/<viewId>`. The n8n Airtable node takes `base` (= `baseId`) and `table` (= `tableId` or table name).\n\nStore in env YAML:\n\n```yaml\nairtable:\n  ordersBase: \"appabc...\"\n  ordersTable: \"tblxyz...\"\n```\n\n## Field-name vs field-id\n\nRecords returned by the API use field NAMES by default. The Airtable node also supports `returnFieldsByFieldId: true` if your field names change frequently — this returns IDs instead and is more stable.\n\nFile v1.0.0:skills/integrations/datadog/README.md\n\n---\nname: integration-datadog\ndescription: Datadog as the audit destination for n8n error handlers — events POST via HTTP Request, region selection, tag conventions for aggregation.\nuser-invocable: false\n---\n\n# Datadog\n\n## Node type\n\n`n8n-nodes-base.httpRequest`. There is no dedicated `n8n-nodes-base.datadog` node — Datadog is HTTP-shaped.\n\n## Credential type\n\n`httpHeaderAuth` with header `DD-API-KEY: <api_key>`. Get the key from Datadog → Organization Settings → API Keys.\n\nFor most error-handling use cases the API key alone is sufficient. The Application Key (`DD-APPLICATION-KEY`) is only required for endpoints that read data on your behalf (querying logs, dashboards, etc.) — POSTing events doesn't need it.\n\nStore as `.env.<env>`:\n\n```bash\n# .env.<env>\nDD_API_KEY=<your-key>\n```\n\nResolve via `{{@:env:credentials.datadog.id}}` / `{{@:env:credentials.datadog.name}}` after running `manage-credentials` to register the n8n credential. See [`skills/manage-credentials.md`](../../manage-credentials.md).\n\n## Endpoint\n\nErrors as **events** (recommended for human-readable incident streams):\n```\nPOST https://api.datadoghq.com/api/v1/events\n```\n\nErrors as **logs** (alternative — better when you want full-text search across high-volume errors):\n```\nPOST https://api.datadoghq.com/api/v2/logs\n```\n\nThe events endpoint shows up in Datadog's Event Stream and can drive monitor alerts directly. The logs endpoint shows up in Log Explorer and indexes the body for full-text search. Pick events if your error volume is low (incidents, not noise); pick logs if you want every error retained and queryable.\n\n### Region selection\n\nDatadog hosts per region. The endpoint changes accordingly:\n\n| Region | Base URL |\n|---|---|\n| US1 (default) | `https://api.datadoghq.com` |\n| US3 | `https://api.us3.datadoghq.com` |\n| US5 | `https://api.us5.datadoghq.com` |\n| EU1 | `https://api.datadoghq.eu` |\n| AP1 | `https://api.ap1.datadoghq.com` |\n| US-Gov | `https://api.ddog-gov.com` |\n\nMake region env-configurable so a single template runs in EU staging + US prod:\n\n```yaml\n# n8n-config/<env>.yml\ndatadog:\n  endpoint: \"https://api.datadoghq.com\"\n  apiKeyEnv: \"DD_API_KEY\"\n```\n\nThen `={{ $env.DD_ENDPOINT || 'https://api.datadoghq.com' }}` in the URL field, or use a `{{@:env:datadog.endpoint}}` placeholder.\n\n## Worked example: HTTP Request POST event\n\nInside an n8n error handler, fed by the `Build Context` Code node from [`patterns/error-handling.md`](../../patterns/error-handling.md):\n\n```json\n{\n  \"type\": \"n8n-nodes-base.httpRequest\",\n  \"typeVersion\": 4.2,\n  \"parameters\": {\n    \"method\": \"POST\",\n    \"url\": \"={{ ($env.DD_ENDPOINT || 'https://api.datadoghq.com') + '/api/v1/events' }}\",\n    \"authentication\": \"genericCredentialType\",\n    \"genericAuthType\": \"httpHeaderAuth\",\n    \"sendHeaders\": true,\n    \"headerParameters\": {\n      \"parameters\": [\n        { \"name\": \"Accept\", \"value\": \"application/json\" },\n        { \"name\": \"Content-Type\", \"value\": \"application/json\" }\n      ]\n    },\n    \"sendBody\": true,\n    \"specifyBody\": \"json\",\n    \"jsonBody\": \"={{ JSON.stringify({ title: 'n8n workflow failed: ' + $json.workflow_name, text: '%%%\\\\n**Workflow:** ' + $json.workflow_name + '\\\\n**Last node:** ' + $json.last_node + '\\\\n**Execution:** [' + $json.execution_id + '](' + ($json.execution_url || '') + ')\\\\n\\\\n**Error:**\\\\n```\\\\n' + $json.message + '\\\\n```\\\\n%%%', tags: ['workflow:' + $json.workflow_name, 'workflow_id:' + $json.workflow_id, 'execution:' + $json.execution_id, 'env:' + ($env.N8N_ENV || 'unknown'), 'last_node:' + $json.last_node, 'alert_type:error'], priority: 'normal', alert_type: 'error', source_type_name: 'n8n', aggregation_key: 'n8n-workflow-' + $json.workflow_id }) }}\",\n    \"options\": { \"response\": { \"response\": { \"neverError\": true } } }\n  },\n  \"credentials\": {\n    \"httpHeaderAuth\": {\n      \"id\": \"{{@:env:credentials.datadog.id}}\",\n      \"name\": \"{{@:env:credentials.datadog.name}}\"\n    }\n  }\n}\n```\n\nTwo things worth knowing:\n\n- **`%%%` markers wrap markdown.** Datadog's events API treats text between `%%%` lines as markdown; outside, plain text. Without the markers, the code block won't render.\n- **`response.neverError: true`** prevents a Datadog 5xx from breaking the handler. Same reasoning as Sentry — log destinations must not crash the handler.\n\n## Event body shape\n\n```json\n{\n  \"title\": \"<one-line summary>\",\n  \"text\": \"%%%\\n<markdown body>\\n%%%\",\n  \"tags\": [\n    \"workflow:<name>\",\n    \"workflow_id:<id>\",\n    \"execution:<id>\",\n    \"env:<dev|staging|prod>\",\n    \"last_node:<name>\",\n    \"alert_type:error\"\n  ],\n  \"priority\": \"normal\",\n  \"alert_type\": \"error\",\n  \"source_type_name\": \"n8n\",\n  \"aggregation_key\": \"n8n-workflow-<workflow_id>\"\n}\n```\n\n| Field | Why |\n|---|---|\n| `title` | Shows in event stream + alerts. Keep short, lead with the workflow name. |\n| `text` | The detail. Use `%%%`-wrapped markdown so code blocks + links render. |\n| `tags` | Datadog tags are `key:value` strings (note the colon, not `key=value`). |\n| `priority` | `normal` or `low`. Errors are normal. |\n| `alert_type` | `error`, `warning`, `info`, `success`, `user_update`. Drives icon + color. |\n| `source_type_name` | Free-form source identifier. Use `\"n8n\"` so monitor filters can target n8n events. |\n| `aggregation_key` | Events with the same key cluster in the stream. Use the workflow id so all errors from the same workflow group together; switch to execution id if you want per-execution clustering instead. |\n\n## Tag conventions\n\nDatadog tags are powerful but quirky:\n\n- **Use `key:value`, not `key=value`** — the colon is the separator.\n- **Lowercase keys.** Datadog is case-insensitive on tag keys but normalizes to lowercase; using lowercase explicitly avoids surprises in dashboard queries.\n- **Don't put PII in tags.** Tags are indexed and persist; emails, user names, etc. should go in the `text` field instead.\n\nThe standard tags from [`patterns/error-handling.md`](../../patterns/error-handling.md):\n\n| Tag | Source expression |\n|---|---|\n| `workflow:` | `={{ $workflow.name }}` |\n| `workflow_id:` | `={{ $workflow.id }}` |\n| `execution:` | `={{ $workflow.errorData?.execution?.id || $execution.id }}` |\n| `env:` | `{{@:env:name}}` |\n| `last_node:` | `={{ $workflow.errorData?.lastNodeExecuted }}` |\n\nAdd business-relevant tags (`scope:`, `user_id:`, `tenant_id:`) for triage filtering.\n\n## Logs endpoint alternative\n\nIf you'd rather ship to Datadog Logs (full-text search, longer retention):\n\n```json\n{\n  \"type\": \"n8n-nodes-base.httpRequest\",\n  \"typeVersion\": 4.2,\n  \"parameters\": {\n    \"method\": \"POST\",\n    \"url\": \"={{ ($env.DD_ENDPOINT || 'https://api.datadoghq.com') + '/api/v2/logs' }}\",\n    \"authentication\": \"genericCredentialType\",\n    \"genericAuthType\": \"httpHeaderAuth\",\n    \"sendBody\": true,\n    \"specifyBody\": \"json\",\n    \"jsonBody\": \"={{ JSON.stringify([{ ddsource: 'n8n', ddtags: 'workflow:' + $json.workflow_name + ',env:' + ($env.N8N_ENV || 'unknown') + ',workflow_id:' + $json.workflow_id, hostname: 'n8n', service: 'n8n-workflow', message: $json.message, status: 'error', workflow_id: $json.workflow_id, workflow_name: $json.workflow_name, execution_id: $json.execution_id, last_node: $json.last_node, execution_url: $json.execution_url }]) }}\"\n  },\n  \"credentials\": {\n    \"httpHeaderAuth\": {\n      \"id\": \"{{@:env:credentials.datadog.id}}\",\n      \"name\": \"{{@:env:credentials.datadog.name}}\"\n    }\n  }\n}\n```\n\nNote: `ddtags` is a single comma-separated string, NOT an array (logs API quirk). The body is an array of log entries (single-element array for one entry).\n\n## See also\n\n- [`patterns/error-handling.md`](../../patterns/error-handling.md) — three-step paradigm + worked handler example.\n- [`manage-credentials.md`](../../manage-credentials.md) — registering the DD-API-KEY credential.\n\nFile v1.0.0:skills/integrations/google-drive/README.md\n\n---\nname: integration-google-drive\ndescription: Google Drive + Sheets nodes. OAuth scopes, folder-vs-file IDs.\nuser-invocable: false\n---\n\n# Google Drive (and Sheets)\n\n## Node types\n\n- `n8n-nodes-base.googleDrive` — file ops on Drive.\n- `n8n-nodes-base.googleSheets` — Sheets read/write.\n\n## Credential type\n\n`googleApi` (single credential covers Drive + Sheets if scopes include both).\n\nFor setup, see [`skills/manage-credentials.md`](../../manage-credentials.md). OAuth scopes minimum:\n\n- Drive: `https://www.googleapis.com/auth/drive`\n- Sheets: `https://www.googleapis.com/auth/spreadsheets`\n\n## File / folder IDs\n\nA Drive URL `https://drive.google.com/drive/folders/<ID>` gives the folder ID. A Sheets URL `https://docs.google.com/spreadsheets/d/<ID>/edit` gives the spreadsheet ID.\n\nStore IDs in env YAML:\n\n```yaml\ngdrive:\n  reportsFolder: \"1abc...\"\n  weeklyReport: \"1xyz...\"  # spreadsheet\n```\n\n## App verification\n\nUsing sensitive scopes (full Drive access, etc.) requires Google's app verification for production-tier apps. For internal-team-only projects, configure the OAuth client as \"internal\" within a Workspace org to bypass verification.\n\nFile v1.0.0:skills/integrations/notion/README.md\n\n---\nname: integration-notion\ndescription: Notion nodes — internal integration tokens, database / page IDs.\nuser-invocable: false\n---\n\n# Notion\n\n## Node type\n\n`n8n-nodes-base.notion`.\n\n## Credential type\n\n`notionApi` — Internal Integration token from `notion.so/profile/integrations`.\n\nFor setup, see [`skills/manage-credentials.md`](../../manage-credentials.md). Path A: paste the `secret_...` token into `.env.<env>` and run.\n\n## Database / page IDs\n\nNotion URLs encode IDs without dashes; the API expects dashes. Example:\n\n- URL: `https://www.notion.so/Workspace/MyDB-abc123def456789012345678901234`\n- API: `abc123de-f456-7890-1234-5678901234`\n\nThe n8n Notion node accepts either form. Store the with-dashes version in env YAML.\n\n## Sharing the integration\n\nDatabase / page operations only work if the page is **shared with the integration** (top-right \"Share\" → invite the integration). This is a common foot-gun — the API returns 404 for unshared pages.\n\n## Per-property field shape\n\nEach property type (title, rich_text, multi_select, …) has a different JSON shape. The n8n Notion node abstracts this for common types but for complex inserts you may need a Code node that builds the body manually. See Notion's API docs for the exact field shapes.\n\nFile v1.0.0:skills/integrations/sentry/README.md\n\n---\nname: integration-sentry\ndescription: Sentry as the audit destination for n8n error handlers — envelope POST via HTTP Request, DSN auth, tag conventions for grouping.\nuser-invocable: false\n---\n\n# Sentry\n\n## Node type\n\n`n8n-nodes-base.httpRequest`. There is no dedicated `n8n-nodes-base.sentry` node — Sentry is HTTP-shaped end-to-end.\n\n## Credential type\n\nTwo ways to authenticate to Sentry's ingest endpoint:\n\n1. **Embedded DSN auth (recommended for the envelope endpoint)** — the DSN string contains both the project ID and the public key. No credential needed; the DSN goes in the URL.\n2. **`httpHeaderAuth` with a Bearer token** — for the legacy `/store/` endpoint or for Sentry's REST management API. Get a token from Sentry → Settings → Account → API → Auth Tokens.\n\nThe envelope endpoint with DSN-in-URL is the simpler, current-recommended path. Treat the DSN as a secret and store it in `.env.<env>`:\n\n```bash\n# .env.<env>\nSENTRY_DSN=https://<public_key>@o<org_id>.ingest.sentry.io/<project_id>\n```\n\nResolve via `{{@:env:sentry.dsn}}` (after wiring `sentry.dsn: \"${SENTRY_DSN}\"` in your env YAML). See [`skills/manage-credentials.md`](../../manage-credentials.md) for the env-secret pattern.\n\n## Endpoint\n\n**Current (envelope):**\n```\nPOST https://o<org_id>.ingest.sentry.io/api/<project_id>/envelope/\n```\n\nThe envelope endpoint accepts events wrapped in a multi-line newline-delimited envelope structure. For the simpler \"send one event\" case, the envelope body is three lines: header, item-header, item-payload.\n\n**Legacy (deprecated, still works):**\n```\nPOST https://sentry.io/api/<project_id>/store/\nHeader: X-Sentry-Auth: Sentry sentry_version=7, sentry_key=<public_key>, sentry_client=n8n/1.0\nBody: <event JSON>\n```\n\nThe legacy `/store/` endpoint is documented as deprecated by Sentry but is widely used in custom integrations and will keep working for the foreseeable future. If your Sentry environment supports the envelope endpoint, prefer it.\n\n## Worked example: HTTP Request node sending an envelope\n\nThis is the node body inside an n8n error handler, fed by the `Build Context` Code node from [`patterns/error-handling.md`](../../patterns/error-handling.md). The DSN can be baked into the URL via expression — DSN-in-URL skips the X-Sentry-Auth header entirely.\n\n```json\n{\n  \"type\": \"n8n-nodes-base.httpRequest\",\n  \"typeVersion\": 4.2,\n  \"parameters\": {\n    \"method\": \"POST\",\n    \"url\": \"={{ 'https://o' + $env.SENTRY_ORG_ID + '.ingest.sentry.io/api/' + $env.SENTRY_PROJECT_ID + '/envelope/' }}\",\n    \"sendHeaders\": true,\n    \"headerParameters\": {\n      \"parameters\": [\n        { \"name\": \"Content-Type\", \"value\": \"application/x-sentry-envelope\" },\n        { \"name\": \"X-Sentry-Auth\", \"value\": \"={{ 'Sentry sentry_version=7, sentry_key=' + $env.SENTRY_PUBLIC_KEY + ', sentry_client=n8n/1.0' }}\" }\n      ]\n    },\n    \"sendBody\": true,\n    \"contentType\": \"raw\",\n    \"rawContentType\": \"application/x-sentry-envelope\",\n    \"body\": \"={{ JSON.stringify({ event_id: $json.execution_id.replace(/-/g,''), sent_at: $json.timestamp, dsn: $env.SENTRY_DSN }) + '\\\\n' + JSON.stringify({ type: 'event', content_type: 'application/json' }) + '\\\\n' + JSON.stringify({ event_id: $json.execution_id.replace(/-/g,''), timestamp: Math.floor(new Date($json.timestamp).getTime() / 1000), platform: 'javascript', logger: 'n8n', level: 'error', logentry: { message: $json.message }, exception: { values: [{ type: 'WorkflowError', value: $json.message, stacktrace: { frames: [{ function: $json.last_node, module: $json.workflow_name, in_app: true }] } }] }, tags: { workflow_id: $json.workflow_id, workflow_name: $json.workflow_name, execution_id: $json.execution_id, env: $env.N8N_ENV, last_node: $json.last_node }, extra: { execution_url: $json.execution_url } }) }}\",\n    \"options\": { \"response\": { \"response\": { \"neverError\": true } } }\n  }\n}\n```\n\nThe body is the three-line envelope: envelope header (with DSN), item header (declaring the event type), then the event JSON payload.\n\nFor most cases the simpler legacy `/store/` POST is more readable:\n\n```json\n{\n  \"type\": \"n8n-nodes-base.httpRequest\",\n  \"typeVersion\": 4.2,\n  \"parameters\": {\n    \"method\": \"POST\",\n    \"url\": \"={{ 'https://sentry.io/api/' + $env.SENTRY_PROJECT_ID + '/store/' }}\",\n    \"sendHeaders\": true,\n    \"headerParameters\": {\n      \"parameters\": [\n        { \"name\": \"Content-Type\", \"value\": \"application/json\" },\n        { \"name\": \"X-Sentry-Auth\", \"value\": \"={{ 'Sentry sentry_version=7, sentry_key=' + $env.SENTRY_PUBLIC_KEY + ', sentry_client=n8n/1.0' }}\" }\n      ]\n    },\n    \"sendBody\": true,\n    \"specifyBody\": \"json\",\n    \"jsonBody\": \"={{ JSON.stringify({ event_id: $json.execution_id.replace(/-/g,''), timestamp: Math.floor(new Date($json.timestamp).getTime() / 1000), platform: 'javascript', logger: 'n8n', level: 'error', logentry: { message: $json.message }, exception: { values: [{ type: 'WorkflowError', value: $json.message }] }, tags: { workflow_id: $json.workflow_id, workflow_name: $json.workflow_name, execution_id: $json.execution_id, env: $env.N8N_ENV, last_node: $json.last_node }, extra: { execution_url: $json.execution_url } }) }}\",\n    \"options\": { \"response\": { \"response\": { \"neverError\": true } } }\n  }\n}\n```\n\n`response.neverError: true` is critical: a Sentry-side rate limit or 5xx must NOT break the error handler. Log silently and continue.\n\n## Event JSON shape (legacy /store/ form)\n\n```json\n{\n  \"event_id\": \"<32-char hex>\",\n  \"timestamp\": <unix-seconds>,\n  \"platform\": \"javascript\",\n  \"logger\": \"n8n\",\n  \"level\": \"error\",\n  \"logentry\": { \"message\": \"<human-readable>\" },\n  \"exception\": {\n    \"values\": [\n      {\n        \"type\": \"WorkflowError\",\n        \"value\": \"<error message>\",\n        \"stacktrace\": {\n          \"frames\": [\n            { \"function\": \"<last_node_name>\", \"module\": \"<workflow_name>\", \"in_app\": true }\n          ]\n        }\n      }\n    ]\n  },\n  \"tags\": {\n    \"workflow_id\": \"...\",\n    \"workflow_name\": \"...\",\n    \"execution_id\": \"...\",\n    \"env\": \"...\",\n    \"last_node\": \"...\"\n  },\n  \"extra\": {\n    \"execution_url\": \"...\"\n  }\n}\n```\n\n## Tag conventions\n\nSentry's grouping algorithm is driven by the `exception.values[].type` and the top frame's `function` + `module`. To make grouping behave well:\n\n- Set `exception.values[0].type` to a stable string like `\"WorkflowError\"` (not the dynamic error message).\n- Put the failing node name in the top frame's `function`. This makes \"all errors at the Foo node\" cluster together regardless of message.\n- Put `workflow_name` in the top frame's `module`. Sentry will then cluster per workflow as the next layer.\n\nAlways include the standard tags from [`patterns/error-handling.md`](../../patterns/error-handling.md):\n\n| Tag | Source expression |\n|---|---|\n| `workflow_id` | `={{ $workflow.id }}` |\n| `workflow_name` | `={{ $workflow.name }}` |\n| `execution_id` | `={{ $workflow.errorData?.execution?.id || $execution.id }}` |\n| `env` | `{{@:env:name}}` |\n| `last_node` | `={{ $workflow.errorData?.lastNodeExecuted }}` |\n\nAdd business-relevant tags (`scope`, `user_id`, `tenant_id`) so Sentry filtering reflects what your on-call actually needs.\n\n## See also\n\n- [`patterns/error-handling.md`](../../patterns/error-handling.md) — three-step paradigm + worked handler example.\n- [`manage-credentials.md`](../../manage-credentials.md) — DSN as env-secret.\n\nFile v1.0.0:skills/integrations/slack/README.md\n\n---\nname: integration-slack\ndescription: Slack nodes — bot/user OAuth, posting messages, threads, error-notification Block Kit pattern.\nuser-invocable: false\n---\n\n# Slack\n\n## Node type\n\n`n8n-nodes-base.slack`.\n\n## Credential type\n\n`slackApi` (bot token) or `slackOAuth2Api` (full OAuth flow).\n\nFor setup, see [`skills/manage-credentials.md`](../../manage-credentials.md). Bot tokens (`slackApi`) are simpler — paste `xoxb-...` into `.env.<env>` and run Path A.\n\n## Common operations\n\n- **Post message:** `resource: message`, `operation: post`. Set `channel` to a channel ID (preferred) or `#name` (sometimes resolves slowly).\n- **Reply in thread:** include `thread_ts` from a previous message.\n- **Update message:** `operation: update` with `ts` of the original message.\n\n## Channel ID gotcha\n\nSlack's UI shows `#channel-name` but the API takes channel IDs (e.g. `C0123456789`). Find the ID by right-clicking the channel → Copy Link → the URL contains the ID. Store the ID in your env YAML, not the name:\n\n```yaml\nslack:\n  alertsChannel: \"C0123456789\"\n```\n\n```json\n{\n  \"type\": \"n8n-nodes-base.slack\",\n  \"parameters\": {\n    \"channel\": \"{{@:env:slack.alertsChannel}}\"\n  }\n}\n```\n\n## Error-notification worked example\n\nUsed inside an n8n error handler, fed by the `Build Context` Code node from [`patterns/error-handling.md`](../../patterns/error-handling.md). Posts a Block Kit message to the alerts channel + captures the response so a downstream node can thread updates onto it.\n\n```json\n{\n  \"type\": \"n8n-nodes-base.slack\",\n  \"typeVersion\": 2.3,\n  \"parameters\": {\n    \"resource\": \"message\",\n    \"operation\": \"post\",\n    \"select\": \"channel\",\n    \"channelId\": {\n      \"__rl\": true,\n      \"value\": \"{{@:env:slack.alertsChannel}}\",\n      \"mode\": \"id\"\n    },\n    \"messageType\": \"block\",\n    \"blocksUi\": \"={{ JSON.stringify({ blocks: [\\n  { type: 'header', text: { type: 'plain_text', text: ':rotating_light: Workflow failed: ' + $json.workflow_name } },\\n  { type: 'section', fields: [\\n    { type: 'mrkdwn', text: '*Workflow:*\\\\n' + $json.workflow_name },\\n    { type: 'mrkdwn', text: '*Last node:*\\\\n`' + ($json.last_node || 'unknown') + '`' },\\n    { type: 'mrkdwn', text: '*Env:*\\\\n' + ($env.N8N_ENV || 'unknown') },\\n    { type: 'mrkdwn', text: '*When:*\\\\n' + $json.timestamp }\\n  ]},\\n  { type: 'section', text: { type: 'mrkdwn', text: '*Error message:*\\\\n```\\\\n' + $json.message + '\\\\n```' } },\\n  { type: 'actions', elements: [\\n    { type: 'button', text: { type: 'plain_text', text: 'Open in n8n' }, url: $json.execution_url || 'https://n8n.example.com', style: 'primary' }\\n  ]}\\n]}) }}\",\n    \"otherOptions\": {}\n  },\n  \"credentials\": {\n    \"slackApi\": {\n      \"id\": \"{{@:env:credentials.slack.id}}\",\n      \"name\": \"{{@:env:credentials.slack.name}}\"\n    }\n  }\n}\n```\n\nThe `messageType: block` mode tells the Slack node to send Block Kit JSON in the `blocksUi` field. The Block Kit structure shipped above is:\n\n- A `header` block with the rotating-light emoji + workflow name (large, can't-miss-it).\n- A `section` with `fields` showing workflow / last-node / env / timestamp in a 2-column grid.\n- A second `section` with the error message in a fenced code block.\n- An `actions` row with a primary button linking to the n8n execution URL.\n\nSlack's response includes `ts` (the message timestamp) — capture it via `$json.ts` for thread updates:\n\n```javascript\n// In a downstream node referencing the Slack post output:\nconst threadTs = $('Slack Post').first().json.ts;\n```\n\n## Threading updates onto the original alert\n\nWhen the cleanup step finishes (lock released, DB invalidated, etc.), reply in-thread on the original alert so on-call sees the resolution without a second top-level message:\n\n```json\n{\n  \"type\": \"n8n-nodes-base.slack\",\n  \"typeVersion\": 2.3,\n  \"parameters\": {\n    \"resource\": \"message\",\n    \"operation\": \"post\",\n    \"select\": \"channel\",\n    \"channelId\": {\n      \"__rl\": true,\n      \"value\": \"{{@:env:slack.alertsChannel}}\",\n      \"mode\": \"id\"\n    },\n    \"text\": \"={{ ':white_check_mark: Cleanup complete: ' + $json.cleanup_summary }}\",\n    \"otherOptions\": {\n      \"thread_ts\": \"={{ $('Slack Post').first().json.ts }}\"\n    }\n  }\n}\n```\n\nThis keeps the alerts channel readable: one top-level :rotating_light: per incident, replies-in-thread for status updates.\n\n## See also\n\n- [`patterns/error-handling.md`](../../patterns/error-handling.md) — three-step paradigm; this Slack post is the \"log to humans\" step.\n- [`manage-credentials.md`](../../manage-credentials.md) — registering the slackApi credential.\n\nFile v1.0.0:skills/integrations/webhooks/README.md\n\n---\nname: integration-webhooks\ndescription: Webhook node patterns — paths, response modes, test vs production URLs.\nuser-invocable: false\n---\n\n# Webhooks\n\n## Node types\n\n- `n8n-nodes-base.webhook` — incoming webhook trigger.\n- `n8n-nodes-base.respondToWebhook` — explicit response (when not using `responseMode: lastNode`).\n\n## Path conventions\n\nWebhook paths are env-agnostic but share a base URL per env. Use a stable, descriptive path:\n\n```json\n{\n  \"type\": \"n8n-nodes-base.webhook\",\n  \"parameters\": {\n    \"httpMethod\": \"POST\",\n    \"path\": \"report/daily\",\n    \"responseMode\": \"lastNode\"\n  }\n}\n```\n\nn8n exposes the webhook at `<instance>/webhook/<path>` (production) and `<instance>/webhook-test/<path>` (test mode in the UI). `run.py` tries production first, then falls back to test.\n\n## Response modes\n\n- `responseMode: lastNode` — n8n returns the last node's output as the response. Simplest case.\n- `responseMode: responseNode` — terminate flow with `respondToWebhook` to control status/headers/body explicitly.\n\n## Authentication\n\n- **None** — public webhook. Fine for triggers from trusted sources (or with HMAC-verifying middleware in the workflow).\n- **Header Auth** — n8n verifies a header against a credential. Credential type: `httpHeaderAuth`.\n- **Basic Auth** — credential type: `httpBasicAuth`.\n\nFor credential setup, see [`skills/manage-credentials.md`](../../manage-credentials.md).\n\n## webhookId placeholder\n\nn8n's webhook node has a `webhookId` field that contains a UUID identifying the public webhook URL. Use `{{@:uuid:webhook-public}}` so each env gets its own (the path is shared but the public webhook URL varies).\n\nFile v1.0.0:_meta.json\n\n{\n  \"ownerId\": \"kn744p2vzxwdnxfjhek7pxsz4n860kr1\",\n  \"slug\": \"n8n-evol-i\",\n  \"version\": \"1.0.0\",\n  \"publishedAt\": 1777805813106\n}\n\nFile v1.0.0:install.md\n\n---\nname: install\ndescription: How to install n8n-evol-I as a skill package and what it expects on the host system.\n---\n\n# install\n\n## Prerequisites\n\n- Python ≥ 3.11 on PATH\n- Pip-installable: `pyyaml`, `requests`, `python-dotenv`\n- An n8n instance the agent can reach via REST + an API key with workflow + credential scopes\n\n## Install (one-time)\n\n### Skill mode (any agent runtime)\n\nClone into your agent's skills directory:\n\n```bash\ncd ~/.claude/skills   # or wherever your agent runtime reads skills from\ngit clone https://github.com/mwamedacen/n8n-evol-I.git\n```\n\nInstall Python deps:\n\n```bash\npip install pyyaml requests python-dotenv\n```\n\n(Optional, only if you'll use `iterate-prompt`:)\n\n```bash\npip install dspy litellm\n```\n\n### Plugin mode (Claude Code only)\n\nCLI form (run in your terminal):\n\n```bash\nclaude plugin install https://github.com/mwamedacen/n8n-evol-I\n```\n\nIn-session form (inside a Claude Code session):\n\n```\n/plugin install https://github.com/mwamedacen/n8n-evol-I\n```\n\nLocal dev (loads from a local checkout):\n\n```bash\nclaude --plugin-dir ./n8n-evol-I\n```\n\n#### Plugin extras\n\nWhen installed as a plugin, n8n-evol-I ships two additional behaviors:\n\n- **Slash commands** — 10 user-facing commands available as `/n8n-evol-I:deploy`, `/n8n-evol-I:tidyup`, etc. Hidden lifecycle skills remain agent-loadable via `SKILL.md` routing but do not appear in `/help`.\n\n- **Auto-tidy hook** — a `PostToolUse` hook fires after every Write/Edit/MultiEdit tool call; the hook script filters to `*.template.json` files and runs `tidy_workflow.py --in-place` on matching files. This keeps node positions clean without any manual step.\n\n  The hook runs asynchronously (`async: true`), so Claude Code does not wait for it before continuing. If the agent reads the template file immediately after writing it (e.g. to feed it into a deploy step), it may observe the un-tidied version. In that case, run `tidy-workflow` manually before deploying, or add a short pause between the write and the read.\n\n  To disable the auto-tidy hook: remove or rename `hooks/hooks.json` in the plugin directory, or disable the plugin in Claude Code settings. Standalone-skill-mode users who want auto-tidy can configure the hook manually in `~/.claude/settings.json`.\n\n## What the harness expects on disk\n\nThe skill package is a directory containing:\n\n- `SKILL.md` — entry point\n- `skills/*.md` and `skills/{patterns,integrations}/...md` — sub-skills\n- `helpers/*.py` — the executable surface\n- `primitives/` — seed templates copied into workspaces on demand\n\nHelpers are invoked by absolute path (no console script, no PATH pollution):\n\n```bash\npython3 <harness>/helpers/<name>.py [args]\n```\n\n## Per-project setup\n\nPer project, the agent runs `init.md` once to scaffold a workspace at `${PWD}/n8n-evol-I-workspace/`. From there, `bootstrap-env.md` configures envs, `create-new-workflow.md` authors workflows, etc. See [`SKILL.md`](SKILL.md) for the full skill catalogue.\n\n## Updating\n\nThe harness's \"version\" is its git SHA. To upgrade:\n\n```bash\ncd ~/.claude/skills/n8n-evol-I\ngit pull\n```\n\nBreaking changes between versions are documented in [`CHANGELOG.md`](CHANGELOG.md). For the migration from the legacy single-repo shape (pre-`d6848fd`), see [`docs/migration-from-d6848fd.md`](docs/migration-from-d6848fd.md).\n\n## Verifying the install\n\n```","readmeExcerpt":"Skill: N8N EVOL I Owner: mwamedacen Summary: A harness to help coding agents build, deploy, maintain, and debug multi-workflow n8n-powered automation systems. No lock-in — work from the agent, continue... Tags: latest:2.0.0 Version history: v2.0.0 | 2026-05-25T22:16:50.188Z | user Queue primitives and lifecycle support added + breaking change on interpolation syntax - Introduced primitives and helper scripts to suppo","codeSnippets":[],"executableExamples":[{"language":"mermaid","snippet":"flowchart LR\n    Agent <-->|edits / resync| Workspace\n    Workspace <-->|deploy / resync| n8n[(n8n instance)]\n    n8n <-->|UI edits| Human"},{"language":"yaml","snippet":"# n8n-config/dev.yml\n  instance: acme.dev.n8n.cloud\n  envSuffix: \" (dev)\"          # appended to every workflow name\n  workflows:\n    lead_enrichment:\n      id: wf_def456\n      displayName: \"Lead Enrichment\"\n    lead_persister:\n      id: wf_abc123\n      displayName: \"Lead Persister\""},{"language":"jsonc","snippet":"// n8n-workflows-template/lead_enrichment.template.json\n  {\n    \"name\": \"{{@env:workflows.lead_enrichment.displayName}}{{@env:envSuffix}}\",\n    \"nodes\": [\n      {\n        \"name\": \"Webhook\",\n        \"type\": \"n8n-nodes-base.webhook\",\n        \"parameters\": { \"path\": \"lead\", \"httpMethod\": \"POST\" }\n      },\n      {\n        \"name\": \"Score Lead\",\n        \"type\": \"@n8n/n8n-nodes-langchain.openAi\",\n        \"parameters\": {\n          \"messages\": [\n            { \"role\": \"system\", \"content\": \"{{@txt:n8n-prompts/prompts/score_lead.txt}}\" }\n          ]\n        }\n      },\n      {\n        \"name\": \"Normalize Fields\",\n        \"type\": \"n8n-nodes-base.code\",\n        \"parameters\": {\n          \"jsCode\": \"{{@js:n8n-functions/js/normalize_lead.js}}\\n\\nreturn normalize(items);\"\n        }\n      },\n      {\n        \"name\": \"Persist via Sub-workflow\",\n        \"type\": \"n8n-nodes-base.executeWorkflow\",\n        \"parameters\": {\n          \"workflowId\": \"{{@env:workflows.lead_persister.id}}\"\n        }\n      }\n    ]\n  }"},{"language":"mermaid","snippet":"graph LR\n    A[tier 0: leaves] --> B[tier 1: handlers] --> C[tier 2: callers]"},{"language":"bash","snippet":"python3 $HARNESS/helpers/inspect_execution.py --env dev --id 91234"},{"language":"text","snippet":"┌──────────┐   ┌──────┐   ┌──────────┐\n  │ acquire  │ → │ work │ → │ release  │\n  │ (scope)  │   │      │   │ (scope)  │\n  └──────────┘   └──────┘   └──────────┘"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: n8n-evol-I\ndescription: A harness to help coding agents build, deploy, maintain, and debug multi-workflow n8n-powered automation systems. No lock-in — work from the agent, continue from the n8n UI, hand back to the agent at any time.\n---\n\n# n8n-evol-I skill router\n\nThis is the entry point. When the user asks anything n8n-related, route to the matching sub-skill.\n\n## Mental model\n\n- The skill package (this directory) is **read-only**. You never edit files here.\n- The user's project state lives in `${PWD}/n8n-evol-I-workspace/` (created by `init.md`).\n- Helpers are Python scripts under `helpers/`. Invoke them with `python3 ${CLAUDE_PLUGIN_ROOT}/helpers/<name>.py [args]` (plugin mode) or `python3 <path-to-harness>/helpers/<name>.py [args]` (skill mode).\n- All helpers default to `--workspace ${PWD}/n8n-evol-I-workspace`. Pass `--workspace <path>` if the user runs from elsewhere.\n- **Agent memory**: read `N8N-WORKSPACE-MEMORY.md` in the workspace at the start of every session; append a dated entry whenever you learn something durable about this project. Full guidance in `AGENTS.md` (also in the workspace root).\n\n## Lifecycle skills (use when the user wants to do X)\n\n| Skill | When |\n|---|---|\n| [init.md](skills/init.md) | First-time setup. Creates the workspace at `${PWD}/n8n-evol-I-workspace/`. |\n| [bootstrap-env.md](skills/bootstrap-env.md) | Configure an environment (`dev` / `staging` / `prod`). Creates env YAML + `.env`, validates, mints placeholder workflow IDs. |\n| [doctor.md](skills/doctor.md) | Health check. Run before/after major changes. |\n| [create-new-workflow.md](skills/create-new-workflow.md) | Author a brand-new workflow. |\n| [register-workflow-to-error-handler.md](skills/register-workflow-to-error-handler.md) | Wire `settings.errorWorkflow`. |\n| [create-lock.md](skills/create-lock.md) | First-time setup for distributed locking (Redis-backed primitives). |\n| [copy-primitive.md](skills/copy-primitive.md) | Copy a single primitive (any) into the workspace. General-purpose; doesn't register. |\n| [add-lock-to-workflow.md](skills/add-lock-to-workflow.md) | Wrap a workflow's main flow in lock acquire/release. |\n| [add-rate-limit-to-workflow.md](skills/add-rate-limit-to-workflow.md) | Gate a workflow's main flow with a Redis-backed fixed-window rate-limit check. |\n| [create-queue.md](skills/create-queue.md) | First-time setup for queue primitive (Redis Streams + atomic-INCR semaphore). |\n| [add-queue-publish-to-workflow.md](skills/add-queue-publish-to-workflow.md) | Wrap a workflow with a producer-side XADD call. |\n| [add-queue-consumer-to-workflow.md](skills/add-queue-consumer-to-workflow.md) | Turn a workflow into a schedule-polled queue consumer with bounded concurrency. |\n| [tidy-workflow.md](skills/tidy-workflow.md) | Apply n8n's canvas-layout algorithm to a workflow template to clean up node positions. |\n| [deploy.md](skills/deploy.md) | Deploy one workflow to one env. |\n| [activate-single-workflow-in-env.md](skills/activate-si"},{"path":"README.md","content":"<img width=\"1376\" height=\"768\" alt=\"n8n_evol_I_hero\" src=\"https://github.com/user-attachments/assets/0a28f1f6-106f-4f84-9ce1-cf7eebcf4cd6\" />\n\n# N8N EVOL I\n\nA harness to help coding agents build, deploy, maintain, and debug multi-workflow n8n-powered automation systems.\n\nNo lock-in. Build with your agent and push upstream to n8n; tweak directly in the n8n UI and pull downstream back into your workspace. Round-trips are template-stable, so humans and agents can work the same project in parallel without stepping on each other's edits.\n\n```mermaid\nflowchart LR\n    Agent <-->|edits / resync| Workspace\n    Workspace <-->|deploy / resync| n8n[(n8n instance)]\n    n8n <-->|UI edits| Human\n```\n\n## Features\n\n- **Multi-environment workflows.** Per-env YAML in `n8n-config/<env>.yml` (instance URL, workflow IDs, credential refs); every helper accepts `--env`. See [`bootstrap-env.md`](skills/bootstrap-env.md).\n\n  ```yaml\n  # n8n-config/dev.yml\n  instance: acme.dev.n8n.cloud\n  envSuffix: \" (dev)\"          # appended to every workflow name\n  workflows:\n    lead_enrichment:\n      id: wf_def456\n      displayName: \"Lead Enrichment\"\n    lead_persister:\n      id: wf_abc123\n      displayName: \"Lead Persister\"\n  ```\n\n- **Build-time substitution.** Code, prompts, schemas, templates, and env values live in workspace files; `{{@js|py|txt|json|html|env|uuid:...}}` placeholders are substituted at deploy time. Template-stable round-trips. See [`code-node-discipline.md`](skills/patterns/code-node-discipline.md).\n\n  ```jsonc\n  // n8n-workflows-template/lead_enrichment.template.json\n  {\n    \"name\": \"{{@env:workflows.lead_enrichment.displayName}}{{@env:envSuffix}}\",\n    \"nodes\": [\n      {\n        \"name\": \"Webhook\",\n        \"type\": \"n8n-nodes-base.webhook\",\n        \"parameters\": { \"path\": \"lead\", \"httpMethod\": \"POST\" }\n      },\n      {\n        \"name\": \"Score Lead\",\n        \"type\": \"@n8n/n8n-nodes-langchain.openAi\",\n        \"parameters\": {\n          \"messages\": [\n            { \"role\": \"system\", \"content\": \"{{@txt:n8n-prompts/prompts/score_lead.txt}}\" }\n          ]\n        }\n      },\n      {\n        \"name\": \"Normalize Fields\",\n        \"type\": \"n8n-nodes-base.code\",\n        \"parameters\": {\n          \"jsCode\": \"{{@js:n8n-functions/js/normalize_lead.js}}\\n\\nreturn normalize(items);\"\n        }\n      },\n      {\n        \"name\": \"Persist via Sub-workflow\",\n        \"type\": \"n8n-nodes-base.executeWorkflow\",\n        \"parameters\": {\n          \"workflowId\": \"{{@env:workflows.lead_persister.id}}\"\n        }\n      }\n    ]\n  }\n  ```\n\n- **Dependency-ordered deployment.** `deploy_all.py` rolls out an env in tier order (set per-workflow in `deployment_order.yml`) so callees deploy before callers. See [`deploy_all.md`](skills/deploy_all.md).\n\n  ```mermaid\n  graph LR\n    A[tier 0: leaves] --> B[tier 1: handlers] --> C[tier 2: callers]\n  ```\n\n- **Execution debugging.** Structured symptom → root-cause flow (dependency graph, candidate pre-screen, causal-linkage, sub-agent cross-check), backed by `list_ex"},{"path":"skills/integrations/airtable/README.md","content":"---\nname: integration-airtable\ndescription: Airtable nodes — PAT credential, base / table IDs.\nuser-invocable: false\n---\n\n# Airtable\n\n## Node type\n\n`n8n-nodes-base.airtable`.\n\n## Credential type\n\n`airtableTokenApi` — Personal Access Token (PAT) from `airtable.com/create/tokens`.\n\nFor setup, see [`skills/manage-credentials.md`](../../manage-credentials.md). Scopes minimum: `data.records:read` and (if writing) `data.records:write`. Each token can be limited to specific bases — granular tokens are best practice.\n\n## Base + table IDs\n\nAirtable URLs: `https://airtable.com/<baseId>/<tableId>/<viewId>`. The n8n Airtable node takes `base` (= `baseId`) and `table` (= `tableId` or table name).\n\nStore in env YAML:\n\n```yaml\nairtable:\n  ordersBase: \"appabc...\"\n  ordersTable: \"tblxyz...\"\n```\n\n## Field-name vs field-id\n\nRecords returned by the API use field NAMES by default. The Airtable node also supports `returnFieldsByFieldId: true` if your field names change frequently — this returns IDs instead and is more stable."},{"path":"skills/integrations/datadog/README.md","content":"---\nname: integration-datadog\ndescription: Datadog as the audit destination for n8n error handlers — events POST via HTTP Request, region selection, tag conventions for aggregation.\nuser-invocable: false\n---\n\n# Datadog\n\n## Node type\n\n`n8n-nodes-base.httpRequest`. There is no dedicated `n8n-nodes-base.datadog` node — Datadog is HTTP-shaped.\n\n## Credential type\n\n`httpHeaderAuth` with header `DD-API-KEY: <api_key>`. Get the key from Datadog → Organization Settings → API Keys.\n\nFor most error-handling use cases the API key alone is sufficient. The Application Key (`DD-APPLICATION-KEY`) is only required for endpoints that read data on your behalf (querying logs, dashboards, etc.) — POSTing events doesn't need it.\n\nStore as `.env.<env>`:\n\n```bash\n# .env.<env>\nDD_API_KEY=<your-key>\n```\n\nResolve via `{{@env:credentials.datadog.id}}` / `{{@env:credentials.datadog.name}}` after running `manage-credentials` to register the n8n credential. See [`skills/manage-credentials.md`](../../manage-credentials.md).\n\n## Endpoint\n\nErrors as **events** (recommended for human-readable incident streams):\n```\nPOST https://api.datadoghq.com/api/v1/events\n```\n\nErrors as **logs** (alternative — better when you want full-text search across high-volume errors):\n```\nPOST https://api.datadoghq.com/api/v2/logs\n```\n\nThe events endpoint shows up in Datadog's Event Stream and can drive monitor alerts directly. The logs endpoint shows up in Log Explorer and indexes the body for full-text search. Pick events if your error volume is low (incidents, not noise); pick logs if you want every error retained and queryable.\n\n### Region selection\n\nDatadog hosts per region. The endpoint changes accordingly:\n\n| Region | Base URL |\n|---|---|\n| US1 (default) | `https://api.datadoghq.com` |\n| US3 | `https://api.us3.datadoghq.com` |\n| US5 | `https://api.us5.datadoghq.com` |\n| EU1 | `https://api.datadoghq.eu` |\n| AP1 | `https://api.ap1.datadoghq.com` |\n| US-Gov | `https://api.ddog-gov.com` |\n\nMake region env-configurable so a single template runs in EU staging + US prod:\n\n```yaml\n# n8n-config/<env>.yml\ndatadog:\n  endpoint: \"https://api.datadoghq.com\"\n  apiKeyEnv: \"DD_API_KEY\"\n```\n\nThen `={{ $env.DD_ENDPOINT || 'https://api.datadoghq.com' }}` in the URL field, or use a `{{@env:datadog.endpoint}}` placeholder.\n\n## Worked example: HTTP Request POST event\n\nInside an n8n error handler, fed by the `Build Context` Code node from [`patterns/error-handling.md`](../../patterns/error-handling.md):\n\n```json\n{\n  \"type\": \"n8n-nodes-base.httpRequest\",\n  \"typeVersion\": 4.2,\n  \"parameters\": {\n    \"method\": \"POST\",\n    \"url\": \"={{ ($env.DD_ENDPOINT || 'https://api.datadoghq.com') + '/api/v1/events' }}\",\n    \"authentication\": \"genericCredentialType\",\n    \"genericAuthType\": \"httpHeaderAuth\",\n    \"sendHeaders\": true,\n    \"headerParameters\": {\n      \"parameters\": [\n        { \"name\": \"Accept\", \"value\": \"application/json\" },\n        { \"name\": \"Content-Type\", \"value\": \"application/json\" }\n      ]\n    },\n    \"sendBody\": true,\n "},{"path":"skills/integrations/google-drive/README.md","content":"---\nname: integration-google-drive\ndescription: Google Drive + Sheets nodes. OAuth scopes, folder-vs-file IDs.\nuser-invocable: false\n---\n\n# Google Drive (and Sheets)\n\n## Node types\n\n- `n8n-nodes-base.googleDrive` — file ops on Drive.\n- `n8n-nodes-base.googleSheets` — Sheets read/write.\n\n## Credential type\n\n`googleApi` (single credential covers Drive + Sheets if scopes include both).\n\nFor setup, see [`skills/manage-credentials.md`](../../manage-credentials.md). OAuth scopes minimum:\n\n- Drive: `https://www.googleapis.com/auth/drive`\n- Sheets: `https://www.googleapis.com/auth/spreadsheets`\n\n## File / folder IDs\n\nA Drive URL `https://drive.google.com/drive/folders/<ID>` gives the folder ID. A Sheets URL `https://docs.google.com/spreadsheets/d/<ID>/edit` gives the spreadsheet ID.\n\nStore IDs in env YAML:\n\n```yaml\ngdrive:\n  reportsFolder: \"1abc...\"\n  weeklyReport: \"1xyz...\"  # spreadsheet\n```\n\n## App verification\n\nUsing sensitive scopes (full Drive access, etc.) requires Google's app verification for production-tier apps. For internal-team-only projects, configure the OAuth client as \"internal\" within a Workspace org to bypass verification."}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"A harness to help coding agents build, deploy, maintain, and debug multi-workflow n8n-powered automation systems. No lock-in — work from the agent, continue... Skill: N8N EVOL I Owner: mwamedacen Summary: A harness to help coding agents build, deploy, maintain, and debug multi-workflow n8n-powered automation systems. No lock-in — work from the agent, continue... Tags: latest:2.0.0 Version history: v2.0.0 | 2026-05-25T22:16:50.188Z | user Queue primitives and lifecycle support added + breaking change on interpolation syntax - Introduced primitives and helper scripts to suppo","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1550,"uniquenessScore":47,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-12T01:16:28.792Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-12T01:16:28.792Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-12T08:36:33.919Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}