Files
zk-data-agent/docs/technical-architecture/11-workspace-runtime.md
T
2026-05-20 14:13:34 +08:00

664 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Workspace Runtime 设计稿
## 背景
当前项目已经有了平台账号、会话目录、Jupyter 远程工作区、Skill/Tools 和文件产物管理,但这些能力还没有被一个统一的“执行环境”概念串起来。
现在的问题不是单纯缺少登录账号,而是需要回答:
```text
谁在使用 Agent
-> 当前会话绑定到哪个工作区
-> 工具以什么身份、在什么目录、用什么权限执行
-> 产物在哪里保存、展示和下载
```
因此,账户体系升级不应该只看账号密码,而应该引入 `Workspace Runtime` 作为平台账号和工具执行之间的核心抽象。
## 核心结论
账户体系分两层:
```text
平台账号 Account
负责登录、角色、会话、Skill 配置、模型配置、记忆和集成状态。
工作区运行时 Workspace Runtime
负责执行身份、工作目录、文件读写、Python 环境、远程连接和进程管理。
```
平台账号不直接等价于 Linux 账号,也不直接等价于 Jupyter 账号。平台账号可以绑定不同类型的 runtime。
## 目标
1. 统一本机工作区、Linux 子账户工作区、Jupyter 工作区和未来 SSH 工作区。
2. 让 Tool handler 不关心执行位置,只面向统一 runtime 执行。
3. 明确权限来源,避免把远程工作区误认为平台托管沙盒。
4. 让每个 session 的输入、输出、scratchpad、Python 环境和文件下载有稳定归属。
5. 为后续多用户、资源限制、审计、团队空间和远程执行打基础。
## 非目标
1. 不在第一阶段实现完整企业 SSO。
2. 不把所有账号体系直接迁移到 Linux PAM。
3. 不强制所有远程工作区都变成平台托管沙盒。
4. 不要求 Skill 感知 runtime 的具体实现细节。
## 对象模型
### Account
平台账号是 Web 产品层的身份。
```text
Account
id
username
display_name
role
status
created_at
updated_at
```
职责:
- 登录和会话 token。
- 模型选择。
- Skill 启用状态。
- 用户记忆。
- 第三方集成状态。
- 默认 workspace runtime 策略。
### Session
Session 是一次 Agent 对话任务。
```text
Session
id
account_id
runtime_id
title
status
created_at
updated_at
```
职责:
- 保存对话历史。
- 绑定一个 runtime。
- 保存工具调用、活动步骤和最终结果。
- 关联输入文件和输出 artifact。
Session 一旦绑定远程 runtime,刷新页面后也应该恢复到同一个 runtime。
### Workspace Runtime
Workspace Runtime 是工具执行的真实环境。
```text
WorkspaceRuntime
id
account_id
session_id
type
root
permissions_source
status
created_at
updated_at
```
`type` 可以是:
```text
local_process
local_linux_user
remote_jupyter
remote_ssh
```
职责:
- 决定 bash/python/file 工具在哪里执行。
- 决定输入输出文件在哪里。
- 决定 Python 环境在哪里。
- 决定进程如何启动、停止和清理。
- 决定文件如何展示、下载和转在线文档。
### Artifact
Artifact 是输入和输出文件的统一抽象。
```text
Artifact
id
account_id
session_id
runtime_id
kind: input | output | scratchpad
uri
name
size
mime
created_at
```
`uri` 可以是:
```text
file:///home/<account_id>/zk-agent/sessions/<session_id>/output/a.jsonl
jupyter://<session_id>/root/zk_agent_workspaces/<session_id>/output/a.jsonl
ssh://<runtime_id>/home/user/zk_agent_workspaces/<session_id>/output/a.jsonl
```
文件列表只需要展示 metadata。下载或转在线文档时,再通过 runtime 拉取内容。
### Executor
Executor 是 Tool handler 和 Runtime 之间的执行适配层。
```text
Executor
run_bash(command, cwd, timeout)
run_python(code_or_file, cwd, timeout)
read_file(path)
write_file(path, content)
list_files(path)
open_file_stream(path)
cancel(run_id)
```
Tool handler 不应该自己判断是在本地、Jupyter 还是 SSH。它只调用当前 session 的 executor。
## Runtime 类型
### local_process
当前已有的默认模式。工具在服务进程所在机器上执行,目录由平台约定。
```text
.port_sessions/accounts/<account_id>/sessions/<session_id>/
```
适合:
- 本地开发。
- 单用户调试。
- 早期兼容。
问题:
- 多用户隔离主要靠代码路径约束。
- 工具进程和平台服务权限一致,风险较高。
### local_linux_user
平台托管的标准多用户工作区。
```text
平台账号: banisherwy
Linux runtime user: banisherwy
workspace root: /home/banisherwy/zk-agent/sessions/<session_id>
```
服务进程可以是 root,工具进程切换到普通 Linux 用户执行。
```text
root backend
-> runuser -u banisherwy -- <runner command>
```
职责分工:
```text
root 服务
创建 runtime 用户
初始化 workspace
设置 owner 和权限
启停进程
管理平台账号和 session
普通 Linux 用户
执行 bash/python
拥有自己的 workspace
拥有自己的 Python 虚拟环境
只能写自己的目录
```
推荐目录:
```text
/home/<account_id>/zk-agent/
sessions/
<session_id>/
input/
output/
scratchpad/
session.json
python/
.venv/
memory/
integrations/
```
推荐约定:
```text
平台用户名 = Linux 用户名
平台密码 = Linux 用户密码
Linux 用户允许 SSH 登录
```
这样用户体验更直接:
- 在平台创建账号时,同步创建同名 Linux 用户。
- 用户可以使用同一套账号密码登录 Web 平台和 SSH。
- Agent 工具执行时也使用同一个 Linux 用户身份。
- 文件 owner、进程 owner、SSH 登录用户和平台用户名一致,便于排查和审计。
但两者在架构语义上仍然保留分层:
```text
平台账号体系
登录、角色、session、Skill、模型配置。
Linux 用户体系
执行隔离、文件权限、进程权限、资源限制。
```
也就是说,账号名和密码保持一致,但平台仍然保留自己的登录态、session、角色和配置管理。Linux 账号负责机器级登录和执行权限。
需要注意:
- 用户名必须同时满足平台账号规范和 Linux 用户名规范。
- 修改平台密码时必须同步修改 Linux 密码。
- 禁用平台账号时,也应该禁用 Linux 登录或锁定 Linux 用户。
- 删除平台账号时,需要明确是否保留 `/home/<account_id>/zk-agent/` 数据。
- root 服务创建用户和改密码时必须走受控 helper,不能把用户输入拼成 shell 命令。
### remote_jupyter
用户授权的远程工作区。
语义是:
```text
用户把自己已有权限的 Jupyter 环境接入平台。
平台代替用户在这个环境里执行。
```
这不是平台托管沙盒。权限边界来自用户提供的 Jupyter 凭证。
```text
Account: banisherwy
Session: xxx
Runtime: remote_jupyter
Root: /root/zk_agent_workspaces/<session_id>
Permissions source: Jupyter password/token 对应的远程用户权限
```
平台需要保证:
- Jupyter 凭证只绑定当前 account/session。
- 刷新后 runtime 状态可恢复。
- 文件列表 metadata-only。
- 下载时通过 Jupyter API 流式拉取。
- 转在线文档时按需拉取,不默认同步大文件。
- 用户明确知道 Agent 在远程环境里的权限等同于该 Jupyter 用户。
平台不能保证:
- 远程机器上的文件权限隔离。
- 远程 Jupyter 用户不是 root。
- 远程挂载目录的访问范围。
短期建议:`remote_jupyter` 先保持当前逻辑,不作为账户体系升级的主战场。
当前已经具备:
- session 级 Jupyter 绑定。
- 刷新后恢复远程工作区状态。
- 输出文件 metadata-only 展示。
- 下载时通过 Jupyter API 流式读取。
- 转在线文档时按需拉取。
因此下一步账户体系升级优先处理本机托管 runtime 和平台账号,不主动重构 Jupyter 执行链路。后续只需要让 Jupyter 工作区在概念上挂到 `WorkspaceRuntime` 模型下。
### remote_ssh
未来可扩展的用户授权远程工作区。
语义和 remote_jupyter 类似:
```text
用户提供 SSH 连接能力。
平台代替用户在远程机器上执行。
权限边界来自 SSH 凭证对应的远程用户。
```
remote_ssh 更适合:
- 远程机器没有 Jupyter。
- 需要更完整 shell 能力。
- 需要使用远程开发机的挂载盘、GPU、模型目录。
但它也更复杂:
- SSH 凭证管理。
- 长连接和心跳。
- relay / OTP / 扫码登录。
- 文件传输和断线恢复。
- 进程树管理。
因此优先级应低于 `local_linux_user` 和已有 `remote_jupyter`
## 权限边界
需要在 UI 和文档中明确区分两类工作区:
```text
平台托管工作区
平台负责权限隔离。
典型类型: local_linux_user。
用户授权工作区
用户提供凭证。
平台不创建权限边界,只复用用户已有权限。
典型类型: remote_jupyter, remote_ssh。
```
UI 可以显示:
```text
当前工作区:Jupyter 远程工作区
权限来源:用户提供的 Jupyter 凭证
Agent 权限:等同于该远程环境当前登录用户
```
或者:
```text
当前工作区:平台托管工作区
执行身份:banisherwy
Agent 权限:普通 Linux 用户权限
```
## Tool 调用关系
目标关系:
```text
Agent Loop
-> Tool handler
-> RuntimeResolver(session_id)
-> Executor
-> local process / linux user / jupyter / ssh
```
工具不应该散落处理路径和远程协议。
例如:
```text
python_exec
-> executor.run_python(...)
write_file
-> executor.write_file(...)
download_artifact
-> executor.open_file_stream(...)
```
这样后续新增 runtime 时,尽量只新增 executor,不重写每个工具。
## 文件策略
### 输入文件
输入文件应该同步到当前 runtime 的 `input/`
```text
local_linux_user
上传文件 -> /home/<account_id>/zk-agent/sessions/<session_id>/input/
remote_jupyter
上传文件 -> 通过 Jupyter API 写入 /root/zk_agent_workspaces/<session_id>/input/
```
### 输出文件
输出文件默认放到当前 runtime 的 `output/`
```text
output/
records.jsonl
report.md
samples.csv
```
对远程 runtime,平台只保存 metadata。
```text
name
size
mtime
uri
runtime_id
```
点击下载时再流式读取。点击转在线文档时再按需拉取,并设置大小限制。
## Python 环境策略
每个 runtime 应有自己的 Python 环境。
```text
local_linux_user
/home/<account_id>/zk-agent/python/.venv
remote_jupyter
/root/zk_agent_workspaces/.zk-agent-python/.venv
```
初始化时只做最小准备:
- 创建 venv。
- 配置 pip 源。
- 不预装大量包。
缺包时由 Agent 根据任务安装,安装也发生在当前 runtime 内。
## 进程管理
每个工具执行必须有 run id 和 process group。
```text
run_id
account_id
session_id
runtime_id
executor_pid 或 remote_execution_id
status
started_at
updated_at
```
停止任务时:
- local_process:杀本地进程组。
- local_linux_user:杀对应 runtime 用户下该 run 的进程组。
- remote_jupyter:中断 kernel 或关闭对应执行任务。
- remote_ssh:杀远程进程组。
不能只停止 Web 请求,否则会出现“前端以为停了,后台 Python 还在跑”的问题。
## 持久化建议
建议把当前 JSON 账号体系逐步迁到 SQLite。
第一阶段可新增这些表:
```text
accounts
id
username
password_hash
role
status
created_at
updated_at
account_sessions
token_hash
account_id
created_at
updated_at
expires_at
workspace_runtimes
id
account_id
session_id
type
root
status
config_json
created_at
updated_at
artifacts
id
account_id
session_id
runtime_id
kind
uri
name
size
mime
created_at
```
敏感信息不要直接明文落库。Jupyter 密码、SSH key、token 至少需要加密或放入受控 secret store。
## 与现有实现的关系
当前已有能力可以映射到新模型:
```text
frontend/app/lib/claw-auth.ts
Account 登录态原型。
.port_sessions/accounts/<account_id>
local_process 模式下的 account workspace。
backend/api/server.py::account_paths
Runtime path resolver 的雏形。
src/jupyter_runtime.py
remote_jupyter executor 的雏形。
RunManager / RunStateStore
run id、活动状态、停止任务的雏形。
frontend 文件面板
Artifact list/download 的雏形。
```
所以这不是推翻重来,而是把已有能力抽象成更稳定的边界。
## 演进路线
### Phase 0:明确概念,不改执行路径
- 在代码和文档中引入 Workspace Runtime 术语。
- 把现有 `.port_sessions/accounts/<account_id>` 视为 `local_process` runtime。
- UI 显示当前工作区类型。
- 对 Jupyter 工作区补充权限提示。
### Phase 1:抽象 RuntimeResolver 和 Executor
- 新增 `RuntimeResolver`,根据 account/session 找当前 runtime。
- 新增统一 `Executor` 接口。
- 先把 `python_exec``bash`、文件工具迁到 executor。
- 保持现有 local 和 Jupyter 行为不变。
### Phase 2:账号存储升级
-`users.json``auth_sessions.json` 迁到 SQLite。
- 增加 `account_id``role``status``expires_at`
- 增加 session token 清理。
- 管理后台去掉 `admin/admin` 和默认 `123456`
### Phase 3local_linux_user runtime
- root 服务创建与平台账号同名的 Linux 用户。
- 平台密码同步设置为 Linux 用户密码。
- Linux 用户允许 SSH 登录。
- 初始化 `/home/<account_id>/zk-agent/`
- 工具执行切到普通 Linux 用户。
- Python venv、session、output 全部进入用户 home。
- 停止任务时按 process group 清理。
### Phase 4:资源限制和审计
- ulimit / cgroup。
- 每账号磁盘 quota。
- 工具执行审计。
- 大文件下载限流。
- session/output 清理策略。
### Phase 5remote_ssh runtime
- 在 remote_jupyter 稳定后再考虑。
- 重点解决认证、relay、长连接、文件传输和远程进程清理。
## 关键待决问题
1. 平台账号是否允许用户自注册,还是只允许管理员创建?
2. 用户自注册时,是否允许自动创建同名 Linux 用户?
3. 删除账号时,是否删除 Linux 用户,是否保留 home 目录?
4. 本机平台托管 workspace 是否统一迁到 `/home/<account_id>/zk-agent/`
5. Jupyter 凭证如何加密保存?
6. 远程 workspace 产物保留多久?
7. 大文件下载、在线文档转换和文件预览的大小限制是多少?
8. 是否需要团队空间:一个 workspace 被多个账号共享?
## 推荐决策
短期建议:
```text
保留平台账号体系。
引入 Workspace Runtime 抽象。
继续稳定 remote_jupyter。
账号存储从 JSON 迁到 SQLite。
开始设计 local_linux_user,但不要立即替换所有执行路径。
```
中期建议:
```text
服务可以 root 运行。
平台账号创建时同步创建同名普通 Linux 用户。
平台密码和 Linux 密码保持一致。
Linux 用户允许 SSH 登录。
工具执行统一通过 runtime executor。
本机默认工作区逐步迁到 /home/<account_id>/zk-agent。
```
长期建议:
```text
平台账号负责产品身份。
Workspace Runtime 负责执行环境。
Artifact 负责跨 runtime 文件抽象。
Executor 负责工具执行适配。
```
这样账户体系、Linux 子账户、Jupyter/SSH 远程工作区、文件下载、Python 环境和工具执行可以合到一个统一设计里,而不是继续各自生长。