跳转至

Evolution(Agent 自进化)

概述

Evolution 是 tRPC-Agent-Go 框架中的自进化系统,使 Agent 能够自动从历史执行中提取可复用技能(Skill),并在后续任务中加载复用。整个流程作为异步后台循环运行,不阻塞主任务路径。

定位

Evolution 用于积累和复用 Agent 的"操作经验"。默认不启用隔离(SkillScopeNone);如需隔离,可显式配置应用级或用户级 scope。当 Agent 完成任务后,后台学习闭环会先执行 review policy 判断(默认:≥4 次工具调用、用户纠错或从错误中恢复),确认这段增量值得 review 后,再由 Reviewer 分析对话记录,将可复用的工作流提取为结构化的 SKILL.md 文件。后续遇到相似任务时,Agent 通过 skill_load 加载对应技能,直接按照已验证的步骤执行,避免重复试错。

它适合积累:稳定的多步骤工作流、工具调用最佳实践、常见错误和规避方法(pitfalls)、领域特定的操作规范。

核心价值

  • 效率提升:相似任务首次需要 agent 多轮探索,后续加载 skill 后一次到位(benchmark 验证 token 节省 17-33%)
  • 灾难压制:skill 提供明确步骤,消除 agent 在某些任务上的随机无限循环(单案例最高节省 94.6%)
  • 经验复用:一次学到的 pitfall 永久生效,不依赖 session 上下文
  • 质量可控:质量门禁确保只有合格的 skill 上线,写入隔离保护用户已有资产

架构

┌─────────────────────────────────────────────────────────────────┐
│                          主任务路径                               │
│  Request ──▶ [skill_load] ──▶ Agent ──▶ Tool Calls ──▶ Result   │
└────────────────────────────────────┬────────────────────────────┘
                                     │ 入队 + review policy
┌─────────────────────────────────────────────────────────────────┐
│                     后台学习闭环(异步)                           │
│                                                                 │
│  ┌──────────┐    ┌────────────┐    ┌───────────┐    ┌────────┐  │
│  │ Reviewer │──▶ │ Reconciler │──▶ │   Gates   │──▶ │Publish │  │
│  │ (LLM)    │    │ (去重/合并) │    │ Spec/Safe │    │        │  │
│  │          │    │            │    │ Effect/   │    │        │  │
│  │          │    │            │    │ Human     │    │        │  │
│  └──────────┘    └────────────┘    └───────────┘    └───┬────┘  │
└─────────────────────────────────────────────────────────┼───────┘
                              ┌───────────────────────────┘
                    ┌───────────────────┐
                    │  Managed Skills   │ ◀── 下一个任务通过
                    │  (SKILL.md files) │     skill_load 读取
                    └───────────────────┘

Pipeline 各环节:

环节 职责 实现
ReviewPolicy 决定是否值得 review(默认 ≥4 tool calls) DefaultReviewPolicy
Reviewer 从 transcript 中提取 skill spec(JSON) LLMReviewer (gpt-4o-mini)
Reconciler 确定性去重/吸收/合并(4 条规则) 纯字符串规则
SpecGate 验证 spec schema、名称规范、查重 确定性规则
SafetyGate 扫描 secrets、危险命令、路径穿越 确定性规则
EffectivenessGate 基于 session outcome 拦截失败 run 确定性规则
HumanGate 可选人工审批 NewAlwaysHoldGate / NewCreateOnlyHoldGate
Publisher 写 SKILL.md 到磁盘 文件型 publisher

快速开始

最简配置

package main

import (
    "trpc.group/trpc-go/trpc-agent-go/agent/llmagent"
    "trpc.group/trpc-go/trpc-agent-go/evolution"
    "trpc.group/trpc-go/trpc-agent-go/model/openai"
    "trpc.group/trpc-go/trpc-agent-go/runner"
    "trpc.group/trpc-go/trpc-agent-go/skill"
)

