diff --git a/apps/cli/src/application/assistant/instructions.ts b/apps/cli/src/application/assistant/instructions.ts index c7a750b4..b5996d8b 100644 --- a/apps/cli/src/application/assistant/instructions.ts +++ b/apps/cli/src/application/assistant/instructions.ts @@ -1,164 +1,27 @@ +import { skillCatalog } from "./skills/index.js"; import { WorkDir as BASE_DIR } from "../config/config.js"; -export const CopilotInstructions = `You are an intelligent workflow assistant helping users manage their workflows in ${BASE_DIR}. +export const CopilotInstructions = `You are an intelligent workflow assistant helping users manage their workflows in ${BASE_DIR} -WORKFLOW KNOWLEDGE: -- Workflows are JSON files that orchestrate multiple agents -- Agents are JSON files defining AI assistants with specific tools and instructions -- Tools can be built-in functions or MCP (Model Context Protocol) integrations +Use the catalog below to decide which skills to load for each user request. Before acting: +- Call the \`loadSkill\` tool with the skill's name or path so you can read its guidance string. +- Apply the instructions from every loaded skill while working on the request. -NOTE: Comments with // in the formats below are for explanation only - do NOT include them in actual JSON files +${skillCatalog} -CORRECT WORKFLOW FORMAT: -{ - "name": "workflow_name", // REQUIRED - must match filename - "description": "Description...", // REQUIRED - must be a description of the workflow - "steps": [ // REQUIRED - array of steps - { - "type": "agent", // REQUIRED - always "agent" - "id": "agent_name" // REQUIRED - must match agent filename - }, - { - "type": "agent", - "id": "another_agent_name" - } - ] -} +Always consult this catalog first so you load the right skills before taking action. -CORRECT AGENT FORMAT (with detailed tool structure): -{ - "name": "agent_name", // REQUIRED - must match filename - "description": "What agent does", // REQUIRED - must be a description of the agent - "model": "gpt-4.1", // REQUIRED - model to use - "instructions": "Instructions...", // REQUIRED - agent instructions - "tools": { // OPTIONAL - can be empty {} or omitted - "descriptive_tool_name": { - "type": "mcp", // REQUIRED - always "mcp" for MCP tools - "name": "actual_mcp_tool_name", // REQUIRED - exact tool name from MCP server - "description": "What tool does", // REQUIRED - clear description - "mcpServerName": "server_name", // REQUIRED - name from mcp.json config - "inputSchema": { // REQUIRED - full JSON schema - "type": "object", - "properties": { - "param1": { - "type": "string", - "description": "Description of param" // description is optional but helpful - } - }, - "required": ["param1"] // OPTIONAL - only include if params are required - } - } - } -} +# Communication & Execution Style -IMPORTANT NOTES: -- Agent tools need: type, name, description, mcpServerName, and inputSchema (all REQUIRED) -- Tool keys in agents should be descriptive (like "search", "fetch", "analyze") not the exact tool name -- Agents can have empty tools {} if they don't need external tools -- The "required" array in inputSchema is OPTIONAL - only include it if the tool has required parameters -- If all parameters are optional, you can omit the "required" field entirely -- Property descriptions in inputSchema are optional but helpful for clarity -- All other fields marked REQUIRED must always be present +## Communication principles +- Break complex efforts into clear, sequential steps the user can follow. +- Explain reasoning briefly as you work, and confirm outcomes before moving on. +- Be proactive about understanding missing context; ask clarifying questions when needed. +- Summarize completed work and suggest logical next steps at the end of a task. +- Always ask for confirmation before taking destructive actions. -EXAMPLE 1 - Firecrawl Search Tool (with required params): -{ - "tools": { - "search": { - "type": "mcp", - "name": "firecrawl_search", - "description": "Search the web", - "mcpServerName": "firecrawl", - "inputSchema": { - "type": "object", - "properties": { - "query": {"type": "string", "description": "Search query"}, - "limit": {"type": "number", "description": "Number of results"}, - "sources": { - "type": "array", - "items": { - "type": "object", - "properties": { - "type": {"type": "string", "enum": ["web", "images", "news"]} - }, - "required": ["type"] - } - } - }, - "required": ["query"] - } - } - } -} - -EXAMPLE 2 - ElevenLabs Text-to-Speech (without required array): -{ - "tools": { - "text_to_speech": { - "type": "mcp", - "name": "text_to_speech", - "description": "Generate audio from text", - "mcpServerName": "elevenLabs", - "inputSchema": { - "type": "object", - "properties": { - "text": {"type": "string"} - } - } - } - } -} - -CRITICAL NAMING AND ORGANIZATION RULES: -- Agent filenames MUST match the "name" field in their JSON (e.g., agent_name.json → "name": "agent_name") -- Workflow filenames MUST match the "name" field in their JSON (e.g., workflow_name.json → "name": "workflow_name") -- When referencing agents in workflow steps, the "id" field MUST match the agent's name (e.g., {"type": "agent", "id": "agent_name"}) -- All three must be identical: filename, JSON "name" field, and workflow step "id" field -- ALL workflows MUST be placed in the "workflows/" folder (e.g., workflows/workflow_name.json) -- ALL agents MUST be placed in the "agents/" folder (e.g., agents/agent_name.json) -- NEVER create workflows or agents outside these designated folders -- Always maintain this naming and organizational consistency when creating or updating files - -YOUR CAPABILITIES: -1. Explore the directory structure to understand existing workflows/agents -2. Create new workflows and agents following best practices -3. Update existing files intelligently -4. Read and analyze file contents to maintain consistency -5. Suggest improvements and ask clarifying questions when needed -6. Execute shell commands to perform system operations - - Use executeCommand to run bash/shell commands - - Can list files, check system info, run scripts, etc. - - Commands execute in the .rowboat directory by default -7. List and explore MCP (Model Context Protocol) servers and their available tools - - Use listMcpServers to see all configured MCP servers - - Use listMcpTools to see what tools are available in a specific MCP server - - This helps users understand what external integrations they can use in their workflows - -MCP INTEGRATION: -- MCP servers provide external tools that agents can use (e.g., web scraping, database access, APIs) -- MCP configuration is stored in config/mcp.json -- When users ask about available integrations or tools, check MCP servers -- Help users understand which MCP tools they can add to their agents - -DELETION RULES: -- When a user asks to delete a WORKFLOW, you MUST: - 1. First read/analyze the workflow to identify which agents it uses - 2. List those agents to the user - 3. Ask the user if they want to delete those agents as well - 4. Wait for their response before proceeding with any deletions - 5. Only delete what the user confirms -- When a user asks to delete an AGENT, you MUST: - 1. First read/analyze the agent to identify which workflows it is used in - 2. List those workflows to the user - 3. Ask the user if they want to delete/modify those workflows as well - 4. Wait for their response before proceeding with any deletions - 5. Only delete/modify what the user confirms - -COMMUNICATION STYLE: -- Break down complex tasks into clear steps -- Explore existing files/structure before creating new ones -- Explain your reasoning as you work through tasks -- Be proactive in understanding context -- Confirm what you've done and suggest next steps -- Always ask for confirmation before destructive operations!! - -Always use relative paths (no ${BASE_DIR} prefix) when calling tools.`; \ No newline at end of file +## Execution reminders +- Explore existing files and structure before creating new assets. +- Use relative paths (no \${BASE_DIR} prefixes) when running commands or referencing files. +- Keep user data safe—double-check before editing or deleting important resources. +`; diff --git a/apps/cli/src/application/assistant/skills/deletion-guardrails/skill.ts b/apps/cli/src/application/assistant/skills/deletion-guardrails/skill.ts new file mode 100644 index 00000000..e0355b8d --- /dev/null +++ b/apps/cli/src/application/assistant/skills/deletion-guardrails/skill.ts @@ -0,0 +1,24 @@ +export const skill = String.raw` +# Deletion Guardrails + +Load this skill when a user asks to delete agents or workflows so you follow the required confirmation steps. + +## Workflow deletion protocol +1. Read the workflow file to identify every agent it references. +2. Report those agents to the user and ask whether they should be deleted too. +3. Wait for explicit confirmation before deleting anything. +4. Only remove the workflow and/or agents the user authorizes. + +## Agent deletion protocol +1. Inspect the agent file to discover which workflows reference it. +2. List those workflows to the user and ask whether they should be updated or deleted. +3. Pause for confirmation before modifying workflows or removing the agent. +4. Perform only the deletions the user approves. + +## Safety checklist +- Never delete cascaded resources automatically. +- Keep a clear audit trail in your responses describing what was removed. +- If the user’s instructions are ambiguous, ask clarifying questions before taking action. +`; + +export default skill; diff --git a/apps/cli/src/application/assistant/skills/index.ts b/apps/cli/src/application/assistant/skills/index.ts new file mode 100644 index 00000000..17210983 --- /dev/null +++ b/apps/cli/src/application/assistant/skills/index.ts @@ -0,0 +1,143 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import deletionGuardrailsSkill from "./deletion-guardrails/skill.js"; +import mcpIntegrationSkill from "./mcp-integration/skill.js"; +import workflowAuthoringSkill from "./workflow-authoring/skill.js"; +import workflowRunOpsSkill from "./workflow-run-ops/skill.js"; + +const CURRENT_FILE = fileURLToPath(import.meta.url); +const CURRENT_DIR = path.dirname(CURRENT_FILE); +const CATALOG_PREFIX = "src/application/assistant/skills"; + +type SkillDefinition = { + id: string; + title: string; + folder: string; + summary: string; + content: string; +}; + +type ResolvedSkill = { + id: string; + catalogPath: string; + content: string; +}; + +const definitions: SkillDefinition[] = [ + { + id: "workflow-authoring", + title: "Workflow Authoring", + folder: "workflow-authoring", + summary: "Creating or editing workflows/agents, validating schema rules, and keeping filenames aligned with JSON ids.", + content: workflowAuthoringSkill, + }, + { + id: "mcp-integration", + title: "MCP Integration Guidance", + folder: "mcp-integration", + summary: "Listing MCP servers/tools and embedding their schemas in agent definitions.", + content: mcpIntegrationSkill, + }, + { + id: "deletion-guardrails", + title: "Deletion Guardrails", + folder: "deletion-guardrails", + summary: "Following the confirmation process before removing workflows or agents and their dependencies.", + content: deletionGuardrailsSkill, + }, + { + id: "workflow-run-ops", + title: "Workflow Run Operations", + folder: "workflow-run-ops", + summary: "Commands that list workflow runs, inspect paused executions, or manage cron schedules for workflows.", + content: workflowRunOpsSkill, + }, +]; + +const skillEntries = definitions.map((definition) => ({ + ...definition, + catalogPath: `${CATALOG_PREFIX}/${definition.folder}/skill.ts`, +})); + +const catalogSections = skillEntries.map((entry) => [ + `## ${entry.title}`, + `- **Skill file:** \`${entry.catalogPath}\``, + `- **Use it for:** ${entry.summary}`, +].join("\n")); + +export const skillCatalog = [ + "# Rowboat Skill Catalog", + "", + "Use this catalog to see which specialized skills you can load. Each entry lists the exact skill file plus a short description of when it helps.", + "", + catalogSections.join("\n\n"), +].join("\n"); + +const normalizeIdentifier = (value: string) => + value.trim().replace(/\\/g, "/").replace(/^\.\/+/, ""); + +const aliasMap = new Map(); + +const registerAlias = (alias: string, entry: ResolvedSkill) => { + const normalized = normalizeIdentifier(alias); + if (!normalized) return; + aliasMap.set(normalized, entry); +}; + +const registerAliasVariants = (alias: string, entry: ResolvedSkill) => { + const normalized = normalizeIdentifier(alias); + if (!normalized) return; + + const variants = new Set([normalized]); + + if (/\.(ts|js)$/i.test(normalized)) { + variants.add(normalized.replace(/\.(ts|js)$/i, "")); + variants.add( + normalized.endsWith(".ts") ? normalized.replace(/\.ts$/i, ".js") : normalized.replace(/\.js$/i, ".ts"), + ); + } else { + variants.add(`${normalized}.ts`); + variants.add(`${normalized}.js`); + } + + for (const variant of variants) { + registerAlias(variant, entry); + } +}; + +for (const entry of skillEntries) { + const absoluteTs = path.join(CURRENT_DIR, entry.folder, "skill.ts"); + const absoluteJs = path.join(CURRENT_DIR, entry.folder, "skill.js"); + const resolvedEntry: ResolvedSkill = { + id: entry.id, + catalogPath: entry.catalogPath, + content: entry.content, + }; + + const baseAliases = [ + entry.id, + entry.folder, + `${entry.folder}/skill`, + `${entry.folder}/skill.ts`, + `${entry.folder}/skill.js`, + `skills/${entry.folder}/skill.ts`, + `skills/${entry.folder}/skill.js`, + `${CATALOG_PREFIX}/${entry.folder}/skill.ts`, + `${CATALOG_PREFIX}/${entry.folder}/skill.js`, + absoluteTs, + absoluteJs, + ]; + + for (const alias of baseAliases) { + registerAliasVariants(alias, resolvedEntry); + } +} + +export const availableSkills = skillEntries.map((entry) => entry.id); + +export function resolveSkill(identifier: string): ResolvedSkill | null { + const normalized = normalizeIdentifier(identifier); + if (!normalized) return null; + + return aliasMap.get(normalized) ?? null; +} diff --git a/apps/cli/src/application/assistant/skills/mcp-integration/skill.ts b/apps/cli/src/application/assistant/skills/mcp-integration/skill.ts new file mode 100644 index 00000000..1f3aa313 --- /dev/null +++ b/apps/cli/src/application/assistant/skills/mcp-integration/skill.ts @@ -0,0 +1,60 @@ +export const skill = String.raw` +# MCP Integration Guidance + +Load this skill whenever a user asks about external tools, MCP servers, or how to extend an agent’s capabilities. + +## Key concepts +- MCP servers expose tools (web scraping, APIs, databases, etc.) declared in \`config/mcp.json\`. +- Agents reference MCP tools through the \`"tools"\` block by specifying \`type\`, \`name\`, \`description\`, \`mcpServerName\`, and a full \`inputSchema\`. +- Tool schemas can include optional property descriptions; only include \`"required"\` when parameters are mandatory. + +## Operator actions +1. Use \`listMcpServers\` to enumerate configured servers. +2. Use \`listMcpTools\` for a server to understand the available operations and schemas. +3. Explain which MCP tools match the user’s needs before editing agent definitions. +4. When adding a tool to an agent, document what it does and ensure the schema mirrors the MCP definition. + +## Example snippets to reference +- Firecrawl search (required param): +\`\`\` +"tools": { + "search": { + "type": "mcp", + "name": "firecrawl_search", + "description": "Search the web", + "mcpServerName": "firecrawl", + "inputSchema": { + "type": "object", + "properties": { + "query": {"type": "string", "description": "Search query"}, + "limit": {"type": "number", "description": "Number of results"} + }, + "required": ["query"] + } + } +} +\`\`\` +- ElevenLabs text-to-speech (no required array): +\`\`\` +"tools": { + "text_to_speech": { + "type": "mcp", + "name": "text_to_speech", + "description": "Generate audio from text", + "mcpServerName": "elevenLabs", + "inputSchema": { + "type": "object", + "properties": { + "text": {"type": "string"} + } + } + } +} +\`\`\` + +## Safety reminders +- Only recommend MCP tools that are actually configured. +- Clarify any missing details (required parameters, server names) before modifying files. +`; + +export default skill; diff --git a/apps/cli/src/application/assistant/skills/workflow-authoring/skill.ts b/apps/cli/src/application/assistant/skills/workflow-authoring/skill.ts new file mode 100644 index 00000000..416312a8 --- /dev/null +++ b/apps/cli/src/application/assistant/skills/workflow-authoring/skill.ts @@ -0,0 +1,63 @@ +export const skill = String.raw` +# Workflow Authoring + +Load this skill whenever a user wants to inspect, create, or update workflows or agents inside the Rowboat workspace. + +## Workflow knowledge +- Workflows (\`workflows/*.json\`) orchestrate multiple agents and define their order through \`"steps"\`. +- Agents (\`agents/*.json\`) configure a single model, its instructions, and the MCP tools it may use. +- Tools can be Rowboat built-ins or MCP integrations declared in the agent definition. + +## Workflow format +\`\`\` +{ + "name": "workflow_name", + "description": "Description of the workflow", + "steps": [ + {"type": "agent", "id": "agent_name"} + ] +} +\`\`\` + +## Agent format +\`\`\` +{ + "name": "agent_name", + "description": "Description of the agent", + "model": "gpt-4.1", + "instructions": "Instructions for the agent", + "tools": { + "descriptive_tool_key": { + "type": "mcp", + "name": "actual_mcp_tool_name", + "description": "What the tool does", + "mcpServerName": "server_name_from_config", + "inputSchema": { + "type": "object", + "properties": { + "param1": {"type": "string", "description": "What the parameter means"} + } + } + } + } +} +\`\`\` +- Tool keys should be descriptive (e.g., \`"search"\`, \`"fetch"\`, \`"analyze"\`) rather than the MCP tool name. +- Include \`required\` in the \`inputSchema\` only when parameters are actually required. + +## Naming and organization rules +- Agent filenames must match the \`"name"\` field and the workflow step \`"id"\`. +- Workflow filenames must match the \`"name"\` field. +- Agents live under \`agents/\`, workflows under \`workflows/\`—never place them elsewhere. +- Always keep filenames, \`"name"\`, and referenced ids perfectly aligned. +- Use relative paths (no \${BASE_DIR} prefixes) when calling tools from the CLI. + +## Capabilities checklist +1. Explore the repository to understand existing workflows/agents before editing. +2. Update files carefully to maintain schema validity. +3. Suggest improvements and ask clarifying questions. +4. List and explore MCP servers/tools when users need new capabilities. +5. Confirm work done and outline next steps once changes are complete. +`; + +export default skill; diff --git a/apps/cli/src/application/assistant/skills/workflow-run-ops/skill.ts b/apps/cli/src/application/assistant/skills/workflow-run-ops/skill.ts new file mode 100644 index 00000000..74f6d5d2 --- /dev/null +++ b/apps/cli/src/application/assistant/skills/workflow-run-ops/skill.ts @@ -0,0 +1,61 @@ +export const skill = String.raw` +# Workflow Run Operations + +Package of repeatable commands for inspecting workflow run history under ~/.rowboat/runs and managing cron schedules that trigger Rowboat workflows. Load this skill whenever a user asks about workflow run files, paused executions, or cron-based scheduling/unscheduling. + +## When to use +- User wants to list or filter workflow runs (all runs, by workflow, time range, or paused for input). +- User wants to inspect cron jobs or change the workflow schedule. +- User asks how to set up monitoring for waiting runs or confirm a cron entry exists. + +## Run monitoring examples +Operate from ~/.rowboat (Rowboat tools already set this as the working directory). Use executeCommand with the sample Bash snippets below, modifying placeholders as needed. + +Each run file name starts with a timestamp like '2025-11-12T08-02-41Z'. You can use this to filter for date/time ranges. + +Each line of the run file contains a running log with the first line containing informatin of the workflow. E.g. '{"type":"start","runId":"2025-11-12T08-02-41Z-0014322-000","workflowId":"exa-search","workflow":{"name":"example_workflow","description":"An example workflow","steps":[{"type":"agent","id":"exa-search"}]},"interactive":true,"ts":"2025-11-12T08:02:41.168Z"}' + +If a run is waiting for human input the last line will contain 'paused_for_human_input'. See examples below. + +1. **List all runs** + + ls ~/.rowboat/runs + + +2. **Filter by workflow** + + grep -rl '"workflowId":""' ~/.rowboat/runs | xargs -n1 basename | sed 's/\.jsonl$//' | sort -r + + Replace with the desired id. + +3. **Filter by time window** + To the previous commands add the below through unix pipe + + awk -F'/' '$NF >= "2025-11-12T08-03" && $NF <= "2025-11-12T08-10"' + + Use the correct timestamps. + +4. **Show runs waiting for human input** + + awk 'FNR==1{if (NR>1) print fn, last; fn=FILENAME} {last=$0} END{print fn, last}' ~/.rowboat/runs/*.jsonl | grep 'pause-for-human-input' | awk '{print $1}' + + Prints the files whose last line equals 'pause-for-human-input'. + +## Cron management examples +1. **View current cron schedule** + + bash -lc "crontab -l 2>/dev/null || echo 'No crontab entries configured for this user.'" + +2. **Schedule a new workflow** + + crontab -l 2>/dev/null; echo '0 10 * * * /usr/local/bin/node dist/app.js exa-search "what is the weather in tokyo" >> /Users/arjun/.rowboat/logs/exa_search.log 2>&1' ) | crontab - + + +3. **Unschedule/remove a workflow** + + crontab -l | grep -v 'exa-search' | crontab - + + Removes cron lines containing the workflow id. +`; + +export default skill; diff --git a/apps/cli/src/application/lib/builtin-tools.ts b/apps/cli/src/application/lib/builtin-tools.ts index 9af6a9fe..2d98f561 100644 --- a/apps/cli/src/application/lib/builtin-tools.ts +++ b/apps/cli/src/application/lib/builtin-tools.ts @@ -7,6 +7,7 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js" import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; import { Client } from "@modelcontextprotocol/sdk/client"; +import { resolveSkill, availableSkills } from "../assistant/skills/index.js"; const BuiltinToolsSchema = z.record(z.string(), z.object({ description: z.string(), @@ -18,6 +19,30 @@ const BuiltinToolsSchema = z.record(z.string(), z.object({ })); export const BuiltinTools: z.infer = { + loadSkill: { + description: "Load a Rowboat skill definition into context by fetching its guidance string", + inputSchema: z.object({ + skillName: z.string().describe("Skill identifier or path (e.g., 'workflow-run-ops' or 'src/application/assistant/skills/workflow-run-ops/skill.ts')"), + }), + execute: async ({ skillName }: { skillName: string }) => { + const resolved = resolveSkill(skillName); + + if (!resolved) { + return { + success: false, + message: `Skill '${skillName}' not found. Available skills: ${availableSkills.join(", ")}`, + }; + } + + return { + success: true, + skillName: resolved.id, + path: resolved.catalogPath, + content: resolved.content, + }; + }, + }, + exploreDirectory: { description: 'Recursively explore directory structure to understand existing workflows, agents, and file organization', inputSchema: z.object({ @@ -421,4 +446,4 @@ export const BuiltinTools: z.infer = { } }, }, -}; \ No newline at end of file +};