feat(beaver): 完成Task Team功能v1实现,重构后端架构支持统一内核

新增内部Task系统,包括验证、反馈门控机制,实现自动质量验证
(通过率>=0.75)和用户反馈闭环(satisfied/revise/abandon)。

实现Agent Team v1协调器,支持sequence/parallel/dag执行策略,
sub-agent复用主AgentLoop,每个run使用独立memory snapshot。

建立Skill学习pipeline,包含draft/审核/发布/回滚完整生命周期,
通过Task验证通过且用户满意才生成学习候选。

重构目录结构,移除third_party依赖,建立统一engine内核,
所有agent共享运行时基础组件。

更新ContextBuilder清理provider消息字段,增强SkillContext版本管理,
集成TaskExecutionPlanner和TaskSkillResolver实现技能解析机制。
This commit is contained in:
2026-05-08 17:14:14 +08:00
parent 5ba5c7e4c1
commit 8a12c30141
93 changed files with 16724 additions and 1247 deletions

View File

@ -0,0 +1,76 @@
"""Evidence selection for skill learning."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
from beaver.engine.session.manager import SessionManager
from beaver.memory.runs.store import RunMemoryStore
@dataclass(slots=True)
class EvidencePacket:
run_ids: list[str]
session_ids: list[str]
task_summaries: list[str]
session_excerpts: list[str]
metadata: dict[str, Any] = field(default_factory=dict)
class EvidenceSelector:
def __init__(self, run_store: RunMemoryStore, session_manager: SessionManager | None = None) -> None:
self.run_store = run_store
self.session_manager = session_manager
def select_runs_for_revision(self, skill_name: str, version: str, limit: int = 5) -> list[str]:
runs = self.run_store.list_runs_by_skill(skill_name, version=version, limit=limit)
return [record.run_id for record in runs]
def select_runs_for_new_skill(self, theme: str, limit: int = 5) -> list[str]:
lowered = theme.lower().strip()
matches = []
for record in self.run_store.list_runs():
if lowered and lowered not in record.task_text.lower():
continue
matches.append(record.run_id)
return matches[-limit:]
def build_evidence_packet(self, run_ids: list[str], session_ids: list[str] | None = None) -> EvidencePacket:
runs_by_id = {record.run_id: record for record in self.run_store.list_runs()}
resolved_run_ids: list[str] = []
resolved_session_ids: list[str] = list(dict.fromkeys(session_ids or []))
task_summaries: list[str] = []
session_excerpts: list[str] = []
for run_id in run_ids:
record = runs_by_id.get(run_id)
if record is None:
continue
resolved_run_ids.append(run_id)
if record.session_id not in resolved_session_ids:
resolved_session_ids.append(record.session_id)
summary = record.task_text.strip()
if summary:
task_summaries.append(summary[:400])
if self.session_manager is not None:
excerpt = self._session_excerpt(record.session_id, run_id)
if excerpt:
session_excerpts.append(excerpt)
return EvidencePacket(
run_ids=resolved_run_ids,
session_ids=resolved_session_ids,
task_summaries=task_summaries[:8],
session_excerpts=session_excerpts[:6],
metadata={"bounded": True},
)
def _session_excerpt(self, session_id: str, run_id: str) -> str:
if self.session_manager is None:
return ""
events = self.session_manager.get_run_event_records(session_id, run_id)
visible: list[str] = []
for event in events:
if not event.context_visible or not event.content:
continue
visible.append(f"{event.role}: {event.content.strip()}")
return "\n".join(visible[:12])[:2000]