func main() {
    agentModel := openai.New("gpt-4o")
    reviewerModel := openai.New("gpt-4o-mini") // reviewer 用小模型即可

    // 1. 创建技能仓库
    //    repo 是 agent 和 evolution 的共享依赖:
    //    - agent 从中读取 skill overview 和 skill_load
    //    - evolution 的 reviewer 从中读取已有 skill 进行去重判断
    //    - evolution publish 新 skill 后调用 repo.Refresh() 使 agent 立即可见
    repo, _ := skill.NewFSRepository("./skills")

    // 2. 创建 evolution 服务
    //    ManagedSkillsDir: evolution 写入 SKILL.md 的目标目录
    //    SkillRepository:  传入与 agent 共享的 repo,确保:
    //      a) reviewer 能看到所有已有 skill(包括 bundled/local)做去重
    //      b) publish 后 Refresh 一次,agent 和 reviewer 都能看到新 skill
    evoSvc := evolution.NewService(reviewerModel,
        evolution.WithManagedSkillsDir("./skills"),
        evolution.WithSkillRepository(repo),
    )
    defer evoSvc.Close()

    // 3. 创建 agent 并接入技能(使用同一个 repo)
    agent := llmagent.New("my-agent",
        llmagent.WithModel(agentModel),
        llmagent.WithSkills(repo),
    )

    // 4. 创建 runner 并接入 evolution
    r := runner.NewRunner("app", agent,
        runner.WithEvolutionService(evoSvc),
    )
    defer r.Close()
    // WithEvolutionService 只借用 evoSvc;仍需保留显式 evoSvc.Close。

    // 5. 正常运行任务 — 技能在后台自动提取
    //    后续任务如果匹配已有 skill,agent 会通过 skill_load 加载
}

完整配置(推荐生产使用)

// 技能发布目录和 revision store 分开
skillsDir := "./skills/evolution"
revisionsDir := "./evolution/revisions"

evoSvc := evolution.NewService(reviewerModel,
    // 基础配置
    evolution.WithManagedSkillsDir(skillsDir),
    evolution.WithSkillRepository(repo),

    // 不可变 revision store(审计 + 回滚)
    evolution.WithCandidateStore(evolution.NewFileCandidateStore(revisionsDir)),
    evolution.WithActivePointer(evolution.NewFileActivePointer(revisionsDir)),

    // 质量门禁链
    evolution.WithSpecGate(evolution.NewDefaultSpecGate()),
    evolution.WithSafetyGate(evolution.NewDefaultSafetyGate()),
    evolution.WithEffectivenessGate(evolution.NewOutcomeBasedEffectivenessGate()),

    // 可选:人工审批
    evolution.WithHumanGate(evolution.NewCreateOnlyHoldGate()),
)

离线反思式优化

异步 Reviewer 解决的是“从一次已完成 session 中提炼一个候选”。对于已经有可重复 benchmark 的 skill,evolution/optimization 提供独立的纯 Go 搜索闭环,机制参考 GEPA,但不依赖 DSPy、Python 或 companion process。

optimization.Optimizer 是与具体算法无关的执行契约。内置 NewGEPA 实现只拥有 GEPA 的反思和 Pareto 搜索;dataset 隔离、预算、实验记录、holdout 验证和可选 revision 提交仍由统一生命周期负责。应用在装配阶段选择具体实现,而不是传入运行时算法字符串。

优化器会:

  1. 先在 validation split 上评估 baseline skill;
  2. 按实例级 Pareto 选择 parent,在 feedback minibatch 上执行,并把当前 skill、case input/expected、score、output、evaluator feedback 和 trace 逐字段限长并进行凭据模式 脱敏后交给 reflection model;
  3. 每次只修改一个 SkillSpec component,且 child 只有在同一 minibatch、同一 seed 下严格优于 parent 才进入候选池;
  4. 维护逐 case validation winner,并按 Pareto coverage 采样后续 parent;
  5. 搜索结束后才在不可见的 holdout split 上配对比较 baseline/final candidate;
  6. 可选地把通过 holdout 的候选接回现有 revision store。外部候选提交时先停在 pending_approval,不会直接修改 live skill;默认会一直等待审批。但如果 service 启用了 WithApprovalTimeout,现有 auto-expiration sweeper 之后可能在没有人工审批的 情况下把过期 revision promote 并发布。

