// Package evolve 实现 Agent 自进化能力. // // 战略定位 (详见 docs/evolve-strategy.md): // evolve = 领域无关引擎 + 行业专属消费层 + ML 真实数据反射器 的飞轮. // 对齐 2025 两篇权威综述 arXiv:2507.21046 和 arXiv:2508.07407, 填补 // 学术承认的 "业务 KPI 闭环系统级演化" 空白. // // 已解决 (credit assignment / 稳定性悖论 / 冷启动) 是研究级开放问题, // 不是工程 TODO. 具体见 docs/evolve-strategy.md §9 §10. // // 设计理念: // - 自进化可追溯可撤销, 每次参数变更强制带 reason 入审计链 // - 进化产物跨会话持久化, 多实现共存 (文件 / SQL / 自定义) // - 消费层审批 / 拒绝任何进化行为 (ApprovalFunc) // - 领域无关 = 全领域支持, 不内嵌任何行业 schema // // v0.1 三大 struct (保留, 未来改造为下文接口矩阵的具体实现): // 1. ToolBuilder - 运行时定义新工具 (Agent 自己写工具代码) // 2. SkillLearner - 将成功工作流保存为可复用技能 // 3. SelfReflector - 分析自身表现并自适应调整 // // v0.2+ 接口矩阵 (定义在 interfaces.go, 10 个 interface + 1 个 func): // // 核心 loop : Generator / Evaluator / Reflector / ApprovalFunc(func) // 参数管理 : ParameterStore / ParameterEvolver // 日志 / 反馈 : LogReplayer / LogSource / FeedbackChannel // 风险缓解 : ShadowRunner // // 接口之间协作关系 (fast loop): // // input → Generator → []Candidate → Evaluator → fitness 排序 → Top-K → 审批 → 执行 // // 接口之间协作关系 (slow loop): // // LogSource → LogReplayer → Reflector → ParameterEvolver.Propose // → ApprovalFunc → ParameterStore.Set package evolve import ( "context" "encoding/json" "fmt" "os" "path/filepath" "sync" "time" "git.flytoex.net/yuanwei/flyto-agent/pkg/flyto" "git.flytoex.net/yuanwei/flyto-agent/pkg/security" ) // L1326 衍生 a (2026-04-16) 重构: evolve 包直接消费 flyto.EventObserver 作为 // Observer 契约, 不再定义本地 EvolveObserver 接口. // // 历史包袱(LEGACY): 早期注释声称 "直接依赖 flyto.EventObserver 会循环依赖" 是误判 -- // flyto 是零外部依赖的契约层 (见 pkg/flyto/doc.go), 整个项目 29 个包 // import flyto, flyto 零反向 import. evolve → flyto 单向依赖完全安全, // 不构成循环. 方法集"逐字相同"+Go 结构化类型鸭子类型隐式满足的模式 // 造成了隐性 coupling 债 (一方加方法另一方不编译报错). // 现在 Evolver.observer 字段直接是 flyto.EventObserver, 契约变化编译期强制同步. // // 替代方案: 保留 EvolveObserver 作为 flyto.EventObserver 的类型别名 // (type EvolveObserver = flyto.EventObserver) - 否决, 两个名字指同一 // 类型增加读者认知负担 (与 memory 包 L1326 决策保持一致). // noopEvolveObserver 是 evolve 包的内部 noop 实现, 在 Observer 未注入时兜底. // 它自然满足 flyto.EventObserver (Event + Error 两个空方法), 不引入对 // engine.NoopObserver 的依赖 (会构成 evolve → engine 循环). type noopEvolveObserver struct{} func (n *noopEvolveObserver) Event(name string, data map[string]any) {} func (n *noopEvolveObserver) Error(err error, ctx map[string]any) {} // Evolver 是自进化系统的主控制器. // 它协调 ToolBuilder,SkillLearner,SelfReflector 三大子系统. type Evolver struct { toolBuilder *ToolBuilder skillLearner *SkillLearner reflector *SelfReflector store *EvolutionStore mu sync.RWMutex approvalFunc ApprovalFunc observer flyto.EventObserver // 可观测性接口,nil 时用 noopEvolveObserver 兜底 // autoApproveReadOnly, when true, short-circuits ApprovalFunc for // read-only evolutions (currently: EvolveNewSkill — adding a markdown // skill file, no code execution). Other evolution types (new tool, // optimize, self-adjust) still require human approval even when this // flag is set, because their content either executes code or mutates // the agent's runtime behavior. See isReadOnlyEvolution for the // authoritative predicate. // // autoApproveReadOnly 为 true 时, 对只读进化 (当前: EvolveNewSkill // -- 添加 markdown 技能文件, 不执行代码) 跳过 ApprovalFunc. 即便此 // 标志为 true, 其他进化类型 (新工具/优化/自适应) 仍要人类审批, // 因为其内容会执行代码或改变 agent 运行时行为. 权威判定见 // isReadOnlyEvolution. autoApproveReadOnly bool } // getObserver 获取 observer,nil 时返回 noop 兜底. func (e *Evolver) getObserver() flyto.EventObserver { if e.observer != nil { return e.observer } return &noopEvolveObserver{} } // ApprovalFunc 是进化行为的审批回调. // 消费层实现此函数来决定是否批准 Agent 的自进化行为. // 这是安全边界 -- Agent 不能在没有人类审批的情况下改造自己. // 精妙之处(CLEVER): 进化审批回调--Agent 不能自行决定是否进化,必须经过人类审批. // 这是整个自进化系统的安全边界:即使 Agent 写出了完美的工具代码, // 没有人类批准就不会被注册到引擎中.approvalFunc 为 nil 时自动拒绝所有提案(安全默认值). type ApprovalFunc func(ctx context.Context, proposal *EvolutionProposal) (bool, error) // EvolutionProposal 是一个进化提案. // Agent 想要进化时,先生成提案,等待审批后才执行. type EvolutionProposal struct { ID string `json:"id"` Type EvolutionType `json:"type"` Title string `json:"title"` Description string `json:"description"` Rationale string `json:"rationale"` // 为什么需要这个进化 Content any `json:"content"` // 具体内容(工具定义/技能/配置变更) CreatedAt time.Time `json:"created_at"` Status ProposalStatus `json:"status"` } // EvolutionType 是进化类型枚举. type EvolutionType string const ( EvolveNewTool EvolutionType = "new_tool" // 创建新工具 EvolveNewSkill EvolutionType = "new_skill" // 学习新技能 EvolveOptimize EvolutionType = "optimize" // 优化现有工作流 EvolveSelfAdjust EvolutionType = "self_adjust" // 自适应调整 ) // ProposalStatus 是提案状态. type ProposalStatus string const ( StatusPending ProposalStatus = "pending" StatusApproved ProposalStatus = "approved" StatusRejected ProposalStatus = "rejected" StatusApplied ProposalStatus = "applied" ) // Config 是 Evolver 的配置. type Config struct { // StoreDir 进化产物的存储目录 StoreDir string // ApprovalFunc 审批回调(nil 则自动拒绝所有提案) ApprovalFunc ApprovalFunc // AutoApproveReadOnly, when true, lets read-only evolutions bypass // ApprovalFunc. "Read-only" means the evolution only adds declarative // content that the engine will read later -- currently limited to // EvolveNewSkill (a markdown skill file). Code-executing evolutions // (EvolveNewTool), workflow mutations (EvolveOptimize), and runtime // self-tuning (EvolveSelfAdjust) are NEVER auto-approved by this flag, // because their content can escape the read-only boundary. // // This is a security ergonomics trade-off: skill learning is the // high-frequency low-risk path (agent reads its own past lessons), // and routing every skill proposal through a human gate either // trains operators to click-through blindly (worse than automation) // or blocks the loop entirely. Everything else stays locked. // // AutoApproveReadOnly 为 true 时, 只读进化绕过 ApprovalFunc. "只读" // 指进化只添加引擎稍后会读的声明性内容 -- 当前仅限 EvolveNewSkill // (markdown 技能文件). 会执行代码的进化 (EvolveNewTool) / 改工作流 // (EvolveOptimize) / 运行时自调 (EvolveSelfAdjust) 永不被此标志 // 自动批准, 因其内容可能突破只读边界. // // 这是安全人机工程权衡: 学技能是高频低风险路径 (agent 读自己以往 // 教训), 每条技能提案过人工必导致运维盲点"无脑通过" (比自动化还糟) // 或直接阻塞 loop. 其他全留锁. AutoApproveReadOnly bool // MaxToolsPerSession 单次会话最多创建的工具数 MaxToolsPerSession int // MaxSkillsPerSession 单次会话最多学习的技能数 MaxSkillsPerSession int // Observer 可观测性接口(可选). // 升华改进(ELEVATED): 通过构造函数注入--进化系统的生命周期与 Engine 一致, // 不会在运行中更换 observer,所以构造时注入最简洁. // 替代方案:SetObserver Setter 注入(多一个状态变更点,不如构造时一次性注入清晰). Observer flyto.EventObserver // SecretGuard 秘密扫描(可选). // 设置后,ToolBuilder 在保存工具脚本前扫描内容,阻止含 API key 的脚本持久化. // nil 时不扫描(向后兼容). SecretGuard security.SecretGuard } // NewEvolver 创建自进化系统. func NewEvolver(cfg *Config) (*Evolver, error) { if cfg.StoreDir == "" { return nil, fmt.Errorf("evolve: StoreDir is required") } if cfg.MaxToolsPerSession <= 0 { cfg.MaxToolsPerSession = 5 } if cfg.MaxSkillsPerSession <= 0 { cfg.MaxSkillsPerSession = 10 } store, err := NewEvolutionStore(cfg.StoreDir) if err != nil { return nil, fmt.Errorf("evolve: init store: %w", err) } var tb *ToolBuilder if cfg.SecretGuard != nil { tb = NewToolBuilderWithGuard(store, cfg.MaxToolsPerSession, cfg.SecretGuard) } else { tb = NewToolBuilder(store, cfg.MaxToolsPerSession) } return &Evolver{ toolBuilder: tb, skillLearner: NewSkillLearner(store, cfg.MaxSkillsPerSession), reflector: NewSelfReflector(store), store: store, approvalFunc: cfg.ApprovalFunc, observer: cfg.Observer, autoApproveReadOnly: cfg.AutoApproveReadOnly, }, nil } // isReadOnlyEvolution reports whether a proposal adds only declarative // content the engine reads later, with no code execution or runtime // mutation. Currently only EvolveNewSkill qualifies: it writes a markdown // file that the agent reads when choosing its next action — no shell // commands, no hook execution, no config flip. // // The predicate is deliberately narrow. EvolveNewTool cannot qualify even // if the tool self-declares ReadOnly=true, because the tool's generated // code still runs inside the process (and a "read-only" tool with a bug // in its argument parsing can still leak info or crash the engine). If a // future proposal type is introduced that is provably content-only, add // it here with a justification comment — never widen by default. // // isReadOnlyEvolution 报告提案是否只添加引擎稍后读的声明性内容, 不执行 // 代码也不改运行时. 当前仅 EvolveNewSkill 符合: 它写一个 markdown 文件, // agent 选下一步行动时读 -- 无 shell 命令, 无 hook 执行, 无 config 翻转. // // 此判定刻意收窄. EvolveNewTool 即便工具自声明 ReadOnly=true 也不符合, // 因为工具生成的代码仍在进程内运行 (一个声称"只读"的工具若参数解析有 // bug 仍可能泄露信息或崩引擎). 未来新增可证只内容的提案类型时,在此 // 添加并带理由注释 -- 永不默认放宽. func isReadOnlyEvolution(proposal *EvolutionProposal) bool { if proposal == nil { return false } return proposal.Type == EvolveNewSkill } // Propose 提交一个进化提案. // Agent 调用此方法表达"我想进化"的意图,由审批流程决定是否执行. func (e *Evolver) Propose(ctx context.Context, proposal *EvolutionProposal) error { obs := e.getObserver() e.mu.Lock() proposal.Status = StatusPending proposal.CreatedAt = time.Now() if proposal.ID == "" { proposal.ID = fmt.Sprintf("evo_%d", time.Now().UnixNano()) } e.mu.Unlock() // 持久化提案 if err := e.store.SaveProposal(proposal); err != nil { obs.Error(err, map[string]any{ "type": string(proposal.Type), "phase": "save_proposal", }) return fmt.Errorf("evolve: save proposal: %w", err) } // 埋点说明:提案提交是自进化系统的核心审计事件-- // 每个进化意图都要记录,用于回溯 Agent 的自主决策历史. obs.Event("evolution_proposed", map[string]any{ "type": string(proposal.Type), "title": proposal.Title, }) // 审批: autoApproveReadOnly short-circuit 优先 // (EvolveNewSkill 只写 markdown, 无代码执行; 见 isReadOnlyEvolution), // 否则走 approvalFunc (nil 即默认拒绝, 安全默认). // // 埋点说明: evolution_auto_approved 与 evolution_approved 分离, 便于 // 安全审计区分自动批准路径 vs 人工批准路径. 出现异常时运维可按事件 // 名过滤 autoApproveReadOnly 翻 true 之后是否出现越权 (例如误把 // EvolveNewTool 归入 read-only). // // Approval: autoApproveReadOnly short-circuit first (EvolveNewSkill // only writes markdown, no code execution; see isReadOnlyEvolution), // otherwise fall through to approvalFunc (nil rejects by default). // Separating evolution_auto_approved from evolution_approved lets // security audit distinguish auto-approval path from human-approval // path; filtering by event name surfaces unexpected auto-approvals // (e.g. a regression that misclassifies EvolveNewTool as read-only). approved := false if e.autoApproveReadOnly && isReadOnlyEvolution(proposal) { approved = true obs.Event("evolution_auto_approved", map[string]any{ "type": string(proposal.Type), "title": proposal.Title, "reason": "auto_approve_read_only", }) } else if e.approvalFunc != nil { var err error approved, err = e.approvalFunc(ctx, proposal) if err != nil { proposal.Status = StatusRejected e.store.SaveProposal(proposal) obs.Error(err, map[string]any{ "type": string(proposal.Type), "phase": "approval", }) return fmt.Errorf("evolve: approval: %w", err) } } if !approved { proposal.Status = StatusRejected e.store.SaveProposal(proposal) // 埋点说明:提案拒绝需要追踪--频繁拒绝说明 Agent 的进化意图与人类预期不符, // 需要调整 Agent 的进化策略或审批标准. obs.Event("evolution_rejected", map[string]any{ "type": string(proposal.Type), "title": proposal.Title, }) return nil } proposal.Status = StatusApproved // 埋点说明:提案批准是安全审计的关键节点-- // 批准的进化会实际改变 Agent 的能力,必须记录以备事后审查. obs.Event("evolution_approved", map[string]any{ "type": string(proposal.Type), "title": proposal.Title, }) // 执行进化 if err := e.apply(ctx, proposal); err != nil { // 埋点说明:进化执行失败需要告警--批准的提案执行失败 // 说明提案内容有问题或运行时环境异常,需要人工排查. obs.Error(err, map[string]any{ "type": string(proposal.Type), "phase": "apply", }) return fmt.Errorf("evolve: apply: %w", err) } proposal.Status = StatusApplied return e.store.SaveProposal(proposal) } // apply 执行已批准的进化提案. func (e *Evolver) apply(ctx context.Context, proposal *EvolutionProposal) error { obs := e.getObserver() switch proposal.Type { case EvolveNewTool: if err := e.toolBuilder.Apply(ctx, proposal); err != nil { return err } // 埋点说明:工具创建成功是 Agent 能力扩展的里程碑事件-- // 新工具会改变 Agent 后续的行为模式,必须记录以便审计和回溯. if def, ok := extractToolDef(proposal); ok { obs.Event("tool_created", map[string]any{ "name": def.Name, "exec_type": string(def.ExecutionType), }) } return nil case EvolveNewSkill: if err := e.skillLearner.Apply(ctx, proposal); err != nil { return err } // 埋点说明:技能学习成功表示 Agent 发现了可复用的工作流模式-- // 追踪学习频率和成功率用于评估自进化系统的有效性. if def, ok := extractSkillDef(proposal); ok { obs.Event("skill_learned", map[string]any{ "name": def.Name, "success_rate": def.SuccessRate, }) } return nil case EvolveOptimize, EvolveSelfAdjust: if err := e.reflector.Apply(ctx, proposal); err != nil { return err } // 埋点说明:反思记录是 Agent 自我认知能力的指标-- // 观察数量和教训数量反映 Agent 的"元认知"深度. if ref, ok := extractReflection(proposal); ok { obs.Event("reflection_saved", map[string]any{ "type": string(ref.Type), "observations": len(ref.Observations), "lessons": countLessons(ref), }) } return nil default: return fmt.Errorf("unknown evolution type: %s", proposal.Type) } } // extractToolDef 从提案中提取工具定义(best-effort,不影响主流程). func extractToolDef(proposal *EvolutionProposal) (*ToolDefinition, bool) { data, err := json.Marshal(proposal.Content) if err != nil { return nil, false } var def ToolDefinition if err := json.Unmarshal(data, &def); err != nil { return nil, false } return &def, def.Name != "" } // extractSkillDef 从提案中提取技能定义(best-effort). func extractSkillDef(proposal *EvolutionProposal) (*SkillDefinition, bool) { data, err := json.Marshal(proposal.Content) if err != nil { return nil, false } var def SkillDefinition if err := json.Unmarshal(data, &def); err != nil { return nil, false } return &def, def.Name != "" } // extractReflection 从提案中提取反思记录(best-effort). func extractReflection(proposal *EvolutionProposal) (*Reflection, bool) { data, err := json.Marshal(proposal.Content) if err != nil { return nil, false } var ref Reflection if err := json.Unmarshal(data, &ref); err != nil { return nil, false } return &ref, true } // countLessons 统计反思记录中的教训数量. func countLessons(ref *Reflection) int { count := 0 for _, adj := range ref.Adjustments { if adj.Type == AdjustLesson { count++ } } return count } // ToolBuilder 返回工具构建器(供 Engine 集成用). func (e *Evolver) ToolBuilder() *ToolBuilder { return e.toolBuilder } // SkillLearner 返回技能学习器. func (e *Evolver) SkillLearner() *SkillLearner { return e.skillLearner } // Reflector 返回自反思器. func (e *Evolver) Reflector() *SelfReflector { return e.reflector } // History 返回进化历史. func (e *Evolver) History() ([]*EvolutionProposal, error) { return e.store.ListProposals() } // EvolutionStore 是进化产物的持久化存储. type EvolutionStore struct { dir string } // NewEvolutionStore 创建存储. func NewEvolutionStore(dir string) (*EvolutionStore, error) { // 创建目录结构 for _, sub := range []string{"proposals", "tools", "skills", "reflections"} { if err := os.MkdirAll(filepath.Join(dir, sub), 0755); err != nil { return nil, err } } return &EvolutionStore{dir: dir}, nil } // SaveProposal 保存提案到磁盘. func (s *EvolutionStore) SaveProposal(p *EvolutionProposal) error { data, err := json.MarshalIndent(p, "", " ") if err != nil { return err } path := filepath.Join(s.dir, "proposals", p.ID+".json") return os.WriteFile(path, data, 0644) } // ListProposals 列出所有提案. func (s *EvolutionStore) ListProposals() ([]*EvolutionProposal, error) { dir := filepath.Join(s.dir, "proposals") entries, err := os.ReadDir(dir) if err != nil { return nil, err } var proposals []*EvolutionProposal for _, entry := range entries { if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" { continue } data, err := os.ReadFile(filepath.Join(dir, entry.Name())) if err != nil { continue } var p EvolutionProposal if err := json.Unmarshal(data, &p); err != nil { continue } proposals = append(proposals, &p) } return proposals, nil }