|
@@ -37,7 +37,7 @@ Agent 生命周期管理,包括:
|
|
|
|
|
|
|
|
- 多个 Trace 可以共享一个 Workspace(主 Agent 和子 Agent)
|
|
- 多个 Trace 可以共享一个 Workspace(主 Agent 和子 Agent)
|
|
|
- 包含 Agent 的配置、记忆、技能等
|
|
- 包含 Agent 的配置、记忆、技能等
|
|
|
-- 路径格式:`~/.gateway/workspaces/{workspace_id}/`
|
|
|
|
|
|
|
+- Gateway 进程内目录:``{GATEWAY_WORKSPACES_ROOT}/{sha256(workspace_id)}/``(默认 ``/root/.gateway/workspaces/<64位hex>/``),**不是**用明文 ``workspace_id`` 作目录名;详见下文「目录与 meta」。
|
|
|
- 需要引用计数机制,确保清理时无活跃 Trace
|
|
- 需要引用计数机制,确保清理时无活跃 Trace
|
|
|
|
|
|
|
|
---
|
|
---
|
|
@@ -72,31 +72,44 @@ gateway/core/lifecycle/
|
|
|
|
|
|
|
|
**核心接口:**
|
|
**核心接口:**
|
|
|
|
|
|
|
|
|
|
+**说明:** Trace 的创建由 **Agent API**(HTTP)完成;Gateway 在拿到 `trace_id` 后通过 `bind_agent_trace` 与 `WorkspaceManager` 建立关联。详见 `gateway/core/lifecycle/trace/manager.py`。
|
|
|
|
|
+
|
|
|
```python
|
|
```python
|
|
|
class TraceManager:
|
|
class TraceManager:
|
|
|
- def create_trace(
|
|
|
|
|
|
|
+ async def prepare_workspace_session(self, workspace_id: str) -> None:
|
|
|
|
|
+ """调用 WorkspaceManager.ensure_session(目录 + 沙箱)"""
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+ async def bind_agent_trace(
|
|
|
self,
|
|
self,
|
|
|
workspace_id: str,
|
|
workspace_id: str,
|
|
|
- agent_type: str, # "personal_assistant" | "digital_employee"
|
|
|
|
|
- metadata: dict = None
|
|
|
|
|
- ) -> str:
|
|
|
|
|
- """创建新 Trace,返回 trace_id"""
|
|
|
|
|
|
|
+ agent_trace_id: str,
|
|
|
|
|
+ agent_type: str,
|
|
|
|
|
+ metadata: dict | None = None,
|
|
|
|
|
+ ) -> None:
|
|
|
|
|
+ """Agent 返回 trace_id 后登记本地 meta 与 workspace 引用"""
|
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
- def get_trace(self, trace_id: str) -> dict:
|
|
|
|
|
- """查询 Trace 信息(调用 Agent 框架)"""
|
|
|
|
|
|
|
+ async def release_agent_trace(self, workspace_id: str, agent_trace_id: str) -> None:
|
|
|
|
|
+ """解除绑定"""
|
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
- def list_traces(
|
|
|
|
|
|
|
+ async def get_trace(self, trace_id: str) -> dict:
|
|
|
|
|
+ """优先请求 Agent API,失败时返回 Gateway 本地登记"""
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+ async def list_traces(
|
|
|
self,
|
|
self,
|
|
|
- workspace_id: str = None,
|
|
|
|
|
- agent_type: str = None
|
|
|
|
|
|
|
+ workspace_id: str | None = None,
|
|
|
|
|
+ agent_type: str | None = None,
|
|
|
|
|
+ *,
|
|
|
|
|
+ limit: int = 50,
|
|
|
) -> list[dict]:
|
|
) -> list[dict]:
|
|
|
- """查询 Trace 列表"""
|
|
|
|
|
|
|
+ """查询 Trace 列表(带 Agent API 查询参数约定)"""
|
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
def get_workspace_id(self, trace_id: str) -> str:
|
|
def get_workspace_id(self, trace_id: str) -> str:
|
|
|
- """获取 Trace 对应的 workspace_id"""
|
|
|
|
|
|
|
+ """同步:解析 workspace_id(Executor、gateway_exec 使用)"""
|
|
|
pass
|
|
pass
|
|
|
```
|
|
```
|
|
|
|
|
|
|
@@ -104,6 +117,51 @@ class TraceManager:
|
|
|
|
|
|
|
|
**实现位置:** `gateway/core/lifecycle/workspace/manager.py`(Docker 编排见同目录 `docker_runner.py`)
|
|
**实现位置:** `gateway/core/lifecycle/workspace/manager.py`(Docker 编排见同目录 `docker_runner.py`)
|
|
|
|
|
|
|
|
|
|
+#### 目录与 meta
|
|
|
|
|
+
|
|
|
|
|
+磁盘布局与 `manager.py` 文档字符串一致:
|
|
|
|
|
+
|
|
|
|
|
+| 根路径(环境变量) | 默认(容器内) | 用途 |
|
|
|
|
|
+|-------------------|----------------|------|
|
|
|
|
|
+| `GATEWAY_WORKSPACES_ROOT` | `/root/.gateway/workspaces` | 每个 workspace 一个子目录(名为 `sha256(workspace_id)`),其下含 `.gateway/meta.json` |
|
|
|
|
|
+| `GATEWAY_SHARED_ROOT` | `/root/.gateway/shared` | 多会话/多 workspace 共享文件;沙箱内挂载为 `/home/agent/shared` |
|
|
|
|
|
+
|
|
|
|
|
+**meta.json** 路径:`{GATEWAY_WORKSPACES_ROOT}/<hex>/.gateway/meta.json`。由 `WorkspaceManager._save_meta` 写入,字段包含 `workspace_id`、`trace_refs`、`workspace_container_id`(沙箱容器 ID)等。在 **`volume_subpath` 模式**下,该目录与沙箱内 `/home/agent/workspace` 为同一块存储(命名卷 + Subpath),故 Agent 在沙箱里也能看到 `workspace/.gateway/meta.json`。
|
|
|
|
|
+
|
|
|
|
|
+#### Docker 沙箱(WorkspaceDockerRunner)
|
|
|
|
|
+
|
|
|
|
|
+**实现位置:** `gateway/core/lifecycle/workspace/docker_runner.py`
|
|
|
|
|
+
|
|
|
|
|
+- 为每个 workspace 保证一个运行中的沙箱容器(镜像默认 `agent/workspace:latest`,容器名前缀 `gws-`)。
|
|
|
|
|
+- **挂载模式** `GATEWAY_WORKSPACE_MOUNT_MODE`:
|
|
|
|
|
+ - **`bind`(默认)**:`workspace_host_path`、`shared_host_path` 解析后的路径直接 bind 到沙箱(适合 Gateway 与本机 Docker 守护进程同环境、路径对 Docker 可见)。
|
|
|
|
|
+ - **`volume_subpath`**:使用命名卷 + `VolumeOptions.Subpath`,将 **workspace 卷** 的子目录 `<hex>` 挂到 `/home/agent/workspace`,**shared 卷** 整卷挂到 `/home/agent/shared`。要求同时设置 `GATEWAY_WORKSPACE_DOCKER_VOLUME` 与 `GATEWAY_SHARED_DOCKER_VOLUME`,且 Gateway 通过 **宿主机 `docker.sock`** 创建的容器与 Compose 使用同一 Docker(卷名一致)。
|
|
|
|
|
+- 镜像与网络:`GATEWAY_WORKSPACE_IMAGE`(默认 `agent/workspace:latest`);`GATEWAY_WORKSPACE_DOCKER_NETWORK` 指定沙箱加入的网络名(如 `agent`)。
|
|
|
|
|
+- 关闭沙箱编排:`GATEWAY_WORKSPACE_DOCKER_ENABLED=false`;若失败需中断会话则设 `GATEWAY_WORKSPACE_DOCKER_REQUIRED=true`。
|
|
|
|
|
+
|
|
|
|
|
+#### Gateway 环境变量(Workspace / Docker)
|
|
|
|
|
+
|
|
|
|
|
+| 变量 | 说明 |
|
|
|
|
|
+|------|------|
|
|
|
|
|
+| `GATEWAY_WORKSPACES_ROOT` | Workspace 根目录(容器内常为 `/root/.gateway/workspaces`) |
|
|
|
|
|
+| `GATEWAY_SHARED_ROOT` | 共享目录(容器内常为 `/root/.gateway/shared`) |
|
|
|
|
|
+| `GATEWAY_WORKSPACE_MOUNT_MODE` | `bind` 或 `volume_subpath` |
|
|
|
|
|
+| `GATEWAY_WORKSPACE_DOCKER_VOLUME` | `volume_subpath` 时:workspace 数据 **Docker 卷名**(与 Compose 中 `name:` 一致,如 `agent_workspace_root`) |
|
|
|
|
|
+| `GATEWAY_SHARED_DOCKER_VOLUME` | `volume_subpath` 时:shared 数据 **Docker 卷名**(如 `agent_workspace_shared`) |
|
|
|
|
|
+| `GATEWAY_WORKSPACE_DOCKER_NETWORK` | 沙箱容器加入的网络名 |
|
|
|
|
|
+| `GATEWAY_WORKSPACE_IMAGE` | 沙箱镜像,默认 `agent/workspace:latest` |
|
|
|
|
|
+| `GATEWAY_WORKSPACE_DOCKER_ENABLED` | 是否启用沙箱容器 |
|
|
|
|
|
+| `GATEWAY_WORKSPACE_DOCKER_REQUIRED` | Docker 失败时是否视为致命错误 |
|
|
|
|
|
+
|
|
|
|
|
+**TraceManager(HTTP)**:`GATEWAY_AGENT_API_BASE_URL`、`GATEWAY_AGENT_API_TIMEOUT`(与 Executor 共用 Agent 基址)。
|
|
|
|
|
+
|
|
|
|
|
+#### 仓库 `docker-compose.yml`(Gateway 服务)
|
|
|
|
|
+
|
|
|
|
|
+- 命名卷 **`workspace_root`**(固定名 **`agent_workspace_root`**)→ `/root/.gateway/workspaces`。
|
|
|
|
|
+- 命名卷 **`workspace_shared`**(固定名 **`agent_workspace_shared`**)→ `/root/.gateway/shared`;使用 **`driver: local` + bind**,将卷绑定到宿主机目录,便于直接查看文件。
|
|
|
|
|
+- **`GATEWAY_SHARED_HOST_BIND`**:**仅用于 Compose 解析**,写在项目根 `.env` 或 shell 环境中;作为 `driver_opts.device` 的宿主机路径。未设置时默认 `${PWD}/.gateway/shared`(请在项目根执行 `docker compose`,并保证目录存在)。**Gateway 进程不读取该变量。**
|
|
|
|
|
+- 与上述卷名对应的环境变量:`GATEWAY_WORKSPACE_DOCKER_VOLUME=agent_workspace_root`、`GATEWAY_SHARED_DOCKER_VOLUME=agent_workspace_shared`,并通常配合 `GATEWAY_WORKSPACE_MOUNT_MODE=volume_subpath`。
|
|
|
|
|
+
|
|
|
**职责:**
|
|
**职责:**
|
|
|
- 创建和初始化 Workspace 目录
|
|
- 创建和初始化 Workspace 目录
|
|
|
- 管理 Workspace 引用计数
|
|
- 管理 Workspace 引用计数
|
|
@@ -113,28 +171,36 @@ class TraceManager:
|
|
|
|
|
|
|
|
```python
|
|
```python
|
|
|
class WorkspaceManager:
|
|
class WorkspaceManager:
|
|
|
- def create_workspace(self, workspace_id: str) -> str:
|
|
|
|
|
- """创建 Workspace 目录,返回路径"""
|
|
|
|
|
|
|
+ async def create_workspace(self, workspace_id: str) -> str:
|
|
|
|
|
+ """创建 Workspace 目录(含 meta),返回绝对路径"""
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+ async def ensure_session(self, workspace_id: str) -> str:
|
|
|
|
|
+ """会话启动:目录 + 共享目录 + 按需启动沙箱;返回 Workspace 绝对路径"""
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+ async def get_workspace_path(self, workspace_id: str) -> str:
|
|
|
|
|
+ """获取 Workspace 路径(不存在则抛错)"""
|
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
- def get_workspace_path(self, workspace_id: str) -> str:
|
|
|
|
|
- """获取 Workspace 路径"""
|
|
|
|
|
|
|
+ def get_workspace_container_id(self, workspace_id: str) -> str | None:
|
|
|
|
|
+ """读 meta 中沙箱容器 ID(供 Executor 注入 gateway_exec)"""
|
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
- def add_trace_ref(self, workspace_id: str, trace_id: str):
|
|
|
|
|
|
|
+ async def add_trace_ref(self, workspace_id: str, trace_id: str) -> None:
|
|
|
"""增加 Trace 引用"""
|
|
"""增加 Trace 引用"""
|
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
- def remove_trace_ref(self, workspace_id: str, trace_id: str):
|
|
|
|
|
|
|
+ async def remove_trace_ref(self, workspace_id: str, trace_id: str) -> None:
|
|
|
"""移除 Trace 引用"""
|
|
"""移除 Trace 引用"""
|
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
- def cleanup_workspace(self, workspace_id: str, force: bool = False):
|
|
|
|
|
|
|
+ async def cleanup_workspace(self, workspace_id: str, force: bool = False) -> None:
|
|
|
"""清理 Workspace(检查引用计数,force=True 强制清理)"""
|
|
"""清理 Workspace(检查引用计数,force=True 强制清理)"""
|
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
- def list_workspaces(self) -> list[dict]:
|
|
|
|
|
- """列出所有 Workspace 及其引用计数"""
|
|
|
|
|
|
|
+ async def list_workspaces(self) -> list[dict]:
|
|
|
|
|
+ """列出所有 Workspace 及其引用计数、容器 ID 等"""
|
|
|
pass
|
|
pass
|
|
|
```
|
|
```
|
|
|
|
|
|
|
@@ -164,31 +230,27 @@ class ConfigWatcher:
|
|
|
|
|
|
|
|
## 典型流程
|
|
## 典型流程
|
|
|
|
|
|
|
|
-### 个人助理型 Agent 首次对话
|
|
|
|
|
|
|
+### 个人助理型 Agent 首次对话(与当前实现一致)
|
|
|
|
|
|
|
|
-1. 用户通过飞书发送消息
|
|
|
|
|
-2. Routing 模块收到消息,查询是否有对应 Trace
|
|
|
|
|
-3. 无 Trace → 调用 `TraceManager.create_trace(workspace_id=user_id, agent_type="personal_assistant")`
|
|
|
|
|
-4. TraceManager 调用 Agent 框架创建 Trace,返回 trace_id
|
|
|
|
|
-5. 调用 `WorkspaceManager.create_workspace(workspace_id=user_id)`(如果不存在)
|
|
|
|
|
-6. 调用 `WorkspaceManager.add_trace_ref(workspace_id=user_id, trace_id=trace_id)`
|
|
|
|
|
-7. 开始对话
|
|
|
|
|
|
|
+1. 用户通过飞书发送消息;渠道层解析 `workspace_id`(如 `feishu:<user_id>`)。
|
|
|
|
|
+2. `TraceManager.prepare_workspace_session(workspace_id)` → 内部 `WorkspaceManager.ensure_session`(目录、shared、沙箱容器)。
|
|
|
|
|
+3. 调用 **Agent HTTP API** 创建/续跑 Trace,得到 `trace_id`。
|
|
|
|
|
+4. `TraceManager.bind_agent_trace(workspace_id, trace_id, agent_type, ...)` 登记引用与本地 meta。
|
|
|
|
|
+5. **Executor** 侧通过 `TaskManager.submit_task(trace_id, ...)` 驱动 `POST /api/traces/{id}/run`(可带 `gateway_exec` 指向沙箱)。
|
|
|
|
|
|
|
|
### 数字员工型 Agent 串行处理
|
|
### 数字员工型 Agent 串行处理
|
|
|
|
|
|
|
|
1. 用户 A 发送消息
|
|
1. 用户 A 发送消息
|
|
|
2. Routing 模块查询数字员工的 Trace(共享 workspace_id)
|
|
2. Routing 模块查询数字员工的 Trace(共享 workspace_id)
|
|
|
-3. 如果无 Trace → 创建(同上)
|
|
|
|
|
|
|
+3. 如果无 Trace → 仍由 Agent API 创建 trace,再 `bind_agent_trace`(同上)
|
|
|
4. 如果有 Trace 且正在处理 → 消息进入队列
|
|
4. 如果有 Trace 且正在处理 → 消息进入队列
|
|
|
5. 当前对话结束 → 从队列取下一条消息
|
|
5. 当前对话结束 → 从队列取下一条消息
|
|
|
|
|
|
|
|
### 主 Agent 调用子 Agent
|
|
### 主 Agent 调用子 Agent
|
|
|
|
|
|
|
|
-1. 主 Agent 执行过程中需要调用子 Agent
|
|
|
|
|
-2. 调用 `TraceManager.create_trace(workspace_id=主Agent的workspace_id, agent_type=...)`
|
|
|
|
|
-3. 子 Trace 共享主 Trace 的 Workspace
|
|
|
|
|
-4. 调用 `WorkspaceManager.add_trace_ref(workspace_id, 子trace_id)`
|
|
|
|
|
-5. 子 Agent 执行完成 → `WorkspaceManager.remove_trace_ref(...)`
|
|
|
|
|
|
|
+1. 主 Agent 执行过程中需要子 Trace 时,由 Agent 侧创建子 trace(HTTP)
|
|
|
|
|
+2. Gateway 对子 trace 调用 `bind_agent_trace(同一 workspace_id, 子 trace_id, ...)`
|
|
|
|
|
+3. `WorkspaceManager.add_trace_ref` 维护引用;子 Agent 结束后 `remove_trace_ref`(或由渠道策略 `release_agent_trace`)
|
|
|
|
|
|
|
|
### Workspace 清理
|
|
### Workspace 清理
|
|
|
|
|
|
|
@@ -201,10 +263,10 @@ class ConfigWatcher:
|
|
|
|
|
|
|
|
## 错误处理
|
|
## 错误处理
|
|
|
|
|
|
|
|
-### Trace 创建失败
|
|
|
|
|
|
|
+### Trace 创建 / 绑定失败
|
|
|
|
|
|
|
|
-- Agent 框架返回错误 → 向用户返回友好错误信息
|
|
|
|
|
-- Workspace 创建失败 → 清理已创建的 Trace,返回错误
|
|
|
|
|
|
|
+- Agent HTTP 创建或续跑失败 → 渠道/Executor 向用户返回友好错误;**勿**在未拿到 `trace_id` 时调用 `bind_agent_trace`
|
|
|
|
|
+- `ensure_session`(Docker 卷、沙箱)失败 → 若 `GATEWAY_WORKSPACE_DOCKER_REQUIRED=true` 则中断;否则可降级为无沙箱(见日志)
|
|
|
|
|
|
|
|
### Workspace 引用计数不一致
|
|
### Workspace 引用计数不一致
|
|
|
|
|
|
|
@@ -228,3 +290,4 @@ class ConfigWatcher:
|
|
|
- [需求规划](../requirements.md):生命周期管理需求
|
|
- [需求规划](../requirements.md):生命周期管理需求
|
|
|
- [架构设计](../architecture.md):模块在整体架构中的位置
|
|
- [架构设计](../architecture.md):模块在整体架构中的位置
|
|
|
- [Agent Core 架构](../../../agent/docs/architecture.md):Trace 数据结构定义
|
|
- [Agent Core 架构](../../../agent/docs/architecture.md):Trace 数据结构定义
|
|
|
|
|
+- 仓库根目录 `docker-compose.yml`:`gateway` 服务的卷与环境变量与上文「Compose」小节一致,部署时以此为准。
|