应用需要提供与任务相关的 evaluator 和用于反思的 model.Model。reflection model 使用框架现有 provider adapter 配置,可以与任务模型相同,也可以是独立 reviewer 模型;evaluator 是唯一需要新增的领域适配:

type benchmarkEvaluator struct {
    // runner/sandbox/test harness 依赖
}

func (e *benchmarkEvaluator) Evaluate(
    ctx context.Context,
    candidate *evolution.SkillSpec,
    cases []optimization.Case,
    seed int64,
) ([]optimization.Evaluation, error) {
    // 在隔离仓库中加载 candidate,执行每个 case,并为每个 case 返回
    // 一个 [0,1] 归一化分数及可操作的 feedback/trace。
    return evaluations, nil
}

revision submission 是独立于异步 session learning 的可选能力。默认 service 实现了 evolution.RevisionSubmitter,但自定义 evolution.Service 可以不实现。应用应当在 装配阶段只解析一次该能力,这样配置错误会在开始优化前暴露:

提交过程会把已配置的 skill repository 作为写入边界:update 必须指向已存在的 skill; 配置 WithManagedSkillsDir 时,该 skill 还必须位于 managed 目录内;create 不得与 repository 中的已有 skill 重名。这里应传入 agent 实际使用的同一个 repository,确保 检查能够同时看到 bundled、用户自建和 evolution-managed skill。

revisionSubmitter, ok := evoSvc.(evolution.RevisionSubmitter)
if !ok {
    return fmt.Errorf("evolution service does not support revision submission")
}

optimizer, err := optimization.NewGEPA(
    reflectionModel,
    evaluator,
    optimization.WithMaxIterations(10),
    optimization.WithReflectionBatchSize(3),
    optimization.WithRandomSeed(7),
    optimization.WithStoreDir("./evolution/experiments"),
    optimization.WithRevisionSubmitter(revisionSubmitter),
)
if err != nil {
    return err
}

result, err := optimizer.Optimize(ctx, optimization.Request{
    Seed:             baselineSpec,
    ParentRevisionID: activeRevisionID,
    Submit:           true,
    Dataset: optimization.Dataset{
        ID:         "managed-skill-regression",
        Version:    "v1",
        Feedback:   feedbackCases,
        Validation: validationCases,
        Holdout:    holdoutCases,
    },
})
if err != nil {
    // search 可能已经选出 candidate,此后 holdout 或可选交付步骤仍可能失败;
    // 保留非 nil result,便于诊断或重试。
    if result != nil {
        fmt.Printf("optimization %s completed but delivery failed: %v\n",
            result.ExperimentID, err)
    }
    return err
}
fmt.Printf("selected skill %q; validation=%.3f holdout=%.3f; promote=%t (%s)\n",
    result.Spec.Name,
    result.CandidateValidation.Score,
    result.CandidateHoldout.Score,
    result.PromotionEligible,
    result.PromotionReason,
)

optimizer 只借用 submitter;evoSvc 的生命周期仍由应用管理并显式关闭。search 选出 candidate 后,如果 holdout、最终记录或提交失败,Optimize 会同时返回非 nil 的部分 result 和 error;各字段表示失败前已完成的阶段,提交错误也会写入 SubmissionReason

WithStoreDir 是可选的节点本地实验 recorder,与 revision 使用的 CandidateStore 不是同一个概念。每次 run 独占一个 UUID 目录;在支持权限位的文件系统上,文件权限为 0600、目录权限为 0700。持久化的每个 evaluator output、feedback、trace 字段最多 保留 16 KiB。dataset 只在 experiment.json 保存一次;各 evaluation record 通过 case ID 引用它,不会在每轮重复写入 input 和 expected。它不负责分布式任务协调,也不是远端 持久存储。多个节点可以使用同一个兼容 POSIX 的共享根目录,因为不同实验不会共用目录,但同一个实验只能由一个节点写入。如果 Pod 磁盘是临时或不共享的,应用需要在实验完成后把目录上传到自己的持久存储。revision 的 CandidateStoreActivePointer 可以替换,但接口没有定义跨后端事务;多节点部署 必须把 revision 变更交给单一 owner,或在 service 外部完成串行化。

