Skip to main content

compile-workflow

Update workflow implementation from its embedded description. Use this after modifying workflow or node descriptions.

Source facts

Repository
timescale/crayon
Last source activity
March 20, 2026 at 13:32
Detected SKILL.md language
English
Stars
10
Forks
4

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
compile-workflow
description
Update workflow implementation from its embedded description. Use this after modifying workflow or node descriptions.
# Compile Workflow Updates the `run()` method of a workflow in `src/crayon/workflows/*.ts` based on its embedded `description` field and the `description` fields in referenced nodes/agents. **Announce at start:** "I'm using the compile-workflow skill to update the workflow implementation from its description." --- ## Pre-Flight Checks 1. **Verify workflow files exist:** - `src/crayon/workflows/` must exist with at least one `.ts` file - If no `.ts` files found, tell user to run `/crayon:create-workflow` first 2. **If no workflow name provided:** - List all workflows in `src/crayon/workflows/` - Ask user to select which one to compile 3. **If workflow name provided:** - Verify `src/crayon/workflows/<name>.ts` exists - If not, list available workflows and ask user to choose --- ## Description Parsing ### Workflow Description Read the `description` field from the `Workflow.create()` call in `src/crayon/workflows/<name>.ts`. The description contains flow-level information: - **Summary** — first line/paragraph - **`## Tasks`** — ordered list of tasks with: - `**Node:**` references (name + type) - `**Condition:**` / `**If true:**` / `**If false:**` for decisions - `**Loop:**` for iteration - `**Return:**` for terminal tasks ### Node/Agent Descriptions For each task's `**Node:**` reference, read the `description` field from the node/agent file to get: - **What the node does** — first paragraph - `**Input Description:**` — plain language inputs - `**Output Description:**` — plain language outputs ### Task Formats **Standard task:** ```markdown ### N. Task Name **Node:** `node-name` (agent|node) ``` Node file contains: ```markdown <Description> **Input Description:** what it needs **Output Description:** what it produces ``` **Decision task** (no Node): ```markdown ### N. Decision Name **Condition:** `expression` **If true:** continue to task M **If false:** return: - field1: value - field2: value ``` **Terminal task** (ends with Return): ```markdown **Return:** - field1: value - field2: value ``` --- ## Node Resolution For each task's `**Node:**` reference, determine what it is and where it lives. ### Node Types | Type | Location | Import Pattern | |------|----------|----------------| | `(builtin)` | Built-in nodes from crayon | `import { webRead } from "runcrayon"` | | `(node)` | User-defined in `src/crayon/nodes/` | `import { nodeName } from "../nodes/<name>"` | | `(agent)` | `src/crayon/agents/<name>.ts` | `import { agentName } from "../agents/<name>"` | **Note:** Agent imports reference the executable file (`src/crayon/agents/<name>.ts`), not the spec file (`src/crayon/agents/<name>.md`). The executable contains the runtime code that loads the spec. **IMPORTANT:** Never use `.js` extensions in import paths. Use extensionless imports (e.g., `"../nodes/check-website"` not `"../nodes/check-website.js"`). Turbopack cannot resolve `.js` → `.ts` in production builds. ### Resolution Steps 1. **Parse node reference:** Extract name and type from `**Node:** \`name\` (type)` in the workflow description 2. **For builtin nodes:** - Check if it's a built-in node (`web_read`, etc.) - Import from `"runcrayon"` 3. **For user-defined nodes:** - Look for `src/crayon/nodes/<name>.ts` - Read its `description` field and `inputSchema`/`outputSchema` for type info - If missing: create it using the stub templates from `/crayon:create-workflow` 4. **For agents:** - Look for `src/crayon/agents/<name>.ts` - Read its `description` field and `inputSchema`/`outputSchema` for type info - If missing: create it using the stub templates from `/crayon:create-workflow` ### Updating Agent Tools from Description When an agent's description contains a `**Tools needed:**` section (added by `/crayon:refine-node`), update the agent's `tools` record and imports to match. Use the tool type to determine the import pattern: | Tool Type | Import Pattern | tools record entry | |-----------|----------------|--------------------| | `(builtin)` | `import { webRead } from "runcrayon"` | `web_read: webRead` | | `(provider)` | `import { createOpenAI } from "@ai-sdk/openai"; const openai = createOpenAI();` | `web_search: openai.tools.webSearch()` | | `(user node in src/crayon/nodes/<file>.ts)` | `import { name } from "../nodes/<file>"` | `enrich_company: enrichCompany` | --- ## Handling Ambiguities When task logic is unclear, make your best guess and generate the code. The user can correct after. Don't block on questions. ### Common Ambiguities and Defaults | Pattern | Problem | Default | |---------|---------|---------| | "if good fit" | Undefined criteria | Use `score >= 80` and add a comment noting the threshold is a guess | | "check if valid" | Undefined validation | Check for presence of required fields | | Untyped output | Can't generate schema | Infer from node description and context | | Missing condition | Decision has no **Condition:** | Infer from surrounding task context, add a TODO comment if truly unknowable | After generating code, tell the user what you assumed so they can correct anything. --- ## Code Generation Rewrite the `run()` method in the existing `src/crayon/workflows/<name>.ts` file. Also update imports and schemas as needed. Preserve the `description` field as-is. ### Generated run() Structure ```typescript async run(ctx, inputs: <Name>Input): Promise<<Name>Output> { // Task 1: <Task Name> // <task description as comment> const <output_var> = await ctx.run(<nodeRef>, { <inputs> }); // Task 2: <Decision or next task> if (<condition>) { // ... } return { <output fields> }; }, ``` ### Side-Effect Node Data Flow When wiring `ctx.run()` calls for side-effect nodes (nodes whose description includes `**Side Effect:**`): 1. **Every target input MUST trace to an explicit source** — Side-effect node inputs like `recipientEmail`, `slackChannel`, `recordId` must come from: - The workflow's own input fields (e.g., `inputs.recipientEmail`) - An upstream node's output field (e.g., `enrichResult.email`) - A deliberate constant with clear rationale **NEVER fabricate or randomly choose** a target value. If the data flow for a target field is unclear, add a `// TODO: wire this to an explicit source` comment and flag it to the user. 2. **Side-effect outputs should be captured** — Always capture the return value of side-effect nodes, even if it's not used by downstream nodes. This ensures the action details (what was sent, to whom, what was updated) appear in the workflow trace. ```typescript // GOOD: capture the result const slackResult = await ctx.run(sendSlackDm, { channel: inputs.slackChannel, message: summary }); // BAD: discard the result await ctx.run(sendSlackDm, { channel: inputs.slackChannel, message: summary }); ``` ### Naming Conventions - Workflow export: `camelCase` (e.g., `urlSummarizer`) - Schema names: `PascalCase` + Schema/Input/Output (e.g., `UrlSummarizerInputSchema`) - Type names: `PascalCase` + Input/Output (e.g., `UrlSummarizerInput`) --- ## Worked Example A workflow enriches Gmail leads and sends results to Slack. Here's how the compiler turns descriptions into the `run()` method. ### Input: Workflow description ```markdown Enrich the 10 most recent Gmail leads from Salesforce with web research and DM results on Slack. ## Tasks ### 1. Fetch Gmail Leads **Node:** `fetch-gmail-leads` (node) ### 2. Enrich Lead **Node:** `enrich-lead` (agent) **Loop:** for each lead in leads ### 3. Send Slack DM **Node:** `send-slack-dm` (node) ``` ### Input: Node schemas (from refined node files) - **fetch-gmail-leads** — `inputSchema: z.object({})`, `outputSchema: z.object({ leads: z.array(LeadSchema) })` - **enrich-lead** — `inputSchema: z.object({ name: z.string(), email: z.string(), company: z.string().nullable(), title: z.string().nullable() })`, `outputSchema: z.object({ name: z.string(), email: z.string(), linkedinUrl: z.string().nullable(), ... })` - **send-slack-dm** — `inputSchema: z.object({ enrichedLeads: z.array(EnrichedLeadSchema) })`, `outputSchema: z.object({ success: z.boolean(), channel: z.string() })` ### Output: Generated run() method ```typescript async run(ctx, inputs: LeadEnrichmentInput): Promise<LeadEnrichmentOutput> { // Task 1: Fetch the 10 most recent Gmail leads from Salesforce const leadsResult = await ctx.run(fetchGmailLeads, {}); // Task 2: Enrich each lead with web research const enrichedLeads = []; for (const lead of leadsResult.leads) { const enriched = await ctx.run(enrichLead, { name: lead.name ?? "", email: lead.email ?? "", company: lead.company, title: lead.title, }); enrichedLeads.push(enriched); } // Task 3: Send enriched lead summary as Slack DM const slackResult = await ctx.run(sendSlackDm, { enrichedLeads }); return { success: slackResult.success, channel: slackResult.channel }; }, ``` Key things the compiler did: 1. **Data flow** — wired `leadsResult.leads` fields into `enrichLead`'s input schema, and `enrichedLeads` array into `sendSlackDm`'s input 2. **Loop** — translated `**Loop:** for each lead in leads` into a `for...of` loop over `leadsResult.leads` 3. **Schema alignment** — matched field names from output schemas to input schemas (e.g., `lead.name` → `name`, `lead.company` → `company`) ### After Compilation 1. **Save a version** — Call the `create_version` MCP tool with a message like: ``` Finalize workflow: <workflow-name> <Describe what changed — like a good git commit message body.> ``` 2. Tell the user: - "Updated `src/crayon/workflows/<name>.ts`" - If any missing nodes/agents were created: list the new files - If agent tools were updated from descriptions: list which agents were updated - "To test it, just say **run it** and I'll trigger it for you." — do NOT suggest using the CLI or opening the Dev UI manually --- ## Compiler Principles 1. **Draft first, ask later** — make your best guess and let the user correct, rather than blocking on questions 2. **No invention** — only emit code that maps to descriptions; guesses should be flagged with comments 3. **Deterministic** — same descriptions → same output 4. **Readable output** — generated code should be understandable 5. **Update descriptions** — when clarifying, update the description field so it stays canonical 6. **Run it yourself** — when the user wants to test a workflow or node, use the `runWorkflow` / `runNode` MCP tools or run CLI commands yourself. 7. **Side-effect targets are never fabricated** — they must come from an explicit source or be a deliberate constant.
View on GitHub