@kairos-sdk/core
v0.13.1
Published
LLM-powered n8n workflow generation with a post-deploy reliability suite (drift detection, chaos testing, self-healing, replay) and a Promise Engine that compiles business commitments into workflows and verifies from real execution evidence whether they w
Maintainers
Readme
@kairos-sdk/core
Kairos is a reliability engine for n8n workflows, and a compiler for verifiable business promises. It generates and deploys automations from a business description — and then keeps working after deploy: stress-testing them against hostile input before they ship, watching them for drift once they're live, and verifying that any change behaves like the version it replaces before that change goes out. For processes with a real business commitment attached (an SLA, an owner, a terminal outcome), Kairos can also compile a ProcessContract into those same workflows and then check — from real n8n execution evidence, not from intent — whether the promise was actually kept.

Generation is the entry point, not the whole product. A workflow going live is the start of what Kairos does with it, not the end:
BUILD ──── generate + validate (129 rules) + deploy
│
CHAOS TEST ─ pre-deploy: attack it with adversarial payloads in an
│ isolated sandbox (kairos chaos audit / chaos run)
│
DEPLOY ──── activate, verify the webhook actually registered
│
WATCH ───── post-deploy: baseline → detect drift → diagnose with a
│ confidence-tiered cause (kairos watch, kairos drift check)
│
REPAIR ──── for the one narrow, deterministic drift class (build-vs-
│ live structural drift), propose and — gated, snapshot-
│ backed, replay-verified, human-confirmed by default —
│ apply a restore (kairos repair propose/apply, kairos rollback)
│
REPLAY ──── prove a candidate change behaves like the version it
replaces, against real recorded traffic, in a sandbox
that never touches production (kairos replay run)Self-healing today is intentionally narrow, not "AI autonomously fixes production": it covers exactly one deterministic drift class (a live workflow diverging from what Kairos itself last deployed), requires an explicit human confirmation or a whitelisted, replay-verified --auto, and never regenerates anything via an LLM call. A separate, experimental (opt-in, off by default) community pattern layer lets installs export anonymized validator-failure patterns and optionally ingest others' — see Community Pattern Sharing below. This README only claims what's built and checkpointed against real n8n instances or a real CLI run, nothing ahead of that. npm run demo:reliability-loop runs the shipped chaos → deploy → watch → diagnose → notify sequence end to end against a real disposable local sandbox (no Docker, no production credentials, cleans up after itself) — the loop above isn't a diagram of intent, it's what that command actually does (repair/replay aren't in the scripted demo yet, but are checkpointed live in docs/plans/reliability-suite-plan.md §8/§7).
Use Kairos as an MCP server (connect to Claude Code, Claude Desktop, or any MCP host — no Anthropic API key needed), a TypeScript SDK, or a CLI. With a seeded template library, generation achieves 100% first-try structural validation pass rate across 20 benchmark prompts.
import { Kairos, PackBuilder } from '@kairos-sdk/core'
const kairos = new Kairos({
anthropicApiKey: process.env.ANTHROPIC_API_KEY!,
n8nBaseUrl: 'https://your-instance.app.n8n.cloud',
n8nApiKey: process.env.N8N_API_KEY!,
})
// Build a single workflow
const result = await kairos.build(
'Every morning at 9am, send a message to #daily-digest on Slack'
)
console.log(result.workflowId) // deployed workflow ID
console.log(result.credentialsNeeded) // what still needs configuring
// Build a complete workflow pack from a business context
const builder = new PackBuilder({ anthropicApiKey: process.env.ANTHROPIC_API_KEY!, kairos })
const plan = await builder.plan('Homecare DME operations')
const pack = await builder.build(plan)
console.log(pack.workflows.map(w => w.name)) // all deployed workflow names
console.log(pack.openQuestions) // questions to answer before activating
console.log(pack.testChecklist) // how to verify each workflowWhat Kairos does and does not do
| Kairos does | Kairos does not guarantee (yet) |
|---|---|
| Generates valid n8n workflow JSON | Perfect business logic |
| Builds complete workflow packs from business context | Correct credentials or API configs |
| Validates structure before deploy (129 rules) | Runtime success for every API |
| Chaos-tests a workflow against adversarial payloads pre-deploy (kairos chaos audit/chaos run) | Speculative or LLM-regenerated repair for anything beyond the one narrow, deterministic drift class it currently handles |
| Watches deployed workflows for drift and diagnoses it (kairos watch, kairos drift check) | A mature, populated cross-install pattern corpus — ingestion (kairos patterns ingest/sync) is real but experimental, off by default, and only as good as what's actually been shared so far |
| Verifies a candidate change behaves like the version it replaces before deploy (kairos replay run) | That every workflow matches intent perfectly |
| Proposes and, gated + snapshot-backed + replay-verified, applies a restore for build-vs-live structural drift (kairos repair propose/apply, kairos rollback) | Full replacement for human review |
| Exports your own confirmed patterns for the community, and optionally ingests others' — informational only, never influencing your local scoring (kairos patterns share/ingest/sync) | — |
| Compiles a business promise (states, SLAs, owners, terminal outcomes) into workflows, tracks whether it was actually kept from real execution evidence, and opens/reports exceptions for a real miss (kairos contract plan/compile/report, kairos watch --contracts) | Autonomous business decisions — exception detection is automatic, but resolution is human-only, always, with no --auto mode at all |
| Grades every promise instance from evidence: kept / at-risk / missed / unverifiable / in-progress (kairos contract report) — see Promise Engine v0 | A guarantee that the promise was kept — a Promise Report is an evidence-graded evaluation against whatever n8n execution data exists, not a certification, and explicitly says so whenever evidence is incomplete |
| Opens an ExceptionDesk item for time-based SLA/expiration drift (kairos watch --contracts) | An ExceptionDesk item for every miss — a fast terminal failure reached before any deadline passes (e.g. flagged "missing info" within minutes) is correctly counted in contract report but currently opens no ExceptionDesk item |
| Drafts a ProcessContract via a guided 11-question interview instead of one free-text paragraph, and deterministically generates + evaluates synthetic business scenarios against it purely in-memory, no n8n (kairos contract intake, kairos contract scenarios generate, kairos contract harness run) | A substitute for a human deciding what the contract should actually say — synthesis is always validator-gated and human-reviewed, never auto-applied |
| Checks a real sandboxed workflow's execution against a contract's own expected business outcome, alongside its existing structural/adversarial checks (kairos replay run --contract, kairos chaos run --contract) | A full end-to-end business-outcome proof from one execution — both are explicit that they verify intake evidence only, never state-transition evidence a separate processing workflow produces |
| Proposes evidence-linked contract amendments from real ProofLedger/ExceptionDesk patterns, and lets a human preview/apply a real, version-archived amendment (kairos contract evolve, kairos contract amend/versions/diff) | Any automatic contract change, ever — every proposal requires human accept/reject, every amendment requires explicit --confirm, and a breaking amendment is refused outright while any promise instance is still in flight unless explicitly overridden |
| Translates real, conservative Promise Report counts into an optional dollar/time value estimate — but only for the exact multipliers a human explicitly supplies (kairos contract value) | Any invented, inferred, or benchmarked dollar/time figure — every value line shows its own count × assumption formula, and the command refuses outright rather than guess a missing currency |
| Finds candidate processes worth building a ProcessContract for, from a single human-supplied CSV export — 10 narrow heuristic checks, evidence-graded, never proven failures (kairos scout analyze) | Any live API/OAuth connector, any scheduled/continuous scanning, any per-worker scoring, or any automatic contract/automation creation — findings are read-only text a human hand-copies into contract intake/contract plan themselves |
| Records a real human's accept/reject decision on a Contract Evolution proposal as a local, per-client LearningNote, with a structural guardrail against ever promoting synthetic-only evidence (kairos learn candidates/list/show/promote/reject) | Any automatic prompt, validator, scenario, compiler, contract, or workflow change from a promoted note — promotion only ever flips the note's own local status; no cross-client learning, no hosted service, no dashboard |
| Documents assumptions, open questions, and test steps | — |
| Syncs node types from your live instance | — |
| Learns from prior builds and failures | — |
| Works through MCP, SDK, or CLI | — |
Use as MCP Server (no code required)
Connect Kairos to any MCP-compatible host — Claude Code, Claude Desktop, Cursor, or any agent that supports the Model Context Protocol. Your host LLM generates the workflow using Kairos's specialized context, then Kairos validates and deploys it. No Anthropic API key needed — no double-LLM calls, no wasted tokens. Kairos auto-syncs your n8n instance's node types so the catalog always matches your exact setup.
Setup
npm install -g @kairos-sdk/coreClaude Code
Add to your Claude Code MCP config (~/.claude/claude_code_config.json):
{
"mcpServers": {
"kairos": {
"command": "kairos-mcp",
"env": {
"N8N_BASE_URL": "https://your-instance.app.n8n.cloud",
"N8N_API_KEY": "your-n8n-key"
}
}
}
}N8N_BASE_URL and N8N_API_KEY are required for all MCP operations. Kairos syncs your instance's node types to generate accurate workflows that match your exact n8n setup.
Claude Desktop
Add to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"kairos": {
"command": "kairos-mcp",
"env": {
"N8N_BASE_URL": "https://your-instance.app.n8n.cloud",
"N8N_API_KEY": "your-n8n-key"
}
}
}
}Then just ask your agent: "Build me a workflow that monitors a webhook and sends a Slack notification" — it will call kairos_prompt for context, generate the workflow itself, validate it with kairos_validate, and deploy with kairos_deploy.
How the MCP flow works
The MCP server does not call an LLM internally. Instead, it gives your host LLM the specialized knowledge and guardrails to generate n8n workflows itself:
- Host LLM calls
kairos_prompt— gets the n8n system prompt, node catalog, library matches, and failure patterns - Host LLM generates the workflow JSON using that context (no separate API call)
- Host LLM calls
kairos_validate— checks the JSON against 129 structural rules - If invalid, the host LLM fixes the issues and validates again
- Host LLM calls
kairos_deploy— sends the validated workflow to n8n
This means Kairos works with any LLM — Claude, GPT, Gemini, Llama, or anything else connected as an MCP host. Zero Anthropic API key needed.
Available MCP Tools
Generation tools
| Tool | Description |
|------|-------------|
| kairos_prompt | Returns the specialized system prompt, node catalog, library matches, and failure patterns for a given description |
| kairos_validate | Validates workflow JSON against 129 structural rules — returns errors and warnings |
| kairos_search | Searches the local workflow library for similar past builds |
| kairos_sync | Manually refresh the node catalog from your n8n instance (auto-runs on first kairos_prompt call) |
| kairos_patterns | Returns pattern analysis — top failure rules, confidence scores, and improvement suggestions derived from build telemetry |
Deployment tools
| Tool | Description |
|------|-------------|
| kairos_deploy | Deploys validated workflow JSON to n8n (re-validates before deploying) |
| kairos_replace | Replaces an existing n8n workflow with a new version — validates before updating, preserves workflow ID |
| kairos_list | List all deployed workflows |
| kairos_get | Get full workflow JSON by ID |
| kairos_activate | Activate a workflow |
| kairos_deactivate | Deactivate a workflow |
| kairos_delete | Delete a workflow |
| kairos_executions | List recent executions with status |
Library tools
| Tool | Description |
|------|-------------|
| kairos_library | Browse or search the local Kairos workflow library — returns metadata, node counts, deploy history, and n8n workflow IDs |
| kairos_outcome | Record build outcome against a library entry — feeds the pattern learning system with attempt counts, failed rules, and generation mode |
| kairos_record_trace | Record the most recent n8n execution of a deployed workflow into the library — grounds retrieval in real runtime behavior (stores node names, error types, and item counts only — never data values) |
MCP Permissions & Security
Kairos's MCP server blocks destructive operations by default — deploying, activating, and deleting workflows all require an explicit opt-in, independent of whether an n8n connection is configured. This is deliberate: an MCP host that can talk to your n8n instance shouldn't be able to touch production automation without you saying so.
Role modes — KAIROS_MCP_MODE (default: deploy):
| Mode | Behavior |
|---|---|
| readonly | Blocks kairos_deploy, kairos_replace, kairos_activate, kairos_delete, kairos_remember, and kairos_recall unconditionally — overrides the ALLOW_* flags below entirely |
| validate | Same as readonly — read/validate/search tools work, all write operations (and memory, read or write) are blocked |
| deploy (default) | Write operations are possible, but each one still needs its own explicit ALLOW_* flag below — deploy mode does not auto-enable anything by itself |
Per-action opt-in flags (only relevant in deploy mode — readonly/validate block these regardless):
| Variable | Enables |
|---|---|
| KAIROS_MCP_ALLOW_DEPLOY | kairos_deploy, kairos_replace — set to exactly true |
| KAIROS_MCP_ALLOW_ACTIVATE | kairos_activate, and the activate: true option on kairos_deploy |
| KAIROS_MCP_ALLOW_DELETE | kairos_delete |
| KAIROS_MCP_ALLOW_MEMORY | kairos_remember and kairos_recall — gated together, deliberately more conservative than the other read-only tools (kairos_search/kairos_list), since client memory can hold business-sensitive context, not just generic workflow templates |
Optional shared-secret auth: set KAIROS_MCP_SECRET and every write-capable tool call must include a matching kairos_secret argument, or it's rejected as unauthorized — useful if the server is reachable by more than just your own trusted agent.
HTTP transport: kairos-mcp --http (default port 3000, override with KAIROS_MCP_PORT) runs over StreamableHTTPServerTransport instead of stdio. The transport itself does not add authentication beyond KAIROS_MCP_SECRET above — if you expose it over a network rather than binding to localhost, put it behind your own auth/reverse proxy, the same way you would any other unauthenticated local service.
Use as SDK
Installation
npm install @kairos-sdk/core @anthropic-ai/sdk@anthropic-ai/sdk is a peer dependency — install it alongside Kairos.
Requirements
- Node.js 18+
- SDK: An Anthropic API key — the SDK calls Claude internally
- MCP: No Anthropic key needed — your host LLM does the generation
- An n8n instance with API access enabled (Cloud or self-hosted) — required for both SDK and MCP (Kairos syncs your instance's node types for accurate generation)
Standalone Validator (kairos-lint)
Don't need the SDK, MCP, or generation at all? The 129-rule structural validator works standalone against any n8n workflow JSON — hand-written, exported from n8n, or from any other tool — not just Kairos-generated ones. Fully offline: no Anthropic key, no n8n instance, no credentials of any kind.
npx @kairos-sdk/core kairos-lint my-workflow.json
npx @kairos-sdk/core kairos-lint my-workflow.json --json # machine-readable outputmy-workflow.json — Validation
──────────────────────────────────────────────────
Issues: 1 error(s), 2 warning(s)
✗ [error] [Rule 66] Node "Fetch Data" HTTP Request URL "api.example.com/data" is missing a protocol prefix — n8n requires a full URL starting with https:// or http://.
⚠ [warn] [Rule 78] Workflow has no errorWorkflow configured in settings...
⚠ [warn] [Rule 126] Node "Fetch Data" has ID "node-1" which is not a valid UUID v4...Exits 1 if any error-severity issue is found (usable directly in CI). See Validator Rules for the full rule list this checks against.
Environment Variables Reference
Every KAIROS_* variable Kairos reads, in one place (the CLI's own --help output has a shorter version of the same list; MCP-specific vars are covered in more depth under MCP Permissions & Security):
| Variable | Applies to | Effect |
|---|---|---|
| KAIROS_MODEL | SDK, CLI | Claude model override (default: claude-sonnet-4-6) |
| KAIROS_MAX_TOKENS | SDK, CLI | Max output tokens for the generation call (default: 16000) — raise this if you see "Claude response was truncated (max_tokens reached)" on large, many-integration workflows |
| KAIROS_TIMEOUT_MS | SDK, CLI | Generation call timeout in ms (default: 300000) — raise this if you see "Request was aborted" on large workflows (a bigger KAIROS_MAX_TOKENS needs more time to stream) |
| KAIROS_TELEMETRY | SDK, CLI, MCP | true for the default directory, or a path — enables JSONL telemetry logging |
| KAIROS_LIBRARY_DIR | CLI | Override the workflow library directory (default: ~/.kairos/library) |
| KAIROS_LIBRARY_SIZE | SDK, CLI, MCP | Max library entries before oldest/least-used are evicted (default: 1500) |
| KAIROS_PROMPT_PROFILE | SDK, CLI, MCP | minimal | standard | rich (default: standard) — how much library context and pattern guidance gets injected into the generation prompt |
| KAIROS_REGISTRY_STRICT | SDK, CLI, MCP | Set to true to warn on any node typeVersion above the registry's known-safe range. Default (unset/false) is lenient — a version higher than the known max is treated as a newer n8n release, not an error |
| KAIROS_WEIGHT_TFIDF / KAIROS_WEIGHT_JACCARD / KAIROS_WEIGHT_OUTCOME / KAIROS_WEIGHT_DEPLOY / KAIROS_WEIGHT_COSINE | SDK, CLI, MCP | Retrieval scoring weights — see How retrieval works |
| KAIROS_MCP_MODE | MCP | readonly | validate | deploy (default) — see MCP Permissions & Security |
| KAIROS_MCP_ALLOW_DEPLOY / KAIROS_MCP_ALLOW_ACTIVATE / KAIROS_MCP_ALLOW_DELETE / KAIROS_MCP_ALLOW_MEMORY | MCP | Per-action opt-in for write operations — see MCP Permissions & Security |
| KAIROS_MCP_SECRET | MCP | Optional shared secret required on write-capable tool calls |
| KAIROS_MCP_PORT | MCP | HTTP transport port when running kairos-mcp --http (default: 3000) |
| KAIROS_CLIENT_ID | SDK, CLI | Enables the per-client memory layer — must match ^[a-z0-9][a-z0-9-]{0,63}$. Omit to leave memory fully inert (default) |
| KAIROS_MEMORY_CAP | SDK, CLI, MCP | Max history/incident memory nodes per client before oldest are evicted (default: 500). preference/reference are never auto-evicted |
| KAIROS_MEMORY_EMBEDDINGS | SDK, CLI, MCP | Set to off to force BM25-only memory retrieval even when the optional fastembed peer dependency is installed |
| KAIROS_PATTERN_REVIEW | CLI, MCP | Set to true to require human approval (kairos patterns approve\|reject) before a pattern crossing the confirm threshold can influence generation — see Audit Trail & Human-Gated Pattern Promotion |
Quick Start
import { Kairos } from '@kairos-sdk/core'
const kairos = new Kairos({
anthropicApiKey: 'sk-ant-...',
n8nBaseUrl: 'https://your-instance.app.n8n.cloud',
n8nApiKey: 'your-n8n-api-key',
})
// Dry run — generates and validates but does not deploy
const preview = await kairos.build(
'Receive a webhook, call an external API, and store the result in Google Sheets',
{ dryRun: true }
)
console.log(preview.name) // workflow name Claude chose
console.log(preview.generationAttempts) // 1–3 (correction loop)
console.log(preview.credentialsNeeded) // services that need credentials configured
// Live deploy
const deployed = await kairos.build(
'Receive a webhook, call an external API, and store the result in Google Sheets'
)
console.log(deployed.workflowId) // now live in n8nBenchmark Results
Tested against 20 workflow prompts of varying complexity (simple triggers, multi-step conditional logic, AI agents with memory). Results measure structural validation pass rate — whether the generated workflow passes all 129 validator rules, not end-to-end execution correctness.
Current results (re-run 2026-07-02, 128-rule validator)
| Metric | Baseline (no library) | Current library (292 entries) | + 14 imported fixtures | |---|---|---|---| | First-try pass rate | 100% (20/20) | 100% (20/20) | 100% (20/20) | | Avg attempts | 1.00 | 1.00 | 1.00 | | Correction loop usage | 0% | 0% | 0% | | Avg generation time | 21.0s | 20.7s | 20.6s | | Failures | 0 | 0 | 0 |
Honest read of this result: the accumulated system-prompt improvements (node catalog, connection-rule documentation, sub-patterns, intent-to-component mapping) plus the growth from 34 to 129 validator rules have together closed the gap this benchmark used to measure — even the no-library baseline now passes first-try on all 20 prompts. That's a genuinely good outcome, but it also means this 20-prompt suite has hit a ceiling and no longer discriminates library-seeding's contribution the way the original 55%→100% result did. The "+ 14 imported fixtures" column confirms the new sync-templates --from-dir bulk-import feature (see Workflow Library & Feedback Loop) doesn't regress quality — it's a null result, not a negative one — but it isn't a real test of importing hundreds or thousands of community workflows; the fixtures were 14 small, hand-authored workflows used only to exercise the import pipeline end-to-end without vendoring any third-party dataset into this repo.
Full results: benchmark-results.json (baseline), benchmark-seeded-results.json (current library), benchmark-imported-results.json (+ imported fixtures).
See BENCHMARKS.md for full methodology, the 282-run backend-viability follow-up, and the three real reliability bugs that investigation found and fixed — including one case where a result that looked like a regression turned out to be sampling noise, and the actual measurement that told the difference.
Recommendation for anyone extending this benchmark: move to a harder prompt set (the full 94-prompt suite in scripts/benchmark.ts, or a curated hard subset) to get a signal that isn't already saturated at 100%.
Note: These results confirm that generated workflows are structurally valid and deployable to n8n. They do not verify runtime execution correctness, credential configuration, or whether the workflow output matches user intent.
Backend-API viability run (2026-07-04)
A 282-run pass (94 prompts × --repeat 3, including a new backendApi tier of 9 CRUD/API-contract-shaped prompts) tested a harder question: not just "does this pass once," but "does it pass reliably." The backendApi tier passed 27/27 with zero inconsistency, and the investigation found and fixed three real reliability gaps along the way. Full writeup: BENCHMARKS.md.
Historical result (34-rule validator, superseded)
The original benchmark — 55% (11/20) baseline vs. 100% (20/20) with a 105-workflow seeded library — was recorded against an earlier 34-rule version of the validator and is kept here for historical context only. It does not reflect current behavior; see the current results above.
How Generation Works
(This section covers the generation step specifically — the "BUILD" box in the reliability loop above. See the CLI section below for chaos testing, watch, and replay — the parts of the loop that run before and after this one.)
SDK flow (calls Claude internally)
- Search — Kairos searches its local workflow library for similar past builds. Matching workflows and their failure patterns are pulled into context.
- Warn — Known failure patterns (from library matches and global telemetry rates) are injected into the system prompt so Claude avoids repeating known mistakes.
- Generate — Your description is sent to Claude with a detailed system prompt, forcing a
generate_workflowtool call that produces structured n8n workflow JSON. - Validate — The workflow is checked against 129 structural rules covering node IDs, types, versions, names, positions, connections, forbidden fields, trigger presence, AI connection direction, cycle detection, webhook pairing, required parameters, and content quality (placeholder URLs, empty code nodes, missing required fields for Slack/Gmail/IF/Set/Schedule/Webhook nodes).
- Correct — If validation fails, the specific rule violations are sent back to Claude for correction (up to 3 attempts, with tighter temperature on the final try).
- Strip — Forbidden server-assigned fields (
id,createdAt,updatedAt, etc.) are stripped before deployment. - Deploy — The validated workflow is posted to your n8n instance via REST API.
- Record — The workflow, its metadata (generation mode, attempt count, failure patterns, credentials needed), and telemetry events are saved locally. Future builds use this history to avoid past mistakes.
MCP flow (your LLM generates)
- Prompt — Your LLM calls
kairos_prompt, which searches the library and returns the specialized system prompt, node catalog, library matches, and failure patterns. - Generate — Your LLM generates the workflow JSON itself using that context. No separate API call.
- Validate — Your LLM calls
kairos_validate, which checks the JSON against the same 129 structural rules. - Correct — If validation fails, your LLM fixes the issues and calls
kairos_validateagain. - Deploy — Your LLM calls
kairos_deploy, which strips forbidden fields and posts the workflow to n8n. - Record — The deployed workflow is saved to the local library for future retrieval.
Validator Rules
The 129-rule validator is the core of what makes Kairos reliable. In baseline testing (no library), Claude needed the correction loop 45% of the time. Each rule targets a specific class of error:
Node catalog generation: Rule 129 (invalid resource/operation value for a node type) is backed by src/validation/node-catalog-generated.ts, a generated file listing every resource/operation value that actually exists for ~300 node types — extracted directly from the real n8n-nodes-base and @n8n/n8n-nodes-langchain packages (installed as devDependencies, never shipped) rather than hand-maintained. Regenerate it with npm run generate:node-catalog after bumping either package. It's an existence catalog, not a resource-to-operation pairing — a value that's valid under one resource might be incorrectly accepted under a different resource on the same node, since resolving that precisely would require evaluating n8n's conditional displayOptions logic against a specific parameter state (deferred; tracked as a follow-on).
| Rule | Severity | What it checks |
|------|----------|----------------|
| 1 | error | Workflow has a non-empty name |
| 2 | error | At least one node exists |
| 3 | error | Every node has a non-empty ID |
| 4 | error | No duplicate node IDs |
| 5 | error | Every node has a type string |
| 6 | error | Every node has a valid typeVersion |
| 7 | error | Every node has a valid [x, y] position |
| 8 | error | Every node has a non-empty name |
| 9 | error | Connections is a plain object |
| 10 | error | Every connection target exists in nodes |
| 11 | warn | Non-trigger nodes have incoming connections |
| 12 | error | No forbidden server-assigned fields |
| 13 | error | Settings is a valid object |
| 14 | error | At least one trigger node present |
| 15 | error | Node type strings match expected format |
| 16 | error | No duplicate node names |
| 17 | error | Credentials have valid id/name shape |
| 18 | error | AI connections originate from sub-nodes, not agent roots |
| 19 | warn | typeVersion is within known safe range |
| 20 | warn | No connection cycles (exempts splitInBatches loops) |
| 21 | warn | Webhook with responseMode="responseNode" has respondToWebhook |
| 22 | warn | Required parameters present for known node types |
| 23 | warn | Node type is recognized in the registry (unknown types may not exist in n8n) |
| 24 | warn | No deprecated $node["..."] accessor syntax in expressions |
| 25 | warn | No $json.items[n] array access (n8n flattens items automatically) |
| 26 | warn | Node references use .first() or .all() (bare $('Node').json throws at runtime) |
| 27 | warn | HTTP Request URLs are real endpoints (not example.com or YOUR_URL placeholders) |
| 28 | warn | Code nodes contain actual executable logic (not empty or comment-only) |
| 29 | warn | Slack message operations specify a channel (channelId with __rl object, or channel) |
| 30 | warn | Gmail send operations specify at least one recipient (to field non-empty) |
| 31 | warn | IF nodes have at least one condition in conditions.conditions |
| 32 | warn | Set nodes have at least one field assignment in assignments.assignments (typeVersion 3.x) |
| 33 | warn | Schedule triggers have at least one rule in rule.interval |
| 34 | warn | Webhook paths are relative — no spaces, no leading slash, no protocol prefix |
| 35 | warn | Email-sending node with no duplicate-prevention signal |
| 36 | warn | Code node output field names don't match downstream $json references (camelCase vs snake_case) |
| 37 | warn | New Date() called on external data without a custom date parsing helper |
| 38 | warn | Multiple parallel AI HTTP calls merge into same downstream node |
| 39 | warn | Deprecated Claude model name in use |
| 40 | warn | __rl resource locator field has wrong shape (plain string instead of {__rl, mode, value}) |
| 41 | warn | HTTP Request has body content but sendBody is not true |
| 42 | warn | SplitInBatches output 0 ("done") loops back into the batch node — outputs likely reversed |
| 43 | warn | IF/Filter node condition uses string operator instead of {type, operation} object (typeVersion 2+) |
| 44 | warn | Google Sheets append/update with columnMappingMode "defineBelow" but empty fieldsUi |
| 45 | error | AI Agent / chain node has no ai_languageModel sub-node connected |
| 46 | warn | HTTP Request has hardcoded API key / token in header values |
| 47 | warn | Switch node has output route(s) with no downstream connections |
| 48 | warn | Deprecated OpenAI model name in use |
| 49 | warn | ExecuteWorkflow node has no workflowId set |
| 50 | warn | AI Agent promptType "auto" with no chatTrigger or formTrigger upstream |
| 51 | warn | Wait node in webhook-resume mode with no resumeUrl sent downstream |
| 52 | warn | SQL injection risk — SQL query built with template literal + $json field in Code node |
| 53 | warn | Merge node mode incompatible with its incoming connection count |
| 54 | warn | HTTP Request to known protected API domain without credentials or auth headers |
| 55 | warn | Google Sheets sheetName is a placeholder literal when documentId is a real ID |
| 56 | warn | Node has continueOnFail but no immediate downstream error check on $json.error |
| 57 | warn | HTTP Request binary upload with missing or empty binaryPropertyName |
| 58 | warn | Wrong credential type key for the node type |
| 59 | warn | Webhook node has no authentication configured |
| 60 | warn | ScheduleTrigger fires every minute (cron minute=* or minutesInterval=1) |
| 61 | warn | ToolWorkflow sub-node missing description |
| 62 | warn | MemoryBufferWindow without chatTrigger and no sessionKey (shared memory) |
| 63 | error | Duplicate webhook path+httpMethod in the same workflow |
| 65 | error | SplitInBatches batchSize <= 0 |
| 66 | error | HTTP Request URL missing protocol prefix (not http:// or https://) |
| 67 | warn | Code node $('NodeName') references a node not in the workflow |
| 68 | warn | Google Calendar create event missing timezone |
| 69 | warn | Gmail send node missing subject |
| 70 | warn | Set node v1 with keepOnlySet=true drops all upstream fields |
| 71 | warn | ToolWorkflow source=database missing workflowId |
| 72 | warn | Code node calls JSON.parse() without a try/catch |
| 73 | warn | AI tool sub-nodes (toolCode, toolHttpRequest, etc.) missing description |
| 74 | warn | Multiple memoryBufferWindow nodes share the same static sessionKey |
| 75 | warn | EmailSend node missing toAddresses, subject, or message |
| 76 | warn | Telegram sendMessage missing chatId |
| 77 | warn | Code node in runOnceForAllItems mode uses $json without $input.all() |
| 78 | warn | Workflow has no errorWorkflow configured in settings |
| 79 | warn | HTTP Request URL contains "webhook-test" (test URL that expires) |
| 80 | warn | Set node v3+ has assignments but includeOtherInputFields is not enabled |
| 81 | error | ExecuteWorkflow calls the current workflow (infinite loop) |
| 82 | warn | Workflow has multiple SplitInBatches nodes (nested loop risk) |
| 83 | error | ToolWorkflow source=parameter has no inline workflow nodes |
| 84 | error | ToolWorkflow source=parameter inline workflow missing executeWorkflowTrigger entry point |
| 85 | warn | HTTP Request has both a credential and a manual Authorization header |
| 86 | error | ScheduleTrigger cronExpression has wrong number of fields |
| 87 | warn | Merge combineByPosition with an upstream Filter node (item count mismatch) |
| 88 | warn | Telegram sendMessage missing text |
| 89 | error | ChainRetrievalQa missing ai_retriever sub-node |
| 90 | error | RespondToWebhook exists but no webhook has responseMode="responseNode" |
| 91 | warn | Filter node has empty conditions (Rule 31 handles IF node; this handles Filter) |
| 92 | warn | Expression calls .toISOString() on a Luxon DateTime ($now/$today) |
| 93 | warn | Expression calls .format() (Moment.js API) instead of .toFormat() (Luxon API) |
| 94 | warn | ToolCode AI tool node has no executable code |
| 95 | error | ToolHttpRequest AI tool has no URL defined |
| 96 | warn | AI Agent/chain has multiple ai_languageModel sub-nodes (n8n only uses index 0) |
| 97 | error | VectorStore node missing ai_embedding sub-node |
| 98 | error | OutputParserStructured has no JSON schema |
| 99 | warn | ChainLlm with output parser connected but {format_instructions} missing from prompt |
| 100 | error | Postgres / MySQL executeQuery node has empty SQL query |
| 101 | warn | FormTrigger has no form fields defined |
| 102 | error | SplitOut node missing fieldToSplitOut parameter |
| 103 | warn | Code node returns array items without the required json wrapper |
| 105 | error | LM model parameter set to a non-routable alias ("latest", "default", etc.) |
| 106 | warn | Switch fallbackOutput is enabled but the fallback output port has no downstream connection |
| 107 | warn | Trigger node expression references $json (no upstream data at trigger time) |
| 108 | warn | Aggregate node in field-specific mode with no fields to aggregate |
| 109 | warn | Airtable create/update/upsert node with no field mappings |
| 110 | warn | Agent with promptType="define" but text is empty |
| 111 | warn | Ai_languageModel connection targets a non-agent/chain node |
| 112 | error | Luxon .add() or .subtract() used in expressions (Moment.js methods) |
| 113 | warn | IF node with unconnected true or false output branch |
| 114 | warn | $('NodeName') in expressions references a node that does not exist |
| 115 | warn | SplitInBatches output 1 (loop body) has no path that loops back |
| 116 | error | LM sub-node using a model name from the wrong provider |
| 117 | warn | Google Calendar create event missing start or end time |
| 118 | warn | Redis node missing key (propertyName) for key-based operations |
| 119 | error | Supabase node missing tableId |
| 120 | warn | Gmail reply operation missing messageId |
| 121 | warn | SplitOut fieldToSplitOut contains a dot (dot-path navigation attempt) |
| 122 | warn | Luxon .plus() or .minus() called with positional (n, 'unit') arguments |
| 123 | warn | HTTP Request sendQuery=true but queryParameters is empty |
| 124 | warn | Code node in runOnceForAllItems mode has no return statement |
| 125 | warn | Luxon uppercase YYYY or DD tokens in .toFormat() calls |
| 126 | warn | Node ID does not match UUID v4 format |
| 127 | warn | Code node language/param mismatch — jsCode/pythonCode populated for the wrong language |
| 128 | warn | OnError "continueErrorOutput" set but the dedicated error output port (index 1) is unwired |
| 129 | warn | Node's resource/operation value doesn't exist in the real n8n schema for its type |
| 130 | warn | AWS S3 / Slack file upload missing binaryPropertyName |
| 131 | warn | Long unbranched node chain (15+ nodes, no If/Switch/Merge/Filter) — consolidation opportunity |
Errors block deployment. Warnings are recorded and fed back into the prompt for future builds.
(All 129 rules — generated from src/validation/validator.ts via npx tsx scripts/generate-rules-table.ts; run it again and re-paste after adding or changing a rule. tests/unit/docs-drift.test.ts fails CI if this table's rule-ID set ever falls out of sync with the code.)
API Reference
new Kairos(options)
| Option | Type | Required | Description |
|---|---|---|---|
| anthropicApiKey | string | ✓ | Anthropic API key |
| n8nBaseUrl | string | ✓ | Base URL of your n8n instance |
| n8nApiKey | string | ✓ | n8n API key |
| model | string | | Claude model to use (default: claude-sonnet-4-6) |
| maxTokens | number | | Max output tokens for the generation call (default: 16000) |
| timeoutMs | number | | Timeout in ms for the generation call (default: 300000) |
| logger | ILogger | | Custom logger (default: silent) |
| telemetry | boolean \| string | | Enable JSONL telemetry logging (true for default dir, or a path) |
| library | IWorkflowLibrary | | Workflow library for learning loop (default: NullLibrary, CLI uses FileLibrary) |
| nodeRegistry | NodeRegistry | | Override the node-type registry used during validation — e.g. one synced from a live n8n instance via kairos sync-nodes. Defaults to the built-in static registry |
kairos.build(description, options?)
Generates and optionally deploys a workflow from a plain-English description.
const result = await kairos.build(description, {
dryRun: false, // set true to skip deployment
name: 'My Workflow', // override the generated name
})Returns BuildResult:
{
workflowId: string | null // null on dry run
name: string
workflow: N8nWorkflow // the full generated workflow JSON — inspect before deploying
generationAttempts: number // 1–3
activationRequired: boolean // true if workflow needs manual activation
credentialsNeeded: Array<{
service: string
credentialType: string
description: string
}>
dryRun: boolean
summary: string // plain-English "what this workflow does" — trigger, steps, credentials, warnings
finalIssues: ValidationIssue[] // the final attempt's structured validation issues (rule/severity/message) -- summary's structured source data, for anything that needs more than the rendered prose (e.g. a risk report)
smokeTest?: SmokeTestResult // set when { smokeTest: true } was passed
webhookVerification?: WebhookReachabilityResult // set for webhook-triggered workflows built with { activate: true }
}summary is built deterministically from data the build already produced (no extra Claude call) — a quick, human-readable readout of what got generated, meant for a non-technical reviewer to skim before approving a deploy:
"Webhook → Send Welcome Email via Gmail"
Trigger: "Webhook" — receives an incoming webhook.
Then (3 steps):
- "Prepare Email Data" — transforms or sets data fields
- "Send Welcome Email" — sends an email via Gmail
- "Respond Success" — sends the webhook response
Credentials needed:
- Gmail (gmailOAuth2): A Gmail OAuth2 credential used to send the welcome email.
Warnings (1):
- Node "Webhook" webhook has no authentication — anyone who knows the URL can trigger this workflow.kairos.replace(id, description) additionally fetches the currently-deployed workflow before overwriting it, and appends a structural diff to summary — matched by node name, since n8n workflows don't carry a stable cross-redeploy node ID:
What changed since the previous version:
~ "Notify" changed from n8n-nodes-base.slack to n8n-nodes-base.postgres
+ now needs a "postgres" credential
- no longer needs a "slackApi" credentialIf the previous workflow can't be fetched (e.g. it was deleted, or a transient n8n API error), replace() still proceeds — the diff is just omitted from summary rather than blocking the update.
Webhook reachability verification ({ activate: true }): n8n's active: true on a workflow does not reliably mean its webhook route was actually registered — confirmed directly against a live n8n Cloud instance, where a workflow survived a manual UI toggle, a fresh webhook path, and a deactivate→reactivate cycle, and its production webhook still 404'd "not registered" every time. Because of this, building a webhook-triggered workflow with activate: true fires one real request at the workflow's own production webhook URL right after activation, and reports the honest result:
⚠ Production webhook NOT reachable — n8n reports this workflow as active, but its production
webhook returned 404 "not registered" — the route was not actually wired up. This is a known
n8n platform gap, not a Kairos error; the active flag alone cannot be trusted for
webhook-triggered workflows.This only applies to webhook-triggered workflows (nothing to probe for schedule/manual triggers) and only runs once per build — if { smokeTest: true } is also passed, its result is reused instead of firing a second probe. { smokeTest: true } (opt-in, requires activate) runs a fuller check and, for webhook triggers, uses this same production-URL probe — this replaces a previous implementation that tested the wrong URL (/webhook-test/..., which only works if a human has just clicked "Execute workflow" in n8n's own editor) and so gave false "passed" results in real automated use.
new PackBuilder(options) + builder.plan() + builder.build()
Build a complete workflow pack from a plain-English business context.
import { Kairos, PackBuilder } from '@kairos-sdk/core'
const kairos = new Kairos({ anthropicApiKey, n8nBaseUrl, n8nApiKey })
const builder = new PackBuilder({ anthropicApiKey, kairos })
// Step 1 — plan (LLM generates workflow list, assumptions, open questions)
const plan = await builder.plan('Homecare DME business operations')
console.log(plan.workflows) // array of { name, description, purpose }
console.log(plan.openQuestions) // questions needing human answers before go-live
// Step 2 — build (deploys each workflow, aggregates credentials and test steps)
const pack = await builder.build(plan, {
dryRun: false,
activate: false,
onProgress: (wf, i, total) => console.log(`[${i+1}/${total}] ${wf.name}`),
})
console.log(pack.workflows) // deployed results with workflowId and credentialsNeeded
console.log(pack.allCredentials) // deduped credential list across all workflows
console.log(pack.sheetsColumns) // Google Sheets required per sheet
console.log(pack.testChecklist) // per-workflow test stepsEscalation instead of building blind: if the plan has any blocking assumptions, build() stops before generating anything and returns an escalation instead of spending API calls building workflows it already knows can't be activated:
const pack = await builder.build(plan)
if (pack.escalation) {
console.log(pack.escalation.questions) // the blocking assumption texts, unmodified
// pack.workflows is [] — nothing was built or spent on this call
} else {
// built normally
}Pass buildDespiteBlocking: true to restore the previous behavior (build everything, just refuse activation). The existing never-activate-when-blocking safety gate still applies regardless. kairos build-pack exits with code 2 (not 1) when it escalates, so scripts can branch on it.
Per-Client Memory
Set clientId (constructor option, KAIROS_CLIENT_ID env var, or CLI --client <id>) to give Kairos persistent, per-client memory across builds — preferences, build history, incidents, and reference facts, stored as human-readable markdown and read back into future prompts automatically. Fully inert and zero filesystem access when unset (the default) — nothing about existing behavior changes unless you opt in.
const kairos = new Kairos({ anthropicApiKey, n8nBaseUrl, n8nApiKey, clientId: 'empire-homecare' })
// Explicit write — e.g. a preference you learned in conversation
await kairos.remember({
type: 'preference',
description: 'Prefers concise Slack notifications, no emoji',
body: 'Client explicitly asked for short, plain-text alerts.',
tags: ['slack', 'tone'],
})
// Every successful build/replace also writes a `history` node automatically —
// no action needed. The next build for this client will have it available.
await kairos.build('When a webhook receives an order, notify #orders on Slack')
// The generation prompt now includes:
// [Client Context — accumulated from prior work with this client]
// - (preference) Prefers concise Slack notifications, no emoji
// Client explicitly asked for short, plain-text alerts.
// - (history) Built "Order Notifier": ...
// Explicit read
const relevant = await kairos.recall('slack notification tone', 5)- Storage:
~/.kairos/clients/<clientId>/memory/<type>/*.md— one markdown file per memory, human-readable and git-diffable, plus a derivedindex.jsonthat's always rebuildable (kairos memory rebuild-index <client-id>) rather than hand-maintained. - Types:
preference(how this client wants things done),history(what was built/changed),incident(escalations/failures),reference(external facts like sheet IDs or channel names).preference/referenceare never auto-evicted;history/incidentare capped (KAIROS_MEMORY_CAP, default 500) and evicted oldest-first. - Retrieval: pure-TypeScript BM25 over each node's description + body + tags, weighted by recency (90-day half-life, floored) and a small boost for
preferencenodes — no embeddings, no external services, no API cost, and works everywhere with no setup. Optionally hybrid: installfastembed(peer dependency, not forced) for local ONNX embeddings (BAAI/bge-small-en-v1.5, 384-dim, same model SOLIVEN used) fused with the BM25 ranking via Reciprocal Rank Fusion — catches semantic/paraphrase matches BM25 alone misses (confirmed live: a stored preference and a query sharing zero related words were still correctly matched once embeddings were enabled). Embeddings are computed once per memory node and cached in a per-clientembeddings.jsonsidecar, recomputed only when a node's content actually changes. SetKAIROS_MEMORY_EMBEDDINGS=offto force BM25-only even withfastembedinstalled. A missing/failed embedding load degrades silently to BM25-only — never an error. - Safety: every clientId is validated (
^[a-z0-9][a-z0-9-]{0,63}$) before any filesystem access — a rejected id can never traverse outside its own directory or reach another client's memory. Every write is scrubbed for credential-shaped text (API keys, bearer tokens, long hex/base64 runs) and rejected if found — memory nodes should only ever reference credential types, never values. A memory read/write failure never blocks a build; it's logged and the build proceeds without it.
CLI:
kairos memory add empire-homecare preference "Prefers concise Slack notifications" --tags slack,tone
kairos memory list empire-homecare [--type preference] [--json]
kairos memory search empire-homecare "slack tone" [--k 5]
kairos memory forget empire-homecare <memory-id>
kairos memory rebuild-index empire-homecare
kairos build "..." --client empire-homecareMCP: kairos_remember / kairos_recall tools, disabled by default — set KAIROS_MCP_ALLOW_MEMORY=true to enable.
validatePack(pack) + generateHandoff(pack)
Check a built pack for issues, and generate a client-ready handoff document:
import { validatePack, generateHandoff } from '@kairos-sdk/core'
const issues = validatePack(pack)
// [{ type: 'duplicate_name' | 'blocking_assumption' | 'unsafe_activation' | 'schedule_conflict',
// severity: 'error' | 'warning', message: string, workflows?: string[] }, ...]
const errors = issues.filter(i => i.severity === 'error')
if (errors.length === 0) {
const handoffMarkdown = generateHandoff(pack)
// Status, blocking issues, workflows, credentials needed, Google Sheets, setup/testing/activation checklists
}Workflow management
// List all workflows
const workflows = await kairos.list()
// Get a specific workflow
const workflow = await kairos.get(workflowId)
// Replace a workflow with a fresh generation from a new description
const updated = await kairos.replace(workflowId, 'new description')
// Activate / deactivate
await kairos.activate(workflowId)
await kairos.deactivate(workflowId)
// Delete (requires explicit confirmation)
await kairos.delete(workflowId, { confirm: true })Executions
// List recent executions for a workflow
const executions = await kairos.executions(workflowId, { limit: 20 })
// Get a specific execution with full details
const detail = await kairos.execution(executionId)Tags
const tags = await kairos.listTags()
const newTag = await kairos.createTag('production')
await kairos.tag(workflowId, [newTag.id])
await kairos.untag(workflowId, [newTag.id])Error Handling
All errors extend KairosError so you can catch them at different levels of specificity:
import {
KairosError,
GenerationError,
ValidationError,
ApiError,
GuardError,
DeployActivationError,
} from '@kairos-sdk/core'
try {
await kairos.build('...')
} catch (err) {
if (err instanceof ValidationError) {
// Claude failed to produce a valid workflow after 3 attempts
for (const issue of err.issues) {
console.error(`[Rule ${issue.rule}] ${issue.message}`)
}
// Attempt metadata and warned rules are also available
console.log(err.attemptMetadata) // per-attempt timing, tokens, issues
console.log(err.warnedRules) // which pattern rules were warned about
} else if (err instanceof DeployActivationError) {
// Workflow deployed successfully to n8n, but activation failed — the workflow
// exists and is recoverable. Kairos never deletes it automatically; you decide
// whether to activate it manually later or clean it up.
console.error(`Workflow ${err.workflowId} deployed but didn't activate:`, err.message)
} else if (err instanceof GenerationError) {
// Anthropic API call failed (auth, quota, timeout)
console.error(err.message, err.cause)
} else if (err instanceof ApiError) {
// n8n returned a 4xx/5xx
console.error(`n8n error ${err.statusCode}:`, err.message)
} else if (err instanceof KairosError) {
// Any other SDK error
console.error(err.message)
}
}| Error class | When it's thrown |
|---|---|
| GenerationError | Anthropic API call failed |
| ResponseParseError | Claude responded but produced no usable tool call |
| ValidationError | Workflow failed 129-rule validation after max retries (carries .attemptMetadata and .warnedRules) |
| ProviderError | Network/auth failure talking to n8n |
| ApiError | n8n returned a 4xx or 5xx (carries .statusCode) |
| GuardError | Input validation failed (empty description) or delete() called without { confirm: true } |
| DeployActivationError | Workflow was deployed successfully but activation failed (carries .workflowId — recoverable, never auto-deleted) |
Promise Engine v0 (ProcessContract → ProofLedger → ExceptionDesk)
Everything above (build/chaos/watch/repair/replay) verifies that a workflow behaves correctly. The Promise Engine is a separate, later layer that verifies something different: whether a business commitment — "every contact form submission gets acknowledged within an hour," "every order ships within 2 business days" — was actually kept, using nothing but real n8n execution evidence.
The loop: a ProcessContract (states, transitions, SLAs, owners, terminal outcomes — drafted from a plain-language description via kairos contract plan, a guided multi-turn interview via kairos contract intake, or written by hand) deterministically compiles into a PackPlan (kairos contract compile, which also structurally verifies every deployed workflow actually carries the evidence wiring the contract expects) and, once built and registered (kairos contract import + a real contract compile --build), kairos ledger poll extracts evidence from n8n execution data into a local, append-only ProofLedger. SLA/expiration compliance runs inside kairos watch --contracts, which opens ExceptionDesk items for real drift. kairos contract report reads the ledger and exception data back into a client-facing Promise Report.
Before any of that touches n8n at all, kairos contract scenarios generate deterministically derives synthetic business scenarios from the contract (happy path, missing data, duplicate submission, after-hours, and more — no LLM call), and kairos contract harness run evaluates them through the exact same evidence-checking code production uses, purely in-memory — a deterministic Node-side simulation/assertion layer, not a runtime replacing n8n. The same scenarios extend into live infrastructure once workflows exist: kairos replay run --contract/kairos chaos run --contract inject a scenario's own intake payload against a real (sandboxed) workflow and check the resulting evidence against the contract's own expected outcome, reported alongside their existing structural diff/adversarial-payload checks, never instead of them.
Once real evidence exists, kairos contract evolve treats the contract as a hypothesis, not permanent truth — it mines ProofLedger/ExceptionDesk patterns for SLA hotspots, never-reached states, and similar signals, and proposes (never applies) amendments a human can accept or reject. An accepted proposal still requires hand-authoring the new contract version; kairos contract amend/versions/diff are the only path that ever actually changes a saved contract — always previewable first, always archiving what it replaces, and refusing a breaking change while any promise instance is still in flight unless explicitly overridden. For a sales/retainer conversation, kairos contract value translates the same conservative Promise Report counts into an optional dollar/time value estimate — but only for the exact per-unit multipliers a human explicitly supplies; it computes nothing on its own.
Read this before treating a Promise Report as authoritative:
- Evidence-graded, not a guarantee. Every promise instance is classified
kept/at_risk/missed/unverifiable/in_progressfrom whatever n8n execution evidence actually exists.unverifiableis never counted askept, and the report always states plainly when evidence is incomplete. This is an evaluation against available evidence, not a certification that the business process is sound. The same discipline applies tocontract evolve's own proposals (evidence-graded confidence, sample-size-aware, never a "the AI thinks so" judgment) and toreplay run --contract/chaos run --contract's own contract-outcome checks, which explicitly say so whenever they can only verify intake evidence, not a full end-to-end business outcome. - ExceptionDesk only reacts to time-based drift. Items are opened/refreshed only inside
kairos watch --contracts, only for SLA/expiration-rule findings that are actually drifting. A terminal outcome reached quickly — a submission flagged "missing info" within minutes, well before any deadline passes — is correctly classified incontract reportbut currently produces no ExceptionDesk item. Usecontract reportfor the complete picture; don't rely on ExceptionDesk alone to catch every miss. - n8n is the current execution substrate. Evidence extraction, correlation, and polling all read from n8n's own execution data (
ledger pollis read-only — GET only, never a write). There is no other data source today. - No autonomous contract changes, anywhere.
contract evolveonly ever proposes; accept/reject only ever record a human decision;contract amendonly ever writes a new version after explicit--confirm. Nothing in this whole arc edits a saved contract, recompiles, or redeploys anything without a human explicitly asking it to. - Validated so far with real infrastructure and a synthetic end-to-end scenario, not yet with a real client's live production traffic. The full contract → compile → workflows → ledger → SLA monitor → exceptions → report loop has been checkpointed against real n8n execution data and a hand-verified synthetic contact-form scenario (5 cases, a truth table written before running any code, zero mismatches). A first real shadow validation against a live business's actual event stream is still pending.
Full CLI reference for contract/ledger/exceptions is in the CLI section below. Design rationale and every live-checkpoint finding: docs/plans/process-contract-promise-engine-plan.md (original Promise Engine v0), docs/plans/intake-scenario-harness-plan.md (intake, scenarios, harness, replay/chaos contract-outcome checks, compiler verification), and docs/plans/contract-evolution-ops-roadmap-plan.md (evolution, amendment/diff).
CLI
Deploy workflows from the command line — no code required:
# First-time setup (prompts for credentials, seeds template library, prints Claude Desktop config)
kairos init
# Generate and deploy a single workflow
kairos build "Every morning at 9am, send a Slack digest to #daily-updates"
# Generate a complete workflow pack from a business context
kairos build-pack "Homecare DME business — patient onboarding, reorder reminders, social media"
# Dry run — plan and validate without deploying
kairos build-pack "E-commerce store operations" --dry-run
# Skip confirmation prompt and build immediately
kairos build-pack "Real estate agency operations" --yes
# If the plan has blocking assumptions, build-pack stops before generating anything and
# prints the open questions (exit code 2) instead of spending API calls on workflows that
# can't be activated yet. Pass --despite-blocking to build anyway (still won't auto-activate).
kairos build-pack "Vaguely described business" --yes --despite-blocking
# Wire deployed pack workflows to real Google Sheet IDs (patches documentId ResourceLocators)
kairos pack wire my-pack --sheet-ids '{"Contacts": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"}'
kairos pack wire my-pack --sheet-ids ./sheet-ids.json --dry-run
# Check a pack for duplicate names, unresolved blocking assumptions, failed
# deploys, and schedule conflicts before activating it
kairos validate-pack my-pack
# Go/no-go launch checklist -- offline by default (saved pack JSON only, no n8n
# required); exits 0 for GO/GO WITH WARNINGS, 1 for NO-GO/BLOCKED, so it's
# scriptable as a real gate.
kairos preflight my-pack
kairos preflight my-pack --json
# --live additionally fetches each workflow's current n8n state and checks:
# credential references still literally "placeholder-id" or empty (unwired),
# Google Sheets document IDs that are empty (confidently flagged) vs. present-
# but-unverified (no placeholder convention exists for Sheet IDs, so a non-empty
# value is only ever "not obviously wrong," never confirmed -- always caveated).
kairos preflight my-pack --live
# --bundle-dir cross-checks against a previously generated --bundle output:
# whether test-payloads.json/contract.openapi.json exist for each webhook-shaped
# workflow (needs --live too, since knowing which workflows are webhook-shaped
# needs the live node graph -- without --live this just says so, it never
# guesses a count), and if bundle-manifest.json is there, surfaces its raw
# generatedAt timestamp plus any artifacts it had to skip (no invented
# staleness threshold -- you judge whether it's stale).
kairos preflight my-pack --live --bundle-dir ./deliverables
# Print the saved pack as JSON, or generate a client-ready Markdown handoff
# (status, blocking issues, credentials needed, setup/testing/activation checklists)
kairos pack export my-pack
kairos pack export my-pack --handoff
# Fetch each workflow's current live n8n definition and write one <name>.workflow.json
# per workflow into a directory (requires N8N_BASE_URL/N8N_API_KEY; skips workflows
# that were never deployed or that n8n can no longer be reached for)
kairos pack export my-pack --workflow-json ./deliverables
# Print a client-readable credentials checklist grouped by service (no n8n required --
# pure render over data already collected during generation)
kairos pack export my-pack --credentials
# Print a risk/production-readiness report -- pack-structural issues (duplicate names,
# schedule conflicts) plus per-workflow validation issues with fix guidance, and an
# overall READY / NEEDS ATTENTION / NOT READY verdict (no n8n required)
kairos pack export my-pack --risk-report
# Print a monitoring plan -- each workflow's current active/inactive status and latest
# execution (status, duration, slowest nodes), plus a static weekly checklist (requires
# N8N_BASE_URL/N8N_API_KEY -- checks live n8n state)
kairos pack export my-pack --monitoring-plan
# Write a heuristic sample test payload (url/method/sample body/query/headers) for each
# webhook-shaped workflow, inferred from $json.body/query/headers expressions in the
# workflow itself -- a best-effort guess, not a verified contract (see the note field in
# each output file). Skips workflows with no webhook trigger. Requires N8N_BASE_URL/N8N_API_KEY.
kairos pack export my-pack --test-payloads ./deliverables
# Write a minimal OpenAPI 3.0.3 document for each webhook-shaped workflow (path/method/
# request schema/query+header params), reusing the same field inference as --test-payloads
# and marked `x-kairos-generated: heuristic` throughout. Skips non-webhook workflows.
# Requires N8N_BASE_URL/N8N_API_KEY.
kairos pack export my-pack --openapi ./deliverables
# Write the FULL client deliverable set in one command: handoff.md, credentials.md,
# risk-report.md, monitoring-plan.md, one workflow.json per workflow, and
# test-payloads.json/contract.openapi.json for every webhook-shaped workflow -- plus a
# bundle-manifest.json listing exactly what was written and what was skipped (and why).
# One failing piece never aborts the rest. Requires N8N_BASE_URL/N8N_API_KEY.
kairos pack export 