# Skill System Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Build a Skill system where Agent can autonomously call Skills based on LLM judgment. **Architecture:** Skill is an "ability plugin" for Agents - a combination of instructions + tools. LLM decides when to use which Skill. Skills are stored in DB, loaded at runtime, and injected into Agent context. **Tech Stack:** FastAPI (backend), Vue 3 + Pinia (frontend), SQLAlchemy (ORM), existing LangGraph agent system --- ## File Structure ### Backend (New Files) - `backend/app/models/skill.py` - Skill ORM model - `backend/app/schemas/skill.py` - Pydantic schemas for API - `backend/app/services/skill_service.py` - Business logic - `backend/app/routers/skill.py` - API endpoints - `backend/app/agents/skill_registry.py` - Loads skills for agents at runtime - `backend/app/agents/skill_executor.py` - Executes skill instructions with tools ### Backend (Modified Files) - `backend/app/main.py` - Register skill_router - `backend/app/routers/__init__.py` - Export skill_router ### Frontend (New Files) - `frontend/src/api/skill.ts` - Skill API client - `frontend/src/views/SkillView.vue` - Skill management UI - `frontend/src/stores/skill.ts` - Skill state (optional, if needed) ### Frontend (Modified Files) - `frontend/src/router/index.ts` - Add /skills route - `frontend/src/components/SidebarNav.vue` - Add "Skill 市场" nav item --- ## Task 1: Skill Model & Database **Files:** - Create: `backend/app/models/skill.py` - Modify: `backend/app/database.py` (auto-import) - [ ] **Step 1: Create Skill model** ```python # backend/app/models/skill.py from sqlalchemy import Column, String, Text, Boolean, JSON, ForeignKey from sqlalchemy.orm import relationship from app.models.base import BaseModel class Skill(BaseModel): __tablename__ = "skills" name = Column(String(100), nullable=False, unique=True, index=True) description = Column(Text, nullable=True) # 供 LLM 理解用途 instructions = Column(Text, nullable=False) # Agent 执行时的指令模板 agent_type = Column(String(50), nullable=False, index=True) # master/planner/executor/librarian/analyst tools = Column(JSON, default=list) # 引用的工具名称列表 required_context = Column(JSON, default=list) # 需要的前置数据 output_format = Column(Text, nullable=True) # 输出格式要求 visibility = Column(String(20), default="private") # private/team/market team_id = Column(String(36), ForeignKey("users.id"), nullable=True) is_active = Column(Boolean, default=True) owner_id = Column(String(36), ForeignKey("users.id"), nullable=False) owner = relationship("User", foreign_keys=[owner_id]) team = relationship("User", foreign_keys=[team_id]) ``` - [ ] **Step 2: Run database migration (auto-create table)** Run: `cd backend && python -c "from app.database import engine, Base; import app.models.skill; Base.metadata.create_all(engine); print('Table created')"` Expected: "Table created" - [ ] **Step 3: Commit** ```bash git add backend/app/models/skill.py git commit -m "feat: add Skill model" ``` --- ## Task 2: Skill Schema (Pydantic) **Files:** - Create: `backend/app/schemas/skill.py` - [ ] **Step 1: Create Skill schemas** ```python # backend/app/schemas/skill.py from pydantic import BaseModel from typing import Optional class SkillCreate(BaseModel): name: str description: Optional[str] = None instructions: str agent_type: str # master/planner/executor/librarian/analyst tools: list[str] = [] required_context: list[str] = [] output_format: Optional[str] = None visibility: str = "private" team_id: Optional[str] = None is_active: bool = True class SkillUpdate(BaseModel): name: Optional[str] = None description: Optional[str] = None instructions: Optional[str] = None agent_type: Optional[str] = None tools: Optional[list[str]] = None required_context: Optional[list[str]] = None output_format: Optional[str] = None visibility: Optional[str] = None team_id: Optional[str] = None is_active: Optional[bool] = None class SkillOut(BaseModel): id: str name: str description: Optional[str] instructions: str agent_type: str tools: list[str] required_context: list[str] output_format: Optional[str] visibility: str team_id: Optional[str] is_active: bool owner_id: str created_at: str updated_at: str model_config = {"from_attributes": True} ``` - [ ] **Step 2: Commit** ```bash git add backend/app/schemas/skill.py git commit -m "feat: add Skill Pydantic schemas" ``` --- ## Task 3: Skill Service **Files:** - Create: `backend/app/services/skill_service.py` - [ ] **Step 1: Create SkillService class** ```python # backend/app/services/skill_service.py from typing import Optional from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, and_, or_ from app.models.skill import Skill from app.models.user import User class SkillService: def __init__(self, db: AsyncSession): self.db = db async def create(self, user_id: str, data: dict) -> Skill: skill = Skill( owner_id=user_id, **data ) self.db.add(skill) await self.db.commit() await self.db.refresh(skill) return skill async def get_by_id(self, skill_id: str) -> Optional[Skill]: result = await self.db.execute(select(Skill).where(Skill.id == skill_id)) return result.scalar_one_or_none() async def list_for_user( self, user_id: str, agent_type: Optional[str] = None, visibility: Optional[str] = None, ) -> list[Skill]: """列出用户可访问的 Skills""" conditions = [ or_( Skill.owner_id == user_id, Skill.visibility == "market", and_(Skill.visibility == "team", Skill.team_id == user_id), ) ] if agent_type: conditions.append(Skill.agent_type == agent_type) if visibility: conditions.append(Skill.visibility == visibility) result = await self.db.execute( select(Skill) .where(*conditions) .where(Skill.is_active == True) .order_by(Skill.created_at.desc()) ) return list(result.scalars().all()) async def update(self, skill_id: str, user_id: str, data: dict) -> Optional[Skill]: skill = await self.get_by_id(skill_id) if not skill or skill.owner_id != user_id: return None for key, value in data.items(): if value is not None: setattr(skill, key, value) await self.db.commit() await self.db.refresh(skill) return skill async def delete(self, skill_id: str, user_id: str) -> bool: skill = await self.get_by_id(skill_id) if not skill or skill.owner_id != user_id: return False await self.db.delete(skill) await self.db.commit() return True async def get_by_agent_type(self, agent_type: str) -> list[Skill]: """获取指定 Agent 类型的所有可用 Skills(供 Agent 运行时加载)""" result = await self.db.execute( select(Skill) .where( and_( Skill.agent_type == agent_type, Skill.is_active == True, Skill.visibility.in_(["market", "private"]), ) ) ) return list(result.scalars().all()) ``` - [ ] **Step 2: Commit** ```bash git add backend/app/services/skill_service.py git commit -m "feat: add SkillService" ``` --- ## Task 4: Skill Router (API Endpoints) **Files:** - Create: `backend/app/routers/skill.py` - Modify: `backend/app/routers/__init__.py` - Modify: `backend/app/main.py` - [ ] **Step 1: Create skill router** ```python # backend/app/routers/skill.py from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.models.user import User from app.routers.auth import get_current_user from app.schemas.skill import SkillCreate, SkillUpdate, SkillOut from app.services.skill_service import SkillService router = APIRouter(prefix="/api/skills", tags=["Skill"]) @router.post("", response_model=SkillOut, status_code=201) async def create_skill( data: SkillCreate, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """创建新 Skill""" svc = SkillService(db) skill = await svc.create(current_user.id, data.model_dump()) return skill @router.get("", response_model=list[SkillOut]) async def list_skills( agent_type: str | None = None, visibility: str | None = None, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """列出用户可访问的 Skills""" svc = SkillService(db) skills = await svc.list_for_user(current_user.id, agent_type, visibility) return skills @router.get("/{skill_id}", response_model=SkillOut) async def get_skill( skill_id: str, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """获取 Skill 详情""" svc = SkillService(db) skill = await svc.get_by_id(skill_id) if not skill: raise HTTPException(status_code=404, detail="Skill not found") return skill @router.put("/{skill_id}", response_model=SkillOut) async def update_skill( skill_id: str, data: SkillUpdate, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """更新 Skill""" svc = SkillService(db) skill = await svc.update(skill_id, current_user.id, data.model_dump(exclude_unset=True)) if not skill: raise HTTPException(status_code=404, detail="Skill not found or not owned") return skill @router.delete("/{skill_id}", status_code=204) async def delete_skill( skill_id: str, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """删除 Skill""" svc = SkillService(db) deleted = await svc.delete(skill_id, current_user.id) if not deleted: raise HTTPException(status_code=404, detail="Skill not found or not owned") ``` - [ ] **Step 2: Update routers __init__.py** Add to `backend/app/routers/__init__.py`: ```python from app.routers.skill import router as skill_router ``` - [ ] **Step 3: Register in main.py** Add import and include_router: ```python from app.routers.skill import router as skill_router # ... app.include_router(skill_router) ``` - [ ] **Step 4: Test API** Run: `cd backend && python -c "from app.main import app; print('Routes:', [r.path for r in app.routes if 'skill' in r.path])"` Expected: Routes containing `/api/skills` - [ ] **Step 5: Commit** ```bash git add backend/app/routers/skill.py backend/app/routers/__init__.py backend/app/main.py git commit -m "feat: add Skill API endpoints" ``` --- ## Task 5: Skill Registry (Agent Integration) **Files:** - Create: `backend/app/agents/skill_registry.py` - Modify: `backend/app/agents/prompts.py` (extend system prompts) - [ ] **Step 1: Create skill registry** ```python # backend/app/agents/skill_registry.py """ Skill Registry - Agent 运行时加载 Skills """ from typing import Optional from app.database import async_session from app.services.skill_service import SkillService # 缓存:agent_type -> list[Skill] _skill_cache: dict[str, list] = {} async def load_skills_for_agent(agent_type: str, force_reload: bool = False) -> list: """加载指定 Agent 类型的可用 Skills""" if not force_reload and agent_type in _skill_cache: return _skill_cache[agent_type] async with async_session() as db: svc = SkillService(db) skills = await svc.get_by_agent_type(agent_type) _skill_cache[agent_type] = skills return skills def get_skills_for_agent(agent_type: str) -> list: """同步接口:返回缓存的 Skills(供 Agent 节点调用)""" return _skill_cache.get(agent_type, []) def build_skill_context(agent_type: str) -> str: """ 构建 Skill 上下文,供注入到 Agent 系统提示 格式:Skill 名称 + 描述 + 工具列表 """ skills = get_skills_for_agent(agent_type) if not skills: return "" lines = ["\n\n【可用的 Skills】"] for s in skills: tools_str = ", ".join(s.tools) if s.tools else "无" lines.append(f""" ## {s.name} - 描述: {s.description or '无'} - 工具: {tools_str} - 指令: {s.instructions[:200]}...""" if len(s.instructions) > 200 else f"- 指令: {s.instructions}") return "\n".join(lines) def clear_cache(): """清除缓存(配置变更时调用)""" global _skill_cache _skill_cache = {} ``` - [ ] **Step 2: Update prompts to include skill context** Modify `backend/app/agents/graph.py` to inject skill context into each agent's system prompt: In each agent node function (planner_node, executor_node, etc.), append skill context: ```python skill_context = build_skill_context(agent_type) # Append to system message ``` - [ ] **Step 3: Commit** ```bash git add backend/app/agents/skill_registry.py git commit -m "feat: add SkillRegistry for agent integration" ``` --- ## Task 6: Frontend - Skill API Client **Files:** - Create: `frontend/src/api/skill.ts` - [ ] **Step 1: Create skill API client** ```typescript // frontend/src/api/skill.ts import api from './index' import type { AxiosResponse } from 'axios' export interface Skill { id: string name: string description: string | null instructions: string agent_type: string tools: string[] required_context: string[] output_format: string | null visibility: 'private' | 'team' | 'market' team_id: string | null is_active: boolean owner_id: string created_at: string updated_at: string } export interface SkillCreate { name: string description?: string instructions: string agent_type: string tools?: string[] required_context?: string[] output_format?: string visibility?: 'private' | 'team' | 'market' team_id?: string is_active?: boolean } export interface SkillUpdate { name?: string description?: string instructions?: string agent_type?: string tools?: string[] required_context?: string[] output_format?: string visibility?: 'private' | 'team' | 'market' team_id?: string is_active?: boolean } export const skillApi = { list: (params?: { agent_type?: string; visibility?: string }): Promise> => { return api.get('/api/skills', { params }) }, get: (id: string): Promise> => { return api.get(`/api/skills/${id}`) }, create: (data: SkillCreate): Promise> => { return api.post('/api/skills', data) }, update: (id: string, data: SkillUpdate): Promise> => { return api.put(`/api/skills/${id}`, data) }, delete: (id: string): Promise> => { return api.delete(`/api/skills/${id}`) }, } ``` - [ ] **Step 2: Commit** ```bash git add frontend/src/api/skill.ts git commit -m "feat: add skill API client" ``` --- ## Task 7: Frontend - SkillView Page **Files:** - Create: `frontend/src/views/SkillView.vue` - [ ] **Step 1: Create SkillView page** ```vue ``` - [ ] **Step 2: Commit** ```bash git add frontend/src/views/SkillView.vue git commit -m "feat: add SkillView page" ``` --- ## Task 8: Frontend - Router & Navigation **Files:** - Modify: `frontend/src/router/index.ts` - Modify: `frontend/src/components/SidebarNav.vue` - [ ] **Step 1: Add route to router** In `frontend/src/router/index.ts`, add to children array: ```typescript { path: 'skills', name: 'skills', component: () => import('@/views/SkillView.vue'), }, ``` - [ ] **Step 2: Add nav item to SidebarNav** In `frontend/src/components/SidebarNav.vue`, add to navItems array: ```typescript { name: 'Skill 市场', path: '/skills', icon: Bot }, ``` Also add Bot to the import from lucide-vue-next. - [ ] **Step 3: Commit** ```bash git add frontend/src/router/index.ts frontend/src/components/SidebarNav.vue git commit -m "feat: add Skill route and navigation" ``` --- ## Task 9: Integration - Inject Skill Context into Agent **Files:** - Modify: `backend/app/agents/graph.py` - [ ] **Step 1: Modify agent nodes to include skill context** In each agent node function, after creating the system message, append skill context: ```python from app.agents.skill_registry import build_skill_context async def planner_node(state: AgentState) -> AgentState: # ... existing code ... system_msgs: list[BaseMessage] = [SystemMessage(content=PLANNER_SYSTEM_PROMPT)] # Inject skill context skill_ctx = build_skill_context("planner") if skill_ctx: system_msgs.append(SystemMessage(content=skill_ctx)) # ... rest of code ... ``` Apply same pattern to: master_node, executor_node, librarian_node, analyst_node - [ ] **Step 2: Commit** ```bash git add backend/app/agents/graph.py git commit -m "feat: inject skill context into agent prompts" ``` --- ## Summary | Task | Description | Files | |------|-------------|-------| | 1 | Skill Model | `backend/app/models/skill.py` | | 2 | Skill Schema | `backend/app/schemas/skill.py` | | 3 | Skill Service | `backend/app/services/skill_service.py` | | 4 | Skill Router | `backend/app/routers/skill.py`, `main.py` | | 5 | Skill Registry | `backend/app/agents/skill_registry.py` | | 6 | Frontend API | `frontend/src/api/skill.ts` | | 7 | SkillView Page | `frontend/src/views/SkillView.vue` | | 8 | Router & Nav | `frontend/src/router/index.ts`, `SidebarNav.vue` | | 9 | Agent Integration | `backend/app/agents/graph.py` | --- ## Verification 1. **Backend API Test:** - Start backend: `cd backend && python -m uvicorn app.main:app --reload` - Test: `curl -X POST http://localhost:8000/api/skills -H "Content-Type: application/json" -d '{"name":"test","instructions":"test","agent_type":"planner"}'` 2. **Frontend Test:** - Start frontend: `cd frontend && npm run dev` - Navigate to `/skills`, verify page loads 3. **Agent Integration Test:** - Create a Skill via API - Send message to chat that triggers the skill's agent type - Verify skill context appears in agent logs