feat(skills): 技能加载/校验/真执行第一片(design/09 §6 定稿落地)

- 加载器/校验:目录式 configs/skills/<id>/skill.yaml(兼容旧单文件 <id>.yaml,先目录后文件);
  新字段 version/when-to-use/invocation(三态)/triggers/paths/allowed-tools/disallowed-tools/
  context(ine|fork)/agent/model/effort/metadata;**依赖环 ⇒ 加载期拒**(运行期绝不挂死);
  depends 指向不存在步骤 ⇒ 拒;同一工具同时在 allowed 与 disallowed ⇒ 拒;错误可分类(哨兵 + %w)
- Runner.Run 真执行(替换 ErrRunPending):步骤拓扑(无依赖可并行、上限 2)+ guardrail 强制点
  (enforced 违反 ⇒ 中止并如实报「哪条、哪一步」;否则 WARN 继续)+ 失败不静默跳过 + ctx 取消传播
- 🔴 能力收缩只收不放:该轮可用工具 = allowed-tools ∩ 安全闸 ∩ 注册表,再减 disallowed-tools
- 另有渐进披露相关的 listing/surface/activation(清单投影与激活判定)

⚠️ 未接线:本片是库与执行器,尚未把技能清单接进模型面(见 report 的分阶段)。
This commit is contained in:
AskaEth committed 2026-10-06 09:03:35 +08:00
1 parent 29181245ce
commit f75e7a9c9b
17 files changed
+3067 -164

No files matched your search

