修改了nanobot,往Hermes agent的风格走,进度1/3

This commit is contained in:
2026-04-20 18:11:14 +08:00
parent cdfc222c9f
commit 36882a7d7b
261 changed files with 12659 additions and 604 deletions

View File

@ -0,0 +1,6 @@
"""Application services for Beaver."""
from .agent_service import AgentService
from .memory_service import MemoryService
__all__ = ["AgentService", "MemoryService"]

View File

@ -0,0 +1,2 @@
"""Administrative application service."""

View File

@ -0,0 +1,212 @@
"""Application service for agent entry.
这层的职责是把“接口层如何调用 AgentLoop”统一收口。
接口层以后不应该各自做这些事情:
1. 自己 new `AgentLoop`
2. 自己决定何时 `boot()`
3. 自己处理 direct run 的同步/异步包装
统一放在 `AgentService` 后CLI / Web / Gateway 才能共享同一条运行主链。
"""
from __future__ import annotations
import asyncio
from pathlib import Path
from typing import Any
from beaver.engine import AgentLoop, AgentProfile, AgentRunResult, EngineLoader
class AgentService:
"""面向 interfaces 的统一 agent 运行入口。
这里明确区分两种调用模式:
1. direct mode
- 不启动后台运行循环
- 直接调用 `process_direct()` / `run_direct()`
2. running mode
- 先 `await start()`
- 之后所有外部任务都必须走 `submit_direct()`
- 不允许再直接调用 `process_direct()`
"""
def __init__(
self,
*,
workspace: str | Path | None = None,
profile: AgentProfile | None = None,
loader: EngineLoader | None = None,
) -> None:
self.profile = profile or AgentProfile()
self.loader = loader or EngineLoader(workspace=workspace)
self._loop: AgentLoop | None = None
self._run_task: asyncio.Task[None] | None = None
def create_loop(self) -> AgentLoop:
"""创建并缓存当前 service 使用的 AgentLoop。"""
if self._loop is None:
self._loop = AgentLoop(profile=self.profile, loader=self.loader)
self._loop.boot()
return self._loop
@property
def has_loop(self) -> bool:
"""当前 service 是否已经创建过 loop。"""
return self._loop is not None
@property
def is_running(self) -> bool:
"""当前 service 是否处于 running mode。"""
return self._run_task is not None and not self._run_task.done()
def close(self) -> None:
"""关闭当前 service 持有的 runtime。"""
if self._run_task is not None and not self._run_task.done():
raise RuntimeError("AgentService.close() requires stop() before closing a running loop")
self._run_task = None
if self._loop is None:
return
try:
self._loop.close()
finally:
self._loop = None
async def start(self) -> None:
"""启动后台运行循环,进入 running mode。
进入 running mode 后:
- 外部任务必须通过 `submit_direct()` 提交
- `process_direct()` 不再允许直接调用
"""
if self._run_task is not None and not self._run_task.done():
return
loop = self.create_loop()
self._run_task = asyncio.create_task(loop.run())
while not loop.is_running:
if self._run_task.done():
await self._run_task
break
await asyncio.sleep(0)
async def _stop_impl(
self,
*,
timeout_seconds: float | None = None,
force: bool = False,
) -> None:
"""内部停止实现,支持 graceful timeout 和可选 force cancel。"""
if self._run_task is None:
return
run_task = self._run_task
loop = self.create_loop()
try:
await loop.stop()
if timeout_seconds is None:
await run_task
else:
try:
await asyncio.wait_for(asyncio.shield(run_task), timeout=timeout_seconds)
except asyncio.TimeoutError as exc:
if force:
run_task.cancel()
try:
await run_task
except asyncio.CancelledError:
pass
else:
raise TimeoutError(
f"AgentService.stop() timed out after {timeout_seconds} seconds while draining queued tasks"
) from exc
finally:
if run_task.done():
self._run_task = None
async def stop(
self,
*,
timeout_seconds: float | None = None,
force: bool = False,
) -> None:
"""停止后台运行循环并等待退出。
参数:
- `timeout_seconds`: graceful drain 的最长等待时间;`None` 表示一直等
- `force`: 超时后是否 cancel 掉运行循环 task
"""
await self._stop_impl(timeout_seconds=timeout_seconds, force=force)
async def shutdown(
self,
*,
timeout_seconds: float | None = None,
force: bool = False,
) -> None:
"""先停运行循环,再释放 runtime。"""
await self._stop_impl(timeout_seconds=timeout_seconds, force=force)
self.close()
async def process_direct(
self,
message: str,
**kwargs: Any,
) -> AgentRunResult:
"""异步 direct run 入口。
仅在 direct mode 下可用。
如果 service 已经 `start()` 进入 running mode
调用方必须改用 `submit_direct()`,不能绕过运行队列直接执行。
"""
if self._run_task is not None and not self._run_task.done():
raise RuntimeError(
"AgentService.process_direct() is unavailable while the service is running; "
"use 'await AgentService.submit_direct(...)' after start()."
)
loop = self.create_loop()
return await loop.process_direct(message, **kwargs)
async def submit_direct(
self,
message: str,
**kwargs: Any,
) -> AgentRunResult:
"""向 running mode 下的 loop 提交 direct task。
这是 `start()` 之后唯一合法的外部任务入口。
"""
loop = self.create_loop()
return await loop.submit_direct(message, **kwargs)
def run_direct(
self,
message: str,
**kwargs: Any,
) -> AgentRunResult:
"""同步 direct run 包装。
主要给当前 CLI 或简单脚本使用。真正的长期方向仍然是让 interfaces
在 direct mode 下直接走 `await process_direct(...)`。
"""
try:
asyncio.get_running_loop()
except RuntimeError:
pass
else:
raise RuntimeError(
"AgentService.run_direct() cannot be used inside an active event loop; "
"use 'await AgentService.process_direct(...)' instead."
)
return asyncio.run(self.process_direct(message, **kwargs))