三个 split 的 case ID 不能重复,score 必须是 [0,1] 内的有限数值。提交 revision 即使 Submit=false,结果也会填充 PromotionEligiblePromotionReason,调用方 无需重复实现 holdout 阈值与 critical case 策略。提交 revision 时每个 split 至少需要 10 个 case。holdout 必须对搜索不可见。optimizer 会先把每个待发送给模型的文本字段 截取为 UTF-8 安全且有界的首尾片段,再执行与在线 Reviewer 相同的常见凭据模式脱敏。 该顺序可以限制脱敏过程的内存消耗,但仍无法识别租户特有的敏感数据;应用仍必须在返回前清洗 seed skill、case input/expected、evaluator output、feedback 和 trace。文件实验记录还会包含 dataset metadata 及上述已清洗字段。candidate agent 应在无生产凭据、无副作用工具的隔离环境中 执行。

触发条件

Evolution 在 runner 完成每个任务后自动判断是否 review。内置 DefaultReviewPolicy 在以下任一条件满足时触发:

条件 说明
ToolCallCount ≥ 4 多步骤任务才有提取价值
HasUserCorrection 用户纠正 agent → 值得记录 pitfall
HasRecoveredError agent 从错误中恢复 → 值得记录经验

调整内置策略:

1
2
3
evolution.WithReviewPolicy(evolution.DefaultReviewPolicy{
    MinToolCalls: 6, // 比默认 4 次更保守
})

自定义策略可以调用中心化服务,也可以实现租户级或实验级规则。Policy 返回 error 时不会推进 review 游标,因此同一段 delta 后续可以重试。

type centralPolicy struct {
    client *ReviewDecisionClient
}

func (p *centralPolicy) ShouldReview(
    ctx context.Context,
    input *evolution.ReviewPolicyInput,
) (bool, error) {
    if input == nil || input.ReviewContext == nil {
        return false, nil
    }
    return p.client.ShouldReview(ctx, ReviewRequest{
        AppName:       input.AppName,
        UserID:        input.UserID,
        SessionID:     input.SessionID,
        ToolCallCount: input.ReviewContext.ToolCallCount,
        Outcome:       input.Outcome,
    })
}

evolution.WithReviewPolicy(&centralPolicy{client: client})

质量门禁

SpecGate

确定性检查 skill spec 的格式和命名规范:

  • Schema 完整性:name / description / when_to_use / steps 必须非空
  • 名称规范:不允许包含数字计数(如 "3 Cities")、不允许超长
  • 查重:与已有 skill 完全同名时拒绝
evolution.WithSpecGate(evolution.NewDefaultSpecGate())

SafetyGate

确定性扫描 skill 内容中的安全风险:

  • Secretssk-*AKIA*、JWT token、private key markers
  • 危险命令rm -rf /chmod 777> /dev/sda
  • 路径穿越../../etc/passwd/root/.ssh/
evolution.WithSafetyGate(evolution.NewDefaultSafetyGate())

EffectivenessGate

基于 session outcome 的效果评估:

  • session 结果为 failagent_error → revision 被拒绝(不从失败中学错误流程)
  • 归一化 session score < 0.8 → revision 进入 pending_eval(可配置阈值)
evolution.WithEffectivenessGate(evolution.NewOutcomeBasedEffectivenessGate())

需要配合 Outcome 一起使用:

1
2
3
4
5
6
7
8
9
// 在 benchmark 或评估场景中提供 outcome
evoSvc.EnqueueLearningJob(ctx, evolution.LearningJob{
    Session: sess,
    Outcome: &evolution.Outcome{
        Status: evolution.OutcomeSuccess, // success / fail / partial / agent_error
        Score:  floatPtr(0.95),           // 归一化到 0-1
        Notes:  "all assertions passed",
    },
})