+192 -25
View File
@@ -1,55 +1,222 @@
package sdk
// Step is one unit of a Skill's reusable workflow.
import (
"errors"
"fmt"
"strings"
)
// ErrInvalidSkillField 表示技能定义里的某个字段取值非法:枚举越界,或声明自相
// 矛盾(例如同一个工具同时出现在 allowed-tools 与 disallowed-tools)。
// ParseInvocation / ParseContextMode 用它分类错误,调用方用 errors.Is 判定。
var ErrInvalidSkillField = errors.New("sdk: invalid skill field")
// Step 是技能工作流里的一步(design/09 §6.2)。
type Step struct {
// Name is a short identifier for the step.
// Name 是步骤标识,技能内唯一;depends 按它引用。
Name string
// Prompt is the instruction the model follows for this step.
// Prompt 是这一步交给模型的指令。
Prompt string
// Depends lists the Names of steps that must finish before this step runs.
// Depends 列出必须先完成的前序步骤名。
Depends []string
}
// Ref is a reference a Skill may pull in for extra context or shared tooling.
// Ref 是技能可以引用的外部素材(工具、文档、文件等)。
type Ref struct {
// Name identifies the reference.
// Name 标识该引用。
Name string
// Type is a free-form kind, for example "tool", "doc" or "file".
// Type 是自由取值的种类,例如 "tool"、"doc"、"file"。
Type string
// Value is the reference payload or pointer.
// Value 是引用载荷或指针。
Value string
// Optional reports whether the reference may be absent without failing.
// Optional 为真表示该引用缺失不算失败。
Optional bool
}
// Guardrail is a constraint a Skill applies while running.
// Guardrail 是技能执行期间施加的约束(design/09 §6.4-4)。
type Guardrail struct {
// ID names the guardrail.
// ID 命名该约束。
ID string
// Message is the rule text enforced on the workflow.
// Message 是约束内容(也是违规时如实回显的那一句)。
Message string
// Enforced reports whether a violation aborts the Skill (true) or is only a
// warning (false).
// Enforced 为真表示违反即中止技能;为假表示只 WARN 后继续。
Enforced bool
}
// Skill is a reusable, higher-order workflow: an ordered set of steps paired
// with prompts, references and guardrails. It is more structured than a Tool
// and typically composes several steps.
// Skill 是可复用的高阶工作流:一组带提示词的步骤,配以基础提示词、引用与约束。
// 它比 Tool 更结构化,通常由多个步骤组合而成。
//
// Implementations must be safe for concurrent use.
// 实现必须并发安全。
type Skill interface {
// Name returns the unique skill identifier.
// Name 返回技能的唯一标识。
Name() string
// Description explains what the Skill accomplishes; the model uses it to
// select a Skill for a task.
// Description 说明技能做什么;模型据此挑选技能。
Description() string
// Prompt returns the base system prompt for the Skill.
// Prompt 返回技能的基础系统提示词。
Prompt() string
// Steps returns the ordered workflow steps.
// Steps 返回有序的工作流步骤。
Steps() []Step
// Refs returns the references the Skill may draw on.
// Refs 返回技能可引用的素材。
Refs() []Ref
// Guardrails returns the constraints the Skill enforces.
// Guardrails 返回技能施加的约束。
Guardrails() []Guardrail
}
// Invocation 是技能的调用三态(design/09 §6.2 定稿,对齐 Agent Skills 的
// `disable-model-invocation` / `user-invocable` 两枚开关压成的一枚枚举)。
type Invocation string
const (
// InvocationBoth:清单含 description,人与模型都可调(缺省)。
InvocationBoth Invocation = "both"
// InvocationModelOnly:清单**不含** description(省上下文),只有模型可调。
InvocationModelOnly Invocation = "model-only"
// InvocationUserOnly:清单含 description,**模型不可调**(只认显式调用)。
InvocationUserOnly Invocation = "user-only"
)
// Valid 报告 i 是否是已知的调用态;空值不算合法(缺省由 OrDefault 补)。
func (i Invocation) Valid() bool {
switch i {
case InvocationBoth, InvocationModelOnly, InvocationUserOnly:
return true
default:
return false
}
}
// OrDefault 把空值补成 InvocationBoth(design/09 §6.2 的缺省列)。
func (i Invocation) OrDefault() Invocation {
if i == "" {
return InvocationBoth
}
return i
}
// ModelInvocable 报告模型是否可以调用该技能(user-only 不可调)。
func (i Invocation) ModelInvocable() bool { return i.OrDefault() != InvocationUserOnly }
// UserInvocable 报告人是否可以显式调用该技能(model-only 不给人调)。
func (i Invocation) UserInvocable() bool { return i.OrDefault() != InvocationModelOnly }
// ListingHasDescription 报告该调用态下清单是否要带 description
// (model-only 不带:清单只给名字,省上下文)。
func (i Invocation) ListingHasDescription() bool { return i.OrDefault() != InvocationModelOnly }
// ParseInvocation 解析磁盘/YAML 上的调用态字符串。空串取缺省 both;其余非法值
// 一律报错(绝不静默当成 both —— 那是把配置写错伪装成成功)。
func ParseInvocation(s string) (Invocation, error) {
trimmed := Invocation(strings.TrimSpace(s))
if trimmed == "" {
return InvocationBoth, nil
}
if !trimmed.Valid() {
return "", fmt.Errorf("sdk: invocation %q: %w", s, ErrInvalidSkillField)
}
return trimmed, nil
}
// ContextMode 是技能的上下文模式(design/09 §6.2/§6.4-6)。
type ContextMode string
const (
// ContextInline:在当前会话上下文内跑(缺省)。
ContextInline ContextMode = "inline"
// ContextFork:在隔离子代理里跑,不看会话历史,结论回灌父会话。
ContextFork ContextMode = "fork"
)
// Valid 报告 c 是否是已知的上下文模式;空值不算合法。
func (c ContextMode) Valid() bool {
switch c {
case ContextInline, ContextFork:
return true
default:
return false
}
}
// OrDefault 把空值补成 ContextInline(缺省)。
func (c ContextMode) OrDefault() ContextMode {
if c == "" {
return ContextInline
}
return c
}
// ParseContextMode 解析磁盘/YAML 上的上下文模式。空串取缺省 inline;其余非法值
// 报错(理由同 ParseInvocation)。
func ParseContextMode(s string) (ContextMode, error) {
trimmed := ContextMode(strings.TrimSpace(s))
if trimmed == "" {
return ContextInline, nil
}
if !trimmed.Valid() {
return "", fmt.Errorf("sdk: context %q: %w", s, ErrInvalidSkillField)
}
return trimmed, nil
}
// SkillSpec 是技能定义的完整只读视图(design/09 §6.2 定稿字段)。
//
// 它是 Skill 之上的**可选**扩展:未实现它的技能一律按各字段的缺省取值看待,
// 因此既有的第三方 Skill 实现不需要改动即可继续注册与运行。
type SkillSpec interface {
Skill
// Version 是定义版本(形状迁移用),缺省 1。
Version() int
// WhenToUse 是「何时使用」补充说明,与 Description 一起进清单并计入预算。
WhenToUse() string
// Invocation 是调用三态。
Invocation() Invocation
// Triggers 是触发词,与 Paths 一起决定自动激活。
Triggers() []string
// Paths 是 glob;仅当工作文件命中时自动激活。
Paths() []string
// AllowedTools 是**能力上限**(不是免审批名单),见 design/09 §6.4-3。
AllowedTools() []string
// DisallowedTools 从上限里剔除。
DisallowedTools() []string
// ContextMode 是上下文模式(对应 YAML 键 `context`;方法名不叫 Context 以
// 避免与标准库 context 混淆)。
ContextMode() ContextMode
// Agent 是 context: fork 时使用的子代理类型。
Agent() string
// Model 是可选模型覆盖;per-role 路由优先于它(design/04 §3)。
Model() string
// Effort 是可选思考档位覆盖。
Effort() string
// Metadata 是自由 KV,宿主不解释。
Metadata() map[string]string
}
// SkillView 把任意 Skill 归一化成 SkillSpec:实现了 SkillSpec 的原样返回,
// 否则套一层全缺省的包装。宿主用它读技能字段,就不必到处写类型断言。
func SkillView(s Skill) SkillSpec {
if s == nil {
return nil
}
if spec, ok := s.(SkillSpec); ok {
return spec
}
return plainSkill{Skill: s}
}
// plainSkill 为只实现基础 Skill 的技能补齐缺省字段。
type plainSkill struct{ Skill }
func (p plainSkill) Version() int { return DefaultSkillVersion }
func (p plainSkill) WhenToUse() string { return "" }
func (p plainSkill) Invocation() Invocation { return InvocationBoth }
func (p plainSkill) Triggers() []string { return nil }
func (p plainSkill) Paths() []string { return nil }
func (p plainSkill) AllowedTools() []string { return nil }
func (p plainSkill) DisallowedTools() []string { return nil }
func (p plainSkill) ContextMode() ContextMode { return ContextInline }
func (p plainSkill) Agent() string { return "" }
func (p plainSkill) Model() string { return "" }
func (p plainSkill) Effort() string { return "" }
func (p plainSkill) Metadata() map[string]string { return nil }
// DefaultSkillVersion 是未声明 version 时的定义版本(design/09 §6.2 缺省列)。
const DefaultSkillVersion = 1