这篇文章整理的是一次把 agent-harness-web 跑成“本地 Agent 控制面 + Open WebUI 聊天入口”的实践过程。目标不是介绍某个单点工具,而是把一套可迁移的本地流程讲清楚:如何构建和启动 agent-harness-web,如何把 Open WebUI 作为 sidecar 接进来,如何让 Codex、Claude Code、OpenCode 等 Agent 的会话进入统一存储,以及如何处理 streaming、标题、loading 状态这些实际使用中的体验问题。

本文刻意不包含任何 token、password、私钥、OAuth 回调数据、真实 bearer token、Cloudflare/GitHub secret 值或私有配置明文。涉及敏感配置时只给出变量名和操作位置。

1. 角色划分

这套方案里有几个组件,各自边界要清楚。

组件角色
agent-harness可迁移的 Agent 上下文、skills、projects、workflows、rules 和知识库。
agent-harness-web本地或 Docker 运行的 WebUI,管理 harness 文件、校验、diff、commit、push、transcripts 和本地 Agent。
agent-gatewayagent-harness-web 内提供 Codex、Claude Code、OpenCode、Reasonix 等 CLI/Agent 的适配层。
harness-mcp / /mcp把 harness 的读写、验证、Git 操作和 transcript 能力暴露给外部工具。
Open WebUI旁路聊天入口,接 OpenAI-compatible provider 和 OpenAPI/MCP 工具,不作为 harness 的主数据源。

一个重要原则是:Open WebUI 适合承载“对话体验”,但不适合成为 agent-harness 的 schema、文件、Git、workflow 的唯一管理面。主控面仍然是 agent-harness-web

2. 本地启动 agent-harness-web

本地模式面向真实机器上的 harness 文件和 Agent CLI,因此通常直接运行在 127.0.0.1

cd ~/projects/My_Github/agent-harness-web
corepack pnpm install
corepack pnpm typecheck
./scripts/local.sh start
./scripts/local.sh status

默认能力包括:

  • 浏览 skills、projects、workflows、knowledge、memory、rules;
  • 预览和编辑 Markdown;
  • 查看 diff、stage、commit、push;
  • 运行 harness 校验;
  • 存储 Agent transcript;
  • 提供 /api/healthz/openapi.json/mcp/v1/models/v1/chat/completions

常用检查:

curl -fsS http://127.0.0.1:3187/api/healthz
curl -fsS http://127.0.0.1:3187/openapi.json
curl -fsS http://127.0.0.1:3187/v1/models

如果要用 Docker 模式,可以单独跑:

./scripts/docker.sh start
./scripts/docker.sh status

本地直连模式更适合开发和调用本机 Agent CLI;Docker 模式更适合隔离运行和自托管验证。

3. Agent Session 单向同步

一开始,历史 session 只有手动导入:

corepack pnpm import:history

这只能解决“补一次历史”的问题,不能持续同步。因此后续补了一个单向同步 companion:

corepack pnpm sync:sessions -- --once
corepack pnpm sync:sessions

本地 ./scripts/local.sh start 会默认启动这个 companion。它定期读取本机 Agent 的 JSONL 历史,并 upsert 到 agent-harness-web 的 SQLite transcript storage。

控制方式:

# 禁用自动同步
AGENT_SESSION_SYNC_ENABLED=0 ./scripts/local.sh start

# 调整同步间隔
AGENT_SESSION_SYNC_INTERVAL_MS=300000 ./scripts/local.sh restart

# 查看同步日志
./scripts/local.sh sync-logs

这个同步是单向的:只从本机 Agent 原始 session 读入 agent-harness-web,不会反写 Codex、Claude Code 或其他 Agent 的原始文件。

4. 安装和启动 Open WebUI sidecar

Open WebUI 用 Docker 运行即可。关键是保持认证开启,并启用实时保存。

docker run -d \
  --name open-webui-agent-harness \
  --restart unless-stopped \
  -p 3300:8080 \
  -e WEBUI_AUTH=True \
  -e ENABLE_SIGNUP=False \
  -e OFFLINE_MODE=True \
  -e BYPASS_EMBEDDING_AND_RETRIEVAL=True \
  -e ENABLE_REALTIME_CHAT_SAVE=True \
  -v open-webui-agent-harness:/app/backend/data \
  ghcr.io/open-webui/open-webui:main

