Skip to content

Commit 474b1af

Browse files
authored
improvement(ui): improved skills UI, validation, and permissions (#3156)
* improvement(ui): improved skills UI, validation, and permissions * stronger typing for Skill interface * added missing docs description * ack comment
1 parent 1e21ec1 commit 474b1af

File tree

10 files changed

+235
-61
lines changed

10 files changed

+235
-61
lines changed

apps/docs/components/icons.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5462,3 +5462,24 @@ export function EnrichSoIcon(props: SVGProps<SVGSVGElement>) {
54625462
</svg>
54635463
)
54645464
}
5465+
5466+
export function AgentSkillsIcon(props: SVGProps<SVGSVGElement>) {
5467+
return (
5468+
<svg
5469+
{...props}
5470+
xmlns='http://www.w3.org/2000/svg'
5471+
width='16'
5472+
height='16'
5473+
viewBox='0 0 16 16'
5474+
fill='none'
5475+
>
5476+
<path
5477+
d='M8 1L14.0622 4.5V11.5L8 15L1.93782 11.5V4.5L8 1Z'
5478+
stroke='currentColor'
5479+
strokeWidth='1.5'
5480+
fill='none'
5481+
/>
5482+
<path d='M8 4.5L11 6.25V9.75L8 11.5L5 9.75V6.25L8 4.5Z' fill='currentColor' />
5483+
</svg>
5484+
)
5485+
}

apps/docs/content/docs/en/skills/index.mdx

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ This means you can attach many skills to an agent without bloating its context w
1818

1919
## Creating Skills
2020

21-
Go to **Settings** (gear icon) and select **Skills** under the Tools section.
21+
Go to **Settings** and select **Skills** under the Tools section.
22+
23+
![Manage Skills](/static/skills/manage-skills.png)
2224

2325
Click **Add** to create a new skill with three fields:
2426

