feat: 移除backend-old目录中的废弃文件
移除以下文件: - .dockerignore 和 .gitignore 配置文件 - A2A_Multiagent_change.md 设计文档 - COMMUNICATION.md 通讯信息文档 - Dockerfile 构建配置 - LICENSE 许可证文件 这些文件属于旧版本后端代码,不再需要维护。
This commit is contained in:
@ -1,2 +0,0 @@
|
||||
"""Plugin system for Beaver."""
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
"""Plugin extension hooks."""
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
"""Plugin loading hooks."""
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
"""Plugin registry."""
|
||||
|
||||
@ -1,262 +0,0 @@
|
||||
"""Import no-credential Hermes Agent skills into Beaver."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from beaver.skills.catalog.utils import parse_frontmatter, strip_frontmatter
|
||||
from beaver.skills.specs import SkillSpec, SkillSpecStore, SkillVersion
|
||||
from beaver.skills.specs.serialization import canonical_hash, normalize_frontmatter, summarize_skill_content
|
||||
|
||||
|
||||
HERMES_REPO_URL = "https://github.com/NousResearch/hermes-agent"
|
||||
|
||||
_CREDENTIAL_PATTERNS = [
|
||||
re.compile(pattern, re.IGNORECASE)
|
||||
for pattern in [
|
||||
r"\bapi[_ -]?key\b",
|
||||
r"\boauth\b",
|
||||
r"\bbearer\s+token\b",
|
||||
r"\baccess[_ -]?token\b",
|
||||
r"\bclient[_ -]?secret\b",
|
||||
r"\bsecret\b",
|
||||
r"\bcredential",
|
||||
r"\bspotify\b",
|
||||
r"\bdiscord\b",
|
||||
r"\bfeishu\b",
|
||||
r"\bhome\s*assistant\b",
|
||||
r"\bfal\b",
|
||||
r"\bopenrouter\b",
|
||||
r"\bwandb\b",
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class HermesMigrationService:
|
||||
store: SkillSpecStore
|
||||
manifest_path: Path | None = None
|
||||
included_tools: list[dict[str, Any]] = field(default_factory=list)
|
||||
skipped_tools: list[dict[str, Any]] = field(default_factory=list)
|
||||
|
||||
def migrate(
|
||||
self,
|
||||
repo_path: str | Path,
|
||||
*,
|
||||
include_optional: bool = True,
|
||||
dry_run: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
repo = Path(repo_path)
|
||||
if not repo.exists():
|
||||
raise ValueError(f"Hermes repository not found: {repo}")
|
||||
skill_files = self._discover_skill_files(repo, include_optional=include_optional)
|
||||
included: list[dict[str, Any]] = []
|
||||
skipped: list[dict[str, Any]] = []
|
||||
for skill_file in skill_files:
|
||||
result = self._migrate_skill(repo, skill_file, dry_run=dry_run)
|
||||
if result["status"] in {"included", "unchanged"}:
|
||||
included.append(result)
|
||||
else:
|
||||
skipped.append(result)
|
||||
manifest = {
|
||||
"source": "hermes-agent",
|
||||
"repo_url": HERMES_REPO_URL,
|
||||
"repo_path": str(repo),
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"dry_run": dry_run,
|
||||
"included": included,
|
||||
"skipped": skipped,
|
||||
"tools": self._tool_manifest(),
|
||||
}
|
||||
path = self.manifest_path or (self.store.workspace / "hermes_migration_manifest.json")
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
return manifest
|
||||
|
||||
def _discover_skill_files(self, repo: Path, *, include_optional: bool) -> list[Path]:
|
||||
roots = [repo / "skills"]
|
||||
if include_optional:
|
||||
roots.append(repo / "optional-skills")
|
||||
files: list[Path] = []
|
||||
for root in roots:
|
||||
if root.exists():
|
||||
files.extend(sorted(root.glob("**/SKILL.md")))
|
||||
return files
|
||||
|
||||
def _migrate_skill(self, repo: Path, skill_file: Path, *, dry_run: bool) -> dict[str, Any]:
|
||||
relative = skill_file.relative_to(repo)
|
||||
content = skill_file.read_text(encoding="utf-8")
|
||||
frontmatter, body = parse_frontmatter(content)
|
||||
skill_name = _safe_skill_name(str(frontmatter.get("name") or skill_file.parent.name))
|
||||
if not skill_name:
|
||||
return _skip(relative, "unsafe_skill_name")
|
||||
credential_reason = _credential_reason(content)
|
||||
if credential_reason:
|
||||
return _skip(relative, credential_reason, skill_name=skill_name)
|
||||
normalized = normalize_frontmatter(
|
||||
{
|
||||
**frontmatter,
|
||||
"name": skill_name,
|
||||
"description": frontmatter.get("description") or skill_name,
|
||||
}
|
||||
)
|
||||
rendered = _render_skill_content(normalized, body)
|
||||
content_hash = canonical_hash(rendered)
|
||||
existing = self.store.read_published_skill(skill_name)
|
||||
existing_spec = self.store.get_skill_spec(skill_name)
|
||||
if existing is not None and existing.version.content_hash == content_hash:
|
||||
return {
|
||||
"status": "unchanged",
|
||||
"skill_name": skill_name,
|
||||
"version": existing.version.version,
|
||||
"path": str(relative),
|
||||
"reason": "same_content_hash",
|
||||
}
|
||||
next_version = self._next_version(skill_name)
|
||||
if dry_run:
|
||||
return {
|
||||
"status": "included",
|
||||
"skill_name": skill_name,
|
||||
"version": next_version,
|
||||
"path": str(relative),
|
||||
"dry_run": True,
|
||||
}
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
skill_version = SkillVersion(
|
||||
skill_name=skill_name,
|
||||
version=next_version,
|
||||
content_hash=content_hash,
|
||||
summary_hash=canonical_hash(strip_frontmatter(rendered).strip()),
|
||||
created_at=now,
|
||||
created_by="hermes_migration",
|
||||
change_reason=f"Import Hermes skill {relative}",
|
||||
parent_version=existing.version.version if existing is not None else None,
|
||||
review_state="published",
|
||||
frontmatter=normalized,
|
||||
summary=summarize_skill_content(body),
|
||||
tool_hints=self.store._extract_tool_hints(normalized),
|
||||
provenance={
|
||||
"source": "hermes-agent",
|
||||
"repo_url": HERMES_REPO_URL,
|
||||
"repo_path": str(repo),
|
||||
"relative_path": str(relative),
|
||||
},
|
||||
)
|
||||
self.store.write_skill_version(skill_version, rendered)
|
||||
self._copy_supporting_files(skill_file.parent, skill_name, next_version)
|
||||
spec = existing_spec or SkillSpec(
|
||||
name=skill_name,
|
||||
display_name=skill_name,
|
||||
description=str(normalized.get("description") or skill_name),
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
current_version=next_version,
|
||||
status="active",
|
||||
tags=[],
|
||||
owners=["hermes-agent"],
|
||||
source_kind="hermes-agent",
|
||||
lineage=[],
|
||||
)
|
||||
spec.current_version = next_version
|
||||
spec.updated_at = now
|
||||
spec.status = "active"
|
||||
spec.source_kind = "hermes-agent"
|
||||
if "hermes-agent" not in spec.owners:
|
||||
spec.owners.append("hermes-agent")
|
||||
self.store.write_skill_spec(spec)
|
||||
self.store.set_current_version(skill_name, next_version)
|
||||
published = self.store.read_index("published")
|
||||
if skill_name not in published:
|
||||
published.append(skill_name)
|
||||
self.store.update_index("published", published)
|
||||
return {
|
||||
"status": "included",
|
||||
"skill_name": skill_name,
|
||||
"version": next_version,
|
||||
"path": str(relative),
|
||||
}
|
||||
|
||||
def _copy_supporting_files(self, source_dir: Path, skill_name: str, version: str) -> None:
|
||||
target_root = self.store.root / skill_name / "versions" / version
|
||||
for source in sorted(source_dir.rglob("*")):
|
||||
if not source.is_file() or source.name == "SKILL.md" or source.is_symlink():
|
||||
continue
|
||||
relative = source.relative_to(source_dir)
|
||||
if any(part in {"", ".", ".."} for part in relative.parts):
|
||||
continue
|
||||
target = target_root / relative
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copyfile(source, target)
|
||||
|
||||
def _next_version(self, skill_name: str) -> str:
|
||||
versions = [item for item in self.store.list_versions(skill_name) if item.startswith("v")]
|
||||
numbers = [int(item[1:]) for item in versions if item[1:].isdigit()]
|
||||
return f"v{(max(numbers) if numbers else 0) + 1:04d}"
|
||||
|
||||
def _tool_manifest(self) -> dict[str, list[dict[str, Any]]]:
|
||||
included = self.included_tools or [
|
||||
{"name": "todo", "reason": "implemented_builtin_no_api"},
|
||||
{"name": "clarify", "reason": "implemented_builtin_no_api"},
|
||||
{"name": "delegate", "reason": "implemented_builtin_no_api"},
|
||||
{"name": "spawn", "reason": "implemented_builtin_no_api"},
|
||||
{"name": "skills_list", "reason": "implemented_builtin_no_api"},
|
||||
{"name": "skill_manage", "reason": "implemented_builtin_no_api"},
|
||||
{"name": "terminal", "reason": "implemented_builtin_no_api"},
|
||||
{"name": "process", "reason": "implemented_builtin_no_api"},
|
||||
{"name": "patch", "reason": "implemented_builtin_no_api"},
|
||||
{"name": "write_file", "reason": "implemented_builtin_no_api"},
|
||||
{"name": "web_fetch", "reason": "implemented_builtin_no_api"},
|
||||
{"name": "web_search", "reason": "implemented_builtin_no_api"},
|
||||
{"name": "execute_code", "reason": "implemented_builtin_no_api"},
|
||||
]
|
||||
skipped = self.skipped_tools or [
|
||||
{"name": "spotify", "reason": "requires_oauth"},
|
||||
{"name": "discord", "reason": "requires_external_token"},
|
||||
{"name": "feishu", "reason": "requires_external_token"},
|
||||
{"name": "home_assistant", "reason": "requires_external_service_credentials"},
|
||||
{"name": "fal_image_generation", "reason": "requires_api_key"},
|
||||
{"name": "remote_web_providers", "reason": "requires_api_key_or_oauth"},
|
||||
]
|
||||
return {"included": included, "skipped": skipped}
|
||||
|
||||
|
||||
def _credential_reason(content: str) -> str | None:
|
||||
for pattern in _CREDENTIAL_PATTERNS:
|
||||
if pattern.search(content):
|
||||
return "requires_external_credentials"
|
||||
return None
|
||||
|
||||
|
||||
def _safe_skill_name(value: str) -> str:
|
||||
cleaned = value.strip().replace(" ", "-")
|
||||
if not cleaned or cleaned in {".", ".."} or "/" in cleaned or "\\" in cleaned:
|
||||
return ""
|
||||
if not re.fullmatch(r"[A-Za-z0-9_.-]+", cleaned):
|
||||
return ""
|
||||
return cleaned
|
||||
|
||||
|
||||
def _skip(relative: Path, reason: str, *, skill_name: str | None = None) -> dict[str, Any]:
|
||||
result = {"status": "skipped", "path": str(relative), "reason": reason}
|
||||
if skill_name:
|
||||
result["skill_name"] = skill_name
|
||||
return result
|
||||
|
||||
|
||||
def _render_skill_content(frontmatter: dict[str, Any], body: str) -> str:
|
||||
lines = ["---"]
|
||||
for key, value in normalize_frontmatter(frontmatter).items():
|
||||
if isinstance(value, list):
|
||||
lines.append(f"{key}:")
|
||||
for item in value:
|
||||
lines.append(f" - {item}")
|
||||
else:
|
||||
lines.append(f"{key}: {value}")
|
||||
lines.extend(["---", "", body.strip()])
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
@ -1,208 +0,0 @@
|
||||
"""Import legacy and staged skills into the Beaver SkillSpecStore."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
import io
|
||||
import json
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from beaver.skills.catalog.utils import parse_frontmatter, strip_frontmatter
|
||||
from beaver.skills.specs import SkillSpec, SkillSpecStore, SkillVersion
|
||||
from beaver.skills.specs.serialization import canonical_hash, normalize_frontmatter, summarize_skill_content
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class SkillMigrationService:
|
||||
store: SkillSpecStore
|
||||
repo_root: Path | None = None
|
||||
|
||||
def migrate_all(self) -> dict[str, Any]:
|
||||
included: list[dict[str, Any]] = []
|
||||
skipped: list[dict[str, Any]] = []
|
||||
for path in self._backend_old_skills():
|
||||
self._migrate_skill_file(path, "backend-old", included, skipped)
|
||||
for path in self._staged_skills():
|
||||
self._migrate_skill_file(path, "stevenli-staged", included, skipped)
|
||||
for path in self._skill_zips():
|
||||
self._migrate_zip(path, included, skipped)
|
||||
manifest = {
|
||||
"generated_at": _now(),
|
||||
"workspace": str(self.store.workspace),
|
||||
"included": included,
|
||||
"skipped": skipped,
|
||||
}
|
||||
manifest_path = self.store.workspace / "skill_migration_manifest.json"
|
||||
manifest_path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
return manifest
|
||||
|
||||
def _backend_old_skills(self) -> list[Path]:
|
||||
root = self._repo_root() / "app-instance" / "backend-old" / "nanobot" / "skills"
|
||||
if not root.exists():
|
||||
return []
|
||||
return sorted(root.glob("*/SKILL.md"))
|
||||
|
||||
def _staged_skills(self) -> list[Path]:
|
||||
root = self.store.workspace / "state" / "skill-reviews"
|
||||
if not root.exists():
|
||||
return []
|
||||
return sorted(root.glob("*/staged/*/SKILL.md"))
|
||||
|
||||
def _skill_zips(self) -> list[Path]:
|
||||
root = self.store.workspace / "skills"
|
||||
if not root.exists():
|
||||
return []
|
||||
return sorted(root.glob("*.zip"))
|
||||
|
||||
def _repo_root(self) -> Path:
|
||||
if self.repo_root is not None:
|
||||
return self.repo_root
|
||||
return Path(__file__).resolve().parents[4]
|
||||
|
||||
def _migrate_skill_file(self, path: Path, source: str, included: list[dict[str, Any]], skipped: list[dict[str, Any]]) -> None:
|
||||
try:
|
||||
content = path.read_text(encoding="utf-8")
|
||||
result = self._publish_content(content, source=source, source_path=str(path))
|
||||
included.append(result)
|
||||
except Exception as exc:
|
||||
skipped.append({"source": source, "source_path": str(path), "reason": str(exc)})
|
||||
|
||||
def _migrate_zip(self, path: Path, included: list[dict[str, Any]], skipped: list[dict[str, Any]]) -> None:
|
||||
try:
|
||||
with zipfile.ZipFile(io.BytesIO(path.read_bytes()), "r") as archive:
|
||||
entries = [info for info in archive.infolist() if not info.is_dir()]
|
||||
skill_entry = _find_skill_entry(entries)
|
||||
content = archive.read(skill_entry).decode("utf-8", errors="replace")
|
||||
result = self._publish_content(content, source="stevenli-zip", source_path=str(path))
|
||||
skill_name = result["skill_name"]
|
||||
version = result["version"]
|
||||
top = Path(skill_entry).parts[0] if len(Path(skill_entry).parts) == 2 else ""
|
||||
for info in entries:
|
||||
raw = info.filename.replace("\\", "/")
|
||||
if raw == skill_entry or raw.startswith("/") or "__MACOSX" in Path(raw).parts:
|
||||
continue
|
||||
parts = Path(raw).parts
|
||||
rel_parts = parts[1:] if top and parts and parts[0] == top else parts
|
||||
if not rel_parts or any(part in {"", ".", ".."} for part in rel_parts):
|
||||
continue
|
||||
target = self.store.root / skill_name / "versions" / version / "/".join(rel_parts)
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_bytes(archive.read(info))
|
||||
included.append(result)
|
||||
except Exception as exc:
|
||||
skipped.append({"source": "stevenli-zip", "source_path": str(path), "reason": str(exc)})
|
||||
|
||||
def _publish_content(self, content: str, *, source: str, source_path: str) -> dict[str, Any]:
|
||||
frontmatter, body = parse_frontmatter(content)
|
||||
skill_name = _safe_name(str(frontmatter.get("name") or Path(source_path).parent.name))
|
||||
if not skill_name:
|
||||
raise ValueError("unsafe or missing skill name")
|
||||
normalized = normalize_frontmatter(
|
||||
{
|
||||
**frontmatter,
|
||||
"name": skill_name,
|
||||
"description": frontmatter.get("description") or skill_name,
|
||||
}
|
||||
)
|
||||
rendered = _render_skill_content(normalized, body)
|
||||
content_hash = canonical_hash(rendered)
|
||||
existing = self.store.read_published_skill(skill_name)
|
||||
if existing is not None and existing.version.content_hash == content_hash:
|
||||
return {
|
||||
"status": "unchanged",
|
||||
"skill_name": skill_name,
|
||||
"version": existing.version.version,
|
||||
"source": source,
|
||||
"source_path": source_path,
|
||||
}
|
||||
version_id = self._next_version(skill_name)
|
||||
now = _now()
|
||||
skill_version = SkillVersion(
|
||||
skill_name=skill_name,
|
||||
version=version_id,
|
||||
content_hash=content_hash,
|
||||
summary_hash=canonical_hash(strip_frontmatter(rendered).strip()),
|
||||
created_at=now,
|
||||
created_by="migration",
|
||||
change_reason=f"Import skill from {source}",
|
||||
parent_version=existing.version.version if existing is not None else None,
|
||||
review_state="published",
|
||||
frontmatter=normalized,
|
||||
summary=summarize_skill_content(body),
|
||||
tool_hints=self.store._extract_tool_hints(normalized),
|
||||
provenance={"source": source, "source_path": source_path, "imported_at": now},
|
||||
)
|
||||
self.store.write_skill_version(skill_version, rendered)
|
||||
spec = self.store.get_skill_spec(skill_name) or SkillSpec(
|
||||
name=skill_name,
|
||||
display_name=skill_name,
|
||||
description=str(normalized.get("description") or skill_name),
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
current_version=version_id,
|
||||
status="active",
|
||||
tags=[],
|
||||
owners=["migration"],
|
||||
source_kind=source,
|
||||
lineage=[],
|
||||
)
|
||||
spec.current_version = version_id
|
||||
spec.updated_at = now
|
||||
spec.status = "active"
|
||||
spec.source_kind = source
|
||||
if "migration" not in spec.owners:
|
||||
spec.owners.append("migration")
|
||||
self.store.write_skill_spec(spec)
|
||||
self.store.set_current_version(skill_name, version_id)
|
||||
published = self.store.read_index("published")
|
||||
if skill_name not in published:
|
||||
published.append(skill_name)
|
||||
self.store.update_index("published", published)
|
||||
return {"status": "included", "skill_name": skill_name, "version": version_id, "source": source, "source_path": source_path}
|
||||
|
||||
def _next_version(self, skill_name: str) -> str:
|
||||
versions = [item for item in self.store.list_versions(skill_name) if item.startswith("v")]
|
||||
numbers = [int(item[1:]) for item in versions if item[1:].isdigit()]
|
||||
return f"v{(max(numbers) if numbers else 0) + 1:04d}"
|
||||
|
||||
|
||||
def _find_skill_entry(entries: list[zipfile.ZipInfo]) -> str:
|
||||
candidates = []
|
||||
for info in entries:
|
||||
raw = info.filename.replace("\\", "/")
|
||||
parts = Path(raw).parts
|
||||
if raw.startswith("/") or any(part in {"", ".", ".."} for part in parts):
|
||||
raise ValueError(f"unsafe archive entry: {info.filename}")
|
||||
if parts and parts[-1] == "SKILL.md" and len(parts) in (1, 2):
|
||||
candidates.append(raw)
|
||||
if not candidates:
|
||||
raise ValueError("zip has no root SKILL.md")
|
||||
return candidates[0]
|
||||
|
||||
|
||||
def _safe_name(value: str) -> str:
|
||||
cleaned = value.strip().replace(" ", "-")
|
||||
if not cleaned or cleaned in {".", ".."} or "/" in cleaned or "\\" in cleaned:
|
||||
return ""
|
||||
return cleaned if re.fullmatch(r"[A-Za-z0-9_.-]+", cleaned) else ""
|
||||
|
||||
|
||||
def _render_skill_content(frontmatter: dict[str, Any], body: str) -> str:
|
||||
lines = ["---"]
|
||||
for key, value in normalize_frontmatter(frontmatter).items():
|
||||
if isinstance(value, list):
|
||||
lines.append(f"{key}:")
|
||||
for item in value:
|
||||
lines.append(f" - {item}")
|
||||
else:
|
||||
lines.append(f"{key}: {value}")
|
||||
lines.extend(["---", "", body.strip()])
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
|
||||
|
||||
def _now() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
@ -1,936 +0,0 @@
|
||||
# Beaver Backend 重构蓝图
|
||||
|
||||
## 命名说明
|
||||
|
||||
当前项目正式名称已经不是 `nanobot`,而是 `beaver`。
|
||||
|
||||
这份文档里如果出现 `nanobot/...`,一律表示“当前仓库里还没迁走的历史代码路径 / 现状实现位置”,不代表目标命名。
|
||||
|
||||
后续重构目标应统一收敛到:
|
||||
|
||||
1. 产品名、项目名、运行时内核名统一按 `beaver` 表达。
|
||||
2. `nanobot` 只作为迁移期遗留路径存在,最终应逐步退出目录、模块和文档命名。
|
||||
3. 新增目录、新增模块、新增文档都应优先使用 `beaver` 命名,而不是继续扩散 `nanobot`。
|
||||
|
||||
## 文档分工
|
||||
|
||||
三份核心文档从现在开始按下面的边界维护:
|
||||
|
||||
1. `flow.md`
|
||||
- 只保留树形运行结构
|
||||
- 只描述“运行时怎么连起来”
|
||||
- 不再承载蓝图解释、阶段判断、参考项目分析
|
||||
2. `施工指南.md`
|
||||
- 保留施工顺序、阶段边界、完成标准、落地步骤
|
||||
3. `change.md`
|
||||
- 保留长期蓝图、设计动机、参考项目借鉴边界、架构取舍
|
||||
|
||||
这样做的目的很简单:
|
||||
|
||||
1. `flow.md` 必须像运行时接线图,而不是混合说明文
|
||||
2. 施工时看 `施工指南.md`
|
||||
3. 讨论为什么这样设计时看 `change.md`
|
||||
|
||||
## 1. 这次重构到底要解决什么
|
||||
|
||||
当前后端已经不是“功能不够”,而是“能力已经长出来了,但结构还停留在早期阶段”。
|
||||
|
||||
现在项目里同时存在这些事实:
|
||||
|
||||
1. `AgentLoop` 已经承担了太多职责,既管主 agent 对话,又管工具、委派、MCP、会话、事件、memory。
|
||||
2. `web/server.py` 已经变成超大文件,FastAPI app factory、chat API、session、文件、skills、cron、A2A、Outlook 都放在一起。
|
||||
3. `agent_team` 已经接上了 `swarms`,但目前更像“业务层直接借用第三方 runtime”,不是“我们自己的多智能体平台”。
|
||||
4. `skills` 已经有加载、安装、审核,但本质还是 Markdown 说明书,不是可学习、可演化、可评估的能力对象。
|
||||
5. 项目里已经隐约出现了三个方向,但还没有被统一成一个完整架构:
|
||||
- `swarms` 提供多智能体架构能力
|
||||
- `hermes-agent` 提供 skill 生命周期与长期演进思路
|
||||
- `OpenHarness` 提供模块化的 harness 设计方法
|
||||
|
||||
所以这次重构不是简单“整理目录”,而是把项目从“围绕一个 CLI 主 agent 生长出来的系统”升级成“所有 agent 共享同一内核的自有 agent harness 平台”。
|
||||
|
||||
### 1.1 当前落地状态(2026-05-07)
|
||||
|
||||
截至当前实现,新 `app-instance/backend/beaver` 已经把主链推进到:
|
||||
|
||||
1. Main Agent 自动 Task 化与反馈门控。
|
||||
- 简单问题直接走 `AgentLoop` 单轮回答。
|
||||
- 复杂任务自动进入内部 Task。
|
||||
- 产品面仍只暴露聊天入口,不暴露显式 Task 创建/管理 API。
|
||||
2. skill 生命周期与学习闭环第一层。
|
||||
- runtime 记录 `SkillActivationReceipt / RunRecord / SkillEffectRecord`。
|
||||
- Task run 自动验证并失败重试一次。
|
||||
- learning candidates 默认不在 run 完成时生成。
|
||||
- 只有“自动验证通过 + 用户满意反馈”才生成成功学习候选。
|
||||
- `abandon` 写 Failure Memory,不生成成功 Skill draft。
|
||||
3. Agent Team v1 轻量 coordinator。
|
||||
- 已有 Beaver 自己的 `AgentDescriptor / DelegationEnvelope / ExecutionNode / ExecutionGraph / TeamRunResult`。
|
||||
- `TeamService.run_team(...)` 是内部服务入口,不新增产品级 Task API。
|
||||
- `LocalAgentRunner` 让 sub-agent 复用主 `AgentLoop.process_direct()` / `submit_direct()`。
|
||||
- 已支持 `sequence / parallel / dag`。
|
||||
- `parallel` 和 DAG 同层节点保持真并发。
|
||||
- 每个 run 使用独立 memory snapshot,避免并发 prompt 串记忆。
|
||||
- 支持 pinned skill 继承、open skill assembly、per-node provider factory。
|
||||
- sub-agent run 归入父 Task,失败节点归一成 `NodeRunResult`。
|
||||
4. Agent Team 已融入 Task mode 内部执行策略。
|
||||
- `TaskExecutionPlanner` 先用 LLM JSON 规划 `single / team`。
|
||||
- team node 只声明 `skill_query / required_capabilities`,不声明固定 specialist 人设。
|
||||
- `TaskSkillResolver` 为每个 generic sub-agent 选择 published skill;未命中时生成 ephemeral guidance,并作为本次 run 的 pinned guidance 使用。
|
||||
- team 模式调用 `TeamService.run_team(...)` 产生 sub-agent runs。
|
||||
- Team 输出只作为主 Agent synthesis run 的内部上下文。
|
||||
- 用户可见最终回答仍由主 Agent 生成,并继续走验证、反馈和学习门控。
|
||||
- planner 失败或 graph 非法时降级 `single`。
|
||||
|
||||
当前仍未落地的部分:
|
||||
|
||||
1. Agent Team 不暴露产品级聊天路由或显式 Task API;当前作为 Task 内部 sub-agent 执行策略。
|
||||
2. `moa / hierarchy / heavy / group_chat / forest / maker / router` 仍是策略预留,不是 v1 完整行为。
|
||||
3. 自动验证目前是 LLM validator,不是 replay sandbox。
|
||||
4. Skill draft synthesis / review / publish 安全链已有基础服务,但还没有做成完整后台学习 pipeline。
|
||||
5. `/api/agents` 和 agent registry 可作为未来外部 agent/A2A 管理面保留,但不参与 Task sub-agent 选择。
|
||||
6. 不允许在线直接改 published skill,这条约束保持不变。
|
||||
|
||||
### 1.2 参考项目核对说明
|
||||
|
||||
这版蓝图不是只根据印象在写。`2026-05-06` 我们已经重新核对过下面三个参考项目的公开入口文档:
|
||||
|
||||
1. `OpenHarness`
|
||||
- <https://github.com/HKUDS/OpenHarness>
|
||||
2. `hermes-agent`
|
||||
- <https://github.com/NousResearch/hermes-agent>
|
||||
3. `swarms`
|
||||
- <https://github.com/kyegomez/swarms>
|
||||
|
||||
这一步的目的不是“照着抄目录”,而是把“到底借什么、不借什么”明确写死,避免后续施工时又把第三方项目的实现细节直接揉回 Beaver。
|
||||
|
||||
## 2. 我是怎么想的
|
||||
|
||||
我的核心判断是:我们不能继续把第三方库、业务流程、执行控制、UI/API 接口揉在一起,而是应该先定义我们自己的稳定边界,再让第三方能力挂进来。
|
||||
|
||||
换句话说,目标不是“把仓库改得更像 swarms / hermes / OpenHarness”,而是:
|
||||
|
||||
1. 用 `swarms` 的强项来解决“团队编排”。
|
||||
2. 用 `hermes-agent` 的强项来解决“skills 怎么创建、维护、学习、沉淀”。
|
||||
3. 用 `OpenHarness` 的强项来解决“工程边界、模块职责、可维护性”。
|
||||
4. 最终收口成我们自己的抽象和目录,而不是长期让第三方结构反向塑造我们。
|
||||
|
||||
这里把三者的借鉴边界再说得更具体一点:
|
||||
|
||||
1. `OpenHarness`
|
||||
- 借它的 harness 分层方式:`engine / tools / skills / permissions / memory / coordinator / prompts / config`
|
||||
- 借它“一条统一 loop + 明确 tool registry / permission / hook 边界”的工程组织方式
|
||||
- 不直接照搬它的 CLI/TUI、commands、plugin 生态,也不要求 Beaver 长成它的目录镜像
|
||||
2. `hermes-agent`
|
||||
- 借它的 memory / session / session_search / skills 运行时关系
|
||||
- 借它对 FTS5 transcript 搜索、长期记忆、显式 skill 注入、session lineage 的处理方向
|
||||
- 不把“自动学习闭环、完整渠道网关、全部终端后端、Honcho 用户建模”当成当前阶段必须同步迁入的范围
|
||||
3. `swarms`
|
||||
- 借它已经验证过的多智能体执行形态,例如 sequential / hierarchy / rearrange / router 这类 orchestration 结构
|
||||
- 借它作为 team execution backend 的角色,而不是借它来定义 Beaver 的主 runtime、session、tool、provider 契约
|
||||
- 不再允许 Beaver 上层直接感知 `third_party/swarms`、`SwarmRouter` 参数细节或 import 副作用
|
||||
|
||||
这意味着后续所有设计都应遵守四条原则:
|
||||
|
||||
### 2.1 我们要有自己的抽象
|
||||
|
||||
不能让业务代码直接依赖:
|
||||
|
||||
- `third_party/swarms` 的导入路径
|
||||
- `SwarmRouter` 的参数细节
|
||||
- 某个第三方 skill 文件格式
|
||||
- 某个第三方 runtime 的副作用
|
||||
|
||||
我们应该先定义自己的核心对象,例如:
|
||||
|
||||
- `AgentDescriptor`
|
||||
- `SkillSpec`
|
||||
- `SkillVersion`
|
||||
- `TeamSpec`
|
||||
- `ExecutionPlan`
|
||||
- `ProcedureRecord`
|
||||
- `RunRecord`
|
||||
- `BridgeResult`
|
||||
|
||||
第三方库只能作为 adapter / backend 存在。
|
||||
|
||||
### 2.2 所有 agent 共享同一套运行内核
|
||||
|
||||
后面不应该再保留“CLI 单 agent”和“其他 agent 另一套执行方式”这种概念分叉。
|
||||
|
||||
正确做法应该是:
|
||||
|
||||
1. 所有 agent 都复用同一个 `AgentLoop` / engine。
|
||||
2. 主 agent、subagent、team member、A2A local specialist 都只是不同的运行配置和上下文。
|
||||
3. tools、skills、memory、permissions、MCP、delegation 都在同一套内核里装载。
|
||||
4. CLI 只是一个 interface,作用是把用户输入送进内核,而不是代表一种单独的 agent 类型。
|
||||
|
||||
这样做的意义是:
|
||||
|
||||
1. 所有 agent 的能力边界一致。
|
||||
2. 不会再出现“这个能力只在 CLI 主 agent 可用,子 agent 不一致”的问题。
|
||||
3. agent 的差异只存在于 profile / policy / prompt / runtime context,而不是存在于不同执行栈里。
|
||||
|
||||
### 2.3 Harness 和业务要分开
|
||||
|
||||
当前很多逻辑混在一起:既有“平台级能力”,也有“具体产品接入”。
|
||||
|
||||
后面应该分成两层:
|
||||
|
||||
1. Harness 层
|
||||
- tool use
|
||||
- skills
|
||||
- memory
|
||||
- delegation
|
||||
- orchestration
|
||||
- governance
|
||||
2. Product / Interface 层
|
||||
- web API
|
||||
- gateway
|
||||
- channel adapters
|
||||
- Outlook / WhatsApp / 外部服务接入
|
||||
|
||||
这样平台能力才能稳定,接入层才能随产品变化而变化。
|
||||
|
||||
### 2.4 多智能体是平台能力,不是工具技巧
|
||||
|
||||
现在 `spawn_agent_team` 已经存在,但在结构上还像“一个高级工具”。
|
||||
|
||||
后面应该把 multi-agent 当成正式 runtime 能力:
|
||||
|
||||
- 有 plan 层
|
||||
- 有 strategy 层
|
||||
- 有 execution backend 层
|
||||
- 有 result normalization 层
|
||||
- 有 memory / procedure reuse 层
|
||||
- 有 governance / safety / skill constraints
|
||||
|
||||
这里要特别说明 `2.4` 和 `2.5` 的关系:
|
||||
|
||||
1. multi-agent 不是独立于 skills 的第二套指导系统。
|
||||
2. 我们仍然保留之前的群组讨论机制,也就是“探索式协作 + 流程化执行”两种能力都保留。
|
||||
3. 但无论是探索式 group discussion,还是流程化 sequential / rearrange / hierarchy,都必须受 skills 指引和约束。
|
||||
4. 也就是说,skills 决定“应该如何思考、遵守什么边界、优先采用什么方法”,而 multi-agent 负责“由几个人、以什么结构去执行”。
|
||||
|
||||
所以后续正确关系应是:
|
||||
|
||||
`skills -> 约束与方法指导`
|
||||
`multi-agent -> 在 skills 约束下进行探索、讨论、流程化执行`
|
||||
|
||||
### 2.5 skills 必须变成生命周期系统
|
||||
|
||||
现在的 skills 更像可读文档包,适合“手工维护”,不适合“自动学习”。
|
||||
|
||||
如果以后要做到自动创建、自动修订、自动推荐、自动淘汰,skills 必须具备:
|
||||
|
||||
- 结构化元数据
|
||||
- 版本号
|
||||
- 来源与 lineage
|
||||
- 审核状态
|
||||
- 效果统计
|
||||
- 与 procedure 的映射关系
|
||||
- 可回滚、可禁用、可发布
|
||||
|
||||
并且这里的 `skills` 不应只服务于“工具使用技巧”,而应成为整个 agent 系统的统一指引层,包括:
|
||||
|
||||
1. 主 agent 如何规划和执行
|
||||
2. subagent / team member 如何行动
|
||||
3. memory 如何参与判断
|
||||
4. procedure reuse 如何被触发和约束
|
||||
5. multi-agent 讨论时允许采用哪些方法、角色分工和输出习惯
|
||||
|
||||
换句话说:
|
||||
|
||||
1. memory / procedure reuse 不是独立于 skills 的平行系统。
|
||||
2. 但 memory 的实现标准要以 `hermes-agent` 为准,而不是继续沿用当前偏自由发挥的记忆模型。
|
||||
3. skills 提供全局行为指引;memory 只保存跨会话仍然有价值的稳定事实;session_search 负责找回历史细节;procedure 只作为可选优化层。
|
||||
|
||||
这里要明确四者分工:
|
||||
|
||||
1. `skills`
|
||||
- 指导“怎么做”
|
||||
- 约束工具使用、讨论方式、流程化执行方式
|
||||
2. `memory`
|
||||
- 保存 durable facts
|
||||
- 例如用户偏好、环境事实、项目约定、工具 quirks
|
||||
3. `session_search`
|
||||
- 检索历史会话细节
|
||||
- 不把大量过程细节直接塞进 memory
|
||||
4. `procedure`
|
||||
- 作为 coordinator 内部的复用优化
|
||||
- 不是主 memory 契约,也不是主要 prompt 注入来源
|
||||
|
||||
## 3. 现有项目现在是咋样的
|
||||
|
||||
### 3.1 当前的主结构
|
||||
|
||||
从代码上看,`app-instance/backend` 当前大致是这几块。
|
||||
|
||||
注意:下面这些路径仍写作 `nanobot/...`,是因为这里描述的是“现状代码位置”,不是目标命名。
|
||||
|
||||
1. 启动与装配
|
||||
- `nanobot/cli/commands.py`
|
||||
- `nanobot/__main__.py`
|
||||
2. agent 运行时
|
||||
- `nanobot/agent/loop.py`
|
||||
- `nanobot/agent/context.py`
|
||||
- `nanobot/agent/tools/*`
|
||||
- `nanobot/session/*`
|
||||
- `nanobot/providers/*`
|
||||
3. 多 agent / 委派
|
||||
- `nanobot/agent/delegation.py`
|
||||
- `nanobot/agent_team/*`
|
||||
- `nanobot/a2a/*`
|
||||
4. Web / Gateway / Channels
|
||||
- `nanobot/web/server.py`
|
||||
- `nanobot/channels/*`
|
||||
- `bridge/`
|
||||
5. 技能与插件
|
||||
- `nanobot/skills/*`
|
||||
- `nanobot/agent/skills.py`
|
||||
- `nanobot/agent/plugins.py`
|
||||
6. 外部运行时耦合点
|
||||
- 当前主要是 vendored `swarms`
|
||||
|
||||
### 3.2 当前已经有的优点
|
||||
|
||||
这套代码不是没基础,相反已经有几个很有价值的雏形:
|
||||
|
||||
1. 已经有 `AgentRegistry`、`DelegationManager`、`agent_team`,说明“统一委派层”思路已经出现。
|
||||
2. 已经有 `ProcedureMemory` 和 `RunMemory`,说明“从执行中学习”的基础数据层已经出现。
|
||||
3. 已经有 `skills` 的加载、安装、审核,说明“受控扩展机制”已经存在。
|
||||
4. 已经有 `SwarmsBridge`、`SwarmsPolicy`、`SwarmsRunPlanner`,说明多智能体桥接已经不是空白。
|
||||
|
||||
所以这次重构不是推倒重来,而是把这些散落的雏形收敛成一个完整架构。
|
||||
|
||||
### 3.3 当前最主要的问题
|
||||
|
||||
#### 问题一:装配逻辑散落
|
||||
|
||||
同一个后端能力,在 CLI、Web、Gateway 中经常重复装配,甚至行为已经开始漂移。
|
||||
|
||||
这会导致:
|
||||
|
||||
1. 同样的配置在不同入口行为不同。
|
||||
2. 改一个入口容易漏另一个入口。
|
||||
3. 测试覆盖变难。
|
||||
|
||||
#### 问题二:`AgentLoop` 太重,但又没有成为唯一内核
|
||||
|
||||
`AgentLoop` 已经不是纯 loop,而是“半个 runtime 内核”。
|
||||
|
||||
这会导致:
|
||||
|
||||
1. 主 agent 与其他 agent 的边界不清。
|
||||
2. tool、memory、delegation、session、events 相互缠绕。
|
||||
3. 很多能力只能靠继续往 `AgentLoop` 里塞。
|
||||
4. 同时又没有真正做到“所有 agent 都统一复用它”。
|
||||
|
||||
#### 问题三:`swarms` 接入边界不干净,而且 `third_party` 目录本身会持续恶化维护成本
|
||||
|
||||
当前 `agent_team` 虽然有 bridge,但仍然直接依赖:
|
||||
|
||||
1. `sys.path` 注入 vendored `swarms`
|
||||
2. 顶层 `swarms` 包导入副作用
|
||||
3. `SwarmRouter` 的参数细节
|
||||
4. `AutoSwarmBuilder` 自己的 LLM 栈
|
||||
|
||||
这意味着现在不是“我们调度 swarms”,而是“我们的平台有一部分被 swarms runtime 反向定义了”。
|
||||
|
||||
另外,`third_party/` 这种目录在这个项目里不应该长期存在。它会带来两个问题:
|
||||
|
||||
1. 仓库边界不清,到底哪些代码是我们的,哪些不是,很难维护。
|
||||
2. 一旦改动第三方源码,升级、回滚、排障都会变得更脆弱。
|
||||
|
||||
#### 问题四:skills 还是静态文档包
|
||||
|
||||
现在的 skill 系统适合:
|
||||
|
||||
- 展示
|
||||
- 人工安装
|
||||
- prompt 注入
|
||||
|
||||
但不适合:
|
||||
|
||||
- 自动学习
|
||||
- 自动合并
|
||||
- 自动评估
|
||||
- 版本回滚
|
||||
- 基于效果做选择
|
||||
|
||||
#### 问题五:接口层和核心层耦合过深
|
||||
|
||||
`web/server.py` 过大说明一个事实:
|
||||
|
||||
平台内核与外部 API、外部接入、外部服务没有完成分层。
|
||||
|
||||
## 4. 后面应该怎么改
|
||||
|
||||
## 4.1 先把系统改成 OpenHarness 风格的能力分组
|
||||
|
||||
这里我建议明确参考 OpenHarness 那种“按能力分组、核心目录更扁平”的结构,而不是继续按历史演化路径堆目录。
|
||||
|
||||
核心思路是:
|
||||
|
||||
1. 用 `engine` 作为唯一运行内核。
|
||||
2. 用 `coordinator` 负责委派和多 agent 编排。
|
||||
3. 用 `tools`、`skills`、`memory`、`permissions` 作为独立能力层。
|
||||
4. 用 `interfaces` 只放 CLI / Web / Gateway / Channels 这类入口。
|
||||
5. 用 `integrations` 放外部协议和外部系统适配。
|
||||
|
||||
这样拆完之后,模块关系应变成:
|
||||
|
||||
`interfaces -> engine/coordinator/tools/skills/memory -> foundation`
|
||||
|
||||
而不是像现在这样互相横穿。
|
||||
|
||||
## 4.2 彻底去掉 `third_party/`,把 `swarms` 改造成可替换 backend
|
||||
|
||||
### 旧实现状态
|
||||
|
||||
旧 `agent_team` 曾经接通:
|
||||
|
||||
- `GroupChat`
|
||||
- `SequentialWorkflow`
|
||||
- `ConcurrentWorkflow`
|
||||
- `AgentRearrange`
|
||||
- `MixtureOfAgents`
|
||||
- `HierarchicalSwarm`
|
||||
|
||||
但这些能力还不是 Beaver 的正式能力集合,而是“旧 bridge 恰好能跑通的一部分 swarms 类型”。
|
||||
|
||||
更重要的是,当前它们依赖 `third_party/swarms` 这个 vendored 目录,这是后续必须去掉的。
|
||||
|
||||
### 当前 Beaver 状态
|
||||
|
||||
新后端已经先落地了不依赖 `third_party/swarms` 的 Agent Team v1:
|
||||
|
||||
1. 自有核心模型:
|
||||
- `AgentDescriptor`
|
||||
- `DelegationEnvelope`
|
||||
- `ExecutionNode`
|
||||
- `ExecutionGraph`
|
||||
- `NodeRunResult`
|
||||
- `TeamRunResult`
|
||||
2. 内部服务入口:
|
||||
- `TeamService.run_team(...)`
|
||||
3. 本地 delegated runner:
|
||||
- `LocalAgentRunner`
|
||||
- sub-agent 复用主 `AgentLoop.process_direct()` / `submit_direct()`
|
||||
4. 已实现策略:
|
||||
- `sequence`
|
||||
- `parallel`
|
||||
- `dag`
|
||||
5. 已固定的安全语义:
|
||||
- parent Task 必须存在且 session 匹配
|
||||
- sub-agent run_ids 回填父 Task
|
||||
- team/sub-agent 默认只写 receipts/effects,不生成 learning candidates
|
||||
- learning candidates 仍只由 Task feedback gate 触发
|
||||
- 节点级异常归一成 `NodeRunResult`
|
||||
- summary 只聚合成功输出并列出失败节点
|
||||
|
||||
### 目标状态
|
||||
|
||||
后续应该继续沿用我们自己的团队执行抽象:
|
||||
|
||||
```text
|
||||
TeamSpec
|
||||
-> TeamPlanner
|
||||
-> ExecutionPlan
|
||||
-> StrategyBackend
|
||||
-> NormalizedResult
|
||||
```
|
||||
|
||||
然后:
|
||||
|
||||
1. `SwarmsBackend` 如果以后存在,也只能是 `StrategyBackend` 的一个实现。
|
||||
2. 平台对外暴露的是自己的策略名和能力矩阵。
|
||||
3. `swarms` 只提供可选执行或策略参考,不再负责定义平台边界。
|
||||
4. 仓库内不再保留 `third_party/`。
|
||||
5. 高级策略可以先编译成 Beaver `ExecutionGraph` 或 step loop,而不是直接暴露 swarms runtime。
|
||||
|
||||
### 具体改法
|
||||
|
||||
1. 保留当前 `coordinator/models.py / local.py / execution/scheduler.py` 作为 v1 core。
|
||||
2. 在平台层继续扩展正式支持的 strategy。
|
||||
- 已实现:`sequence / parallel / dag`
|
||||
- 预留:`moa / hierarchy / heavy / group_chat / forest / maker / router`
|
||||
3. 高级 strategy preset 先转成 `ExecutionGraph` 或 step loop。
|
||||
4. 如果后续接外部 swarms,单独放进 `coordinator/backends/swarms/`,并统一输入输出为 Beaver models。
|
||||
|
||||
### 结果
|
||||
|
||||
改完之后:
|
||||
|
||||
1. `third_party/` 目录消失。
|
||||
2. 上层不再知道 `third_party/swarms` 这个路径。
|
||||
3. 对上层透明的是 Beaver 自有 team model 和 `TeamService`,不是 vendored 源码目录。
|
||||
|
||||
## 4.3 把 `skills` 从静态文档升级成能力生命周期系统
|
||||
|
||||
### 当前状态
|
||||
|
||||
现在 skill 基本等于:
|
||||
|
||||
- 一个目录
|
||||
- 一个 `SKILL.md`
|
||||
- 一点 frontmatter
|
||||
- 一点审核流程
|
||||
|
||||
### 目标状态
|
||||
|
||||
后续 skill 至少要分成三类对象:
|
||||
|
||||
1. `SkillDraft`
|
||||
- 自动生成或人工创建
|
||||
- 还没发布
|
||||
2. `SkillVersion`
|
||||
- 某个稳定版本
|
||||
- 可启用/禁用/回滚
|
||||
3. `SkillRuntimeView`
|
||||
- 当前对模型暴露的生效版本
|
||||
|
||||
同时 skill 应该带这些元信息:
|
||||
|
||||
- `id`
|
||||
- `name`
|
||||
- `version`
|
||||
- `summary`
|
||||
- `usage_rules`
|
||||
- `inputs`
|
||||
- `outputs`
|
||||
- `dependencies`
|
||||
- `source`
|
||||
- `derived_from_procedure`
|
||||
- `review_status`
|
||||
- `metrics`
|
||||
|
||||
### 自动学习建议
|
||||
|
||||
不要直接让 agent 在线改 live skills。
|
||||
|
||||
正确链路应该是:
|
||||
|
||||
`run result -> procedure candidate -> skill draft -> review -> publish -> runtime use`
|
||||
|
||||
这比“自动改 `SKILL.md`”安全得多,也更适合生产环境。
|
||||
|
||||
### 结果
|
||||
|
||||
改完之后,skills 不再只是 prompt 资源,而是平台知识层的一等对象。
|
||||
|
||||
## 4.4 以 `hermes-agent` 的 memory 模型为基线重做 memory 层
|
||||
|
||||
这里要明确:新的 memory 设计不再以当前 `ProcedureMemory` 为中心,而是以 `hermes-agent` 的 memory 模型为准。
|
||||
|
||||
### 主 memory 契约
|
||||
|
||||
新的主 memory 契约应是:
|
||||
|
||||
1. 一个统一的 `memory` tool
|
||||
2. 三个核心动作:
|
||||
- `add`
|
||||
- `replace`
|
||||
- `remove`
|
||||
3. 两个目标存储:
|
||||
- `memory`:agent 的环境事实、项目约定、工具经验
|
||||
- `user`:用户画像、偏好、习惯、纠正记录
|
||||
|
||||
它的行为应对齐 Hermes:
|
||||
|
||||
1. `add`
|
||||
- 追加新条目
|
||||
- 精确重复时跳过
|
||||
- 超限时返回当前条目和占用情况
|
||||
2. `replace`
|
||||
- 用 `old_text` 的短语义片段匹配条目并整体替换
|
||||
- 多条匹配时要求更精确的 `old_text`
|
||||
3. `remove`
|
||||
- 也是通过 `old_text` 的语义片段删除
|
||||
- 多条匹配时同样要求更精确匹配
|
||||
|
||||
这里要采用“子串匹配”而不是 UUID,因为这更符合 LLM 的操作习惯。
|
||||
|
||||
### 写入安全与并发安全
|
||||
|
||||
新的 memory 层应保留 Hermes 这几个关键约束:
|
||||
|
||||
1. 写入前扫描注入/渗透模式
|
||||
2. 在锁内重新从磁盘加载目标文件
|
||||
3. 做重复检测和字符上限检测
|
||||
4. 通过临时文件 + `os.replace()` 做原子写入
|
||||
|
||||
也就是说,并发安全的关键不是“先读后写”,而是:
|
||||
|
||||
`scan -> lock -> reload -> validate -> atomic write`
|
||||
|
||||
### 冻结快照模式
|
||||
|
||||
新的 memory 层必须采用 frozen snapshot,而不是“每次 memory 写入都改 system prompt”。
|
||||
|
||||
规则是:
|
||||
|
||||
1. 会话开始时,从磁盘加载 `memory` 和 `user`
|
||||
2. 立刻冻结成 system prompt snapshot
|
||||
3. 会话中写入 memory 时,只更新磁盘上的 live state
|
||||
4. 当前会话里的 system prompt 保持不变
|
||||
5. 下一个会话开始时,再重新加载最新 memory
|
||||
|
||||
### session_search 取代“把所有过程细节塞进 memory”
|
||||
|
||||
大量过程细节不应继续塞进 `memory`。
|
||||
|
||||
因此新后端应该明确区分:
|
||||
|
||||
1. `memory`
|
||||
- 保存小而精的、跨会话稳定有效的事实
|
||||
2. `session_search`
|
||||
- 检索历史会话
|
||||
- 支持“无 query 浏览最近会话”和“有 query 的全文搜索 + 摘要”
|
||||
|
||||
这个能力后续应在 Beaver 中落成:
|
||||
|
||||
- `beaver/memory/curated/*`
|
||||
- `beaver/memory/search/*`
|
||||
- `beaver/tools/builtins/memory.py`
|
||||
- `beaver/tools/builtins/session_search.py`
|
||||
|
||||
### `ProcedureMemory` 的新定位
|
||||
|
||||
这不表示 `ProcedureMemory` 没价值,而是它的地位要下降:
|
||||
|
||||
1. `ProcedureMemory` 不再是主 memory 契约
|
||||
2. 它不应该直接承担“跨会话记忆”职责
|
||||
3. 它更适合作为 coordinator 内部的流程复用与路由优化层
|
||||
|
||||
新的优先级应是:
|
||||
|
||||
1. 用户偏好、纠正、环境事实 -> `memory`
|
||||
2. 历史会话细节 -> `session_search`
|
||||
3. 稳定方法论和工作法 -> `skills`
|
||||
4. 团队/流程复用优化 -> `ProcedureMemory`
|
||||
|
||||
## 4.5 CLI 不再代表单 agent 模式,只保留为薄入口
|
||||
|
||||
当前入口层太厚,后续应该改成:
|
||||
|
||||
1. CLI 只做参数解析与 runtime 启动
|
||||
2. Web 只做 API 与 request/response 映射
|
||||
3. Gateway 只做渠道接入与消息转发
|
||||
|
||||
所有核心能力都由统一的 application services 提供,例如:
|
||||
|
||||
- `ChatApplicationService`
|
||||
- `DelegationApplicationService`
|
||||
- `TeamRunApplicationService`
|
||||
- `SkillApplicationService`
|
||||
- `MemoryApplicationService`
|
||||
|
||||
同时要明确一条原则:
|
||||
|
||||
CLI 不是“单 agent 专用模式”。
|
||||
|
||||
它只是这些 interface 之一:
|
||||
|
||||
- CLI
|
||||
- Web
|
||||
- Gateway
|
||||
- Channel
|
||||
|
||||
无论从哪个入口进来,最终都进入同一套 `AgentLoop` / engine。
|
||||
|
||||
这样就不会再出现“CLI 一套 agent,其他入口另一套 agent”的问题。
|
||||
|
||||
## 5. 具体改动后会是什么样
|
||||
|
||||
## 5.1 所有 agent 共用同一套 engine
|
||||
|
||||
### 现在
|
||||
|
||||
`CLI/Web/Gateway -> 各自装配一套 AgentLoop 或相关依赖`
|
||||
|
||||
### 之后
|
||||
|
||||
`CLI/Web/Gateway/Channel -> AgentEntryService -> AgentLoop(engine) -> tools/skills/memory/permissions/delegation`
|
||||
|
||||
结果是:
|
||||
|
||||
1. 主 agent、subagent、team member 复用同一套 engine。
|
||||
2. 装载逻辑只在 engine 内统一处理一次。
|
||||
3. 不再保留“CLI 单 agent 概念”。
|
||||
4. 测试可以直接测 engine 和 service,而不是分别测入口分支。
|
||||
|
||||
## 5.2 多 agent 场景
|
||||
|
||||
### 现在
|
||||
|
||||
`TeamService.run_team -> TeamGraphScheduler -> LocalAgentRunner -> AgentLoop.process_direct / submit_direct`
|
||||
|
||||
Task mode 内部已经变成:
|
||||
|
||||
`AgentService._run_task_mode -> TaskExecutionPlanner -> optional TeamService.run_team -> 主 Agent synthesis run -> ValidationService`
|
||||
|
||||
### 之后
|
||||
|
||||
`TeamService`
|
||||
`-> strategy preset`
|
||||
`-> ExecutionGraph`
|
||||
`-> TeamGraphScheduler`
|
||||
`-> LocalAgentRunner / optional StrategyBackend`
|
||||
`-> NormalizedTeamResult`
|
||||
|
||||
结果是:
|
||||
|
||||
1. 团队能力不再绑定某个第三方 runtime 结构。
|
||||
2. v1 已经支持 `sequence / parallel / dag`。
|
||||
3. 可以逐步增加高级 preset 或第二种 backend,而不推翻平台层。
|
||||
3. `swarms` 只是其中一个可插拔执行器。
|
||||
|
||||
## 5.3 skill 场景
|
||||
|
||||
### 现在
|
||||
|
||||
`SkillsLoader -> 读 SKILL.md -> 摘要注入 / 手动审核安装`
|
||||
|
||||
### 之后
|
||||
|
||||
`SkillCatalog`
|
||||
`-> SkillDraftStore`
|
||||
`-> SkillReviewService`
|
||||
`-> SkillPublisher`
|
||||
`-> SkillRuntimeResolver`
|
||||
|
||||
结果是:
|
||||
|
||||
1. skill 可以有版本。
|
||||
2. skill 可以从 procedure 生成。
|
||||
3. skill 可以审核和回滚。
|
||||
4. skill 可以做效果分析和推荐。
|
||||
|
||||
## 5.4 运行学习场景
|
||||
|
||||
### 现在
|
||||
|
||||
`Run details 混在 session / memory / procedure 中`
|
||||
|
||||
### 之后
|
||||
|
||||
`Run transcript`
|
||||
`-> session_search index`
|
||||
|
||||
`Durable fact`
|
||||
`-> memory(add/replace/remove)`
|
||||
|
||||
`Stable method / workaround / reusable workflow`
|
||||
`-> SkillCandidateGenerator`
|
||||
`-> SkillDraft`
|
||||
`-> Review`
|
||||
`-> Publish`
|
||||
|
||||
`Repeated execution pattern`
|
||||
`-> optional ProcedureMemory`
|
||||
|
||||
结果是:
|
||||
|
||||
1. durable facts、历史细节、稳定方法三类信息终于分层。
|
||||
2. 自动学习不会把临时过程污染到主 memory。
|
||||
3. skills 仍是最高层指导系统,而 memory 变成受控 CRUD 系统。
|
||||
|
||||
## 6. 分阶段落地建议
|
||||
|
||||
这次重构不应该一次性推翻,建议分四期做。
|
||||
|
||||
### 第一期:边界清理
|
||||
|
||||
目标:
|
||||
|
||||
1. 把入口装配统一掉
|
||||
2. 把 `web/server.py` 开始拆分
|
||||
3. 先落地 Beaver 自有 Agent Team v1 core,避免继续依赖 vendored swarms
|
||||
|
||||
交付物:
|
||||
|
||||
- 统一 app factory / service wiring
|
||||
- 初步拆分 web routes
|
||||
- `coordinator/models.py / local.py / execution/scheduler.py`
|
||||
|
||||
### 第二期:平台抽象固化
|
||||
|
||||
目标:
|
||||
|
||||
1. 定义 team / skill / memory / session_search 的正式模型
|
||||
2. 让上层只依赖平台模型
|
||||
|
||||
交付物:
|
||||
|
||||
- `AgentDescriptor / ExecutionGraph / TeamRunResult`
|
||||
- `SkillSpec`
|
||||
- `ExecutionPlan`
|
||||
- `MemoryEntry`
|
||||
- `MemorySnapshot`
|
||||
- `SessionSearchResult`
|
||||
- `SkillDraft`
|
||||
- `SkillVersion`
|
||||
|
||||
### 第三期:skills 生命周期
|
||||
|
||||
目标:
|
||||
|
||||
1. 从“文档技能”升级到“版本化能力”
|
||||
2. 打通“稳定方法 -> SkillDraft”
|
||||
3. 按 Hermes 基线完成 memory CRUD、frozen snapshot、session_search
|
||||
|
||||
交付物:
|
||||
|
||||
- skill catalog
|
||||
- review/publish flow
|
||||
- runtime resolver
|
||||
- memory tool
|
||||
- session search tool
|
||||
|
||||
### 第四期:高级多智能体能力
|
||||
|
||||
目标:
|
||||
|
||||
1. 放开更多正式支持的 strategy
|
||||
2. 评估 `GraphWorkflow`、`HeavySwarm`
|
||||
3. 增加 fallback / retry / policy routing
|
||||
|
||||
交付物:
|
||||
|
||||
- 完整 strategy registry
|
||||
- 多 backend 能力矩阵
|
||||
- team execution fallback
|
||||
|
||||
## 7. 重构后的推荐目录
|
||||
|
||||
下面这个目录我已经按你说的方向收紧了:
|
||||
|
||||
1. 不保留 `third_party/`
|
||||
2. 不保留“CLI 单 agent”这类结构暗示
|
||||
3. 尽量参考 OpenHarness 那种按能力分组、观感更规整的布局
|
||||
4. 每个目录后面都加中文说明
|
||||
|
||||
```text
|
||||
app-instance/backend/
|
||||
├── change.md # 这份重构蓝图
|
||||
├── README.md # 后端总说明
|
||||
├── workflow.md # 运行链路说明
|
||||
├── docs/ # 架构文档和迁移文档
|
||||
│ ├── architecture/ # 核心架构说明
|
||||
│ └── migration/ # 分阶段迁移计划
|
||||
├── beaver/
|
||||
│ ├── foundation/ # 最底层公共设施:配置、模型、事件、错误、工具函数
|
||||
│ │ ├── config/ # 配置定义与加载
|
||||
│ │ ├── models/ # 全局共享数据模型
|
||||
│ │ ├── events/ # 统一事件模型与事件派发
|
||||
│ │ ├── errors/ # 统一错误类型
|
||||
│ │ └── utils/ # 通用工具函数
|
||||
│ ├── engine/ # 统一 agent 内核,所有 agent 都复用这里
|
||||
│ │ ├── loop.py # AgentLoop 主循环与执行入口
|
||||
│ │ ├── loader.py # tools、skills、memory、permissions 的统一装载
|
||||
│ │ ├── context/ # 上下文拼装
|
||||
│ │ ├── session/ # 会话状态与持久化
|
||||
│ │ ├── providers/ # LLM provider 适配
|
||||
│ │ └── runtime/ # 运行时辅助对象与执行上下文
|
||||
│ ├── tools/ # 工具系统
|
||||
│ │ ├── registry/ # 工具注册与发现
|
||||
│ │ ├── builtins/ # 内置工具
|
||||
│ │ ├── mcp/ # MCP 工具适配
|
||||
│ │ └── policies/ # 工具权限与调用约束
|
||||
│ ├── skills/ # 技能系统
|
||||
│ │ ├── builtin/ # 内置技能内容
|
||||
│ │ ├── catalog/ # 技能目录、索引与查询
|
||||
│ │ ├── drafts/ # 自动生成或待审核的 skill draft
|
||||
│ │ ├── reviews/ # 技能审核流
|
||||
│ │ ├── publisher/ # 技能发布与版本切换
|
||||
│ │ └── resolver/ # 运行时技能解析与注入
|
||||
│ ├── memory/ # 记忆与经验沉淀系统
|
||||
│ │ ├── curated/ # Hermes 风格的 MEMORY / USER 持久记忆
|
||||
│ │ ├── search/ # session_search 与历史会话检索
|
||||
│ │ ├── runs/ # 单次执行记录
|
||||
│ │ ├── procedures/ # 可选的流程复用优化层
|
||||
│ │ └── stores/ # 底层存储与原子写实现
|
||||
│ ├── permissions/ # 权限、沙箱、治理规则
|
||||
│ │ ├── policies/ # 权限策略
|
||||
│ │ ├── guards/ # 执行前检查
|
||||
│ │ └── profiles/ # 不同 agent 运行权限画像
|
||||
│ ├── coordinator/ # 多 agent 协调层,参考 OpenHarness 的 coordinator 风格
|
||||
│ │ ├── models.py # AgentDescriptor / ExecutionGraph / TeamRunResult
|
||||
│ │ ├── local.py # LocalAgentRunner:复用主 AgentLoop
|
||||
│ │ ├── execution/ # sequence / parallel / dag 调度与聚合
|
||||
│ │ ├── backends/ # 后续可替换多 agent backend
|
||||
│ │ └── team/ # team 级模型 re-export / 后续高级编排对象
|
||||
│ ├── services/ # application services,对外提供统一能力入口
|
||||
│ │ ├── agent_service.py # 统一 agent 运行入口
|
||||
│ │ ├── team_service.py # 多 agent 执行入口
|
||||
│ │ ├── skill_service.py # 技能管理入口
|
||||
│ │ ├── memory_service.py # memory 查询与写入入口
|
||||
│ │ └── admin_service.py # 平台管理入口
|
||||
│ ├── interfaces/ # 薄入口层,不承载核心业务
|
||||
│ │ ├── cli/ # CLI 入口,只负责把请求送进 services/engine
|
||||
│ │ ├── web/ # FastAPI 接口层
|
||||
│ │ │ ├── app.py # Web app factory
|
||||
│ │ │ ├── routes/ # 路由拆分
|
||||
│ │ │ ├── schemas/ # Web 请求/响应模型
|
||||
│ │ │ └── deps.py # Web 依赖装配
|
||||
│ │ ├── gateway/ # 常驻 worker / gateway 入口
|
||||
│ │ └── channels/ # Telegram/Slack/Email 等渠道入口
|
||||
│ ├── integrations/ # 外部系统与协议集成
|
||||
│ │ ├── a2a/ # A2A 协议与 client
|
||||
│ │ ├── mcp/ # MCP 连接与管理
|
||||
│ │ ├── outlook/ # Outlook 集成
|
||||
│ │ ├── whatsapp/ # WhatsApp bridge 适配
|
||||
│ │ └── providers/ # 外部 provider 特定集成
|
||||
│ ├── plugins/ # 插件系统
|
||||
│ │ ├── loader.py # 插件发现与装载
|
||||
│ │ ├── registry.py # 插件注册表
|
||||
│ │ └── hooks.py # 插件 hooks
|
||||
│ └── templates/ # 默认模板、system prompt 模板、内置文本资源
|
||||
├── tests/ # 测试
|
||||
│ ├── unit/ # 单元测试
|
||||
│ ├── integration/ # 集成测试
|
||||
│ ├── e2e/ # 端到端测试
|
||||
│ └── fixtures/ # 测试数据与夹具
|
||||
└── bridge/ # 独立 Node/bridge 代码,作为外部桥接层保留
|
||||
```
|
||||
|
||||
## 8. 最终结论
|
||||
|
||||
这次重构的本质不是“把代码拆小一点”,而是完成三件事:
|
||||
|
||||
1. 把当前项目从“围绕 `AgentLoop` 生长的单体系统”升级成“所有 agent 共用一个 engine 的可维护 harness 平台”。
|
||||
2. 把 `swarms` 从“放在 `third_party/` 里的深耦合运行时”降级成“可替换的多智能体 backend”。
|
||||
3. 把 `skills` 从“静态 Markdown 包”升级成“可学习、可审核、可发布、可回滚的能力系统”。
|
||||
|
||||
如果这三件事做成了,后面再扩多智能体架构、自动学习、插件生态、外部接入,代码就不会继续失控。
|
||||
|
||||
---
|
||||
|
||||
## 9. 最新落地状态:Task Team 后三件套
|
||||
|
||||
本轮已经把 Task Team 融合后的三个缺口推进到 v1 可用状态:
|
||||
|
||||
1. **Task Sub-agent Skill Resolver**
|
||||
- 新增 `beaver/tasks/skill_resolver.py`。
|
||||
- sub-agent 是临时 generic worker,不承载固定角色人设。
|
||||
- `TaskExecutionPlanner` 的 team node 输出 `skill_query / required_capabilities / expected_output`。
|
||||
- `TaskSkillResolver` 从 published skill catalog 中选择合适 skill,并写入 node pinned skills。
|
||||
- 如果没有命中 published skill,会创建 ephemeral guidance,并作为本次 sub-agent 的 pinned skill context 使用。
|
||||
- ephemeral guidance 不写入 draft store,不自动 approve/publish,不进入 runtime catalog。
|
||||
- agent registry / target resolver 不参与 Task sub-agent strategy,可作为未来外部 agent/A2A 管理面保留。
|
||||
|
||||
2. **Task Team Process Projection**
|
||||
- Task attempt 隐藏事件增加 `skill_queries / selected_skill_names / ephemeral_guidance_ids / skill_resolution_report / node_results / task_synthesis_completed`。
|
||||
- 新增 `GET /api/sessions/{session_id}/process`。
|
||||
- 前端 `ChatWorkbench` 已接入 `ProcessLane` 和移动端 `Process` tab。
|
||||
- 展示规划、skill selection、ephemeral guidance、team node、main synthesis、validation/retry,不把 team summary 直接当最终回答。
|
||||
|
||||
3. **Learning Pipeline 闭环**
|
||||
- 新增 `SkillLearningPipelineService`。
|
||||
- Web API 覆盖 candidates、drafts、submit、approve、reject、publish、disable、rollback。
|
||||
- `/skills` 页面增加 Published / Candidates / Drafts tabs。
|
||||
- publish 仍要求 approved draft;rejected draft 不可 publish;draft 不进入 runtime catalog。
|
||||
|
||||
验证状态:
|
||||
|
||||
- 后端:`76 passed`。
|
||||
- 前端:`npm run typecheck` 通过,`npm test` 通过,`npm run lint` 通过但仍有既有 warnings。
|
||||
@ -1,8 +0,0 @@
|
||||
# Phase 1
|
||||
|
||||
第一阶段先完成结构搭建与边界清理:
|
||||
|
||||
1. 创建新的 `beaver` 包结构。
|
||||
2. 保留旧实现于 `backend-old/`。
|
||||
3. 后续逐步把旧能力迁入新结构。
|
||||
|
||||
@ -1,905 +0,0 @@
|
||||
# Beaver Backend Flow
|
||||
|
||||
这份文档只保留**树形运行结构**。
|
||||
|
||||
- 原理、参考项目边界、长期蓝图:看 `change.md`
|
||||
- 施工顺序、阶段目标、完成标准:看 `施工指南.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 总入口
|
||||
|
||||
```text
|
||||
用户输入(用户在不同入口发来一句话或一个任务)
|
||||
│
|
||||
├─ CLI(命令行入口)
|
||||
├─ Web(网页前端入口)
|
||||
├─ Gateway(消息通道入口,比如以后接 Slack / Telegram)
|
||||
└─ future channels(未来扩展入口)
|
||||
│
|
||||
└─ AgentService(统一服务层:所有入口都先汇总到这里)
|
||||
├─ Intent Agent / MainAgentRouter(第一层意图判断:simple chat / continue task / create task / close / abandon)
|
||||
├─ load intent-agent-router skill(内部 skill 指引:只做路由,不回答用户,不使用工具)
|
||||
├─ classify(...)(LLM 语义判断)
|
||||
├─ session hidden event: intent_agent_decision_snapshotted(记录选择 simple_chat / create_task / continue_task 等)
|
||||
├─ create_loop()(创建 AgentLoop 运行核心)
|
||||
├─ start()(启动后台运行模式)
|
||||
├─ submit_direct()(把任务提交到运行队列)
|
||||
├─ process_direct()(直接处理一次任务,不走队列)
|
||||
├─ submit_feedback()(记录聊天反馈并驱动内部 Task 状态)
|
||||
├─ stop()(停止接收新任务,并等待队列收尾)
|
||||
├─ shutdown()(停止运行并释放资源)
|
||||
└─ close()(关闭已经创建的 runtime)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Boot / Loader
|
||||
|
||||
```text
|
||||
AgentService.create_loop()(服务层创建运行核心)
|
||||
│
|
||||
└─ AgentLoop(profile, loader)(Agent 主循环:真正跑任务的核心对象)
|
||||
│
|
||||
└─ AgentLoop.boot()(启动前装配依赖)
|
||||
│
|
||||
└─ EngineLoader.load()(加载所有运行时模块)
|
||||
├─ SessionManager(会话管理:保存聊天记录和隐藏事件)
|
||||
├─ MemoryStore(长期记忆存储:真正落盘的 curated memory)
|
||||
├─ MemoryService(记忆服务:运行时访问 memory 的唯一入口)
|
||||
├─ RunMemoryStore(运行记录存储:保存每次 run 的结果)
|
||||
├─ SkillLearningStore(技能学习存储:保存表现统计和学习候选)
|
||||
├─ ToolRegistry(工具注册表:登记系统有哪些工具)
|
||||
├─ ToolAssembler(工具选择器:决定本轮暴露哪些工具)
|
||||
├─ ToolExecutor(工具执行器:真正调用工具)
|
||||
├─ SkillsLoader(技能目录加载器:只加载可用技能)
|
||||
├─ SkillAssembler(技能选择器:决定本轮激活哪些 skill)
|
||||
├─ SkillSpecStore(技能生命周期存储:保存版本、草稿、审核)
|
||||
├─ DraftService(草稿服务:创建 skill draft)
|
||||
├─ ReviewService(审核服务:approve / reject draft)
|
||||
├─ SkillPublisher(发布服务:publish / disable / rollback)
|
||||
├─ EvidenceSelector(证据选择器:为学习闭环挑选历史证据)
|
||||
├─ SkillDraftSynthesizer(草稿合成器:让 LLM 生成 skill draft)
|
||||
├─ SkillLearningService(技能学习服务:生成学习候选和草稿)
|
||||
├─ TaskService(内部 Task 服务:自动 Task 化、状态、事件、反馈)
|
||||
├─ TaskExecutionPlanner(Task 执行规划器:决定 single / team)
|
||||
├─ ValidationService(结果验证服务:Task run 完成后的自动验证)
|
||||
└─ ContextBuilder(上下文构建器:拼 system prompt 和 messages)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Main Agent Routing / Internal Task
|
||||
|
||||
```text
|
||||
AgentService.process_direct / submit_direct(聊天入口统一进入服务层)
|
||||
│
|
||||
├─ resolve session_id(复用请求 session,或生成新 session)
|
||||
├─ task_service.get_latest_open_task(session_id)(查找同会话未关闭 Task)
|
||||
├─ MainAgentRouter.classify(message, active_task, recent_messages, intent-agent-router skill)(Intent Agent 语义分类)
|
||||
│ ├─ Intent Agent 只返回 JSON 路由结果,不直接回答用户
|
||||
│ ├─ Intent Agent 没有 tools;凡是需要工具、实时/外部数据、文件、执行、验证的请求都应进入 Task
|
||||
│ ├─ session hidden event: intent_agent_decision_snapshotted(调试日志展示 choice / reason / short_title)
|
||||
│ ├─ simple(简单问题)
|
||||
│ │ └─ runner(message, include_skill_assembly=False, include_tools=False)(不创建 Task,不跑 skills/tools)
|
||||
│ │
|
||||
│ ├─ continue_task(继续当前 Task)
|
||||
│ │ └─ reuse active Task(只要话题没有完全无关,就继续当前 open Task)
|
||||
│ │
|
||||
│ ├─ new_task(明确开启新任务)
|
||||
│ │ └─ TaskService.create_task(...)(内部创建 Task,并保存 short_title)
|
||||
│ │
|
||||
│ ├─ close_task / abandon_task(用户明确结束或放弃)
|
||||
│ │ └─ TaskService.close_task / abandon_task(关闭当前 Task)
|
||||
│ │
|
||||
│ └─ task execution
|
||||
│ └─ AgentService._run_task_mode(...)(进入 Task 模式执行)
|
||||
```
|
||||
|
||||
```text
|
||||
TaskService(内部 Task 状态机)
|
||||
│
|
||||
├─ TaskRecord
|
||||
│ ├─ task_id
|
||||
│ ├─ session_id
|
||||
│ ├─ goal / description / constraints
|
||||
│ ├─ metadata.short_title(5-15 字左右的短标题,用于前端当前任务标识)
|
||||
│ ├─ status
|
||||
│ │ ├─ open
|
||||
│ │ ├─ running
|
||||
│ │ ├─ validating
|
||||
│ │ ├─ awaiting_feedback
|
||||
│ │ ├─ needs_revision
|
||||
│ │ ├─ closed
|
||||
│ │ └─ abandoned
|
||||
│ ├─ run_ids
|
||||
│ ├─ skill_names
|
||||
│ ├─ validation_result
|
||||
│ └─ feedback
|
||||
│
|
||||
└─ TaskEvent
|
||||
├─ created
|
||||
├─ run_started
|
||||
├─ run_completed
|
||||
├─ validated
|
||||
├─ feedback_satisfied
|
||||
├─ feedback_revise
|
||||
└─ feedback_abandon
|
||||
```
|
||||
|
||||
```text
|
||||
Task Mode Execution(复杂任务执行)
|
||||
│
|
||||
├─ attempt 1
|
||||
│ ├─ task_service.start_run(...)
|
||||
│ ├─ TaskExecutionPlanner.plan(...)(LLM 规划 single / team)
|
||||
│ ├─ session hidden event: task_execution_planned
|
||||
│ ├─ if plan.mode == team
|
||||
│ │ ├─ TeamService.run_team(parent_task_id=task_id, parent_session_id=session_id)
|
||||
│ │ ├─ sub-agent runs -> parent Task run_ids
|
||||
│ │ ├─ session hidden event: task_team_run_completed / task_team_run_failed
|
||||
│ │ └─ team summary + node results -> 主 Agent synthesis execution_context
|
||||
│ ├─ AgentLoop.process_direct / submit_direct(..., task_id, task_mode=True, attempt_index=1)
|
||||
│ ├─ ValidationService.validate_task_result(..., team_summaries=...)
|
||||
│ ├─ TaskService.record_validation(...)
|
||||
│ ├─ RunMemoryStore.update_run_record(validation_result=...)
|
||||
│ └─ session hidden event: task_validation_snapshotted
|
||||
│
|
||||
├─ if validation accepted
|
||||
│ └─ return result with task_id / task_status / validation_result
|
||||
│
|
||||
└─ if validation failed
|
||||
├─ session_manager.set_run_context_visible(run_id, false)(隐藏失败草稿尝试)
|
||||
├─ attempt 2 重新规划 single / team
|
||||
├─ revision request + team result -> 主 Agent synthesis execution_context
|
||||
└─ 第二次结果无论验证是否通过,都返回并等待用户反馈
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Direct Run
|
||||
|
||||
```text
|
||||
AgentLoop.process_direct(task)(直接执行一轮用户任务)
|
||||
│
|
||||
├─ 生成 session_id(确定这句话属于哪个会话)
|
||||
├─ 生成 run_id(给本次运行生成唯一编号)
|
||||
├─ memory_service.capture_snapshot_for_run()(每个 run 捕获独立记忆快照)
|
||||
│ └─ fresh MemoryStore(root).load_from_disk()(不写共享 `_snapshot`,避免并发串记忆)
|
||||
│
|
||||
├─ session_manager.ensure_session(session_id)(确保会话存在)
|
||||
├─ session_manager.append_message(event_type="run_started", hidden)(记录隐藏事件:本轮开始)
|
||||
│
|
||||
├─ make_provider_bundle()(装配模型 provider 组合)
|
||||
│ ├─ main_runtime(主模型配置)
|
||||
│ ├─ main_provider(主模型调用器)
|
||||
│ ├─ fallback_runtime(备用模型配置)
|
||||
│ ├─ fallback_provider(备用模型调用器)
|
||||
│ ├─ auxiliary_runtime(辅助模型配置)
|
||||
│ ├─ auxiliary_provider(辅助模型调用器,用于选 skill 等)
|
||||
│ └─ embedding_runtime(向量模型配置,用于语义召回)
|
||||
│
|
||||
├─ if include_skill_assembly=False(simple_chat 默认关闭)
|
||||
│ └─ skip SkillAssembler(不激活 skill,不注入 skill 正文)
|
||||
│
|
||||
├─ if include_skill_assembly=True(Task mode 默认开启,在 Task 创建/复用和规划之后执行)
|
||||
│ └─ skill_assembler.assemble(...)(选择本轮应该激活哪些 published skill)
|
||||
│ ├─ input task_description = skill_selection_context or current user input
|
||||
│ │ ├─ Task goal / description
|
||||
│ │ ├─ current user request
|
||||
│ │ ├─ attempt / revision / team synthesis phase
|
||||
│ │ ├─ validation feedback(重试时)
|
||||
│ │ ├─ team summary / plan(team synthesis 时)
|
||||
│ │ └─ previously activated skills(只作为 reuse bias,不是 pinned)
|
||||
│ ├─ SkillsLoader.build_selection_candidates()(列出候选技能摘要)
|
||||
│ ├─ embedding retrieve skill candidates(用向量召回相关技能)
|
||||
│ ├─ LLM shortlist candidate names(先用摘要粗选少量候选)
|
||||
│ │ └─ if retrieved candidates <= max_detailed_candidates -> skip shortlist
|
||||
│ ├─ SkillsLoader.load_published_skill(...)(系统侧内部读取粗选候选正文,不暴露 skill_view 给主 Agent)
|
||||
│ ├─ LLM final select activated skills(结合候选正文做最终选择)
|
||||
│ ├─ if no matching skill -> return [] and continue run without skills
|
||||
│ └─ 返回 activated skills(返回本轮被激活的技能)
|
||||
│ ├─ name(技能名称)
|
||||
│ ├─ content(技能正文)
|
||||
│ ├─ version(技能版本)
|
||||
│ ├─ content_hash(技能内容哈希,用于追踪)
|
||||
│ ├─ activation_reason(为什么激活)
|
||||
│ └─ tool_hints(技能建议使用哪些工具)
|
||||
│
|
||||
├─ ContextBuilder.build_skill_activation_messages(...)(把激活技能变成模型可读消息)
|
||||
├─ 构造 SkillActivationReceipt[](构造技能激活收据)
|
||||
├─ session_manager.append_message(...)(记录隐藏事件:本轮用了哪些技能)
|
||||
│ ├─ event_type="skill_activation_snapshotted"(技能激活快照)
|
||||
│ ├─ hidden(不进入普通聊天上下文)
|
||||
│ └─ payload(隐藏数据)
|
||||
│ ├─ receipts(技能激活收据)
|
||||
│ └─ activation_messages(实际注入给模型的技能消息)
|
||||
│
|
||||
├─ tool_assembler.assemble(...)(选择本轮应该暴露哪些工具;simple_chat 默认跳过)
|
||||
│ ├─ always tools(默认总是可用的工具)
|
||||
│ ├─ activated skill tool hints(被激活技能推荐的工具)
|
||||
│ ├─ embedding retrieve tools(用向量召回相关工具)
|
||||
│ └─ 返回 selected ToolSpec[](返回本轮工具列表)
|
||||
│
|
||||
├─ session_manager.append_message(event_type="tool_selection_snapshotted", hidden)(记录隐藏事件:工具选择快照)
|
||||
│
|
||||
├─ ContextBuilder.build_messages(...)(构造发给模型的完整 messages)
|
||||
│ ├─ build_system_prompt()(构造 system prompt)
|
||||
│ │ ├─ base system prompt(基础系统提示词)
|
||||
│ │ ├─ session metadata(当前会话元信息)
|
||||
│ │ ├─ execution context(本轮额外执行上下文)
|
||||
│ │ └─ frozen memory snapshot(冻结记忆快照)
|
||||
│ ├─ insert activated skill messages(插入已激活技能正文)
|
||||
│ ├─ append visible history(追加可见历史聊天)
|
||||
│ └─ append current user input(追加当前用户输入)
|
||||
│
|
||||
├─ session_manager.update_system_prompt(...)(把本轮 system prompt 快照写回会话)
|
||||
├─ session_manager.append_message(event_type="skill_selection_context_snapshotted", hidden)(完整记录 skill query)
|
||||
├─ session_manager.append_message(event_type="system_prompt_snapshotted", hidden)(记录隐藏事件:system prompt 快照)
|
||||
├─ session_manager.append_message(event_type="user_message_added")(记录可见事件:用户消息)
|
||||
│
|
||||
├─ 进入 tool loop(进入模型回答和工具调用循环)
|
||||
│
|
||||
├─ 成功时(模型正常结束)
|
||||
│ ├─ session_manager.append_message(event_type="run_completed", hidden)(记录隐藏事件:运行完成)
|
||||
│ └─ _record_run_receipts(...)(记录运行证据,不生成学习候选)
|
||||
│
|
||||
├─ 失败时(运行中出现异常)
|
||||
│ ├─ append assistant error message(写入 assistant 错误消息)
|
||||
│ ├─ session_manager.append_message(event_type="run_failed", hidden)(记录隐藏事件:运行失败)
|
||||
│ └─ _record_run_receipts(...)(即使失败也记录运行证据)
|
||||
│
|
||||
└─ return AgentRunResult(返回本轮结果)
|
||||
├─ session_id(会话编号)
|
||||
├─ run_id(运行编号)
|
||||
├─ output_text(最终回复文本)
|
||||
├─ finish_reason(结束原因)
|
||||
├─ tool_iterations(工具循环次数)
|
||||
├─ provider_name(模型供应商)
|
||||
├─ model(模型名称)
|
||||
├─ usage(token 用量)
|
||||
├─ task_id(Task 模式下返回)
|
||||
├─ task_status(Task 模式下返回)
|
||||
└─ validation_result(Task 模式下返回)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Tool Loop
|
||||
|
||||
```text
|
||||
tool loop(工具调用循环)
|
||||
│
|
||||
├─ session_manager.append_message(event_type="llm_request_snapshotted", hidden)(完整记录本次 provider messages / tools)
|
||||
├─ provider.chat(messages, tools=schemas)(把消息和工具 schema 发给模型)
|
||||
├─ session_manager.update_usage(...)(累计 token 用量)
|
||||
├─ session_manager.append_message(event_type="assistant_message_added")(记录 assistant 回复)
|
||||
├─ ContextBuilder.add_assistant_message(...)(把 assistant 回复追加到本轮 messages)
|
||||
│
|
||||
├─ if no tool calls(如果模型没有要求调用工具)
|
||||
│ └─ finish(结束本轮回答)
|
||||
│
|
||||
└─ if tool calls(如果模型要求调用工具)
|
||||
├─ ToolExecutor.execute_tool_call(...)(执行一个工具调用)
|
||||
├─ session_manager.append_message(event_type="tool_result_recorded")(记录工具结果)
|
||||
├─ ContextBuilder.add_tool_result(...)(把工具结果追加到 messages)
|
||||
└─ 回到 provider.chat(...)(带着工具结果继续问模型)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Run Evidence / Skill Effect Recording
|
||||
|
||||
```text
|
||||
AgentLoop._record_run_receipts(...)(记录本轮运行证据;不直接学习)
|
||||
│
|
||||
├─ 构造 RunRecord(构造本轮运行记录)
|
||||
│ ├─ run_id(运行编号)
|
||||
│ ├─ session_id(会话编号)
|
||||
│ ├─ task_text(用户原始任务)
|
||||
│ ├─ task_id(内部 Task 编号,简单问题可为空)
|
||||
│ ├─ attempt_index(Task 模式下的尝试序号)
|
||||
│ ├─ started_at(开始时间)
|
||||
│ ├─ ended_at(结束时间)
|
||||
│ ├─ success(是否成功)
|
||||
│ ├─ finish_reason(结束原因)
|
||||
│ ├─ validation_result(Task 模式下的验证结果)
|
||||
│ ├─ feedback(用户反馈)
|
||||
│ └─ activated_skills(本轮激活过的技能收据)
|
||||
│
|
||||
├─ 构造 SkillEffectRecord[](构造技能效果记录)
|
||||
│ └─ 每个 activated skill 一条(每个被用到的技能都单独记一条)
|
||||
│
|
||||
├─ skill_learning_service.collect_run_receipts(...)(收集运行收据)
|
||||
│ ├─ RunMemoryStore.append_run_record(...)(把 RunRecord 写入 memory/runs/runs.jsonl)
|
||||
│ ├─ RunMemoryStore.append_skill_effect(...)(把 SkillEffectRecord 写入 memory/runs/skill-effects.jsonl)
|
||||
│ ├─ SkillLearningService.rescore_skill_versions()(重新统计每个技能版本表现)
|
||||
│ │ └─ SkillLearningStore.update_performance_snapshot(...)(更新表现快照)
|
||||
│ └─ never build learning candidates in runtime hot path(运行完成时永不生成候选)
|
||||
│
|
||||
└─ session_manager.append_message(...)(记录隐藏事件:技能效果快照)
|
||||
├─ event_type="skill_effects_snapshotted"(技能效果已快照)
|
||||
├─ hidden(不进入普通聊天上下文)
|
||||
└─ payload(隐藏数据)
|
||||
├─ run_record(本轮运行记录)
|
||||
├─ skill_effects(技能效果记录)
|
||||
├─ candidate_generation_allowed(本轮是否允许生成候选;runtime 固定 false)
|
||||
└─ learning_candidates(学习候选;默认空)
|
||||
```
|
||||
|
||||
```text
|
||||
runtime invariant(运行期不直接学习)
|
||||
│
|
||||
├─ run completed / run failed
|
||||
│ └─ 只写 RunRecord + SkillEffectRecord + performance snapshot
|
||||
│
|
||||
├─ simple chat
|
||||
│ └─ 不创建 Task,不触发 learning candidate
|
||||
│
|
||||
└─ Task attempt / sub-agent run
|
||||
└─ 只留下证据,等待 feedback gate 决定是否学习
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Chat Feedback / Learning Gate
|
||||
|
||||
```text
|
||||
POST /api/chat/feedback(聊天反馈接口,不是 Task 管理 API)
|
||||
│
|
||||
├─ input
|
||||
│ ├─ session_id
|
||||
│ ├─ run_id
|
||||
│ ├─ feedback_type
|
||||
│ │ ├─ satisfied
|
||||
│ │ ├─ revise
|
||||
│ │ └─ abandon
|
||||
│ └─ comment?
|
||||
│
|
||||
├─ AgentService.submit_feedback(...)
|
||||
│ ├─ TaskService.get_task_by_run_id(run_id)
|
||||
│ ├─ reject if task/session mismatch
|
||||
│ ├─ reject conflicting feedback for same run
|
||||
│ ├─ same feedback is idempotent
|
||||
│ └─ TaskService.add_feedback(...)
|
||||
│
|
||||
├─ satisfied
|
||||
│ ├─ if validation accepted
|
||||
│ │ ├─ Task status -> closed
|
||||
│ │ └─ SkillLearningService.build_learning_candidates_for_task(task_id, trigger_run_id)
|
||||
│ └─ if validation not accepted
|
||||
│ └─ 记录人工接受但保留验证风险;不自动生成 learning candidate
|
||||
│
|
||||
├─ revise
|
||||
│ ├─ Task status -> needs_revision
|
||||
│ ├─ 更新 run / skill effect 为需修订证据
|
||||
│ └─ 下一条用户消息默认复用该 Task;不生成 learning candidate
|
||||
│
|
||||
└─ abandon
|
||||
├─ Task status -> abandoned
|
||||
├─ 更新 run / skill effect 为失败证据
|
||||
├─ 追加 task_failure_evidence_recorded 隐藏事件
|
||||
└─ 默认不写主 memory,不生成成功 Skill draft
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Agent Team v1 / Local Coordinator
|
||||
|
||||
```text
|
||||
TeamService.run_team(...)(内部 team 执行入口,不暴露产品级 Task API)
|
||||
│
|
||||
├─ validate parent task(如果传 parent_task_id,先校验 Task 存在且 session 匹配)
|
||||
│
|
||||
├─ TeamGraphScheduler.run(...)
|
||||
│ ├─ graph.validate()
|
||||
│ │ ├─ v1 implemented strategies: sequence / parallel / dag
|
||||
│ │ └─ reserved strategies: moa / hierarchy / heavy / group_chat / forest / maker / router
|
||||
│ ├─ provider_bundle_factory(node)(推荐:每个节点拿 fresh provider bundle)
|
||||
│ ├─ inherited_pinned_skills(主 agent 明确委派给 sub-agent 的 pinned skills)
|
||||
│ ├─ inherited_pinned_skill_contexts(missing skill 生成的一次性 ephemeral guidance)
|
||||
│ └─ allow_candidate_generation=False(默认只写 receipts,不绕过 Task feedback gate)
|
||||
│
|
||||
├─ LocalAgentRunner.run(envelope)
|
||||
│ ├─ 生成 child_session_id
|
||||
│ ├─ parent_session_id -> 主 session(建立 session lineage)
|
||||
│ ├─ AgentLoop.process_direct / submit_direct(...)(复用主 AgentLoop / ContextBuilder / ToolAssembler / SkillAssembler / MemoryService)
|
||||
│ ├─ pinned_skill_names -> AgentLoop(published pinned skill 必须注入)
|
||||
│ ├─ pinned_skill_contexts -> AgentLoop(ephemeral guidance 只在本次 run 注入)
|
||||
│ └─ provider_bundle + node model/provider override 禁止混用
|
||||
│
|
||||
├─ strategy execution
|
||||
│ ├─ sequence:前一节点成功输出进入后一节点 dependency_outputs
|
||||
│ ├─ parallel:同层节点 asyncio.gather 真并发执行
|
||||
│ └─ dag:按依赖拓扑分批并发;失败节点会阻断依赖它的后续节点
|
||||
│
|
||||
├─ node-level failure normalization
|
||||
│ ├─ provider factory / runner 普通异常 -> NodeRunResult(success=False, finish_reason="error")
|
||||
│ ├─ asyncio.CancelledError 继续抛出
|
||||
│ └─ blocked dependency -> NodeRunResult(success=False, finish_reason="blocked")
|
||||
│
|
||||
├─ TeamRunResult
|
||||
│ ├─ success
|
||||
│ ├─ summary(只聚合成功节点输出;失败节点列入 Failed nodes)
|
||||
│ ├─ node_results
|
||||
│ ├─ run_ids
|
||||
│ ├─ session_ids
|
||||
│ └─ task_id(父 Task)
|
||||
│
|
||||
└─ attach runs to parent Task
|
||||
└─ TaskService.append_run(parent_task_id, sub_run_id, skill_names=...)
|
||||
```
|
||||
|
||||
```text
|
||||
Team v1 scope(当前边界)
|
||||
│
|
||||
├─ 已实现
|
||||
│ ├─ Beaver 自有 coordinator models
|
||||
│ ├─ sequence / parallel / dag 三个执行原语
|
||||
│ ├─ pinned skill 继承 + open skill assembly
|
||||
│ ├─ per-run memory snapshot,支持真并发 prompt 构建
|
||||
│ ├─ per-node provider factory 语义
|
||||
│ ├─ parent Task 一致性校验
|
||||
│ └─ 节点失败归一和 summary 失败区块
|
||||
│
|
||||
├─ 已接入 Task mode 内部执行链
|
||||
│ ├─ TaskExecutionPlanner 先决定 single / team
|
||||
│ ├─ team run 只作为内部 sub-agent 执行策略
|
||||
│ ├─ TeamRunResult 不直接返回给用户
|
||||
│ └─ 主 Agent synthesis run 生成用户可见最终回答
|
||||
│
|
||||
└─ 仍不暴露产品级 team / Task API
|
||||
└─ 外部仍只使用聊天入口和反馈入口
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Session Module
|
||||
|
||||
```text
|
||||
SessionManager(会话管理门面)
|
||||
│
|
||||
├─ ensure_session(...)(确保会话存在)
|
||||
├─ append_message(...)(追加一条事件或聊天消息)
|
||||
├─ get_event_records(session_id)(获取完整事件流)
|
||||
├─ get_run_event_records(session_id, run_id)(获取某次 run 的事件)
|
||||
├─ update_latest_assistant_event_payload(...)(把 task/validation/feedback 状态投影到最新 assistant 消息)
|
||||
├─ set_run_context_visible(session_id, run_id, visible)(隐藏失败重试草稿等 run)
|
||||
├─ list_run_ids(session_id)(列出某个会话下所有 run_id)
|
||||
├─ get_messages_as_conversation(session_id)(获取可作为聊天展示的消息)
|
||||
├─ get_visible_history(session_id)(获取可进入 prompt 的历史)
|
||||
├─ update_system_prompt(...)(更新当前会话 system prompt 快照)
|
||||
├─ update_usage(...)(更新 token 用量)
|
||||
├─ end_session(...)(结束会话)
|
||||
├─ reopen_session(...)(重新打开会话)
|
||||
├─ list_sessions_rich(...)(列出带摘要的会话)
|
||||
├─ search_messages(...)(搜索历史消息)
|
||||
└─ resolve_session_id(...)(根据前缀解析 session_id)
|
||||
```
|
||||
|
||||
```text
|
||||
SessionStore (SQLite)(SQLite 会话数据库)
|
||||
│
|
||||
├─ sessions table(会话表)
|
||||
├─ messages table(消息和事件表)
|
||||
├─ messages_fts(全文搜索索引)
|
||||
├─ WAL(SQLite 写入日志模式)
|
||||
├─ parent_session_id(父会话字段,给未来分支会话用)
|
||||
└─ hidden / visible event split(隐藏事件和可见消息分离)
|
||||
```
|
||||
|
||||
```text
|
||||
hidden events(隐藏事件类型)
|
||||
│
|
||||
├─ run_started(运行开始)
|
||||
├─ skill_activation_snapshotted(技能激活快照)
|
||||
├─ tool_selection_snapshotted(工具选择快照)
|
||||
├─ system_prompt_snapshotted(系统提示词快照)
|
||||
├─ run_completed(运行完成)
|
||||
├─ run_failed(运行失败)
|
||||
├─ skill_effects_snapshotted(技能效果快照)
|
||||
├─ task_validation_snapshotted(Task 验证快照)
|
||||
└─ task_feedback_recorded(Task 用户反馈快照)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Memory Module
|
||||
|
||||
```text
|
||||
MemoryService(记忆服务)
|
||||
│
|
||||
├─ initialize()(初始化记忆存储)
|
||||
├─ reload_for_new_run()(每轮开始前刷新记忆快照)
|
||||
├─ get_snapshot()(获取本轮冻结记忆快照)
|
||||
└─ get_store()(获取底层 MemoryStore)
|
||||
```
|
||||
|
||||
```text
|
||||
MemoryStore(长期记忆存储)
|
||||
│
|
||||
├─ target: memory(项目/任务级长期记忆)
|
||||
├─ target: user(用户偏好记忆)
|
||||
├─ add(...)(新增记忆)
|
||||
├─ replace(...)(替换记忆)
|
||||
├─ remove(...)(删除记忆)
|
||||
├─ load_from_disk()(从磁盘读取)
|
||||
├─ save_to_disk()(保存到磁盘)
|
||||
└─ format_for_system_prompt(...)(格式化成 system prompt 段落)
|
||||
```
|
||||
|
||||
```text
|
||||
memory runtime semantics(记忆运行语义)
|
||||
│
|
||||
├─ run start(本轮开始)
|
||||
│ └─ refresh live state -> capture frozen snapshot(刷新 live memory,并冻结本轮快照)
|
||||
│
|
||||
├─ run middle(本轮进行中)
|
||||
│ ├─ memory tool may write durable state(memory 工具可以写入长期记忆)
|
||||
│ └─ current run prompt snapshot stays frozen(但本轮 prompt 里的记忆不变)
|
||||
│
|
||||
└─ next run(下一轮)
|
||||
└─ newly written memory becomes visible(上一轮写入的新记忆开始可见)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Skills Module
|
||||
|
||||
```text
|
||||
SkillsLoader(技能加载器)
|
||||
│
|
||||
├─ workspace published catalog(工作区正式发布的技能目录)
|
||||
├─ workspace legacy skills/*/SKILL.md(旧格式技能文件)
|
||||
├─ builtin skills(内置技能)
|
||||
├─ list_skills()(列出运行时可见技能)
|
||||
├─ list_published_skills()(只列正式发布技能)
|
||||
├─ get_current_version()(获取当前正式版本)
|
||||
├─ load_published_skill()(加载正式版本正文)
|
||||
├─ get_skill_record()(获取技能元数据记录)
|
||||
├─ get_skill_metadata()(获取 frontmatter 元数据)
|
||||
├─ get_skill_tool_hints()(获取技能推荐工具)
|
||||
├─ load_skills_for_context()(把多个技能加载成上下文块)
|
||||
├─ build_skills_summary()(构造技能摘要索引)
|
||||
├─ build_selection_candidates()(构造给 SkillAssembler 的候选摘要)
|
||||
├─ list_skill_supporting_files()(列出技能支持文件)
|
||||
└─ get_always_skills()(获取 always 类型技能)
|
||||
```
|
||||
|
||||
```text
|
||||
SkillAssembler(技能选择器)
|
||||
│
|
||||
├─ input(输入)
|
||||
│ ├─ task_description(Task-aware query:Task 描述 / 当前用户消息 / previous skills / attempt context / validation revision context / team context)
|
||||
│ ├─ candidate skill summaries(候选技能摘要)
|
||||
│ ├─ embedding runtime(向量模型配置)
|
||||
│ └─ selector provider/model(用于选择技能的模型)
|
||||
│
|
||||
├─ embedding retrieve candidates(先用向量召回相关技能)
|
||||
├─ LLM shortlist names(用摘要粗选需要查看正文的候选)
|
||||
│ └─ skip when candidate count <= max_detailed_candidates(候选很少时直接读取正文)
|
||||
├─ internal load shortlisted SKILL.md(SkillAssembler 内部读取候选正文)
|
||||
├─ LLM final select names(结合候选正文选择最终技能名)
|
||||
├─ no match returns [](没有对应 published skill 时返回空,不阻塞任务)
|
||||
└─ return SkillContext[](返回技能上下文)
|
||||
├─ name(技能名)
|
||||
├─ content(技能正文)
|
||||
├─ version(技能版本)
|
||||
├─ content_hash(内容哈希)
|
||||
├─ activation_reason(激活原因)
|
||||
└─ tool_hints(推荐工具)
|
||||
```
|
||||
|
||||
```text
|
||||
skills lifecycle baseline(技能生命周期基线)
|
||||
│
|
||||
├─ SkillSpecStore(技能生命周期文件存储)
|
||||
│ ├─ skill.json(技能总信息)
|
||||
│ ├─ current.json(当前版本指针)
|
||||
│ ├─ versions/(正式版本目录)
|
||||
│ ├─ drafts/(草稿目录)
|
||||
│ ├─ reviews/(审核记录目录)
|
||||
│ └─ archive/(归档目录)
|
||||
│
|
||||
├─ DraftService(草稿服务)
|
||||
│ ├─ create_new_skill_draft(...)(创建新技能草稿)
|
||||
│ ├─ create_revision_draft(...)(创建修订草稿)
|
||||
│ ├─ create_merge_draft(...)(创建合并草稿)
|
||||
│ ├─ create_retire_proposal(...)(创建退役提案)
|
||||
│ ├─ list_drafts(...)(列出草稿)
|
||||
│ └─ get_draft(...)(读取单个草稿)
|
||||
│
|
||||
├─ ReviewService(审核服务)
|
||||
│ ├─ submit_for_review(...)(提交审核)
|
||||
│ ├─ approve(...)(批准草稿)
|
||||
│ └─ reject(...)(拒绝草稿)
|
||||
│
|
||||
└─ SkillPublisher(发布服务)
|
||||
├─ publish(...)(发布 approved 草稿为正式版本)
|
||||
├─ apply_retire_proposal(...)(应用退役提案,不创建新版本)
|
||||
├─ disable(...)(禁用技能)
|
||||
└─ rollback(...)(回滚到旧版本)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. Tools Module
|
||||
|
||||
```text
|
||||
ToolRegistry(工具注册表)
|
||||
│
|
||||
├─ echo(回显工具)
|
||||
├─ memory(写入/管理长期记忆)
|
||||
├─ session_search(搜索会话历史)
|
||||
├─ list_directory(列目录)
|
||||
├─ read_file(读文件)
|
||||
└─ search_files(搜索文件)
|
||||
```
|
||||
|
||||
```text
|
||||
ToolAssembler(工具选择器)
|
||||
│
|
||||
├─ selected = always tools(先加入默认工具)
|
||||
├─ selected += activated skill tool hints(再加入技能推荐工具)
|
||||
├─ selected += embedding top-k tools(再用向量召回任务相关工具)
|
||||
└─ return ToolSpec[](返回本轮可用工具列表;不通过工具动态加载 skill)
|
||||
```
|
||||
|
||||
```text
|
||||
ToolExecutor(工具执行器)
|
||||
│
|
||||
├─ normalize tool call(规范化模型发来的工具调用)
|
||||
├─ resolve tool(找到对应工具)
|
||||
├─ invoke tool(执行工具)
|
||||
└─ return ToolResult(返回工具结果)
|
||||
```
|
||||
|
||||
```text
|
||||
filesystem tool boundary(文件系统工具边界)
|
||||
│
|
||||
├─ workspace scoped(只能访问 workspace 范围)
|
||||
├─ realpath enforcement(用真实路径校验)
|
||||
├─ reject path escape(拒绝路径逃逸)
|
||||
├─ reject symlink escape(拒绝软链接逃逸)
|
||||
└─ reject binary file reads(拒绝读取二进制文件)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. Provider Module
|
||||
|
||||
```text
|
||||
make_provider_bundle(...)(创建模型调用组合)
|
||||
│
|
||||
├─ main_runtime(主模型配置)
|
||||
├─ main_provider(主模型调用器)
|
||||
├─ fallback_runtime(备用模型配置)
|
||||
├─ fallback_provider(备用模型调用器)
|
||||
├─ auxiliary_runtime(辅助模型配置)
|
||||
├─ auxiliary_provider(辅助模型调用器)
|
||||
└─ embedding_runtime(向量模型配置)
|
||||
```
|
||||
|
||||
```text
|
||||
provider roles(模型角色分工)
|
||||
│
|
||||
├─ main(主模型)
|
||||
│ └─ assistant/tool loop(负责正常回答和工具循环)
|
||||
├─ fallback(备用模型)
|
||||
│ └─ main failure recovery(主模型失败时兜底)
|
||||
├─ auxiliary(辅助模型)
|
||||
│ └─ skill selection / future helper tasks(负责选择技能等辅助任务)
|
||||
└─ embedding(向量模型)
|
||||
└─ skill/tool semantic retrieval(负责 skill / tool 的语义召回)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. Service Lifecycle
|
||||
|
||||
```text
|
||||
AgentService(服务生命周期)
|
||||
│
|
||||
├─ MainAgentRouter(请求进入 AgentLoop 前先分类 simple / task)
|
||||
├─ submit_feedback(...)(聊天反馈入口,内部更新 Task 状态)
|
||||
│
|
||||
├─ direct mode(直接模式:适合 CLI / 单次调用)
|
||||
│ └─ process_direct(...)(直接处理一次任务)
|
||||
│
|
||||
└─ running mode(后台运行模式:适合 Web / Gateway)
|
||||
├─ start()(启动 AgentLoop.run)
|
||||
├─ submit_direct(...)(向队列提交任务)
|
||||
├─ stop(timeout_seconds, force)(停止并等待任务收尾)
|
||||
├─ shutdown(timeout_seconds, force)(停止并释放 runtime)
|
||||
└─ close()(关闭已停止的 loop)
|
||||
```
|
||||
|
||||
```text
|
||||
running mode semantics(后台运行语义)
|
||||
│
|
||||
├─ start()(启动)
|
||||
│ └─ AgentLoop.run()(进入队列消费循环)
|
||||
│
|
||||
├─ submit_direct()(提交任务)
|
||||
│ └─ enqueue _DirectRunRequest(把任务放入队列)
|
||||
│
|
||||
├─ stop()(停止)
|
||||
│ ├─ stop accepting new tasks(不再接收新任务)
|
||||
│ └─ drain queued tasks(等待已排队任务处理完)
|
||||
│
|
||||
└─ close()(关闭)
|
||||
└─ requires loop already stopped(必须先 stop,才能 close)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 15. Task Team Registry / Process / Learning 闭环
|
||||
|
||||
```text
|
||||
TaskExecutionPlanner(Task 内部执行规划)
|
||||
│
|
||||
├─ LLM planner
|
||||
│ ├─ 输出 single / team
|
||||
│ └─ team 只允许 sequence / parallel / dag
|
||||
│
|
||||
├─ TaskSkillResolver
|
||||
│ ├─ 从 published skill catalog 检索候选
|
||||
│ ├─ 按 skill_query / required_capabilities / node task 选择 skill
|
||||
│ ├─ 命中 published skill 后写入 graph.nodes[].inherited_pinned_skills
|
||||
│ └─ 无命中时创建 ephemeral guidance,并写入 graph.nodes[].inherited_pinned_skill_contexts
|
||||
│
|
||||
└─ TaskExecutionPlan
|
||||
├─ graph.nodes[].agent 只是 generic runtime trace identity
|
||||
└─ to_event_payload() 写入 skill_queries / selected_skill_names / ephemeral_guidance_ids / skill_resolution_report
|
||||
```
|
||||
|
||||
```text
|
||||
Task mode attempt(每次 Task attempt)
|
||||
│
|
||||
├─ task_execution_planned(隐藏事件)
|
||||
│ └─ plan_mode / strategy / node_ids / skill_resolution_report
|
||||
│
|
||||
├─ team run(仅 team plan)
|
||||
│ ├─ sub-agent run_ids 回填父 Task
|
||||
│ ├─ team summary 只进入主 Agent synthesis context
|
||||
│ └─ task_team_run_completed / task_team_run_failed(隐藏事件)
|
||||
│
|
||||
├─ main Agent synthesis
|
||||
│ ├─ 输出最终用户可见回答
|
||||
│ └─ task_synthesis_completed(隐藏事件)
|
||||
│
|
||||
└─ validation
|
||||
├─ task_validation_snapshotted(隐藏事件)
|
||||
├─ 第一次失败隐藏草稿并重试一次
|
||||
└─ 第二次或验证通过后等待反馈
|
||||
```
|
||||
|
||||
```text
|
||||
Frontend process projection
|
||||
│
|
||||
├─ GET /api/sessions/{session_id}/process
|
||||
│ ├─ 读取隐藏 Task/team/validation 事件
|
||||
│ ├─ 合并 run memory records
|
||||
│ └─ 输出 processRuns / processEvents / processArtifacts / agents
|
||||
│
|
||||
└─ ChatWorkbench
|
||||
├─ 桌面端显示 ProcessLane
|
||||
├─ 移动端显示 Process tab
|
||||
└─ 不直接暴露隐藏事件原始 JSON
|
||||
```
|
||||
|
||||
```text
|
||||
Learning pipeline
|
||||
│
|
||||
├─ evidence recording
|
||||
│ ├─ every run -> RunRecord
|
||||
│ ├─ activated skills -> SkillEffectRecord
|
||||
│ └─ no candidates generated here
|
||||
│
|
||||
├─ feedback gate
|
||||
│ ├─ validation accepted + satisfied -> scoped learning candidate
|
||||
│ ├─ validation rejected + satisfied -> 记录人工接受风险,不生成候选
|
||||
│ ├─ revise -> 保持 Task 打开,不生成候选
|
||||
│ └─ abandon -> 失败证据,不写主 memory,不生成成功候选
|
||||
│
|
||||
├─ scoped candidate generation
|
||||
│ ├─ source = current task run_ids
|
||||
│ ├─ no published skill -> new_skill
|
||||
│ └─ published skill used -> revise_skill
|
||||
│
|
||||
├─ SkillLearningPipelineService
|
||||
│ ├─ candidate -> queued / synthesizing
|
||||
│ ├─ worker/run-once -> draft
|
||||
│ ├─ draft -> safety report
|
||||
│ ├─ draft -> lightweight eval report
|
||||
│ ├─ safety_failed / eval_failed 阻断发布
|
||||
│ ├─ draft -> submit review
|
||||
│ ├─ approve / reject
|
||||
│ ├─ approved + safety passed + eval not failed -> publish
|
||||
│ ├─ retire proposal -> apply retire
|
||||
│ └─ rollback / disable
|
||||
│
|
||||
├─ SkillLearningWorker
|
||||
│ ├─ 默认按配置定时扫描 open candidates
|
||||
│ ├─ 自动生成 draft_ready / safety_failed / eval_failed
|
||||
│ └─ 永不自动 approve / publish
|
||||
│
|
||||
├─ Web review workbench
|
||||
│ ├─ Candidates
|
||||
│ ├─ Draft detail
|
||||
│ ├─ Safety report
|
||||
│ └─ Eval report
|
||||
│
|
||||
└─ Runtime catalog
|
||||
└─ 只有 published skill 进入运行时选择;draft 不生效
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16. Web / Gateway
|
||||
|
||||
```text
|
||||
Web(网页入口)
|
||||
│
|
||||
├─ FastAPI lifespan(FastAPI 生命周期)
|
||||
│ ├─ create or receive AgentService(创建或接收 AgentService)
|
||||
│ ├─ start() when app owns lifecycle(如果 Web app 拥有生命周期,就启动 service)
|
||||
│ ├─ start SkillLearningWorker when enabled(按配置启动技能学习 worker)
|
||||
│ └─ shutdown() when app owns lifecycle(如果 Web app 拥有生命周期,就关闭 service / worker)
|
||||
│
|
||||
├─ GET /api/ping(健康检查接口)
|
||||
│ └─ return status / running / mode(返回状态、是否运行、运行模式)
|
||||
│
|
||||
├─ POST /api/chat(聊天接口)
|
||||
│ ├─ validate WebChatRequest(校验请求体)
|
||||
│ ├─ agent_service.submit_direct(...)(把用户消息提交给 AgentService)
|
||||
│ └─ return WebChatResponse(返回模型回复 + run/task/validation 元数据)
|
||||
│
|
||||
├─ WS /ws/{session_id}(网页 WebSocket 适配层)
|
||||
│ ├─ ping -> pong
|
||||
│ ├─ message -> agent_service.submit_direct(...)
|
||||
│ ├─ return status / assistant message(携带 run/task/validation 元数据)
|
||||
│ └─ return session_updated(通知前端刷新 session/process)
|
||||
│
|
||||
└─ POST /api/chat/feedback(聊天反馈接口)
|
||||
├─ validate WebChatFeedbackRequest
|
||||
├─ agent_service.submit_feedback(...)
|
||||
└─ return WebChatFeedbackResponse
|
||||
|
||||
Skills learning admin API
|
||||
│
|
||||
├─ GET /api/skills/candidates
|
||||
├─ POST /api/skills/candidates/{candidate_id}/draft
|
||||
├─ POST /api/skills/candidates/{candidate_id}/regenerate
|
||||
├─ POST /api/skills/learning/run-once
|
||||
├─ GET /api/skills/{skill_name}/drafts/{draft_id}/safety
|
||||
├─ GET /api/skills/{skill_name}/drafts/{draft_id}/eval
|
||||
└─ POST /api/skills/{skill_name}/drafts/{draft_id}/publish
|
||||
└─ requires approved review + safety passed + eval not failed
|
||||
```
|
||||
|
||||
```text
|
||||
Gateway(消息通道入口)
|
||||
│
|
||||
├─ MessageBus(内部消息总线)
|
||||
├─ ChannelAdapter(Telegram / Slack / Email / WhatsApp 等只作为 adapter)
|
||||
├─ inbound -> AgentService.handle_inbound_message(...)(外部消息进入 AgentService)
|
||||
├─ outbound <- OutboundMessage(AgentService 返回结构化输出消息)
|
||||
└─ ChannelManager(按 message.channel 分发 outbound)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17. Bus Mode Skeleton
|
||||
|
||||
```text
|
||||
AgentLoop.run()(后台队列运行模式)
|
||||
│
|
||||
├─ create queue(创建任务队列)
|
||||
├─ mark running(标记为运行中)
|
||||
├─ consume _DirectRunRequest(消费一个任务请求)
|
||||
├─ call _process_direct_impl(...)(调用真正的单轮执行逻辑)
|
||||
├─ set future result / exception(把结果或异常写回等待方)
|
||||
├─ stop() -> enqueue sentinel(停止时放入结束标记)
|
||||
└─ drain pending queue on exit(退出时清理未处理任务)
|
||||
```
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -1,350 +0,0 @@
|
||||
# backend-old -> backend 可用移植指南
|
||||
|
||||
这份文档描述的是:从 `app-instance/backend-old` 迁到新的 `app-instance/backend` 时,哪些 Python 代码是可用的、应该放到新目录的哪里、哪些可以直接迁、哪些必须拆分后迁。
|
||||
|
||||
本文默认遵守以下前提:
|
||||
|
||||
1. 新后端统一使用 `beaver` 命名。
|
||||
2. 所有 agent 最终都复用同一套 `beaver.engine.AgentLoop`。
|
||||
3. 新代码按当前新目录落点来放,不再向 `nanobot/` 回写。
|
||||
4. 不保留 `third_party/`。
|
||||
5. memory 设计以 `hermes-agent` 为基线:统一 CRUD memory tool + frozen snapshot + session_search。
|
||||
|
||||
## 1. 迁移范围
|
||||
|
||||
本文覆盖的是 `backend-old` 中的自有 Python 源码:
|
||||
|
||||
- `backend-old/nanobot/**/*.py`
|
||||
- `backend-old/nanobot/llm_audit.py`
|
||||
|
||||
本文明确不覆盖:
|
||||
|
||||
- `.venv/`
|
||||
- `.pytest_cache/`
|
||||
- `.ruff_cache/`
|
||||
- `third_party/`
|
||||
- `bridge/` 里的 Node 代码
|
||||
|
||||
## 2. 迁移判定说明
|
||||
|
||||
| 判定 | 含义 |
|
||||
| --- | --- |
|
||||
| `可直接迁移` | 改导入路径、命名后可基本原样落到新位置 |
|
||||
| `小幅重构` | 主体逻辑可复用,但要改依赖注入、类型名或路径 |
|
||||
| `拆分迁移` | 旧文件职责过大,必须拆成多个新文件 |
|
||||
| `重写迁移` | 只保留行为目标,不建议原样搬代码 |
|
||||
| `不迁移` | 不进入新后端 |
|
||||
|
||||
## 3. 总体迁移顺序
|
||||
|
||||
建议按下面顺序迁:
|
||||
|
||||
1. `foundation/config + foundation/events + foundation/models + foundation/utils`
|
||||
2. `skills + plugins`
|
||||
3. `memory(curated + session_search baseline)`
|
||||
4. `engine/session + engine/providers + engine/context + engine/loop`
|
||||
5. `tools`
|
||||
6. `coordinator`
|
||||
7. `interfaces/web + interfaces/cli + interfaces/channels + services`
|
||||
8. `integrations/a2a + integrations/outlook + integrations/authz + integrations/mcp`
|
||||
|
||||
原因:
|
||||
|
||||
1. `engine`、`coordinator`、`interfaces` 都依赖 `config`、`events`、`models`。
|
||||
2. `skills` 和 `memory` 必须先定契约,因为 system prompt 注入、memory tool、session_search 都会反向约束 engine。
|
||||
3. `web/server.py`、`cli/commands.py` 只有在服务层和内核层稳定后才值得拆。
|
||||
|
||||
## 4. 包初始化文件如何处理
|
||||
|
||||
下面这些旧 `__init__.py` 不建议原样迁移,只保留“最小 re-export”或空包文件:
|
||||
|
||||
- `nanobot/__init__.py`
|
||||
- `nanobot/a2a/__init__.py`
|
||||
- `nanobot/agent/__init__.py`
|
||||
- `nanobot/agent_team/__init__.py`
|
||||
- `nanobot/authz/__init__.py`
|
||||
- `nanobot/bus/__init__.py`
|
||||
- `nanobot/channels/__init__.py`
|
||||
- `nanobot/cli/__init__.py`
|
||||
- `nanobot/config/__init__.py`
|
||||
- `nanobot/cron/__init__.py`
|
||||
- `nanobot/heartbeat/__init__.py`
|
||||
- `nanobot/providers/__init__.py`
|
||||
- `nanobot/session/__init__.py`
|
||||
- `nanobot/templates/__init__.py`
|
||||
- `nanobot/templates/memory/__init__.py`
|
||||
- `nanobot/utils/__init__.py`
|
||||
- `nanobot/web/__init__.py`
|
||||
|
||||
统一处理规则:
|
||||
|
||||
1. 不复制旧的 lazy import / `__getattr__` 设计。
|
||||
2. 目标文件稳定后,再在对应 `beaver/*/__init__.py` 里做显式导出。
|
||||
|
||||
## 5. Foundation 层迁移映射
|
||||
|
||||
| 旧文件 | 关键类/函数 | 新位置 | 判定 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `nanobot/config/schema.py` | `Config`, `AgentDefaults`, `AgentsConfig`, `ProviderConfig`, `ProvidersConfig`, `ToolsConfig`, `ChannelsConfig`, `AuthzConfig`, `BackendIdentityConfig` | `beaver/foundation/config/schema.py` | `小幅重构` | 主体模型可迁;`Config._match_provider/get_provider*` 这类 provider 解析逻辑移到 `beaver/engine/providers/factory.py`。 |
|
||||
| `nanobot/config/loader.py` | `get_config_path`, `get_data_dir`, `load_config`, `save_config`, `_migrate_config` | `beaver/foundation/config/loader.py` | `可直接迁移` | 迁移时把默认路径和 `beaver` 命名改掉。 |
|
||||
| `nanobot/config/paths.py` | `get_data_dir`, `get_media_dir` | `beaver/foundation/config/paths.py` | `可直接迁移` | 纯 path helper。 |
|
||||
| `nanobot/utils/helpers.py` | `ensure_dir`, `get_workspace_path`, `get_sessions_path`, `get_skills_path`, `timestamp`, `truncate_string`, `safe_filename`, `parse_session_key` | `beaver/foundation/utils/helpers.py` | `可直接迁移` | 纯工具函数,直接复用。 |
|
||||
| `nanobot/bus/events.py` | `InboundMessage`, `OutboundMessage` | `beaver/foundation/events/messages.py` | `可直接迁移` | 建议作为全局消息模型。 |
|
||||
| `nanobot/bus/queue.py` | `MessageBus` | `beaver/foundation/events/message_bus.py` | `小幅重构` | 类本身可迁,但后续由 `services` 和 `interfaces/gateway` 注入。 |
|
||||
| `nanobot/agent/process_events.py` | `new_run_id`, `utc_now_iso`, `process_event_sink`, `process_run_context`, `current_process_run_id`, `has_process_event_sink`, `emit_process_event` | `beaver/foundation/events/process.py` | `可直接迁移` | 这是全局过程事件层,应该上提。 |
|
||||
| `nanobot/agent/run_result.py` | `normalize_summary_text`, `contains_placeholder_summary`, `has_meaningful_summary`, `AgentRunResult` | `beaver/foundation/models/run_result.py` | `可直接迁移` | 给 engine、coordinator、services 共享。 |
|
||||
| `nanobot/cron/types.py` | `CronSchedule`, `CronPayload`, `CronAction`, `CronExecutionResult`, `CronJobState`, `CronJob`, `CronStore` | `beaver/foundation/models/cron.py` | `可直接迁移` | 纯类型定义。 |
|
||||
| `nanobot/llm_audit.py` | `write_llm_audit_event`, `redact_mapping`, `summarize_messages`, `summarize_tool_calls`, `summarize_tools`, `summarize_exception` | `beaver/foundation/utils/llm_audit.py` | `可直接迁移` | 审计逻辑不应挂在旧根路径。 |
|
||||
|
||||
## 6. Engine 层迁移映射
|
||||
|
||||
| 旧文件 | 关键类/函数 | 新位置 | 判定 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `nanobot/session/manager.py` | `Session`, `SessionManager` | `beaver/engine/session/models.py`, `beaver/engine/session/manager.py` | `可直接迁移` | `Session` 和 `SessionManager` 建议拆开。 |
|
||||
| `nanobot/agent/context.py` | `ContextBuilder.build_system_prompt`, `build_messages`, `add_tool_result`, `add_assistant_message` | `beaver/engine/context/builder.py` | `小幅重构` | 需要改成只注入 frozen memory snapshot,而不是直接读 live memory。 |
|
||||
| `nanobot/agent/loop.py` | `AgentLoop` 全类 | `beaver/engine/loop.py` | `拆分迁移` | 旧文件过载,不能整块搬。 |
|
||||
| `nanobot/agent/memory.py` | `MemoryStore.read_long_term`, `write_long_term`, `append_history`, `get_memory_context`, `consolidate` | `beaver/memory/curated/store.py`, `beaver/memory/curated/snapshot.py`, `beaver/memory/search/transcript_store.py` | `重写迁移` | 新标准不再沿用旧 `consolidate()` 模型;按 Hermes 改成 CRUD memory + frozen snapshot + session transcript search。 |
|
||||
| `nanobot/providers/base.py` | `ToolCallRequest`, `LLMResponse`, `LLMProvider` | `beaver/engine/providers/base.py` | `可直接迁移` | provider 契约层。 |
|
||||
| `nanobot/providers/registry.py` | `ProviderSpec`, `find_by_model`, `find_gateway`, `find_by_name` | `beaver/engine/providers/registry.py` | `可直接迁移` | registry 自包含度高。 |
|
||||
| `nanobot/providers/litellm_provider.py` | `LiteLLMProvider` | `beaver/engine/providers/litellm.py` | `小幅重构` | 主要改导入路径和 `Config` 依赖。 |
|
||||
| `nanobot/providers/openai_codex_provider.py` | `OpenAICodexProvider`, `_convert_messages`, `_consume_sse`, `_friendly_error` | `beaver/engine/providers/codex.py` | `小幅重构` | 主体可迁。 |
|
||||
| `nanobot/providers/custom_provider.py` | `CustomProvider` | `beaver/engine/providers/custom.py` | `可直接迁移` | 文件小,直接迁。 |
|
||||
| `nanobot/providers/transcription.py` | `GroqTranscriptionProvider` | `beaver/engine/providers/transcription.py` | `可直接迁移` | 辅助 provider,不阻塞主线。 |
|
||||
| `nanobot/agent/subagent.py` | `SubagentManager.run_local_task`, `_build_local_tools`, `_build_subagent_prompt`, `_strip_think`, `_tool_hint` | `beaver/engine/runtime/local_runner.py` | `重写迁移` | 新架构下不应保留 `SubagentManager` 这个名字;只抽出本地 agent 运行能力。 |
|
||||
| `nanobot/agent/subagents.py` | `normalize_subagent_id`, `SubagentSpec`, `LocalSubagentStore` | `beaver/coordinator/registry/local_subagent_store.py` | `小幅重构` | 数据结构可复用,但要切到 `beaver` 命名和新 registry。 |
|
||||
|
||||
### 6.1 `agent/loop.py` 函数级拆分
|
||||
|
||||
旧 `nanobot/agent/loop.py` 应这样拆:
|
||||
|
||||
| 旧函数 | 新位置 |
|
||||
| --- | --- |
|
||||
| `AgentLoop.__init__` | `beaver/engine/loop.py` |
|
||||
| `apply_runtime_config` | `beaver/engine/loader.py` |
|
||||
| `_register_default_tools` | `beaver/engine/loader.py` + `beaver/tools/registry/tool_registry.py` |
|
||||
| `_connect_mcp`, `_clear_mcp_tools`, `reload_mcp_servers`, `get_mcp_servers_view` | `beaver/engine/runtime/mcp_runtime.py` |
|
||||
| `_set_tool_context` | `beaver/engine/loop.py` |
|
||||
| `_build_skills_loader` | `beaver/skills/resolver/runtime.py` |
|
||||
| `_run_agent_loop`, `_process_message`, `_save_turn`, `process_system_announcement`, `process_direct` | `beaver/engine/loop.py` |
|
||||
| `_get_consolidation_lock`, `_prune_consolidation_lock`, `_consolidate_memory` | `不直接迁移;改成 beaver/tools/builtins/memory.py + beaver/memory/curated/* + beaver/memory/search/*` |
|
||||
| `run`, `stop`, `close_mcp` | `beaver/engine/loop.py` |
|
||||
|
||||
## 7. Tools 层迁移映射
|
||||
|
||||
| 旧文件 | 关键类/函数 | 新位置 | 判定 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `nanobot/agent/tools/base.py` | `Tool`, `validate_params`, `to_schema` | `beaver/tools/base.py` | `可直接迁移` | 工具基类。 |
|
||||
| `nanobot/agent/tools/registry.py` | `ToolRegistry` | `beaver/tools/registry/tool_registry.py` | `可直接迁移` | registry 逻辑自包含。 |
|
||||
| `nanobot/agent/tools/filesystem.py` | `ReadFileTool`, `WriteFileTool`, `EditFileTool`, `ListDirTool` | `beaver/tools/builtins/filesystem.py` | `小幅重构` | 路径保护规则建议进一步抽到 `beaver/permissions/guards/filesystem.py`。 |
|
||||
| `nanobot/agent/tools/shell.py` | `ExecTool`, `_guard_command`, `_guard_protected_paths` | `beaver/tools/builtins/shell.py` | `小幅重构` | 命令保护逻辑建议下沉到 `permissions/guards/shell.py`。 |
|
||||
| `nanobot/agent/tools/web.py` | `WebSearchTool`, `WebFetchTool` | `beaver/tools/builtins/web.py` | `可直接迁移` | 改导入路径即可。 |
|
||||
| `nanobot/agent/tools/message.py` | `MessageTool`, `set_context`, `set_send_callback`, `start_turn` | `beaver/tools/builtins/message.py` | `可直接迁移` | 保持 tool 形态。 |
|
||||
| `nanobot/agent/tools/spawn.py` | `DelegationTool`, `SpawnSubagentTool`, `SpawnAgentTeamTool`, `NestedDelegateTool` | `beaver/tools/builtins/spawn.py` | `小幅重构` | tool 壳可留;执行全部接 `beaver/coordinator/delegation/manager.py`。 |
|
||||
| `nanobot/agent/tools/cron.py` | `CronTool` | `beaver/tools/builtins/cron.py` | `可直接迁移` | 与 `CronService` 对接即可。 |
|
||||
| `nanobot/agent/tools/cron_action.py` | `CronActionTool` | `beaver/tools/builtins/cron.py` | `小幅重构` | 合并成同一 cron 工具模块更合理。 |
|
||||
| `nanobot/agent/tools/mcp.py` | `MCPToolWrapper`, `connect_mcp_servers`, `_describe_mcp_exception` | `beaver/tools/mcp/wrapper.py`, `beaver/tools/mcp/connect.py` | `小幅重构` | 连接逻辑和 wrapper 分开。 |
|
||||
| `无旧文件一一对应(以 Hermes 为准新增)` | `memory_tool(action, target, content, old_text)` | `beaver/tools/builtins/memory.py` | `新增实现` | 统一 memory CRUD 工具,优先级高于旧 `MemoryStore.consolidate()` 逻辑。 |
|
||||
| `无旧文件一一对应(以 Hermes 为准新增)` | `session_search(query, role_filter, limit)` | `beaver/tools/builtins/session_search.py` | `新增实现` | 历史会话检索不再靠把大量过程细节塞进 memory。 |
|
||||
|
||||
## 8. Skills、Plugins、Memory 层迁移映射
|
||||
|
||||
| 旧文件 | 关键类/函数 | 新位置 | 判定 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `nanobot/agent/skills.py` | `SkillsLoader.list_skills`, `load_skill`, `load_skills_for_context`, `build_skills_summary`, `get_always_skills`, `get_skill_metadata`, `get_skill_agent_cards`, `list_skill_agent_cards` | `beaver/skills/catalog/loader.py`, `beaver/skills/resolver/runtime.py` | `拆分迁移` | catalog 负责扫描/索引/元数据;resolver 负责运行时注入。 |
|
||||
| `nanobot/agent/skill_reviews.py` | `SkillReviewManager` | `beaver/skills/reviews/manager.py` | `小幅重构` | ZIP 解包、安全检查、review 状态管理都能复用。 |
|
||||
| `nanobot/agent/plugins.py` | `PluginAgent`, `PluginCommand`, `Plugin`, `PluginLoader` | `beaver/plugins/models.py`, `beaver/plugins/loader.py`, `beaver/plugins/registry.py` | `小幅重构` | 这是最适合先迁的一批。 |
|
||||
| `nanobot/agent/marketplace.py` | `MarketplaceEntry`, `MarketplacePluginInfo`, `MarketplaceManager` | `beaver/plugins/marketplace.py` | `可直接迁移` | 市场逻辑相对独立。 |
|
||||
| `nanobot/agent_team/memory.py` | `ProcedureMemory`, `RunMemory`, `task_tokens`, `similarity_score`, `clip_confidence` | `beaver/memory/procedures/procedure_memory.py`, `beaver/memory/runs/run_memory.py` | `小幅重构` | 这些不再代表主 memory 契约,而是 coordinator/analytics 的可选优化层。 |
|
||||
| `nanobot/agent/memory.py` | `MemoryStore` | `beaver/memory/curated/store.py`, `beaver/memory/curated/snapshot.py`, `beaver/memory/search/transcript_store.py` | `重写迁移` | 见第 6 节;memory 基线改成 Hermes 风格。 |
|
||||
| `nanobot/skills/subagent-manager/scripts/subagentctl.py` | `cmd_list`, `cmd_show`, `cmd_create`, `cmd_delete`, `cmd_set_system_prompt`, `cmd_add_mcp_http`, `cmd_add_mcp_stdio`, `cmd_remove_mcp` | `beaver/interfaces/cli/subagentctl.py` | `小幅重构` | 只迁 CLI 管理能力,不再绑定 `nanobot` store。 |
|
||||
|
||||
### 8.1 `agent/skills.py` 函数级拆分
|
||||
|
||||
| 旧函数/方法 | 新位置 |
|
||||
| --- | --- |
|
||||
| `list_skills`, `get_skill_metadata`, `get_skill_agent_cards`, `list_skill_agent_cards` | `beaver/skills/catalog/loader.py` |
|
||||
| `load_skill`, `load_skills_for_context`, `build_skills_summary`, `get_always_skills` | `beaver/skills/resolver/runtime.py` |
|
||||
| `_strip_frontmatter`, `_parse_nanobot_metadata`, `_check_requirements`, `_get_missing_requirements`, `_get_skill_description` | `beaver/skills/catalog/utils.py` 或 `loader.py` 内部私有函数 |
|
||||
|
||||
### 8.2 Memory 迁移基线
|
||||
|
||||
新的 memory 迁移必须遵守下面这条,而不是直接复制旧 `MemoryStore + ProcedureMemory` 设计:
|
||||
|
||||
1. 持久化记忆只保留两类:
|
||||
- `memory`
|
||||
- `user`
|
||||
2. 写操作统一通过 `memory` tool:
|
||||
- `add`
|
||||
- `replace`
|
||||
- `remove`
|
||||
3. `replace/remove` 使用短语义片段匹配,不要求 UUID
|
||||
4. 写入协议必须是:
|
||||
- 注入扫描
|
||||
- 文件锁
|
||||
- 锁内 reload
|
||||
- 重复/上限检测
|
||||
- 原子写入
|
||||
5. system prompt 只注入 frozen snapshot
|
||||
6. 历史细节通过 `session_search` 检索,不扩大 memory
|
||||
7. 稳定的方法论、工作法、可复用技巧进入 `skills`
|
||||
8. `ProcedureMemory` 只保留为 coordinator 的复用优化
|
||||
|
||||
## 9. Coordinator 层迁移映射
|
||||
|
||||
| 旧文件 | 关键类/函数 | 新位置 | 判定 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `nanobot/agent/agent_registry.py` | `AgentDescriptor`, `WorkspaceAgentStore`, `AgentRegistry` | `beaver/coordinator/registry/models.py`, `workspace_store.py`, `agent_registry.py` | `拆分迁移` | descriptor、store、registry 三类职责应拆开。 |
|
||||
| `nanobot/agent/delegation.py` | `DelegationRun`, `DelegationManager` | `beaver/coordinator/delegation/manager.py`, `beaver/coordinator/execution/delegation_run.py`, `beaver/coordinator/delegation/events.py` | `拆分迁移` | 旧文件职责最重,不能原样搬。 |
|
||||
| `nanobot/a2a/client.py` | `A2AClient`, `A2AError`, `A2AUnsupportedMethodError`, `A2AStreamEvent` | `beaver/integrations/a2a/client.py` | `小幅重构` | A2A 是协议层,适合独立迁。 |
|
||||
| `nanobot/agent_team/types.py` | `ExecutionMode`, `ResolvedTeamPlan`, `SwarmsRunSpec`, `SwarmsRunResult`, `BridgeResult` | `beaver/coordinator/models.py` | `重写迁移` | v1 已改为 Beaver 自有 `AgentDescriptor / ExecutionGraph / TeamRunResult`,不直接保留 swarms wire shape。 |
|
||||
| `nanobot/agent_team/orchestrator.py` | `AgentTeamOrchestrator.run_task` | `beaver/services/team_service.py`, `beaver/coordinator/execution/scheduler.py` | `重写迁移` | v1 入口是 `TeamService.run_team(...)`,调度由 `TeamGraphScheduler` 承担。 |
|
||||
| `nanobot/agent_team/provisioning.py` | `ProvisioningManager`, `SpecialistProvisionResult` | 后续 `beaver/coordinator/team/provisioning.py` | `暂缓迁移` | v1 不做自动 provisioning;先由显式 `AgentDescriptor` 描述节点。 |
|
||||
| `nanobot/agent_team/target_resolver.py` | `TargetResolver.resolve_team_targets`, `_select_existing_for_role_with_llm` | 后续 `beaver/coordinator/team/target_resolver.py` | `暂缓迁移` | v1 不做 registry/target resolver;后续高级策略再补。 |
|
||||
| `nanobot/agent_team/swarms_policy.py` | `SwarmsPolicy` | 后续 `beaver/coordinator/backends/swarms/policy.py` 或 strategy preset policy | `暂缓迁移` | v1 不接 swarms runtime;策略约束先落在 Beaver graph validation / scheduler。 |
|
||||
| `nanobot/agent_team/swarms_planner.py` | `SwarmsRunPlanner` | 后续 strategy preset -> `ExecutionGraph` | `重写迁移` | 只吸收策略形态,不保留 `third_party` 假设。 |
|
||||
| `nanobot/agent_team/swarms_bridge.py` | `SwarmsBridge` | 后续 `beaver/coordinator/backends/swarms/bridge.py` | `暂缓迁移` | 只有确实接外部 swarms backend 时才需要。 |
|
||||
| `nanobot/agent_team/swarms_adapter.py` | `ensure_swarms_importable`, `load_swarms_runtime`, `safe_swarms_name`, `NanobotAgentAdapter` | 后续 `beaver/coordinator/backends/swarms/runtime.py`, `adapter.py` | `重写迁移` | 不再允许 `third_party/` 路径探测;v1 不依赖 swarms runtime。 |
|
||||
|
||||
### 9.1 `agent/delegation.py` 函数级拆分
|
||||
|
||||
| 旧函数/方法 | 新位置 |
|
||||
| --- | --- |
|
||||
| `dispatch_subagent`, `dispatch_agent_team`, `_dispatch`, `_run_dispatch` | `beaver/coordinator/delegation/manager.py` |
|
||||
| `_emit_team_progress`, `_emit_agent_started`, `_emit_agent_finished`, `_emit_agent_cancelled`, `_emit_group_started`, `_emit_group_finished`, `_publish_prefixed_progress`, `_emit_direct_user_message` | `beaver/coordinator/delegation/events.py` |
|
||||
| `_run_team_member_for_swarms`, `_execute_descriptor`, `_build_progress_callback`, `_build_task_callback` | `beaver/coordinator/execution/member_runner.py` |
|
||||
| `_resolve_single`, `_resolve_nested_delegate`, `_normalize_skill_names`, `_build_skill_context`, `_augment_task_with_skills` | `beaver/coordinator/delegation/manager.py` |
|
||||
| `cancel`, `cancel_all`, `_cancel_remote_tasks`, `_announce_cancelled` | `beaver/coordinator/execution/cancel.py` 或先保留在 `manager.py` |
|
||||
| `_announce_single_result`, `_announce_orchestrator_result`, `_publish_announcement`, `_notify_direct_announcement` | `beaver/coordinator/delegation/announcement.py` |
|
||||
|
||||
## 10. Services 层迁移映射
|
||||
|
||||
| 旧文件 | 关键类/函数 | 新位置 | 判定 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `nanobot/cron/service.py` | `CronService` | `beaver/services/cron_service.py` | `小幅重构` | 保持后台服务角色,不要再埋在 `nanobot/cron/` 目录。 |
|
||||
| `nanobot/cron/runtime.py` | `run_cron_job`, `_build_execution_context`, `_resolve_session_key` | `beaver/services/cron_runtime.py` | `小幅重构` | 与 `AgentLoop`、Web、CLI 的对接点要更新。 |
|
||||
| `nanobot/heartbeat/service.py` | `HeartbeatService` | `beaver/services/heartbeat_service.py` | `可直接迁移` | 后台服务类,自包含度高。 |
|
||||
|
||||
## 11. Interfaces 层迁移映射
|
||||
|
||||
### 11.1 CLI
|
||||
|
||||
| 旧文件 | 关键类/函数 | 新位置 | 判定 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `nanobot/__main__.py` | 模块入口 | `beaver/interfaces/cli/main.py` | `可直接迁移` | 只保留入口壳。 |
|
||||
| `nanobot/cli/commands.py` | `main`, `version_callback`, `agent`, `gateway`, `web`, `onboard`, `_create_workspace_templates`, `status`, `channels_*`, `cron_*`, `provider_*` | `beaver/interfaces/cli/main.py`, `beaver/interfaces/cli/commands/*.py`, `beaver/services/*.py` | `拆分迁移` | 旧 CLI 同时做了命令声明、provider 装配、gateway/web 启动、cron 管理。 |
|
||||
|
||||
### 11.2 `cli/commands.py` 函数级拆分
|
||||
|
||||
| 旧函数 | 新位置 |
|
||||
| --- | --- |
|
||||
| `main`, `version_callback` | `beaver/interfaces/cli/main.py` |
|
||||
| `agent` | `beaver/interfaces/cli/commands/agent.py`,内部调用 `beaver/services/agent_service.py` |
|
||||
| `gateway` | `beaver/interfaces/gateway/main.py` |
|
||||
| `web` | `beaver/interfaces/cli/commands/web.py`,内部调用 `beaver/interfaces/web/app.py` |
|
||||
| `_make_provider` | `beaver/engine/providers/factory.py` |
|
||||
| `onboard`, `_create_workspace_templates`, `status` | `beaver/services/admin_service.py` + CLI 薄包装 |
|
||||
| `channels_main`, `channels_status`, `channels_login` | `beaver/interfaces/cli/commands/channels.py` |
|
||||
| `cron_main`, `cron_list`, `cron_add`, `cron_remove`, `cron_enable`, `cron_run` | `beaver/interfaces/cli/commands/cron.py` + `beaver/services/cron_service.py` |
|
||||
| `provider_main`, `_register_login`, `provider_login`, `_login_openai_codex`, `_login_github_copilot` | `beaver/interfaces/cli/commands/providers.py` + `beaver/services/admin_service.py` |
|
||||
| `_flush_pending_tty_input`, `_restore_terminal`, `_init_prompt_session`, `_print_agent_response`, `_is_exit_command`, `_read_interactive_input_async`, `_exit_after_group_help`, `_get_bridge_dir` | `beaver/interfaces/cli/tty.py` |
|
||||
|
||||
### 11.3 Web
|
||||
|
||||
| 旧文件 | 关键类/函数 | 新位置 | 判定 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `nanobot/web/server.py` | `create_app`, `WebSocketBroadcaster`, 所有 `*Request/*Response` 模型、所有 auth / handoff / route helper | `beaver/interfaces/web/app.py`, `deps.py`, `realtime.py`, `auth.py`, `routes/*.py`, `schemas/*.py` | `拆分迁移` | 这是第二个最大拆分热点。 |
|
||||
| `nanobot/web/files.py` | `save_file`, `get_file_metadata`, `list_files`, `browse_workspace`, `save_to_workspace`, `delete_workspace_path`, `create_workspace_dir` | `beaver/interfaces/web/files.py`, `beaver/interfaces/web/routes/files.py` | `小幅重构` | 纯文件 API 逻辑,应该从 `server.py` 分离出去。 |
|
||||
| `nanobot/web/outlook.py` | `connect_workspace`, `disconnect_workspace`, `outlook_status`, `get_overview`, `get_message_detail`, `list_messages`, `list_events`, `ensure_outlook_mcp_registration` | `beaver/integrations/outlook/service.py`,由 `beaver/interfaces/web/routes/outlook.py` 调用 | `小幅重构` | 核心逻辑应放 integration,不应继续留在 web 包下。 |
|
||||
|
||||
### 11.4 `web/server.py` 函数级拆分
|
||||
|
||||
| 旧函数/类型 | 新位置 |
|
||||
| --- | --- |
|
||||
| `create_app` | `beaver/interfaces/web/app.py` |
|
||||
| `ChatRequest`, `ChatResponse` | `beaver/interfaces/web/schemas/chat.py` |
|
||||
| `AddCronJobRequest`, `ToggleCronJobRequest` | `beaver/interfaces/web/schemas/cron.py` |
|
||||
| `AddMarketplaceRequest`, `ApproveSkillReviewRequest` | `beaver/interfaces/web/schemas/plugins.py`, `skills.py` |
|
||||
| `AddAgentRequest`, `_discover_agent_payload`, `_manual_agent_payload`, `_should_auto_discover_agent` | `beaver/interfaces/web/routes/agents.py` + `beaver/interfaces/web/schemas/agents.py` |
|
||||
| `MCPServerRequest` | `beaver/interfaces/web/schemas/mcp.py` |
|
||||
| `SubagentRequest` | `beaver/interfaces/web/schemas/delegation.py` |
|
||||
| `OutlookConnectionRequest` | `beaver/interfaces/web/schemas/outlook.py` |
|
||||
| `LoginRequest`, `RegisterRequest`, `AuthzRegisterBackendRequest`, `LocalBackendIdentityRequest`, `HandoffConsumeRequest` | `beaver/interfaces/web/schemas/auth.py` |
|
||||
| `WebSocketBroadcaster` | `beaver/interfaces/web/realtime.py` |
|
||||
| `_issue_web_token`, `_require_web_user`, `_issue_handoff_code`, `_consume_handoff_code`, `_prune_handoff_codes`, `_handoff_*` | `beaver/interfaces/web/auth.py` |
|
||||
| `_register_routes` | 删除;改为 `beaver/interfaces/web/routes/*.py` 各自注册 |
|
||||
| `_make_provider` | 删除;使用 `beaver/engine/providers/factory.py` |
|
||||
| `_serialize_job` | `beaver/interfaces/web/serializers/cron.py` 或 `schemas/cron.py` |
|
||||
|
||||
### 11.5 Channels
|
||||
|
||||
| 旧文件 | 关键类/函数 | 新位置 | 判定 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `nanobot/channels/base.py` | `BaseChannel` | `beaver/interfaces/channels/base.py` | `可直接迁移` | 通道抽象基类。 |
|
||||
| `nanobot/channels/manager.py` | `ChannelManager` | `beaver/interfaces/channels/manager.py` | `小幅重构` | 初始化逻辑要改为 `beaver` config。 |
|
||||
| `nanobot/channels/dingtalk.py` | `NanobotDingTalkHandler`, `DingTalkChannel` | `beaver/interfaces/channels/dingtalk.py` | `小幅重构` | 改命名和 config 依赖。 |
|
||||
| `nanobot/channels/discord.py` | `_split_message`, `DiscordChannel` | `beaver/interfaces/channels/discord.py` | `可直接迁移` | 主要改导入路径。 |
|
||||
| `nanobot/channels/email.py` | `EmailChannel` | `beaver/interfaces/channels/email.py` | `可直接迁移` | 通道逻辑自包含。 |
|
||||
| `nanobot/channels/feishu.py` | `_extract_*`, `FeishuChannel` | `beaver/interfaces/channels/feishu.py` | `可直接迁移` | 保持通道粒度。 |
|
||||
| `nanobot/channels/matrix.py` | `_filter_matrix_html_attribute`, `_render_markdown_html`, `_build_matrix_text_content`, `MatrixChannel` | `beaver/interfaces/channels/matrix.py` | `可直接迁移` | 主要改导入。 |
|
||||
| `nanobot/channels/mochat.py` | `MochatBufferedEntry`, `DelayState`, `MochatTarget`, `MochatChannel` | `beaver/interfaces/channels/mochat.py` | `小幅重构` | 依赖 config 较多。 |
|
||||
| `nanobot/channels/qq.py` | `_make_bot_class`, `QQChannel` | `beaver/interfaces/channels/qq.py` | `可直接迁移` | 主要改配置路径。 |
|
||||
| `nanobot/channels/slack.py` | `SlackChannel` | `beaver/interfaces/channels/slack.py` | `可直接迁移` | 主要改导入路径。 |
|
||||
| `nanobot/channels/telegram.py` | `_markdown_to_telegram_html`, `_split_message`, `TelegramChannel` | `beaver/interfaces/channels/telegram.py` | `可直接迁移` | 通道逻辑自包含。 |
|
||||
| `nanobot/channels/whatsapp.py` | `WhatsAppChannel` | `beaver/interfaces/channels/whatsapp.py` | `小幅重构` | 作为通道入口保留;桥接细节可抽到 `beaver/integrations/whatsapp/bridge.py`。 |
|
||||
|
||||
## 12. Integrations 层迁移映射
|
||||
|
||||
| 旧文件 | 关键类/函数 | 新位置 | 判定 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `nanobot/a2a/client.py` | `A2AClient` 全类 | `beaver/integrations/a2a/client.py` | `小幅重构` | 见第 9 节。 |
|
||||
| `nanobot/web/outlook.py` | Outlook MCP 连接、状态、消息和日历方法 | `beaver/integrations/outlook/service.py` | `小幅重构` | 见第 11 节。 |
|
||||
| `nanobot/authz/client.py` | `BackendRegistrationResult`, `AuthzClient` | `beaver/integrations/authz/client.py` | `可直接迁移` | 纯外部服务 client;新目录里需新增 `authz/`。 |
|
||||
| `nanobot/providers/transcription.py` | `GroqTranscriptionProvider` | `beaver/integrations/providers/transcription.py` 或 `beaver/engine/providers/transcription.py` | `可直接迁移` | 二选一,取决于后续是否把 transcription 视为主 provider。 |
|
||||
| `nanobot/agent/tools/mcp.py` | `connect_mcp_servers` | `beaver/integrations/mcp/connection.py` + `beaver/tools/mcp/wrapper.py` | `小幅重构` | 协议连接与工具包装分开。 |
|
||||
|
||||
## 13. 明确不迁移的内容
|
||||
|
||||
| 路径 | 处理方式 | 原因 |
|
||||
| --- | --- | --- |
|
||||
| `backend-old/third_party/**` | `不迁移` | 新后端不保留 vendored 第三方目录。 |
|
||||
| `backend-old/.venv/**` | `不迁移` | 环境文件。 |
|
||||
| `backend-old/.pytest_cache/**` | `不迁移` | 缓存。 |
|
||||
| `backend-old/.ruff_cache/**` | `不迁移` | 缓存。 |
|
||||
| `backend-old/bridge/**` | `保留为外部桥接层,不按 Python 代码移植` | 这是独立 Node bridge。 |
|
||||
|
||||
## 14. 第一批最值得迁的文件
|
||||
|
||||
如果要先挑“收益最高、风险最低”的一批,顺序建议是:
|
||||
|
||||
1. `nanobot/config/loader.py` -> `beaver/foundation/config/loader.py`
|
||||
2. `nanobot/config/paths.py` -> `beaver/foundation/config/paths.py`
|
||||
3. `nanobot/config/schema.py` -> `beaver/foundation/config/schema.py`
|
||||
4. `nanobot/utils/helpers.py` -> `beaver/foundation/utils/helpers.py`
|
||||
5. `nanobot/agent/process_events.py` -> `beaver/foundation/events/process.py`
|
||||
6. `nanobot/agent/run_result.py` -> `beaver/foundation/models/run_result.py`
|
||||
7. `nanobot/session/manager.py` -> `beaver/engine/session/models.py` + `manager.py`
|
||||
8. `nanobot/providers/base.py` / `registry.py` / `custom_provider.py` / `litellm_provider.py` / `openai_codex_provider.py`
|
||||
9. `nanobot/agent/context.py` -> `beaver/engine/context/builder.py`
|
||||
10. `nanobot/agent/tools/base.py` / `registry.py` / `filesystem.py` / `shell.py` / `web.py` / `message.py`
|
||||
11. `nanobot/agent/plugins.py` -> `beaver/plugins/*`
|
||||
12. `nanobot/agent/skills.py` -> `beaver/skills/catalog/loader.py` + `resolver/runtime.py`
|
||||
13. `nanobot/agent_team/types.py` -> `beaver/coordinator/models.py`(按 v1 models 重写)
|
||||
14. `nanobot/agent_team/memory.py` -> `beaver/memory/procedures/*` + `beaver/memory/runs/*`
|
||||
15. 以 Hermes 基线新增 `beaver/tools/builtins/memory.py`
|
||||
16. 以 Hermes 基线新增 `beaver/tools/builtins/session_search.py`
|
||||
|
||||
## 15. 最后一句话
|
||||
|
||||
从 `backend-old` 移到新的 `backend`,最重要的不是“先把文件复制过来”,而是始终按这个原则落:
|
||||
|
||||
1. `foundation` 放公共模型、配置、事件、工具函数
|
||||
2. `engine` 放统一 agent 内核
|
||||
3. `tools` 放工具本身
|
||||
4. `skills` 放全系统指导层
|
||||
5. `memory` 放经验沉淀
|
||||
6. `coordinator` 放委派和多 agent 编排
|
||||
7. `services` 放应用服务
|
||||
8. `interfaces` 放 CLI / Web / Gateway / Channels 薄入口
|
||||
9. `integrations` 放 A2A / MCP / Outlook / Authz 这类外部系统
|
||||
|
||||
只要旧文件进入新目录时严格按这条边界落,新后端就不会再次长回 `backend-old` 那种结构。
|
||||
Reference in New Issue
Block a user