# 架构设计 中文 · [English](architecture.md) ## 设计目标 K1412 Agent 是一个多用户网页编码 Agent,其 Agent 循环可以独立于账户系统和用户界面进行修改。架构将高成本的实验面控制在较小范围内:研究者应当能够改变上下文、记忆、调度、工具、委派或完成策略,而不必分叉鉴权、聊天记录、Docker 生命周期或整个前端。 系统有意只保留一条 Agent 路径。早期曾计划在自定义 Work 循环之外并存一条轻量 Chat 循环,但这会重复产品行为、混淆模型选择,并增加前后端必须保持兼容的代码量,因此已经移除。 ## 系统上下文 ```mermaid flowchart LR U["浏览器用户"] --> P["DNS、TLS 与 Nginx Proxy Manager"] P --> W["Open WebUI
鉴权、RBAC、历史记录、界面"] W --> R["Agent Runtime
循环、上下文、记忆、模型路由"] R --> M["模型提供方
K1412 API 或 DeepSeek"] R --> G["Workspace Gateway
身份与执行策略"] G -->|Tailscale + SSH Docker| X["每用户工作区容器
home-node-itx"] W --> DB["PostgreSQL
用户与对话"] R --> DB W --> Q["Redis
协调"] ``` 只有 Web 对公网开放。Runtime、Gateway、PostgreSQL 和 Redis 位于私有 Compose 网络中。工作区容器运行在另一台物理 Docker 主机上,不接收应用密钥,也不暴露主机端口。 ## 职责边界 | 层 | 负责 | 不应负责 | | --- | --- | --- | | Open WebUI | 注册、登录、会话、管理员审批、RBAC、对话持久化、渲染 | Agent 调度、模型提供方密钥、Docker 访问 | | Agent Runtime | 模型目录、上下文策略、Agent 循环、工具、调度、委派、记忆、计划、运行事件、证据门禁 | 浏览器会话、Docker socket | | Workspace Gateway | 签名身份校验、工作区路径策略、每用户锁、Docker 生命周期、文件交付 | 模型调用、公开鉴权界面 | | 工作区容器 | 用户代码、依赖、生成文件、命令执行 | 其他用户的数据、服务凭据、Docker socket | | PostgreSQL | Open WebUI 持久化状态,以及 Runtime 事件、计划和记忆 | 代码执行 | | 共享基础设施 | TLS、镜像仓库、源码托管、私有路由、模型服务 | Agent 行为 | 这种分层允许 Web UI 按自己的节奏升级,同时让 Agent 循环可以频繁迭代。 ## 请求生命周期 ### Agent 请求 ```mermaid sequenceDiagram participant B as 浏览器 participant W as Open WebUI participant R as Runtime participant M as 模型提供方 participant G as Gateway participant X as 用户工作区 B->>W: 已鉴权的对话请求 W->>R: OpenAI 兼容请求 + 服务密钥 + 签名用户身份 R->>R: 校验身份、构建上下文、记录 run.created loop 直到有证据支持完成,或预算耗尽 R->>M: 最近上下文 + 工具 + 提供方策略 M-->>R: 助手内容和/或工具调用 R->>R: 规范化、校验、限流并调度调用 R->>G: 工具请求 + 服务密钥 + 新签名身份 G->>G: 校验身份并绑定用户工作区 G->>X: 在资源和路径约束下执行 X-->>G: 退出码、输出或文件结果 G-->>R: 结构化工具结果 R->>R: 持久化事件并评估完成证据 end R-->>W: SSE 回答和已完成的工具详情块 W-->>B: 渲染后的 Agent 响应 ``` Open WebUI 发送的身份在 Runtime 接受请求时校验一次。之后,Runtime 会为每一次 Gateway 调用重新签发已经校验过的身份。这样可以避免一次耗时较长的 Agent 运行,在模型或工具工作期间继续复用已经过期的身份令牌。 ### 工作区文件请求 ```text 带 Open WebUI 会话的浏览器 -> /api/v1/k1412/workspace/* -> Open WebUI 校验当前登录用户 -> Web 签发短期用户身份 -> Gateway 校验服务密钥和签名身份 -> Gateway 只选择该用户对应的哈希容器与数据卷 -> 返回浏览、下载或流式归档响应 ``` 浏览器永远不会获得 Gateway 服务密钥,也不会直接连接 Docker。 ## Runtime 内部结构 从 Open WebUI 的视角看,Runtime 是一个 OpenAI 兼容的模型提供方,但普通聊天补全实际由 K1412 循环实现: - `ModelProvider` 将内部模型规格转换为提供方请求,并统一流式补全响应。 - `ContextPolicy` 移除已渲染的工具详情标记,前置系统契约和持久记忆,并在每个模型的字符预算内保留最新的可见消息。 - `ToolRegistry` 声明工具 schema,将工作区工具路由到 Gateway,将状态工具路由到 `RuntimeStore`。 - `AgentLoop` 负责迭代、工具规范化、校验、调度、恢复、委派和基于证据的完成判定。 - `RuntimeStore` 持久化运行事件、每对话计划和用户记忆。 更多细节参见 [Agent 循环实现](agent-loop.zh-CN.md)。 ## 工作区架构 `local-docker` 与 `ssh-docker` 实现同一套 `ExecutionProvider` 契约。生产环境使用 `ssh-docker`:Gateway 通过只读挂载的专用 SSH 配置,连接 `home-node-itx` 上的 Docker。 每个不可变的 Open WebUI 用户 ID 都映射到一个由 SHA-256 派生的工作区 ID,并且恰好对应一组: - 名为 `k1412-ws-` 的容器; - 名为 `k1412-ws-data-` 的数据卷; - 名为 `k1412-ws-net-` 的桥接网络。 容器以 UID/GID 1000 运行,根文件系统只读,移除全部 Linux capabilities,启用 `no-new-privileges`,设置 PID/CPU/内存限制,不挂载 Docker socket,也不发布端口。`/workspace` 是唯一持久可写的文件系统。`/tmp` 可写,但以 `noexec` 方式挂载。 Python 环境位于 `/workspace/.venv`。用户级 home 与软件包缓存位于 `/workspace/.agent` 下。镜像会把虚拟环境和用户二进制目录放在 `PATH` 前部。命令在启用 `pipefail` 的 Bash 中运行,因此失败的安装不会被成功的 `tail` 或类似管道末端命令掩盖。 当 `WORKSPACE_IMAGE` 改为新的不可变标签后,Gateway 会在下一次访问时替换过期容器,但重新挂载已有命名数据卷。因此,镜像升级可以改变执行环境而不删除用户文件。 ## 网络与部署 生产环境以一个 Unraid Compose Manager 项目运行: - `public`:Web 入口; - `internal`:私有服务间通信; - `egress`:Runtime 访问模型提供方,以及 Gateway 的 SSH 访问。 只有 Web 发布 NAS 端口 `12004`。VPS 上的 Nginx Proxy Manager 为 `agent.k1412.top` 终止 TLS,并通过 Tailscale 转发。镜像以 `linux/amd64` 构建,使用不可变的“时间戳-提交”标签推送到 `docker.k1412.top/wuyang/*`,部署时只更新相关服务。 文档门户打包在 Web 镜像中,通过 `/doc/` 提供,不会增加新的公开服务、端口、数据库或代理规则。 ## 稳定面与实验面 稳定的平台契约: - 已鉴权的 Web 到 Runtime 请求; - Runtime/Web 到 Gateway 的签名身份; - `ExecutionProvider` 工作区语义; - 每用户数据卷隔离; - 运行事件持久化; - 公开模型 ID; - 工作区文件 API。 预期持续实验的部分: - 提示词与上下文策略; - 记忆选择; - 工具目录与意图路由; - 调度器与并行; - 委派; - 恢复与证据规则; - 模型预算与路由。 所有对实验结果有影响的行为,都应在 `run.created` 中带版本号,使实现变化前后的结果仍然可比较。