@@ -52,11 +54,22 @@ Use when the user asks you to write, optimize, or debug SQL queries.
5254
...
5355
```
5456

57+
**Recommended structure:**
58+
- **When to use** — Specific triggers and scenarios
59+
- **Instructions** — Step-by-step guidance with numbered lists
60+
- **Examples** — Input/output samples showing expected behavior
61+
- **Common Patterns** — Reusable approaches for frequent tasks
62+
- **Edge Cases** — Gotchas and special considerations
63+
64+
Keep skills focused and under 500 lines. If a skill grows too large, split it into multiple specialized skills.
65+
5566
## Adding Skills to an Agent
5667

5768
Open any **Agent** block and find the **Skills** dropdown below the tools section. Select the skills you want the agent to have access to.
5869

59-
Selected skills appear as chips that you can click to edit or remove.
70+
![Add Skill](/static/skills/add-skill.png)
71+
72+
Selected skills appear as cards that you can click to edit or remove.
6073

6174
### What Happens at Runtime
6275

@@ -69,12 +82,50 @@ When the workflow runs:
6982

7083
This works across all supported LLM providers — the `load_skill` tool uses standard tool-calling, so no provider-specific configuration is needed.
7184

72-
## Tips
85+
## Common Use Cases
86+
87+
Skills are most valuable when agents need specialized knowledge or multi-step workflows:
88+
89+
**Domain Expertise**
90+
- `api-integration-expert` — Best practices for calling specific APIs (authentication, rate limiting, error handling)
91+
- `data-transformation` — ETL patterns, data cleaning, and validation rules
92+
- `code-reviewer` — Code review guidelines specific to your team's standards
93+
94+
**Workflow Templates**
95+
- `bug-investigation` — Step-by-step debugging methodology (reproduce → isolate → test → fix)
96+
- `feature-implementation` — Development workflow from requirements to deployment
97+
- `document-generator` — Templates and formatting rules for technical documentation
98+
99+
**Company-Specific Knowledge**
100+
- `our-architecture` — System architecture diagrams, service dependencies, and deployment processes
101+
- `style-guide` — Brand guidelines, writing tone, UI/UX patterns
102+
- `customer-onboarding` — Standard procedures and common customer questions
103+
104+
**When to use skills vs. agent instructions:**
105+
- Use **skills** for knowledge that applies across multiple workflows or changes frequently
106+
- Use **agent instructions** for task-specific context that's unique to a single agent
107+
108+
## Best Practices
109+
110+
**Writing Effective Descriptions**
111+
- **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"
112+
- **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")
113+
- **Keep it under 200 words** — Agents scan descriptions quickly; make every word count
73114

74-
- **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"
115+
**Skill Scope and Organization**
75116
- **One skill per domain** — A focused `sql-expert` skill works better than a broad `database-everything` skill
76-
- **Use markdown structure** — Headers, lists, and code blocks help the agent parse and follow instructions
77-
- **Test iteratively** — Run your workflow and check if the agent activates the skill when expected
117+
- **Limit to 5-10 skills per agent** — More skills = more decision overhead; start small and add as needed
118+
- **Split large skills** — If a skill exceeds 500 lines, break it into focused sub-skills
119+
120+
**Content Structure**
121+
- **Use markdown formatting** — Headers, lists, and code blocks help agents parse and follow instructions
122+
- **Provide examples** — Show input/output pairs so agents understand expected behavior
123+
- **Be explicit about edge cases** — Don't assume agents will infer special handling
124+
125+
**Testing and Iteration**
126+
- **Test activation** — Run your workflow and verify the agent loads the skill when expected
127+
- **Check for false positives** — Make sure skills aren't activating when they shouldn't
128+
- **Refine descriptions** — If a skill isn't loading when needed, add more keywords to the description
78129

79130
## Learn More
80131

apps/docs/content/docs/en/tools/airweave.mdx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,21 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
1010
color="#6366F1"
1111
/>
1212

13+
{/* MANUAL-CONTENT-START:intro */}
14+
[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.
15+
16+
With Airweave, you can:
17+
18+
- **Search smarter**: Use natural language queries to uncover information stored across your connected tools and databases
19+
- **Unify your data**: Seamlessly access content from sources like code, docs, chat, emails, cloud files, and more
20+
- **Customize retrieval**: Select between hybrid (semantic + keyword), neural, or keyword search strategies for optimal results
21+
- **Boost recall**: Expand search queries with AI to find more comprehensive answers
22+
- **Rerank results using AI**: Prioritize the most relevant answers with powerful language models
23+
- **Get instant answers**: Generate clear, AI-powered responses synthesized from your data
24+
25+
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.
26+
{/* MANUAL-CONTENT-END */}
27+
1328
## Usage Instructions
1429

1530
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.
27.9 KB
Loading
56.2 KB
Loading

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/skill-input/skill-input.tsx

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -130,39 +130,52 @@ export function SkillInput({
130130
onOpenChange={setOpen}
131131
/>
132132

133-
{selectedSkills.length > 0 && (
134-
<div className='flex flex-wrap gap-[4px]'>
135-
{selectedSkills.map((stored) => {
136-
const fullSkill = workspaceSkills.find((s) => s.id === stored.skillId)
137-
return (
133+
{selectedSkills.length > 0 &&
134+
selectedSkills.map((stored) => {
135+
const fullSkill = workspaceSkills.find((s) => s.id === stored.skillId)
136+
return (
137+
<div
138+
key={stored.skillId}
139+
className='group relative flex flex-col overflow-hidden rounded-[4px] border border-[var(--border-1)] transition-all duration-200 ease-in-out'
140+
>
138141
<div
139-
key={stored.skillId}
140-
className='flex cursor-pointer items-center gap-[4px] rounded-[4px] border border-[var(--border-1)] bg-[var(--surface-5)] px-[6px] py-[2px] font-medium text-[12px] text-[var(--text-secondary)] hover:bg-[var(--surface-6)]'
142+
className='flex cursor-pointer items-center justify-between gap-[8px] rounded-t-[4px] bg-[var(--surface-4)] px-[8px] py-[6.5px]'
141143
onClick={() => {
142144
if (fullSkill && !disabled && !isPreview) {
143145
setEditingSkill(fullSkill)
144146
}
145147
}}
146148
>
147-
<AgentSkillsIcon className='h-[10px] w-[10px] text-[var(--text-tertiary)]' />
148-
<span className='max-w-[140px] truncate'>{resolveSkillName(stored)}</span>
149-
{!disabled && !isPreview && (
150-
<button
151-
type='button'
152-
onClick={(e) => {
153-
e.stopPropagation()
154-
handleRemove(stored.skillId)
155-
}}
156-
className='ml-[2px] rounded-[2px] p-[1px] text-[var(--text-tertiary)] hover:bg-[var(--surface-7)] hover:text-[var(--text-secondary)]'
149+
<div className='flex min-w-0 flex-1 items-center gap-[8px]'>
150+
<div
151+
className='flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[4px]'
152+
style={{ backgroundColor: '#e0e0e0' }}
157153
>
158-
<XIcon className='h-[10px] w-[10px]' />
159-
</button>
160-
)}
154+
<AgentSkillsIcon className='h-[10px] w-[10px] text-[#333]' />
155+
</div>
156+
<span className='truncate font-medium text-[13px] text-[var(--text-primary)]'>
157+
{resolveSkillName(stored)}
158+
</span>
159+
</div>
160+
<div className='flex flex-shrink-0 items-center gap-[8px]'>
161+
{!disabled && !isPreview && (
162+
<button
163+
type='button'
164+
onClick={(e) => {
165+
e.stopPropagation()
166+
handleRemove(stored.skillId)
167+
}}
168+
className='flex items-center justify-center text-[var(--text-tertiary)] transition-colors hover:text-[var(--text-primary)]'
169+
aria-label='Remove skill'
170+
>
171+
<XIcon className='h-[13px] w-[13px]' />
172+
</button>
173+
)}
174+
</div>
161175
</div>
162-
)
163-
})}
164-
</div>
165-
)}
176+
</div>
177+
)
178+
})}
166179
</div>
167180

168181
<SkillModal

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-editor-subblock-layout.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
isSubBlockVisibleForMode,
77
} from '@/lib/workflows/subblocks/visibility'
88
import type { BlockConfig, SubBlockConfig, SubBlockType } from '@/blocks/types'
9+
import { usePermissionConfig } from '@/hooks/use-permission-config'
910
import { useWorkflowDiffStore } from '@/stores/workflow-diff'
1011
import { mergeSubblockState } from '@/stores/workflows/utils'
1112
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
@@ -35,6 +36,7 @@ export function useEditorSubblockLayout(
3536
const blockDataFromStore = useWorkflowStore(
3637
useCallback((state) => state.blocks?.[blockId]?.data, [blockId])
3738
)
39+
const { config: permissionConfig } = usePermissionConfig()
3840

3941
return useMemo(() => {
4042
// Guard against missing config or block selection
@@ -100,6 +102,9 @@ export function useEditorSubblockLayout(
100102
const visibleSubBlocks = (config.subBlocks || []).filter((block) => {
101103
if (block.hidden) return false
102104

105+
// Hide skill-input subblock when skills are disabled via permissions
106+
if (block.type === 'skill-input' && permissionConfig.disableSkills) return false
107+
103108
// Check required feature if specified - declarative feature gating
104109
if (!isSubBlockFeatureEnabled(block)) return false
105110

@@ -149,5 +154,6 @@ export function useEditorSubblockLayout(
149154
activeWorkflowId,
150155
isSnapshotView,
151156
blockDataFromStore,
157+
permissionConfig.disableSkills,
152158
])
153159
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { useCustomTools } from '@/hooks/queries/custom-tools'
4040
import { useMcpServers, useMcpToolsQuery } from '@/hooks/queries/mcp'
4141
import { useCredentialName } from '@/hooks/queries/oauth-credentials'
4242
import { useReactivateSchedule, useScheduleInfo } from '@/hooks/queries/schedules'
43+
import { useSkills } from '@/hooks/queries/skills'
4344
import { useDeployChildWorkflow } from '@/hooks/queries/workflows'
4445
import { useSelectorDisplayName } from '@/hooks/use-selector-display-name'
4546
import { useVariablesStore } from '@/stores/panel'
@@ -618,6 +619,48 @@ const SubBlockRow = memo(function SubBlockRow({
618619
return `${toolNames[0]}, ${toolNames[1]} +${toolNames.length - 2}`
619620
}, [subBlock?.type, rawValue, customTools, workspaceId])
620621

622+
/**
623+
* Hydrates skill references to display names.
624+
* Resolves skill IDs to their current names from the skills query.
625+
*/
626+
const { data: workspaceSkills = [] } = useSkills(workspaceId || '')
627+
628+
const skillsDisplayValue = useMemo(() => {
629+
if (subBlock?.type !== 'skill-input' || !Array.isArray(rawValue) || rawValue.length === 0) {
630+
return null
631+
}
632+
633+
interface StoredSkill {
634+
skillId: string
635+
name?: string
636+
}
637+
638+
const skillNames = rawValue
639+
.map((skill: StoredSkill) => {
640+
if (!skill || typeof skill !== 'object') return null
641+
642+
// Priority 1: Resolve skill name from the skills query (fresh data)
643+
if (skill.skillId) {
644+
const foundSkill = workspaceSkills.find((s) => s.id === skill.skillId)
645+
if (foundSkill?.name) return foundSkill.name
646+
}
647+
648+
// Priority 2: Fall back to stored name (for deleted skills)
649+
if (skill.name && typeof skill.name === 'string') return skill.name
650+
651+
// Priority 3: Use skillId as last resort
652+
if (skill.skillId) return skill.skillId
653+
654+
return null
655+
})
656+
.filter((name): name is string => !!name)
657+
658+
if (skillNames.length === 0) return null
659+
if (skillNames.length === 1) return skillNames[0]
660+
if (skillNames.length === 2) return `${skillNames[0]}, ${skillNames[1]}`
661+
return `${skillNames[0]}, ${skillNames[1]} +${skillNames.length - 2}`
662+
}, [subBlock?.type, rawValue, workspaceSkills])
663+
621664
const isPasswordField = subBlock?.password === true
622665
const maskedValue = isPasswordField && value && value !== '-' ? '•••' : null
623666

@@ -627,6 +670,7 @@ const SubBlockRow = memo(function SubBlockRow({
627670
dropdownLabel ||
628671
variablesDisplayValue ||
629672
toolsDisplayValue ||
673+
skillsDisplayValue ||
630674
knowledgeBaseDisplayName ||
631675
workflowSelectionName ||
632676
mcpServerDisplayName ||

0 commit comments

Comments
 (0)