diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index f5d604d1d..d62410d7f 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -5462,3 +5462,24 @@ export function EnrichSoIcon(props: SVGProps) { ) } + +export function AgentSkillsIcon(props: SVGProps) { + return ( + + + + + ) +} diff --git a/apps/docs/content/docs/en/skills/index.mdx b/apps/docs/content/docs/en/skills/index.mdx index 1af685ceb..6f5a95d3f 100644 --- a/apps/docs/content/docs/en/skills/index.mdx +++ b/apps/docs/content/docs/en/skills/index.mdx @@ -18,7 +18,9 @@ This means you can attach many skills to an agent without bloating its context w ## Creating Skills -Go to **Settings** (gear icon) and select **Skills** under the Tools section. +Go to **Settings** and select **Skills** under the Tools section. + +![Manage Skills](/static/skills/manage-skills.png) Click **Add** to create a new skill with three fields: @@ -52,11 +54,22 @@ Use when the user asks you to write, optimize, or debug SQL queries. ... ``` +**Recommended structure:** +- **When to use** — Specific triggers and scenarios +- **Instructions** — Step-by-step guidance with numbered lists +- **Examples** — Input/output samples showing expected behavior +- **Common Patterns** — Reusable approaches for frequent tasks +- **Edge Cases** — Gotchas and special considerations + +Keep skills focused and under 500 lines. If a skill grows too large, split it into multiple specialized skills. + ## Adding Skills to an Agent Open any **Agent** block and find the **Skills** dropdown below the tools section. Select the skills you want the agent to have access to. -Selected skills appear as chips that you can click to edit or remove. +![Add Skill](/static/skills/add-skill.png) + +Selected skills appear as cards that you can click to edit or remove. ### What Happens at Runtime @@ -69,12 +82,50 @@ When the workflow runs: This works across all supported LLM providers — the `load_skill` tool uses standard tool-calling, so no provider-specific configuration is needed. -## Tips +## Common Use Cases -- **Keep descriptions actionable** — Instead of "Helps with SQL", write "Write optimized SQL queries for PostgreSQL, MySQL, and SQLite, including index recommendations and query plan analysis" +Skills are most valuable when agents need specialized knowledge or multi-step workflows: + +**Domain Expertise** +- `api-integration-expert` — Best practices for calling specific APIs (authentication, rate limiting, error handling) +- `data-transformation` — ETL patterns, data cleaning, and validation rules +- `code-reviewer` — Code review guidelines specific to your team's standards + +**Workflow Templates** +- `bug-investigation` — Step-by-step debugging methodology (reproduce → isolate → test → fix) +- `feature-implementation` — Development workflow from requirements to deployment +- `document-generator` — Templates and formatting rules for technical documentation + +**Company-Specific Knowledge** +- `our-architecture` — System architecture diagrams, service dependencies, and deployment processes +- `style-guide` — Brand guidelines, writing tone, UI/UX patterns +- `customer-onboarding` — Standard procedures and common customer questions + +**When to use skills vs. agent instructions:** +- Use **skills** for knowledge that applies across multiple workflows or changes frequently +- Use **agent instructions** for task-specific context that's unique to a single agent + +## Best Practices + +**Writing Effective Descriptions** +- **Be specific and keyword-rich** — Instead of "Helps with SQL", write "Write optimized SQL queries for PostgreSQL, MySQL, and SQLite, including index recommendations and query plan analysis" +- **Include activation triggers** — Mention specific words or phrases that should prompt the skill (e.g., "Use when the user mentions PDFs, forms, or document extraction") +- **Keep it under 200 words** — Agents scan descriptions quickly; make every word count + +**Skill Scope and Organization** - **One skill per domain** — A focused `sql-expert` skill works better than a broad `database-everything` skill -- **Use markdown structure** — Headers, lists, and code blocks help the agent parse and follow instructions -- **Test iteratively** — Run your workflow and check if the agent activates the skill when expected +- **Limit to 5-10 skills per agent** — More skills = more decision overhead; start small and add as needed +- **Split large skills** — If a skill exceeds 500 lines, break it into focused sub-skills + +**Content Structure** +- **Use markdown formatting** — Headers, lists, and code blocks help agents parse and follow instructions +- **Provide examples** — Show input/output pairs so agents understand expected behavior +- **Be explicit about edge cases** — Don't assume agents will infer special handling + +**Testing and Iteration** +- **Test activation** — Run your workflow and verify the agent loads the skill when expected +- **Check for false positives** — Make sure skills aren't activating when they shouldn't +- **Refine descriptions** — If a skill isn't loading when needed, add more keywords to the description ## Learn More diff --git a/apps/docs/content/docs/en/tools/airweave.mdx b/apps/docs/content/docs/en/tools/airweave.mdx index f5ce4994f..59764a4c0 100644 --- a/apps/docs/content/docs/en/tools/airweave.mdx +++ b/apps/docs/content/docs/en/tools/airweave.mdx @@ -10,6 +10,21 @@ import { BlockInfoCard } from "@/components/ui/block-info-card" color="#6366F1" /> +{/* MANUAL-CONTENT-START:intro */} +[Airweave](https://airweave.ai/) is an AI-powered semantic search platform that helps you discover and retrieve knowledge across all your synced data sources. Built for modern teams, Airweave enables fast, relevant search results using neural, hybrid, or keyword-based strategies tailored to your needs. + +With Airweave, you can: + +- **Search smarter**: Use natural language queries to uncover information stored across your connected tools and databases +- **Unify your data**: Seamlessly access content from sources like code, docs, chat, emails, cloud files, and more +- **Customize retrieval**: Select between hybrid (semantic + keyword), neural, or keyword search strategies for optimal results +- **Boost recall**: Expand search queries with AI to find more comprehensive answers +- **Rerank results using AI**: Prioritize the most relevant answers with powerful language models +- **Get instant answers**: Generate clear, AI-powered responses synthesized from your data + +In Sim, the Airweave integration empowers your agents to search, summarize, and extract insights from all your organization’s data via a single tool. Use Airweave to drive rich, contextual knowledge retrieval within your workflows—whether answering questions, generating summaries, or supporting dynamic decision-making. +{/* MANUAL-CONTENT-END */} + ## Usage Instructions Search across your synced data sources using Airweave. Supports semantic search with hybrid, neural, or keyword retrieval strategies. Optionally generate AI-powered answers from search results. diff --git a/apps/docs/public/static/skills/add-skill.png b/apps/docs/public/static/skills/add-skill.png new file mode 100644 index 000000000..80428e88a Binary files /dev/null and b/apps/docs/public/static/skills/add-skill.png differ diff --git a/apps/docs/public/static/skills/manage-skills.png b/apps/docs/public/static/skills/manage-skills.png new file mode 100644 index 000000000..67f7ccd20 Binary files /dev/null and b/apps/docs/public/static/skills/manage-skills.png differ diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/skill-input/skill-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/skill-input/skill-input.tsx index b61a96414..713cbf183 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/skill-input/skill-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/skill-input/skill-input.tsx @@ -130,39 +130,52 @@ export function SkillInput({ onOpenChange={setOpen} /> - {selectedSkills.length > 0 && ( -
- {selectedSkills.map((stored) => { - const fullSkill = workspaceSkills.find((s) => s.id === stored.skillId) - return ( + {selectedSkills.length > 0 && + selectedSkills.map((stored) => { + const fullSkill = workspaceSkills.find((s) => s.id === stored.skillId) + return ( +
{ if (fullSkill && !disabled && !isPreview) { setEditingSkill(fullSkill) } }} > - - {resolveSkillName(stored)} - {!disabled && !isPreview && ( - - )} + +
+ + {resolveSkillName(stored)} + +
+
+ {!disabled && !isPreview && ( + + )} +
- ) - })} - - )} + + ) + })} state.blocks?.[blockId]?.data, [blockId]) ) + const { config: permissionConfig } = usePermissionConfig() return useMemo(() => { // Guard against missing config or block selection @@ -100,6 +102,9 @@ export function useEditorSubblockLayout( const visibleSubBlocks = (config.subBlocks || []).filter((block) => { if (block.hidden) return false + // Hide skill-input subblock when skills are disabled via permissions + if (block.type === 'skill-input' && permissionConfig.disableSkills) return false + // Check required feature if specified - declarative feature gating if (!isSubBlockFeatureEnabled(block)) return false @@ -149,5 +154,6 @@ export function useEditorSubblockLayout( activeWorkflowId, isSnapshotView, blockDataFromStore, + permissionConfig.disableSkills, ]) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx index 636fd559d..c0f89e2b3 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx @@ -40,6 +40,7 @@ import { useCustomTools } from '@/hooks/queries/custom-tools' import { useMcpServers, useMcpToolsQuery } from '@/hooks/queries/mcp' import { useCredentialName } from '@/hooks/queries/oauth-credentials' import { useReactivateSchedule, useScheduleInfo } from '@/hooks/queries/schedules' +import { useSkills } from '@/hooks/queries/skills' import { useDeployChildWorkflow } from '@/hooks/queries/workflows' import { useSelectorDisplayName } from '@/hooks/use-selector-display-name' import { useVariablesStore } from '@/stores/panel' @@ -618,6 +619,48 @@ const SubBlockRow = memo(function SubBlockRow({ return `${toolNames[0]}, ${toolNames[1]} +${toolNames.length - 2}` }, [subBlock?.type, rawValue, customTools, workspaceId]) + /** + * Hydrates skill references to display names. + * Resolves skill IDs to their current names from the skills query. + */ + const { data: workspaceSkills = [] } = useSkills(workspaceId || '') + + const skillsDisplayValue = useMemo(() => { + if (subBlock?.type !== 'skill-input' || !Array.isArray(rawValue) || rawValue.length === 0) { + return null + } + + interface StoredSkill { + skillId: string + name?: string + } + + const skillNames = rawValue + .map((skill: StoredSkill) => { + if (!skill || typeof skill !== 'object') return null + + // Priority 1: Resolve skill name from the skills query (fresh data) + if (skill.skillId) { + const foundSkill = workspaceSkills.find((s) => s.id === skill.skillId) + if (foundSkill?.name) return foundSkill.name + } + + // Priority 2: Fall back to stored name (for deleted skills) + if (skill.name && typeof skill.name === 'string') return skill.name + + // Priority 3: Use skillId as last resort + if (skill.skillId) return skill.skillId + + return null + }) + .filter((name): name is string => !!name) + + if (skillNames.length === 0) return null + if (skillNames.length === 1) return skillNames[0] + if (skillNames.length === 2) return `${skillNames[0]}, ${skillNames[1]}` + return `${skillNames[0]}, ${skillNames[1]} +${skillNames.length - 2}` + }, [subBlock?.type, rawValue, workspaceSkills]) + const isPasswordField = subBlock?.password === true const maskedValue = isPasswordField && value && value !== '-' ? '•••' : null @@ -627,6 +670,7 @@ const SubBlockRow = memo(function SubBlockRow({ dropdownLabel || variablesDisplayValue || toolsDisplayValue || + skillsDisplayValue || knowledgeBaseDisplayName || workflowSelectionName || mcpServerDisplayName || diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/skills/components/skill-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/skills/components/skill-modal.tsx index 36b7c9ddd..99a473fd2 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/skills/components/skill-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/skills/components/skill-modal.tsx @@ -27,6 +27,13 @@ interface SkillModalProps { const KEBAB_CASE_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/ +interface FieldErrors { + name?: string + description?: string + content?: string + general?: string +} + export function SkillModal({ open, onOpenChange, @@ -43,7 +50,7 @@ export function SkillModal({ const [name, setName] = useState('') const [description, setDescription] = useState('') const [content, setContent] = useState('') - const [formError, setFormError] = useState('') + const [errors, setErrors] = useState({}) const [saving, setSaving] = useState(false) useEffect(() => { @@ -57,7 +64,7 @@ export function SkillModal({ setDescription('') setContent('') } - setFormError('') + setErrors({}) } }, [open, initialValues]) @@ -71,24 +78,26 @@ export function SkillModal({ }, [name, description, content, initialValues]) const handleSave = async () => { + const newErrors: FieldErrors = {} + if (!name.trim()) { - setFormError('Name is required') - return - } - if (name.length > 64) { - setFormError('Name must be 64 characters or less') - return - } - if (!KEBAB_CASE_REGEX.test(name)) { - setFormError('Name must be kebab-case (e.g. my-skill)') - return + newErrors.name = 'Name is required' + } else if (name.length > 64) { + newErrors.name = 'Name must be 64 characters or less' + } else if (!KEBAB_CASE_REGEX.test(name)) { + newErrors.name = 'Name must be kebab-case (e.g. my-skill)' } + if (!description.trim()) { - setFormError('Description is required') - return + newErrors.description = 'Description is required' } + if (!content.trim()) { - setFormError('Content is required') + newErrors.content = 'Content is required' + } + + if (Object.keys(newErrors).length > 0) { + setErrors(newErrors) return } @@ -113,7 +122,7 @@ export function SkillModal({ error instanceof Error && error.message.includes('already exists') ? error.message : 'Failed to save skill. Please try again.' - setFormError(message) + setErrors({ general: message }) } finally { setSaving(false) } @@ -135,12 +144,17 @@ export function SkillModal({ value={name} onChange={(e) => { setName(e.target.value) - if (formError) setFormError('') + if (errors.name || errors.general) + setErrors((prev) => ({ ...prev, name: undefined, general: undefined })) }} /> - - Lowercase letters, numbers, and hyphens (e.g. my-skill) - + {errors.name ? ( +

{errors.name}

+ ) : ( + + Lowercase letters, numbers, and hyphens (e.g. my-skill) + + )}
@@ -153,10 +167,14 @@ export function SkillModal({ value={description} onChange={(e) => { setDescription(e.target.value) - if (formError) setFormError('') + if (errors.description || errors.general) + setErrors((prev) => ({ ...prev, description: undefined, general: undefined })) }} maxLength={1024} /> + {errors.description && ( +

{errors.description}

+ )}
@@ -169,13 +187,19 @@ export function SkillModal({ value={content} onChange={(e: ChangeEvent) => { setContent(e.target.value) - if (formError) setFormError('') + if (errors.content || errors.general) + setErrors((prev) => ({ ...prev, content: undefined, general: undefined })) }} className='min-h-[200px] resize-y font-mono text-[13px]' /> + {errors.content && ( +

{errors.content}

+ )}
- {formError && {formError}} + {errors.general && ( +

{errors.general}

+ )} diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 969f5be13..d62410d7f 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -5468,18 +5468,18 @@ export function AgentSkillsIcon(props: SVGProps) { - + ) }