Add Linux account workspace runtime
This commit is contained in:
@@ -0,0 +1,663 @@
|
||||
# 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 3:local_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 5:remote_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 环境和工具执行可以合到一个统一设计里,而不是继续各自生长。
|
||||
Reference in New Issue
Block a user