Skip to main content

refine-node

Refine node definitions - determines HOW each node is implemented (SDKs, libraries, input/output structures, tools, guidelines).

Quellinformationen

Repository
timescale/crayon
Letzte Quellaktivität
20. März 2026 um 11:53
Erkannte Sprache von SKILL.md
Englisch
Sterne
10
Forks
4

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
refine-node
description
Refine node definitions - determines HOW each node is implemented (SDKs, libraries, input/output structures, tools, guidelines).
# Refine Node Refine node definitions in existing node and agent files. While create-workflow determines **WHAT** each node does, this phase determines **HOW**: - **Input/Output structures** - Exact typed schemas (field names, types) - **Implementation approach** - Which SDKs, libraries, or APIs to use - **Tools** - For agent nodes, which tools they need - **Guidelines** - Behavioral guidelines for agent nodes --- ## Usage ``` /crayon:refine-node <workflow-name> /crayon:refine-node <workflow-name> <node-name> ``` - With just workflow name: refines all unrefined nodes in the workflow - With node name: refines only that specific node --- ## Process ### Step 1: Load and Assess Read `src/crayon/workflows/<workflow-name>.ts` and parse its `description` field to find all task nodes. Then read each referenced node/agent file and check its `description` field. A node **needs refinement** if its `inputSchema` / `outputSchema` are still empty `z.object({})`. ### Step 2: Research Implementation Approaches Before drafting, gather the information needed: 1. **For nodes that interact with external systems** (Salesforce, HubSpot, Slack, etc.): - Invoke `/crayon:integrations` to determine which SDK/library/API to use - For listed integrations: read the specific file (e.g., `salesforce.md`) - For unlisted systems: read `unlisted.md` and research the best option - **CRITICAL — Connection Gate:** For each integration the node needs: 1. Call `get_connection` with the integration ID, workflow name, and node name. **If it succeeds**, the connection is already assigned — proceed with implementation using the returned config. 2. **If it fails** (no connection assigned), call `list_connections` for that integration to see all available connections. - **If connections exist:** Present them to the user and ask which one to use. Then call `assign_connection` to map the chosen connection to the workflow/node. Finally call `get_connection` again to retrieve provider-specific config (e.g., Salesforce `instance_url`). - **If no connections exist:** You MUST stop immediately. Do NOT create SDK files, client code, integration directories, or any implementation that depends on a live connection. Tell the user which connections are missing. If `list_connections` returned a `credentials_page_url`, tell the user to visit that URL to add a connection. Otherwise tell them to open the Dev UI Credentials page. Then say "continue" when ready. 2. **For agent nodes**, check AI SDK provider docs for available tools: - **OpenAI:** https://ai-sdk.dev/providers/ai-sdk-providers/openai - **Anthropic:** https://ai-sdk.dev/providers/ai-sdk-providers/anthropic - Use `WebFetch` to read these pages for provider tool options - **IMPORTANT:** When an agent uses a provider (OpenAI, Anthropic, etc.), declare it in `integrations: ["openai"]` so the framework fetches the API key at runtime via `ctx.getConnection()`. Do NOT rely on env vars like `OPENAI_API_KEY`. The agent executor automatically detects the model provider in the integrations list and calls `ctx.getConnection()` to get the key. 3. **For simple compute nodes**: determine if any libraries are needed ### Step 3: Draft All Refinements Draft the complete refined definition for **every node that needs it**, then update all files at once. For each node, determine: - **Implementation approach** — SDK, library, or "pure TypeScript" - **Zod input schema** — derived from the Input Description - **Zod output schema** — derived from the Output Description - **Tools** (agent nodes only) — selected from the three categories below - **Guidelines** (agent nodes only) — behavioral rules, preferred sources, edge case handling Use your judgment to propose reasonable schemas and tool selections based on the descriptions. The user can correct anything after. ### Side-Effect Node Safety When refining a node that performs side effects (sends emails/messages, updates database records, calls write APIs, modifies external state): 1. **Targets must be intentional** — The recipient, channel, record ID, or destination must be either an explicit input field OR a deliberate constant with clear rationale. Never randomly choose or fabricate a target. If the target isn't obvious from the node description, ask the user — don't guess. 2. **Output schema MUST include action details** — Every side-effect node must return what it did (or would have done), regardless of whether the action was actually performed: - `messageSent: z.string().describe("The message that was sent")` - `recipientEmail: z.string().describe("The email address the message was sent to")` - `fieldsUpdated: z.record(z.string(), z.unknown()).describe("The fields that were updated and their new values")` - `testMode: z.boolean().describe("Whether the node ran in test mode (action was skipped)")` 3. **Guidelines for agents with side-effect tools** — If an agent uses tools that have side effects, add explicit guidelines: - "Only send messages to the channel/recipient specified in the input" - "Never modify records other than those explicitly identified in the input" ### Test Mode Support Side-effect nodes must support **test mode** via `ctx.testMode`. When `ctx.testMode` is `true`, the node should: - Skip the actual side effect (do not send the email, do not post to Slack, do not write to the database) - Still return the full output schema with all action details filled in (what WOULD have been done) - Include `testMode: ctx.testMode` in the output **Example pattern for side-effect nodes:** ```typescript execute: async (ctx, inputs) => { const message = `Summary for ${inputs.recipientName}: ...`; if (!ctx.testMode) { // Actually send the message await slackClient.postMessage({ channel: inputs.slackChannel, text: message }); } return { messageSent: message, channelName: inputs.slackChannel, recipientName: inputs.recipientName, testMode: ctx.testMode, }; }, ``` The key insight: the output schema is the SAME in both modes. The node always returns what it did (or would have done). Test mode only skips the external call. ### Tool Categories (Agent Nodes) | Category | Description | Examples | |----------|-------------|---------| | **Built-in nodes** | Ships with crayon | `webRead` | | **Provider tools** | From AI SDK providers (see import below) | `openai.tools.webSearch()`, `openai.tools.codeInterpreter()` | | **User nodes** | Custom nodes in `src/crayon/nodes/` | `enrichCompany`, `sendSlackMessage` | Common mappings: | Need | Tool | Category | |------|------|----------| | Fetch web pages | `webRead` | builtin | | Search the web | `openai.tools.webSearch()` | provider | | Run Python code | `openai.tools.codeInterpreter()` | provider | | Domain-specific (CRM, email) | User must implement | user node | **Provider tool imports:** Provider tools require creating a provider instance from `@ai-sdk/openai` (NOT from `crayon` or `openai`): ```typescript import { createOpenAI } from "@ai-sdk/openai"; const openai = createOpenAI(); // Then use: openai.tools.webSearch(), openai.tools.codeInterpreter(), etc. ``` ### What to Update in Node Files For each node/agent file (`src/crayon/nodes/<name>.ts` or `src/crayon/agents/<name>.ts`), update: 1. **The `description` field** — add typed schemas and (for agents) tools/guidelines: **Refined node description:** ```markdown <Expanded description> **Implementation:** <SDK, library, or approach> **Input Description:** <original from create-workflow> **Output Description:** <original from create-workflow> ``` **Refined agent description:** ```markdown <Expanded description> **Implementation:** <SDK, library, or approach> **Tools needed:** - webRead (builtin) - openai.tools.webSearch() (provider) - myCustomNode (user node in src/crayon/nodes/my-custom-node.ts) **Guidelines:** <specific guidelines> **Input Description:** <original from create-workflow> **Output Description:** <original from create-workflow> ``` 2. **The `inputSchema` and `outputSchema`** — replace empty `z.object({})` with proper Zod types 3. **For agents: the `integrations` array** — add the model provider (e.g. `"openai"`, `"anthropic"`) and any external services the agent needs. This is how the framework knows to fetch API keys at runtime via `ctx.getConnection()`. 4. **For agents: the `tools` record** — add tool imports and entries based on `**Tools needed:**` 5. **For agents: the spec file** (`src/crayon/agents/<name>.md`) — update guidelines, output format sections, and optionally add `model` and `maxSteps` to the YAML frontmatter: - `model: openai/gpt-4o` — override the default model (use when a specific model is needed, e.g. cheaper model for simple tasks, stronger model for complex reasoning) - `maxSteps: 10` — max tool-call iterations (increase for agents that need many sequential tool calls, decrease for simple single-shot agents) ### Step 4: Write and Continue After writing all refinements: 1. **Save a version** — Call the `create_version` MCP tool with a message like: ``` Refine nodes: <workflow-name> <Describe what changed — like a good git commit message body.> ``` 2. Tell the user the node files have been updated 3. Invoke `/crayon:compile-workflow` to regenerate the workflow's `run()` method with proper types --- ## Worked Example A workflow has an `enrich-lead` agent with empty schemas from create-workflow: **Before** (`src/crayon/agents/enrich-lead.ts`): ```typescript export const enrichLead = Agent.create({ name: "enrich-lead", integrations: ["openai"], description: ` Searches the web to find additional professional info about a lead. **Input Description:** A lead's name, email, and any known company or title from Salesforce. **Output Description:** Enriched lead info including LinkedIn URL, company, job title, and summary. `, inputSchema: z.object({}), outputSchema: z.object({}), tools: {}, specPath: path.resolve(__dirname, "./enrich-lead.md"), }); ``` **After refinement** — schemas filled, description enriched, tools added: ```typescript import { createOpenAI } from "@ai-sdk/openai"; const openai = createOpenAI(); export const enrichLead = Agent.create({ name: "enrich-lead", integrations: ["openai"], description: ` Given a lead's name, email, and any existing Salesforce fields, searches the web to find additional information: LinkedIn profile URL, current company, job title, and other relevant professional details. **Implementation:** OpenAI GPT-4o with web search tool **Tools needed:** - openai.tools.webSearch() (provider) **Guidelines:** Search by name + email domain or company. Prioritize LinkedIn as primary source. Do not fabricate URLs. **Input Description:** A lead's name, email, and any known company or title from Salesforce. **Output Description:** Enriched lead info including LinkedIn URL, company, job title, and summary. `, inputSchema: z.object({ name: z.string().describe("Lead's full name"), email: z.string().describe("Lead's email address"), company: z.string().nullable().describe("Company from Salesforce, if known"), title: z.string().nullable().describe("Title from Salesforce, if known"), }), outputSchema: z.object({ name: z.string(), email: z.string(), linkedinUrl: z.string().nullable().describe("LinkedIn profile URL if found"), company: z.string().nullable().describe("Current company if found"), jobTitle: z.string().nullable().describe("Current job title if found"), summary: z.string().describe("Brief summary of findings"), }), tools: { webSearch: openai.tools.webSearch({}), }, specPath: path.resolve(__dirname, "./enrich-lead.md"), }); ``` What changed: 1. **Description** — expanded with Implementation, Tools needed, Guidelines 2. **inputSchema** — filled with typed fields derived from Input Description 3. **outputSchema** — filled with typed fields derived from Output Description 4. **tools** — `webSearch` added based on Tools needed --- ## Principles 1. **Draft first, ask later** — propose complete schemas based on descriptions; let the user correct rather than interrogating 2. **Preserve descriptions** — keep the original Input/Output Description fields from create-workflow 3. **Concrete types** — every field needs a type; no `any` or untyped fields 4. **Research before guessing** — check integration skills and provider docs before selecting tools/SDKs 5. **Side effects require intentional targets** — never let a side-effect node target an ambiguous or fabricated recipient/destination. The target must be a required input field or deliberate constant.
Auf GitHub ansehen