HumanGate(可选人工审批)

在所有自动门禁通过后,可选择拦截 revision 等待人工审批:

1
2
3
4
5
// 拦截所有 revision
evolution.WithHumanGate(evolution.NewAlwaysHoldGate())

// 只拦截新建 skill,update 自动放行
evolution.WithHumanGate(evolution.NewCreateOnlyHoldGate())

HumanGate 可拦截 create/update/delete revision。被拦截的 revision 状态为 pending_approval,需通过 ApprovalService 审批:

approvalSvc := evolution.NewApprovalService(store, pointer, publisher)

// 查看待审列表
pending, _ := approvalSvc.ListPending(ctx, evolution.ListPendingOpts{})

// 批准
approvalSvc.Decide(ctx, evolution.ApprovalDecision{
    RevisionID: pending[0].RevisionID,
    SkillID:    pending[0].SkillID,
    Approved:   true,
    Reviewer:   "alice@example.com",
    Comment:    "looks good",
})

// 拒绝
approvalSvc.Decide(ctx, evolution.ApprovalDecision{
    RevisionID: pending[0].RevisionID,
    SkillID:    pending[0].SkillID,
    Approved:   false,
    Reviewer:   "alice@example.com",
    Comment:    "steps too vague",
})

审批结果会结构化写入 HumanReportApprovedReviewerCommentDecidedAt)并追加 audit log。被 HumanGate 拦截的 delete revision 在批准前不会删除 live skill;批准后会调用 Publisher.DeleteSkill 并清空 active pointer。

自动过期(Auto-expire)

为防止 pending_approval 状态的 revision 永久阻塞,可配置过期时长。超过该时长仍未被人工处理的 revision 将自动被 promote 到 active(Reviewer 字段记录为 auto-expire,可在 audit log 中查询):

evoSvc := evolution.NewService(reviewerModel,
    evolution.WithManagedSkillsDir(skillsDir),
    evolution.WithSkillRepository(repo),
    evolution.WithCandidateStore(evolution.NewFileCandidateStore(revisionsDir)),
    evolution.WithActivePointer(evolution.NewFileActivePointer(revisionsDir)),
    evolution.WithHumanGate(evolution.NewCreateOnlyHoldGate()),
    evolution.WithApprovalTimeout(72*time.Hour), // 3 天后自动 promote
    // 可选:覆盖默认扫描周期 min(timeout/4, 1h)
    // evolution.WithApprovalSweepInterval(15*time.Minute),
)

扫描器以后台 goroutine 形式运行,Service.Close() 时退出。ApprovalTimeout 设为 0(默认)即关闭该机制。

openclaw YAML 中使用 duration 字符串配置:

1
2
3
4
5
6
evolution:
  enabled: true
  human_gate: "create"
  approval_timeout: "72h"
  # 可选:默认 min(approval_timeout/4, 1h)
  approval_sweep_interval: "15m"

Rollback(版本回退)

当新版本质量回退时,可将当前 active revision 回退到此前的 archived 版本:当前 active 被降级为 archived,目标 archived 被重新 promote 为 active,并立即更新 publisher,agent 下次读取时即生效。

1
2
3
4
5
6
res, err := approvalSvc.Rollback(ctx, "weather-monitor", evolution.RollbackOpts{
    Reviewer: "alice@example.com",
    Comment:  "regressed quality",
    // TargetRevisionID: "20260601T120000.000-abc123", // 可选:指定具体的 archived revision
})
fmt.Printf("rolled back %s → %s\n", res.PreviousActiveID, res.RestoredID)

TargetRevisionID 为空时,自动选择 revision store 排序中最新的 archived revision。若没有可回退的 archived revision,返回 ErrNoArchivedRevision

openclaw CLI 提供同名命令:

openclaw evolution rollback <skill-id> --dir <revisions-dir> [--revision <rev-id>] [--reviewer <id>] [--comment <text>]

自定义 Gate

实现对应接口即可接入自定义门禁:

type HumanGate interface {
    ShouldHold(ctx context.Context, rev *Revision, outcome *Outcome) (bool, error)
}

