Workspace 实例的生命周期。它是 agentscope.workspace 模块在服务侧的对应实现 —— 每一种 WorkspaceBase 子类都有一个配套的 WorkspaceManagerBase 的子类来负责创建、缓存与删除。
它的特点:
- 可配置的隔离粒度 ——
PER_AGENT(默认)、PER_SESSION或PER_USER—— 决定 workspace 在(user_id, agent_id, session_id)三个维度上如何共享或隔离,让同一套部署可以干净地服务多个租户。 - 可插拔的沙箱后端 —— 同一份生命周期契约既可以包裹本地目录、Docker 容器、E2B 云沙箱,也可以对应 Kubernetes Pod。切换后端只需在
create_app中改一行。
可选的实现
| 类 | 说明 |
|---|---|
LocalWorkspaceManager | 直接落在宿主目录下的裸机式 workspace。零基础设施、零沙箱化 —— 智能体直接跑在开发者的宿主文件系统上。 |
DockerWorkspaceManager | 每个 workspace 对应一个 Docker 容器,宿主目录以 bind mount 的方式挂进去做持久化。需要一个可达的 Docker daemon。 |
E2BWorkspaceManager | 通过 E2B 托管的云沙箱。空闲时自动挂起,下一次访问时恢复。 |
K8sWorkspaceManager | 在 Kubernetes 集群上为每个 workspace 起一个 Pod + PVC。适合已经在跑其他负载的生产集群。 |
接入智能体服务
在create_app 入口处设置对应的 WorkspaceManager 实例即可。下面是四种内置实现的示例:
隔离粒度
isolation 决定 workspace 在 (user_id, agent_id, session_id) 三元组上如何共享或隔离。Manager 会在 session 创建时按所选策略生成一个 workspace_id;后续所有带着同一个 workspace_id 的请求都会落到同一个底层 workspace 上。
| 取值 | 共享规则 | 典型场景 |
|---|---|---|
PER_AGENT(默认) | 同一 (user_id, agent_id) 下的所有 session 共享一个 workspace | 让每个 agent 拥有一份持久化的、按用户区分的工作目录 —— 文件、skill、MCP 注册跨 session 也能保留下来。 |
PER_SESSION | 每个 session 一个独立的 workspace | 需要保证 session 之间互不泄露状态的场景 —— 例如一次性沙箱评测、短生命周期的自动化任务。 |
PER_USER | 同一 user_id 下的所有 session 共享一个 workspace,不区分是哪个 agent | 同一用户的多个 agent 在同一份文件系统上协作(少见,谨慎使用)。 |
在 session 创建请求中显式指定
workspace_id 永远会覆盖策略。这就是内置团队工具(AgentCreate / AgentInvite)用来让子 agent 与团队 leader 共用同一个 workspace 的机制。在智能体服务中的工作原理
工作区管理器是一个应用级单例资源:一个智能体服务进程只有一份实例,所有请求共享。它的职责有两个:(a)在配置的isolation 策略下判断一次请求属于哪个 workspace;(b)把该 workspace 作为一个廉价依赖交给需要它的路由和服务。
接线方式
开发者传给create_app(workspace_manager=...) 的实例会被挂到 app.state 上,并通过一个 FastAPI 依赖对外暴露:
- Router ——
/session、/workspace/*(MCP、skill)。它们调用Depends(get_workspace_manager),然后用manager.get_workspace(...)解析出调用方对应的 workspace。 ChatService—— 在 lifespan 里通过构造函数拿到 manager,每次 chat 运行时调用get_workspace拿到 agent 将要作用其中的 workspace。- 内置团队工具(
AgentCreate/AgentInvite)—— 拿到 manager 后在派生子 agent session 时调用assign_workspace_id(...),让子 agent 落到与 leader 相同的 workspace 里。
生命周期
Manager 是一个异步上下文管理器。应用的lifespan 会在启动时进入它,在关闭时通过一个 AsyncExitStack 退出它,所以它所拥有的任何后台机制(例如 DockerWorkspaceManager / E2BWorkspaceManager / K8sWorkspaceManager 的空闲 TTL sweeper)都会与服务的其他部分同步启停:
端到端请求流程
服务跑起来之后,一次典型的交互长这样:-
创建 session ——
/sessionrouter 只计算一次 workspace id 并把它写到 session 记录上:显式传入的body.workspace_id永远优先(团队工具就是靠它让子 agent 共享 leader 的 workspace);否则由 manager 按配置的isolation策略生成一个。assign_workspace_id是一个纯函数 —— 不做 I/O,不查缓存 —— 所以在热路径上调用非常便宜。 -
后续每个请求带着这个 session —— 无论是 chat 运行、MCP 注册还是 skill 上传 —— 都会从 session 记录里读出保存好的
workspace_id,然后请求 manager 把它落到具体的 workspace 上:get_workspace是缓存命中的路径。它在 manager 内部的dict里查workspace_id,刷新一下最后访问时间,就在 O(1) 内返回已初始化好的Workspace。只有 miss 的时候才会抢一把asyncio.Lock,把后端准备好(构建 Docker 镜像、启动 E2B 沙箱、创建 K8s Pod……),再把结果缓存起来 —— 这把锁能防止多个请求同时冲进来给同一个 id 起两份后端。 -
空闲驱逐 —— 沙箱类的 manager 会跑一个后台 sweeper(在
__aenter__里启动),周期性遍历缓存,把最后访问时间超过ttl的 workspace 关闭并踢出。下一次访问该 id 时会透明地重新准备一份。 -
关闭 —— lifespan 退出 manager,manager 调用
close_all()拆掉所有缓存中的 workspace 及其后台 sweeper。
由于
workspace_id 只在 session 创建时生成一次并持久化,运行中的部署改动 isolation 策略并不会对现存 session 重新分组 —— 它们会继续保留当初分配的 id。只有新 session 才会走新策略。核心 API
每个 manager 都要实现来自WorkspaceManagerBase 的以下契约。自定义子类只需要补齐这些抽象方法即可;assign_workspace_id 里的隔离逻辑由基类提供,并由 isolation 构造参数驱动。
| 方法 | 用途 |
|---|---|
assign_workspace_id(*, user_id, agent_id, session_id) -> str | 在配置的 isolation 策略下为一个新 session 生成 workspace id。纯函数,无 I/O。当客户端在 session 创建请求中省略了 workspace_id 时会被调用。 |
get_workspace(user_id, agent_id, session_id, workspace_id=None) -> WorkspaceBase | 返回一个绑定到 workspace_id 的已初始化 workspace。热路径上的缓存命中路径;miss 时 manager 会准备底层后端并缓存结果。workspace_id=None 会回退到 assign_workspace_id。 |
create_workspace(user_id, agent_id, session_id) -> WorkspaceBase | 全新准备一个 workspace 并纳入管理。调用方还没有可复用的 id 时使用。 |
close(workspace_id) | 把某一个 workspace 从缓存中踢掉,并拆掉它的后端。 |
close_all() | 踢掉所有缓存的 workspace —— 服务关闭时调用。 |
async with manager: ... | 进入 / 退出 manager 的生命周期。进入时启动后台机制(例如 TTL sweeper);退出时调用 close_all。 |
自定义 Manager
任何基于WorkspaceBase 构建的 workspace 类,都可以通过配一个 WorkspaceManagerBase 子类的方式接到智能体服务里。大多数情况下只需要写一小段准备 + 缓存的样板代码 —— 隔离策略由基类自动继承。
自定义 Manager
MyWorkspaceManager 实例传给 create_app(workspace_manager=...),服务就会把它当作内置 manager 一样使用。
分布式部署
LocalWorkspaceManager 与 DockerWorkspaceManager 都是单节点的 —— workspace 状态活在跑服务进程的那台机器上。如果做水平扩容(负载均衡后挂多个智能体服务 worker 节点),同一个 workspace_id 的请求可能落到任意一个节点,而这两个 manager 都够不到其他节点上准备好的 workspace。
分布式部署要用云托管的沙箱后端:
E2BWorkspaceManager—— 沙箱可以通过 metadata 跨节点寻址;缓存 miss 时服务直接重连已存在的沙箱即可。K8sWorkspaceManager—— Pod 与 PVC 都是集群级资源;集群内任意服务副本都可以按 workspace-id 派生出的名字重连到同一个 Pod。