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

@ -1,2 +1,6 @@
"""Run records."""
from .models import RunOutcome, RunRecord, SkillEffectRecord
from .store import RunMemoryStore
__all__ = ["RunMemoryStore", "RunOutcome", "RunRecord", "SkillEffectRecord"]

View File

@ -0,0 +1,142 @@
"""Run-level receipts and skill effect records."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
from beaver.skills.specs import SkillActivationReceipt
@dataclass(slots=True)
class RunOutcome:
success: bool
finish_reason: str
feedback_score: float | None = None
notes: str = ""
def to_dict(self) -> dict[str, Any]:
return {
"success": self.success,
"finish_reason": self.finish_reason,
"feedback_score": self.feedback_score,
"notes": self.notes,
}
@classmethod
def from_dict(cls, payload: dict[str, Any]) -> "RunOutcome":
return cls(
success=bool(payload.get("success")),
finish_reason=str(payload.get("finish_reason") or ""),
feedback_score=_coerce_optional_float(payload.get("feedback_score")),
notes=str(payload.get("notes") or ""),
)
@dataclass(slots=True)
class RunRecord:
run_id: str
session_id: str
task_text: str
started_at: str
ended_at: str
success: bool
finish_reason: str
feedback: dict[str, Any] = field(default_factory=dict)
activated_skills: list[SkillActivationReceipt] = field(default_factory=list)
task_id: str | None = None
attempt_index: int | None = None
validation_result: dict[str, Any] | None = None
def to_dict(self) -> dict[str, Any]:
return {
"run_id": self.run_id,
"session_id": self.session_id,
"task_id": self.task_id,
"attempt_index": self.attempt_index,
"task_text": self.task_text,
"started_at": self.started_at,
"ended_at": self.ended_at,
"success": self.success,
"finish_reason": self.finish_reason,
"feedback": dict(self.feedback),
"activated_skills": [receipt.to_dict() for receipt in self.activated_skills],
"validation_result": self.validation_result,
}
@classmethod
def from_dict(cls, payload: dict[str, Any]) -> "RunRecord":
return cls(
run_id=str(payload["run_id"]),
session_id=str(payload["session_id"]),
task_id=_coerce_optional_str(payload.get("task_id")),
attempt_index=_coerce_optional_int(payload.get("attempt_index")),
task_text=str(payload.get("task_text") or ""),
started_at=str(payload.get("started_at") or ""),
ended_at=str(payload.get("ended_at") or ""),
success=bool(payload.get("success")),
finish_reason=str(payload.get("finish_reason") or ""),
feedback=dict(payload.get("feedback") or {}),
activated_skills=[
SkillActivationReceipt.from_dict(item)
for item in payload.get("activated_skills") or []
if isinstance(item, dict)
],
validation_result=(
dict(payload["validation_result"])
if isinstance(payload.get("validation_result"), dict)
else None
),
)
@dataclass(slots=True)
class SkillEffectRecord:
run_id: str
skill_name: str
skill_version: str
success: bool
feedback_score: float | None
notes: str
created_at: str
def to_dict(self) -> dict[str, Any]:
return {
"run_id": self.run_id,
"skill_name": self.skill_name,
"skill_version": self.skill_version,
"success": self.success,
"feedback_score": self.feedback_score,
"notes": self.notes,
"created_at": self.created_at,
}
@classmethod
def from_dict(cls, payload: dict[str, Any]) -> "SkillEffectRecord":
return cls(
run_id=str(payload["run_id"]),
skill_name=str(payload["skill_name"]),
skill_version=str(payload["skill_version"]),
success=bool(payload.get("success")),
feedback_score=_coerce_optional_float(payload.get("feedback_score")),
notes=str(payload.get("notes") or ""),
created_at=str(payload.get("created_at") or ""),
)
def _coerce_optional_float(value: Any) -> float | None:
if value in (None, ""):
return None
return float(value)
def _coerce_optional_int(value: Any) -> int | None:
if value in (None, ""):
return None
return int(value)
def _coerce_optional_str(value: Any) -> str | None:
if value in (None, ""):
return None
return str(value)

View File

@ -0,0 +1,98 @@
"""File-backed run receipt store."""
from __future__ import annotations
import json
from pathlib import Path
from .models import RunRecord, SkillEffectRecord
class RunMemoryStore:
def __init__(self, root: str | Path) -> None:
self.root = Path(root)
self.root.mkdir(parents=True, exist_ok=True)
self.runs_path = self.root / "runs.jsonl"
self.effects_path = self.root / "skill-effects.jsonl"
def append_run_record(self, record: RunRecord) -> None:
self._append_jsonl(self.runs_path, record.to_dict())
def update_run_record(self, run_id: str, **updates: object) -> RunRecord | None:
records = self.list_runs()
updated: RunRecord | None = None
for index, record in enumerate(records):
if record.run_id != run_id:
continue
payload = record.to_dict()
payload.update(updates)
updated = RunRecord.from_dict(payload)
records[index] = updated
break
if updated is None:
return None
self.runs_path.parent.mkdir(parents=True, exist_ok=True)
self.runs_path.write_text(
"".join(
json.dumps(record.to_dict(), ensure_ascii=False, sort_keys=True) + "\n"
for record in records
),
encoding="utf-8",
)
return updated
def append_skill_effect(self, effect: SkillEffectRecord) -> None:
self._append_jsonl(self.effects_path, effect.to_dict())
def list_runs(self) -> list[RunRecord]:
return [RunRecord.from_dict(item) for item in self._read_jsonl(self.runs_path)]
def list_runs_by_skill(self, skill_name: str, version: str | None = None, limit: int | None = None) -> list[RunRecord]:
results: list[RunRecord] = []
for record in self.list_runs():
matched = False
for receipt in record.activated_skills:
if receipt.skill_name != skill_name:
continue
if version is not None and receipt.skill_version != version:
continue
matched = True
break
if matched:
results.append(record)
if limit is not None:
return results[-limit:]
return results
def list_skill_effects(self, skill_name: str, version: str | None = None, limit: int | None = None) -> list[SkillEffectRecord]:
results: list[SkillEffectRecord] = []
for payload in self._read_jsonl(self.effects_path):
effect = SkillEffectRecord.from_dict(payload)
if effect.skill_name != skill_name:
continue
if version is not None and effect.skill_version != version:
continue
results.append(effect)
if limit is not None:
return results[-limit:]
return results
@staticmethod
def _append_jsonl(path: Path, payload: dict) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("a", encoding="utf-8") as handle:
handle.write(json.dumps(payload, ensure_ascii=False, sort_keys=True) + "\n")
@staticmethod
def _read_jsonl(path: Path) -> list[dict]:
if not path.exists():
return []
results: list[dict] = []
for line in path.read_text(encoding="utf-8").splitlines():
cleaned = line.strip()
if not cleaned:
continue
payload = json.loads(cleaned)
if isinstance(payload, dict):
results.append(payload)
return results