스킬 시스템
라우팅, 활성화, 위험 게이트 작동 방식과 사용 가능한 전체 스킬 카탈로그
스킬은 Minara Agent의 기능 단위입니다. 스킬은 프롬프트 조각, 도구 허용 목록, 그리고 라우터에게 관련성을 알려주는 메타데이터와 위험 게이트에게 안전한 활성화 여부를 알려주는 메타데이터를 하나로 묶습니다. Agent가 필요에 따라 로드하는 플러그인으로 이해하면 됩니다.
MCP를 사용하지 않는 이유 Minara는 핵심 기능에 자체 스킬 및 툴 레지스트리를 사용합니다(MCP 대신). 금융 도구에는 호출별 권한 등급, 타입 스키마, MCP 스펙에서 지원하지 않는 L3 위험 게이트가 필요하기 때문입니다. 외부 프로바이더는 여전히 MCP 서버를 통해 통합됩니다. Custom MCP를 참고하십시오.
이 페이지는 작동 원리(라우팅, 활성화, 위험 게이트)와 Agent가 활성화할 수 있는 모든 스킬의 전체 카탈로그를 다룹니다.
실제 사용 예시: 기능 페이지에서는 각각 활성화하는 스킬을 참조합니다. 스킬 설치에서는 외부 스킬 벤더링의 사용자 측면을 보여줍니다.
세 가지 레이어
위 다이어그램은 L0에서 LLM을 거쳐 L3까지의 전체 라우팅 파이프라인을 보여줍니다.
L0 라우터는 결정론적입니다. 동일한 입력은 항상 동일한 카탈로그를 생성합니다. LLM의 activate_skills 호출이 유일한 비결정론적 단계입니다. L3 위험 게이트는 다시 결정론적입니다. 따라서 활성화 파이프라인의 3분의 2는 LLM 없이도 테스트할 수 있으며, 이는 의도적인 설계 결정입니다.
L0 라우팅: 결정론적 사전 필터
apps/agent/src/skills/router.ts의 buildRoutedCatalog()는 TurnRoutingContext를 받아 등록된 모든 스킬에 점수를 매깁니다.
| 신호 | 점수 |
|---|---|
always-on (base / activation=always) | +1000 |
| 사용자 메시지의 키워드 적중 | 각 +30, 최대 +60 |
| 라이프사이클 단계 일치 | +20 |
| 자산 클래스 일치 | +15 |
다른 적중 항목의 co_activate | +10 |
| 신호 소스 일치 (크론 턴) | +25 |
| 폴백 (우선순위 타이브레이커) | 100 - priority |
| 부정 키워드 적중 | −∞ |
conditions 실패 | −∞ |
-∞ 점수를 받은 스킬은 카탈로그에서 완전히 제거되며 LLM에게 노출되지 않습니다. conditions는 점진적 성능 저하를 제어하는 조정 장치입니다.
conditions: {
requires_env: ["GLASSNODE_API_KEY"], // skill hidden without key
requires_tools: ["glassnode_metric"], // skill hidden without tool
requires_toolsets: ["documents"],
fallback_for_tools:["web_extract"], // only surface when web_extract absent
platforms: ["darwin", "linux"], // hide on Windows
}출력 RoutedCatalogEntry[]에는 스킬이 점수를 얻은 이유(예: ["kw:buy", "stage:decide"])가 포함됩니다. 레지스트리의 buildCatalogFor()는 이를 카탈로그 블록에 LLM을 위한 인라인 힌트로 렌더링합니다.
활성화: 메인 LLM의 결정
기본 시스템 프롬프트는 항상 하나의 메타 도구를 노출합니다.
activate_skills(ids: string[], confirmed?: boolean)LLM은 라우팅된 카탈로그를 읽고 최대 4개의 지연 스킬을 선택한 뒤 activate_skills(["minara.core", "memory-personal"])을 호출합니다. 도구 핸들러는 SkillSession.activate()를 호출하고, 이는 L3 위험 게이트를 실행합니다.
활성화는 이전 지연 세트를 교체합니다. 초점을 유지하면 프롬프트 비대를 방지합니다. kind: "base" 스킬과 activation: "always" 스킬은 이 메커니즘의 대상이 아닙니다.
L3 위험 게이트: 확인 장벽
SkillSession.activate() 내부:
for (const id of requestedIds) {
const skill = registry.get(id);
if (!skill) { rejected.push({ id, reason: "unknown" }); continue; }
if (skill.risk_tier > ctx.allowRiskTier) {
rejected.push({ id, reason: `risk_tier ${skill.risk_tier} exceeds ceiling` });
continue;
}
if (skill.requires_user_confirmation && !confirmed) {
pending.push({
id,
risk_tier: skill.risk_tier,
summary: skill.description,
stage: skill.routing?.lifecycle_stages,
});
continue; // session state NOT mutated
}
accepted.push(id);
}핵심 속성은 다음과 같습니다. pending_confirmation이 비어 있지 않으면 세션 상태는 전혀 변경되지 않습니다. LLM의 다음 턴은 시스템 프롬프트의 pending_confirmation 블록을 통해 사용자에게 확인을 요청하도록 안내받습니다. 사용자가 승인한 후에만 LLM이 activate_skills(..., confirmed: true)를 재발급합니다.
따라서 손상된 도구 출력이 activate_skills 호출을 에코하더라도 미확인 자금 이동 스킬을 활성화할 수 없습니다. 게이트는 프롬프트가 아닌 상태 머신으로 강제됩니다.
라이프사이클 단계와 위험 상한선
라우터는 사용자 메시지에서 라이프사이클 단계를 추론합니다.
| 단계 | 트리거 조건 | allowRiskTier |
|---|---|---|
discover | "what's trending", "show me", "price of" | 1 |
evaluate | "should I", "analyze", "compare", "risk" | 2 |
decide | "buy", "sell", "long", "short", "swap" | 3 |
manage | "close", "stop loss", "my positions" | 3 |
discover로 추론된 턴은 LLM이 요청하더라도 티어 3 스킬을 활성화할 수 없습니다. 이는 무해해 보이는 질문 속에 거래 호출을 숨기는 전형적인 탈옥 시도를 방지합니다.
세션 내 단계 전환(예: evaluate → decide)도 risk_tier >= 3인 스킬에 대해 확인을 강제합니다. 의도치 않게 거래 모드로 전환될 수 없습니다.
신호: 크론 및 웹훅 턴
크론 실행 또는 웹훅 전달 시 SignalContext가 생성됩니다.
{
source: "cron",
signal_id: "btc_drop_5pct",
asset: { symbol: "BTC", asset_class: "crypto_major" },
severity: "warn",
preload_skills: ["market.watch", "memory.alerts"],
suggested_stages: ["evaluate"],
trace_id: "t_abc123",
}preload_skills는 라우터에게 LLM의 activate_skills 호출을 기다리지 않고 해당 ID들을 활성화하도록 지시하는 힌트입니다. L3 위험 게이트는 프리로드에도 동일하게 실행됩니다. 프리로드는 allowRiskTier를 초과하여 에스컬레이션할 수 없으며, requires_user_confirmation 스킬로의 프리로드는 여전히 자동 활성화 대신 pending_confirmation을 생성합니다. trace_id는 WorkflowInstance, SkillAuditRecord, 도구 실행 로그 전체에 걸쳐 연결됩니다. 따라서 "14:03 BTC 알림이 무엇을 했는지"는 단일 SQL 쿼리로 조회할 수 있습니다.
좋은 스킬 작성하기
내장 스킬에서 축적된 구체적인 지침입니다.
- 프롬프트 조각은 800 token(약 3 KB) 미만으로 유지하십시오. 긴 프롬프트는 캐시 가능한 카탈로그 블록을 초과하여 모든 턴을 느리게 만듭니다.
description을 구체적으로 작성하십시오 (80자 이하). LLM은 description으로 스킬을 선택하며, 프롬프트는 활성화 후에만 표시됩니다. "Perps: open, close, monitor positions on Hyperliquid"는 좋은 예입니다. "Trading stuff"는 좋지 않습니다.activation: "always"는 절제해서 사용하십시오. 항상 켜진 스킬은 모든 턴에서 프롬프트 조각 비용을 지불합니다. 특정 키워드를 사용하는"auto"를 권장합니다.risk_tier를 정확하게 태그하십시오. 확실하지 않으면 높게 설정하십시오. "활성화를 쉽게 하기 위해" 스왑 스킬을 티어 2로 설정하면 분석과 거래의 경계가 무너지며 코드 리뷰에서 적발됩니다.- 모든 외부 프로바이더에
conditions.requires_env를 선언하십시오. 레지스트리는 키가 없는 개발 환경에서 스킬을 자동으로 숨깁니다. 이것이 올바른 동작입니다. tool_names는 최소한으로 유지하십시오. 스킬이 실제로 사용하는 도구만 나열하십시오. 20개의 도구를 가진 스킬은 보통 두 스킬이 하나인 척하는 경우입니다.
참조 구현:
- 최소 프롬프트 전용 스킬:
workspace-files.ts - 도구 중심 스킬:
market-perps.ts - 프롬프트 중심 스킬:
deep-research.ts
외부 스킬
apps/agent/src/skills/external/에는 벤더링된 서드파티 SKILL.md 패키지가 있습니다. minara skills add <git-url>로 추가하며, 이 명령은 다음 과정을 수행합니다.
apps/agent/src/skills/external/<id>/에 저장소를 클론합니다.- 라이선스를 감지하여
.minara-skill.json에 기록합니다. - 독점 라이선스 콘텐츠 추가를 거부합니다(과거 사례: Anthropic의 오피스 스킬).
- 부트 시
buildExternalDomainSkills()를 통해 SKILL.md 프론트매터에서DomainSkill을 빌드합니다.
외부 스킬은 일급 구성원입니다. 내장 스킬과 동일한 레지스트리, 라우터, 위험 게이트를 거칩니다. 별도의 코드 경로는 없습니다.
스킬 카탈로그
모든 내장 스킬과 외부 스킬의 전체 카탈로그는 참조 섹션에 있습니다. 각 스킬의 SKILL.md에서 자동 생성되므로 코드와 어긋나지 않습니다.
같은 기능이 두 형태로 모두 있을 때는 내장 스킬을 우선하세요. 바이너리에 함께 배포되고, CI에서 타입 검사를 거치며, 내부 도구 레지스트리를 직접 호출합니다. 내장 스킬이 필요한 제공자를 다루지 않거나, 릴리스를 기다리지 않고 업스트림 SKILL.md 업데이트를 받고 싶을 때 외부 스킬을 사용하세요.
스킬 시스템은 Minara Agent의 금융 안전 모델이 가장 잘 드러나는 곳입니다. 자금을 이동하는 스킬을 추가한다면, 코드를 작성하기 전에 전체 파이프라인(L0 점수 산정, LLM 활성화 의도, L3 게이트, 확인 플로우)을 검토하십시오. 잘못된 거래 후 패치하는 것보다 설계 실수를 여기서 잡는 것이 훨씬 비용이 적습니다.