docs/multi-tenant/workspace-multi-user-architecture.md
状态:ARCHITECTURE BASELINE — isolation kernel implemented; SaaS activation gates remain
本文描述 Cloud v2 的目标架构和安全边界。详细的 Runtime、Box、PostgreSQL、pgvector 与 stdio MCP 决策以 pending-architecture-decisions.md 为权威来源;已经落地的实现选择记录在 implementation-decisions.md。
“隔离内核已实现”仅表示开源 Core/SDK 已具备多租户数据和运行时隔离所需的基础能力, 不表示闭源控制面、计费、生产部署或 Cloud v2 已经可以上线。
Cloud v2 采用以下模型:
SaaS 对外只有一个逻辑 LangBot 实例,全部 Workspace 都是该实例内的租户; 开源 Core 提供完整隔离内核,闭源 Cloud Control Plane 管理 SaaS 目录、订阅、权益和计费。
核心决策如下:
Workspace 是数据、成员、权限、用量和不可信执行的租户边界,不是一个 Pod、namespace、数据库或独立 LangBot 部署。global 逻辑 sandbox,实际命令继续以 nsjail 子进程执行。本轮重构的最高目标是:
共享可信控制面和基础设施池,隔离不可信执行单元;减少独立部署和常驻组件,使新增 Account 或空 Workspace 的静态成本接近零。
减少组件数量不意味着合并安全边界。插件进程、sandbox、secret、可写文件和租户数据仍必须严格隔离。
历史客户数据、账户和财务记录如需迁移,应单独立项;旧部署拓扑不作为本架构的设计约束。
| 术语 | 定义 |
|---|---|
| Account | 登录主体。OSS 中是实例本地账户;SaaS 中是全局账户 |
| Workspace | 逻辑 LangBot 实例内的租户,是资源、成员、权限、用量和不可信执行的首要边界 |
| Membership | Account 与 Workspace 的关系,包含固定角色、状态和权限版本 |
| Invitation | 邀请一个 Account 或邮箱加入 Workspace 的一次性凭证 |
| Logical Instance | 对外唯一的 LangBot 服务与安全域,拥有稳定 instance_uuid,不等同于某个进程或 Pod |
| Replica | Core、Plugin Runtime 或 Box Runtime 的短期内部运行副本,不是产品实体 |
| Execution Generation | Workspace 执行所有权和撤销的单调代数,用于隔离旧任务、旧连接和故障转移 |
| Billing Account | SaaS 付款主体,可以为一个或多个 Workspace 付费 |
| Entitlement | Control Plane 签发、Core 与 Runtime 本地执行的功能和数值额度快照 |
| Cloud Control Plane | 闭源 SaaS 控制面,管理全局身份、Workspace 目录、订阅、权益、计费和生命周期 |
| LangBot Core | 开源数据面,执行 Bot、Pipeline、Plugin、MCP、RAG 等业务并实施最终授权与隔离 |
当前代码中的 placement_generation 字段在迁移完成前保留兼容;其架构语义和目标命名均为
execution_generation,不表达 Workspace 属于某个产品级部署单元。
instance_uuid;所有副本共享该身份。replica_id、worker_id、Pod 名称、进程地址和数据库连接地址都是短期运行信息,不能进入业务资源的永久主键或外部 URL。workspace_uuid 是租户数据、任务、缓存、文件、日志、用量和运行时隔离的稳定键,也是未来内部路由与分片的候选键。workspace_uuid,并使用 (workspace_uuid, resource_uuid) 定位。flowchart LR
User["Browser / API / Bot traffic"] --> Edge["SaaS Edge"]
User --> CP["Closed Cloud Control Plane
directory + subscription + billing"]
Edge --> Core["One logical LangBot instance
Core replica pool; MVP = 1"]
CP -->|"signed manifest, directory projection,
entitlement and desired state"| Core
Core -->|"usage outbox and observed state"| CP
Core --> PG["Shared PostgreSQL business database
RLS + pgvector"]
Core --> PluginRT["Shared Plugin Runtime
trusted supervisor"]
Core --> BoxRT["Shared Box Runtime
trusted supervisor"]
PluginRT --> PluginA["Workspace A installation
isolated nsjail process"]
PluginRT --> PluginB["Workspace B installation
isolated nsjail process"]
BoxRT --> SandboxA["Workspace A
persistent global logical sandbox"]
BoxRT --> SandboxB["Workspace B
persistent global logical sandbox"]
Core --> ObjectStore["Shared durable object storage
Workspace-scoped keys"]
这里的“一个逻辑实例”是一个服务、安全域和稳定身份,不是“永远只有一个 OS 进程”。 MVP 不实现分布式,但从第一天保留内部扩展所需的身份、幂等、generation 和 owner 抽象。
| 阶段 | 内部部署形态 | 新 Workspace 静态成本 | 启用条件 |
|---|---|---|---|
| M0 单副本 MVP | 一个 Core、一个共享 Plugin Runtime、一个共享 Box Runtime、一个 PostgreSQL business database | 只新增目录和业务行 | 当前目标 |
| M1 同逻辑实例横向扩展 | 按容量增加 Core/Runtime 副本;使用 owner lease、fencing 和 generation;PostgreSQL 可增加 shared shard | 不创建 Workspace 专属部署 | 出现容量或可用性证据后 |
| M2 Dedicated 资源等级 | 特定 workload 使用独享 worker pool、sandbox class 或 database shard,但沿用相同身份、协议和 schema | 仅购买该等级的客户承担 | 合规、驻留或超大负载需求 |
M1 是 M0 的透明扩容,M2 是相同架构下的资源等级。外部 API 只认识稳定的
instance_uuid 和 workspace_uuid,不认识 replica、worker、pool 或 shard。
instance_uuid、workspace_uuid 和 execution_generation,不依赖进程地址表达身份。workspace_uuid 可直接作为未来 shard key。| 能力 | OSS | SaaS |
|---|---|---|
| Workspace 数量 | 实例固定一个 | Account 可拥有或加入多个,受 ProductPolicy 约束 |
| Workspace 成员 | 多用户 | 多用户,受 entitlement 约束 |
| 邀请成员 | 支持 | 支持 |
| 固定 RBAC | 支持 | 支持 |
| 自定义角色 | 不支持 | 后续商业能力 |
| Workspace 创建 | 首次初始化创建唯一 Workspace | 注册自动创建个人 Workspace;后续创建受 ProductPolicy 约束 |
| Workspace 切换 | 无需展示 | 支持 |
| 订阅与计费 | 无远端依赖 | 闭源 Control Plane 管理 |
| 租户隔离 | 完整实现 | 完整实现 |
OSS edition policy 应表达为:
workspace_limit = 1
members_enabled = true
invitations_enabled = true
fixed_rbac_enabled = true
multi_workspace_enabled = false
不能用 member_limit = 1、关闭邀请或移除 RBAC 来实现单租户限制。
首次初始化在一个事务中完成:
初始化后默认关闭公开注册。后续用户由 owner/admin 创建一次性 Invitation,注册或登录后接受邀请并加入唯一 Workspace。 OSS 后续注册不创建第二个 Workspace。未配置 SMTP 时,系统返回只展示一次的邀请链接供管理员通过可信渠道发送。
普通注册由 Control Plane 通过幂等工作流完成:
注册只创建逻辑记录,不启动 Runtime 或租户专属基础设施。
通过邀请注册的新用户也创建自己的 personal Workspace,同时加入受邀 Workspace;已注册用户接受邀请时只新增目标 Membership。 个人 Workspace 与团队 Workspace 的付费关系必须由 ProductPolicy 明确,不允许代码根据名称或创建路径隐式推断。
expires_at、accepted_at、revoked_at,只能使用一次。sessionStorage。Core 权威定义 owner、admin、developer、operator 和 viewer 固定角色。
权限按能力划分,例如资源查看、资源管理、运行操作、成员管理、provider secret 管理、审计查看和数据导出。
规则:
Core 负责:
Core 是 Bot、Pipeline、Model、Knowledge、Plugin installation、MCP configuration 和 Monitoring 数据的权威来源, 也是每个业务和运行时请求的最终授权边界。
Control Plane 负责:
首期不把这些职责拆成多个租户、计费和调度微服务。推荐以一个独立于 Core 的闭源模块化单体承载, 并通过模块边界复用已有账户、OAuth、支付、邮件和运营能力。历史 Cloud 的租户专属部署代码不复用。
Control Plane 不保存 Bot、Pipeline、Model 或 Knowledge 等业务内容,也不代理普通消息执行。
Core 中只保留薄的协议适配层:
适配层不得 monkey patch ORM、绕过 Core 权限检查或在普通资源请求中同步调用 Control Plane。
| 数据 | OSS | SaaS |
|---|---|---|
| Account、Workspace、Membership | Core 本地数据库 | Control Plane 权威,Core 保存版本化投影 |
| Invitation | Core 本地数据库 | Control Plane 权威,不向 Core 投影 pending secret |
| Bot、Pipeline、Model、KB、Plugin、MCP | Core | Core |
| Subscription、Payment、Invoice、Usage ledger | 无远端依赖 | Control Plane |
| Feature 和 quota | 本地 edition policy | Control Plane 签发,Core/Runtime 验证执行 |
| Execution generation | OSS 固定本地值 | Control Plane desired state,Core 执行 |
| 运行时授权 | Core | Core 根据本地投影和 entitlement 执行 |
SaaS 不维护两套可写目录。Control Plane 是目录权威写模型;Core 只保存带 revision 的执行投影。
仅设置 system.edition=cloud、环境变量或前端 feature flag 不得启用 SaaS 多 Workspace。
Cloud bootstrap 必须验证由预置根信任签名的 InstanceManifest,并据此安装闭源 Workspace policy。
Manifest 至少绑定:
iss, aud, sub, jti, iat, nbf, exp
instance_uuid
release
capabilities
tenant_isolation_version
execution_generation
delegated issuers and keyset revision
签名错误、audience 不匹配、过期、generation 回滚或信任链缺失时必须失败关闭,不能降级为 OSS 默认 Workspace。
Control Plane 通过 transactional outbox 发布 Account、Workspace 和 Membership 的版本化事件。
Core 使用 inbox 按 event_id 去重,以 aggregate revision 拒绝旧写,并追踪连续应用水位。启动时读取一个 PostgreSQL
REPEATABLE READ 事务内生成的签名全量 snapshot;运行时先消费携带当前 high-water 的签名事件页,再只请求该页涉及的 Workspace 签名增量。
增量响应不携带新的事件 cursor,因此即使其内容已包含并发提交的后续 revision,也不能跳过尚未消费的事件。
要求:
directory.changed 重新读取和投影全部 Workspace。MVP 可采用一个共享、原子且可恢复的 Control Plane store;未来多副本不能继续使用进程内状态承担一次性 token 或目录水位。
Entitlement 使用版本化签名快照,至少绑定:
instance_uuid
workspace_uuid
plan_revision
entitlement_revision
status
features
limits
nbf, exp, grace_until
Core 校验 issuer、audience、subject、instance、revision、时间和签名;旧 revision 不覆盖新快照。 套餐名称和价格规则只存在于闭源 Control Plane,Core 与 Runtime 只理解通用 capability 和数值限额。
Control Plane 故障时,已缓存且仍有效的快照可继续执行;过期后只能进入明确、有限的 grace 模式或失败关闭。
用量事件 append-only、至少一次投递,Control Plane 按 event_id 去重。事件至少包含:
event_id
instance_uuid
workspace_uuid
execution_generation
meter
quantity_integer
unit
source
occurred_at
entitlement_revision
schema_version
Core 不计算账单金额,也不在普通请求中同步扣费。业务写入与相应 business outbox 必须在同一事务中提交; generation-aware write fence 与 outbox 原子性尚是 SaaS 激活门禁。
闭源控制面发布版本化的 release、capacity 和 execution desired state,Core/Runtime 幂等 reconcile 并上报 observed state。 desired state 只描述同一逻辑实例内部的执行所有权和容量,不产生新的产品级实例或租户实体。
Workspace 安全状态由 directory revision 决定,订阅状态由 entitlement revision 决定,执行撤销由 execution generation 决定。三者取最严格有效状态,但任何通道都不能修改另一个通道的权威字段。
租户业务入口统一解析不可变的 RequestContext:
@dataclass(frozen=True)
class RequestContext:
instance_uuid: str
workspace_uuid: str
execution_generation: int
principal_type: str
principal_uuid: str
permissions: frozenset[str]
auth_method: str
entitlement_revision: int | None
request_id: str
不同入口的 Workspace 来源:
| 入口 | Workspace 来源 |
|---|---|
| Browser Account token | X-Workspace-Id 只作候选;服务端校验 Membership |
| API Key | key 记录绑定的 Workspace,忽略 caller selector |
| Public Bot / Webhook | Bot 或 webhook route 的可信所有权 |
| Background job | durable payload 中的完整 scope,执行前重新验证 generation |
| Plugin Host API | 认证控制连接和 immutable action context |
| Box operation | 已验证 entitlement、admission grant 和 Runtime namespace |
| System operation | 显式、最小能力的 SystemContext,禁止隐式全局上下文 |
禁止从模块全局变量、进程默认 Workspace、请求 payload 或“第一个 Workspace”推断 scope。
sub,并绑定 issuer、当前 instance_uuid audience 和 expiry。| 场景 | 语义 |
|---|---|
| 未认证或 token 无效 | 401 |
| 同 Workspace 资源存在但权限不足 | 403 |
| 资源不存在或属于其他 Workspace | 404 |
| edition / entitlement / quota 禁止 | 稳定领域错误码,不伪装为 500 |
| execution generation 过期 | fail closed,并停止旧运行态 |
| 未处理异常 | 稳定 internal_error + request ID;细节只进入服务端日志 |
核心实体至少包含:
Account
uuid
email_normalized
display_name
status
auth bindings
Workspace
uuid
name
status
source: local | cloud_projection
directory_revision
WorkspaceExecutionState
workspace_uuid
instance_uuid
execution_generation
status
write_fenced_at
revision
WorkspaceMembership
workspace_uuid
account_uuid
role
status
directory_revision
约束:
(workspace_uuid, account_uuid) 唯一。OSS Invitation 存在 Core 本地数据库;SaaS Invitation 只存在于闭源目录。
WorkspaceInvitation
uuid
workspace_uuid
email_normalized
role
token_hash
expires_at
accepted_at
revoked_at
created_by
数据库约束必须保证同一 Workspace 与邮箱只有一个有效邀请,并保证 token hash 全局唯一。
所有租户资源显式包含 workspace_uuid,包括但不限于:
唯一键、索引、缓存 key、object key、日志维度和幂等键都必须包含 Workspace scope。
服务层不得暴露可绕过 Workspace 条件的普通 get(id)、list() 或 delete(id)。
workspace_uuid 非空并有外键。public shared schema 和共享连接池。SET LOCAL 建立 scope,并由统一 TenantUnitOfWork 保证 context 与 SQL 使用同一事务和连接。ENABLE + FORCE ROW LEVEL SECURITY 是第二道边界。BYPASSRLS、无 role membership 和跨 schema 权限。首期 migrator 和 runtime URL 必须连接同一个 host、port、database,但使用不同 role。 生产部署还必须证明 runtime credential 无法连接 PostgreSQL 集群中的其他 database;专用 endpoint 或经验证的 HBA/proxy 隔离仍是激活门禁。
(workspace_uuid, knowledge_base_uuid, vector_id)。整个逻辑实例共享一个可信 Plugin Runtime 逻辑控制面;M0 由一个 supervisor replica 承担。新 Workspace 不创建专属 Runtime、连接、卷或进程。
每个运行中的 plugin installation 独占一个 nsjail worker process tree;enabled-resident 是 desired semantics。worker 运行期间永久绑定:
instance_uuid
workspace_uuid
execution_generation
installation_uuid
runtime_revision
artifact_digest
插件不能通过 payload、Host API 参数、环境变量或重连改变该绑定。Supervisor 不在自身解释器中加载第三方插件代码。 停用、删除、revision/generation 变化或 entitlement 撤销时,旧 worker 必须停止并失去 Host API 权限。
data/plugin-runtime/
├── artifacts/sha256/<artifact_digest>/code/ # 已验证、只读共享
├── environments/sha256/<environment_digest>/ # 原子发布、只读共享
└── installations/<installation_uuid>/
├── home/ # 私有可写
├── tmp/ # 私有可写
└── data/ # 私有持久数据
/proc、mount、PID、IPC、UTS、cgroup 与 rlimit 阻止读取其他文件、枚举或 signal 其他进程。.env;secret 只由可信控制面按 installation 注入。资源限制只来自实例级 data/config.yaml,并支持现有环境变量覆写;plugin manifest 不能声明、放宽或覆盖。
plugin:
worker:
max_cpus: 1.0
max_memory_mb: 512
max_pids: 128
max_open_files: 256
max_file_size_mb: 512
require_hard_limits: true
CPU、内存和 PID 使用 cgroup 硬限制,open files 和单文件大小使用 rlimit。 Cloud deployment profile 强制 nsjail;硬限制不可用时 readiness 失败,不能降级为普通子进程。 installation 总磁盘配额需要可原子拒绝写入的 quota provider,不能以目录扫描冒充硬限制。
真实 Linux/nsjail/cgroup 与受控 egress 的 Cloud 部署验证尚未完成,是生产激活门禁。
整个逻辑实例共享一个可信 Box Runtime 逻辑控制面;M0 由一个 Runtime replica 承担。Core 与 Runtime 控制通道绑定稳定 instance identity,
每个 operation 绑定 workspace_uuid、execution_generation、session revision 和短期 admission grant。
首期 entitlement 模型:
{
"features": {
"managed_sandbox": true,
"external_sandbox": false
},
"limits": {
"managed_sandbox_sessions": 1
}
}
闭源订阅模块把套餐映射为该通用 capability;Core 与 Runtime 不判断 plan == pro。
预期 Pro 得到 managed_sandbox_sessions = 1,其他套餐为 0。
global 逻辑 session。global 表示 Workspace 内默认逻辑 sandbox,不表示跨 Workspace 共享。/workspace 持久数据保留。Cloud readiness 必须证明 cgroup、namespace、mount、Workspace/Skill/ephemeral byte quota 和 inode quota 均为硬限制。 当前普通 nsjail backend 不具备全部硬磁盘能力,因此 Cloud Box 应失败关闭,直到绿地部署提供并验证真实 quota provider; 不能把软目录扫描写成“生产已就绪”。
非 Pro 用户后续可在 WebUI 配置 Workspace 自有的远程 E2B sandbox。该功能尚未实现,首期不纳入。 未来 credential 必须属于 Workspace、加密存储且读取受 secret 权限保护,不消耗 Cloud managed sandbox 配额。
mcp:
stdio:
enabled: true
true 保持兼容。MCP__STDIO__ENABLED=false 强制关闭。box.enabled、managed sandbox entitlement 和 session quota。OSS 与 SaaS 执行面共用通用 Workspace API:
GET /api/v1/workspaces
GET /api/v1/workspaces/{workspace_uuid}
GET /api/v1/workspaces/{workspace_uuid}/members
POST /api/v1/workspaces/{workspace_uuid}/invitations
PATCH /api/v1/workspaces/{workspace_uuid}/members/{account_uuid}
DELETE /api/v1/workspaces/{workspace_uuid}/members/{account_uuid}
Cloud policy 下,目录 mutation 由闭源 Control Plane 负责;Core 对本地创建、邀请和成员修改返回稳定的
control_plane_required,只提供执行投影的安全读取。
所有 tenant resource route 必须经过统一 decorator/middleware:
SaaS 产品 API 包含:
POST /cloud/workspaces
GET /cloud/workspaces
POST /cloud/workspaces/{workspace_uuid}/invitations
POST /cloud/invitations/{token}/accept
GET /cloud/workspaces/{workspace_uuid}/subscription
POST /cloud/workspaces/{workspace_uuid}/checkout
GET /cloud/workspaces/{workspace_uuid}/usage
这些 API 管理目录、产品和计费,不直接操作 Bot/Pipeline 等 Core 业务资源。
OSS:
SaaS:
以下情况必须拒绝新的租户业务和副作用:
不能把上述错误静默降级为 OSS singleton、普通子进程、Chroma、软 quota 或 caller-supplied Workspace。
当前分支已经实现或具备基础的部分包括:
这些是代码能力边界,不等于完成闭源 SaaS 产品或生产部署验收。
以下事项完成并取得真实环境证据前,不得宣称 Cloud v2 production-ready:
暂缓项不得被实现代码用隐式默认值提前固化。
真实浏览器至少覆盖:
Cloud v2 的产品模型只有一个逻辑 LangBot 实例和实例内多个 Workspace。 当前选择单副本 MVP 是为了减少组件和新增租户成本,不是把单进程假设写进业务身份或协议。 未来需要容量或高可用时,在同一逻辑实例内部增加 Core/Runtime 副本和 PostgreSQL shard, Workspace 的 UUID、权限、数据边界和外部 API 均保持不变。
开源 Core 必须完整实现安全的 Workspace 隔离和 OSS 单 Workspace 多用户;闭源 Control Plane 管理 SaaS 的全局目录、订阅、权益、计费和生命周期。共享可信控制面、连接池、只读 artifact 和数据库组件, 同时让每个不可信插件进程、sandbox、secret、可写文件和 tenant transaction 保持独占边界, 才能在不增加每租户部署的前提下最大化降低新增用户成本。
在闭源控制面、事务 fence/outbox、真实 Runtime hard isolation、Box hard quota 和 PostgreSQL 生产隔离等门禁完成之前, 本架构仍处于隔离内核阶段,不应被描述为可上线的 SaaS 多租户部署。