// 示例:只拦截 description 中包含敏感词的 skill
type sensitiveWordGate struct {
    words []string
}

func (g *sensitiveWordGate) ShouldHold(_ context.Context, rev *Revision, _ *Outcome) (bool, error) {
    for _, w := range g.words {
        if strings.Contains(rev.Spec.Description, w) {
            return true, nil
        }
    }
    return false, nil
}

Reconciler(确定性去重)

Reconciler 在门禁之前对 reviewer 输出执行以下规则,防止技能库膨胀:

规则 触发条件 动作
Rule 1: Strict-superset 新 skill 名是已有 skill 的 task-variant 超集(如 "Weather - 5 Cities" vs "Weather Multi-City") create → update
Rule 2: Intra-batch dedup 同一批次中 reviewer 输出多个同名/同结构 skill 保留第一个,丢弃后续
Rule 3: Quantified-sibling count-specific 名(3 Cities)对应已有 generic-parent(Multi-City create → update
Rule 4: Word-overlap 新 skill 名与已有 skill 共享 ≥50% 显著词(如 "Geopolitical Market Snapshot" vs "Geopolitical Market Analysis") create → update

所有规则都是确定性的(纯字符串操作),不消耗 LLM token。

Revision 生命周期

每次技能变更都存储为不可变 revision:

pending ──→ [SpecGate 失败]      ──→ rejected
        ──→ [SafetyGate 失败]    ──→ rejected
        ──→ [EffectivenessGate]  ──→ pending_eval
        ──→ [HumanGate 拦截]     ──→ pending_approval
        ──→ [全部通过]           ──→ active

pending_approval ──→ [批准] ──→ active
                 ──→ [拒绝] ──→ rejected

active ──→ [被新版本取代] ──→ archived(可回滚)

delete revision 被批准后会记录该删除 revision,并清空 active pointer,避免旧版本继续可见。

磁盘结构:

1
2
3
4
5
6
7
revisions/
  <skill-id>/
    revisions/
      <revision-id>/
        meta.json          ← Revision 完整快照
    active.txt             ← 当前生效的 revision ID
    audit.log              ← append-only 审计日志(JSON lines)

配置选项

服务选项

选项 说明 默认值
WithManagedSkillsDir(dir) evolution 写入 SKILL.md 的目录(Publisher 目标) 必填
WithSkillRepository(repo) 技能仓库(供 reviewer 读取已有技能做去重;应与 agent 共享同一实例) 必填
WithSkillRepositoryProvider(p) SkillScope 动态解析技能仓库(多租户隔离,见下文) nil
WithSkillScopeMode(mode) 技能隔离粒度:SkillScopeApp(按 app 共享)/ SkillScopeUser(按 app+user 隔离) SkillScopeNone(不隔离)
WithReviewPolicy(p) review 触发策略 DefaultReviewPolicy(≥4 tool calls)
WithCandidateStore(store) 不可变 revision store nil(不启用 revision 追踪)
WithActivePointer(ptr) Active revision 指针 nil
WithSpecGate(gate) 规范检查 nil
WithSafetyGate(gate) 安全扫描 nil
WithEffectivenessGate(gate) 效果评估 nil
WithHumanGate(gate) 人工审批 nil(禁用)
WithApprovalGateShadow(bool) Shadow 模式 — 评估门禁但不拦截 false
WithApprovalTimeout(d) 自动 promote 超过 d 仍在 pending_approval 的 revision 0(关闭)
WithApprovalSweepInterval(d) 自动过期扫描周期 min(timeout/4, 1h)
WithWorkerNum(n) 异步 worker 数量 1
WithQueueSize(n) 每个 worker 的 job 队列大小 10
WithExistingSkillBodyMaxChars(n) 传给 reviewer 的已有 skill body 截取长度 600
WithReviewerOptions(...) LLM reviewer 选项(temperature 等) -
WithReviewer(r) 自定义 Reviewer 实现 LLMReviewer
WithPublisher(p) 自定义 Publisher 实现 文件型 publisher

Worker 配置

Worker 在后台异步处理 learning job。当 queue 满时自动 fallback 到同步处理,确保不丢失任何 job:

evolution.WithWorkerNum(2),   // 2 个并发 worker
evolution.WithQueueSize(32),  // 每个 worker 32 job 缓冲

写入隔离

当配置了 ManagedSkillsDir 后,evolution 的写入操作遵循以下隔离规则:

  • Create:始终允许 — 新 skill 写入 ManagedSkillsDir
  • Update:只允许更新 ManagedSkillsDir 下的 skill;对 bundled 或用户手写 skill 的 update 被跳过(log warn)
  • Delete:同 update 规则

这确保 evolution 不会意外修改用户手写技能或内置技能。

多租户隔离

默认情况下(SkillScopeNone),所有 session 共享同一套技能库与 revision 目录。在多 app / 多用户场景中,可以让 evolution 按 appapp+user 维度隔离技能,避免不同租户互相污染学到的技能。

隔离粒度(SkillScopeMode

模式 说明 文件布局
SkillScopeNone(默认) 不隔离,全局共享 <root>/...
SkillScopeApp 按 app 共享,同一 app 下所有用户共用 <root>/apps/<app>/...
SkillScopeUser 按 app+user 隔离,每个用户独立技能库 <root>/users/<app>/<user>/...

SkillScope 由 session 的 AppName / UserID 推导(也可通过 LearningJob.Scope 显式指定)。app/user 标识会被做文件系统安全化处理:非法字符或过长的标识会被替换为稳定的哈希片段(id-<hash>),既避免路径穿越也保证幂等。

接入方式

技能仓库的解析通过 skill.RepositoryProvider 完成。它根据传入的 SkillScope 返回对应的 skill.Repository

// 按 scope 动态解析技能仓库(实现自行决定按 app 还是按 user 切目录)。
provider := skill.RepositoryProviderFunc(
    func(ctx context.Context, scope skill.SkillScope) (skill.Repository, error) {
        roots, err := resolveScopedRoots(scope) // 你的目录映射逻辑
        if err != nil {
            return nil, err
        }
        return skill.NewFSRepository(roots...)
    },
)

evoSvc := evolution.NewService(reviewModel,
    evolution.WithManagedSkillsDir(managedDir),
    evolution.WithSkillRepositoryProvider(provider),
    evolution.WithSkillScopeMode(skill.SkillScopeUser), // 按 app+user 隔离
    // ...
)

LLMAgent 侧使用对应的 llmagent.WithSkillRepositoryProvider / llmagent.WithSkillScopeMode,保证 agent 注入提示词时看到的技能与 evolution 写入的技能处于同一 scope。

说明:PublisherCandidateStoreActivePointer 接口本身保持简洁(不带 SkillScope 参数),由 worker 负责根据 scope 把文件型后端路由到对应子目录。因此默认的文件实现无需改动即可获得多租户隔离能力。

Metrics

通过 ApprovalGateMetricsProvider 接口读取门禁活动指标(只读快照,不暴露内部 worker 实现):

1
2
3
4
5
6
7
8
9
if mp, ok := evoSvc.(evolution.ApprovalGateMetricsProvider); ok {
    m := mp.ApprovalGateMetrics()
    fmt.Printf("Candidates seen:      %d\n", m.CandidatesSeen)
    fmt.Printf("Revisions promoted:   %d\n", m.RevisionsPromoted)
    fmt.Printf("Spec-gate rejected:   %d\n", m.SpecGateRejected)
    fmt.Printf("Safety-gate rejected: %d\n", m.SafetyGateRejected)
    fmt.Printf("Effect-gate held:     %d\n", m.EffectivenessGateRejected)
    fmt.Printf("Human-gate held:      %d\n", m.HumanGateHeld)
}

示例

参见 examples/evolution/ 获取完整可运行示例,展示:

  • 多轮任务中 skill 的自动提取和复用
  • 从冷启动到 warm-start 的演进过程
  • 质量门禁 metrics
  • 自定义 ReviewPolicy 配置