View File

@ -0,0 +1,65 @@
"""Beaver memory 应用服务。
这层不是新的 memory 实现,而是对现有 `MemoryStore + MemorySnapshot` 的应用层包装。
目标只有三个:
1. 把“本轮运行前需要 refresh live state”这件事集中到一个地方
2. 把“给 context builder 的只能是 frozen snapshot”这条规则写死
3. 让 `AgentLoop` 不再直接操作 `MemoryStore` 细节
设计边界:
1. 记忆实际读写逻辑仍然在 `beaver.memory.curated.store.MemoryStore`
2. memory tool 仍然直接写 store
3. 本服务只负责 runtime 接入策略,不负责 CRUD 业务本身
"""
from __future__ import annotations
from pathlib import Path
from beaver.memory.curated.snapshot import MemorySnapshot, capture_memory_snapshot
from beaver.memory.curated.store import MemoryStore
class MemoryService:
"""统一封装 runtime 对 curated memory 的访问方式。"""
def __init__(
self,
root: str | Path,
*,
store: MemoryStore | None = None,
) -> None:
self.root = Path(root)
self.store = store or MemoryStore(self.root)
self._snapshot: MemorySnapshot | None = None
def initialize(self) -> None:
"""启动时加载一次磁盘内容,建立首份 frozen snapshot 基线。"""
self.store.load_from_disk()
self._snapshot = capture_memory_snapshot(self.store)
def reload_for_new_run(self) -> None:
"""每次新 run 开始前刷新 live state。
这是 Hermes 风格 memory policy 的关键点:
- 上一次会话中通过 tool 写入的持久记忆,下一次运行应该能看到
- 但同一次 run 中途写入的新记忆,不应反向修改当前 frozen snapshot
"""
self.store.load_from_disk()
self._snapshot = capture_memory_snapshot(self.store)
def get_snapshot(self) -> MemorySnapshot:
"""获取当前 run 应注入 system prompt 的 frozen snapshot。"""
if self._snapshot is None:
# 兜底场景:如果调用方绕过 initialize/reload首次读取时仍建立一份快照。
self._snapshot = capture_memory_snapshot(self.store)
return self._snapshot
def get_store(self) -> MemoryStore:
"""暴露底层 store 给需要直接调用 CRUD 的工具层。"""
return self.store

View File

@ -0,0 +1,2 @@
"""Application service for skills."""

View File

@ -0,0 +1,10 @@
"""Application service for coordinated team runs."""
class TeamService:
"""Placeholder service for multi-agent execution."""
def run(self, task: str) -> str:
"""Return a placeholder summary until real backends are migrated."""
return f"team run placeholder: {task}"