这里有两个经验点:

  1. 如果 Open WebUI 已经存在用户,就不要再把 WEBUI_AUTH 改成 False。Open WebUI 会拒绝在已有用户的数据库上关闭认证。
  2. ENABLE_REALTIME_CHAT_SAVE=True 很关键。没有它时,长时间 streaming 对话切换页面后容易只剩 loading 状态,看不到中间内容。

5. 接入 agent-harness-web

Open WebUI 里接两条线。

第一条是 OpenAI-compatible provider:

Base URL: http://host.docker.internal:3187/v1
Models:
  - agent-harness-codex
  - agent-harness-claude
  - agent-harness-opencode
  - agent-harness-reasonix

第二条是工具能力:

OpenAPI spec: http://host.docker.internal:3187/openapi.json

这样 Open WebUI 可以把对话请求发给本机 Agent,也能通过工具读取 harness 资源、查询 transcripts、触发验证等。

6. Streaming 体验优化

直接把 Agent CLI 接到 Open WebUI 后,常见问题是“前台一直 loading,看不到 Agent 正在做什么”。解决思路是把 Agent 事件拆成两类:

  • status/progress:包装为 <think>...</think> 或 reasoning 内容;
  • final content:作为正常 assistant content 输出。

这样 Open WebUI 在对话过程中能持续保存中间内容。对于 Codex、Claude Code、OpenCode 这类输出格式不同的 Agent,需要分别解析其 JSONL 或 stream-json 输出,而不是把原始 JSON 直接塞进聊天窗口。

实践中做过的适配包括:

  • Codex:解析 codex exec --json 的 thread、turn、agent message 事件;
  • Claude Code:使用 --print --output-format stream-json --verbose 获取更完整的流;
  • OpenCode:解析 JSON 格式输出,避免在 UI 中显示不可读的原始 JSONL。

7. Open WebUI watchdog

Open WebUI 在长流式回答后,偶尔会出现两个状态问题:

  • assistant 消息已经 done=true,但侧边栏仍显示 loading;
  • 新建对话标题长期停留在 New Chat

为此增加了 watchdog:

corepack pnpm openwebui:task-watchdog -- --once
corepack pnpm openwebui:task-watchdog

watchdog 做两件事:

  1. 如果最新 assistant 消息已完成,就清理 stale active task;
  2. 如果标题还是 New Chat,就用第一条用户消息生成一个短标题。

这类修复不要依赖手动刷新页面。对 sidecar 来说,后台轻量守护进程比让用户反复重启浏览器更可靠。

8. 构建、提交和镜像发布

本地修改完成后,至少做:

corepack pnpm typecheck
node --check scripts/sync-agent-sessions.mjs
node --check scripts/openwebui-task-watchdog.mjs
git status --short
git add <changed-files>
git commit -m "..."
git push

如果项目配置了 GitHub Actions,可以让 push 触发 Docker image 构建,再检查 workflow:

gh run list --workflow "Docker Image" --limit 5
gh run watch <run-id> --exit-status

这一步的重点不是“每次都发版”,而是保证 local/docker 路径仍然可构建,避免只在当前机器上能跑。

9. 安全边界

这套流程要长期可复用,必须把敏感边界写清楚:

  • 不把 token、password、private key、bearer token 写入文档或 Git;
  • Open WebUI token 只放在本机临时文件或环境变量里;
  • GitHub、Cloudflare secret 只通过 CLI/平台 secret 管理;
  • 公开文章只写命令形态和变量名,不写真实 secret 值;
  • Agent session 原文默认进入本地 app storage,不进入公开仓库;
  • cloud 模式不能假设能运行本机 CLI 或本机 git,需要走 GitHub API、Actions 或远程任务。

10. 最终形成的操作闭环

整理后,这套流程大致是:

启动 agent-harness-web
  -> 自动同步本机 Agent sessions
  -> Open WebUI 连接 /v1 和 /openapi.json
  -> 对话请求进入本机 Agent
  -> streaming 状态实时保存
  -> watchdog 修复 stale loading 和 New Chat 标题
  -> transcripts 进入 agent-harness-web storage
  -> 代码和流程沉淀进 workflow/skill
  -> GitHub Actions 构建镜像验证可迁移性

我认为这套结构的价值在于,它没有让任何一个工具“统治”所有数据。文件、Git、SQLite、Open WebUI、Agent CLI、MCP/OpenAPI 各自承担最合适的角色。这样以后迁移到另一台机器,或者换一个聊天入口、换一个 Agent CLI,都不需要推倒重来。

继续阅读