1. 上下文不够是制约 AI Agent 使用的关键因素
Agent 完成工作的质量,既取决于模型能力,也受限于能触达的环境上下文。
对于开发、运维来说,更难的是环境可能还是分裂的,线上有完整日志、文档在知识库、代码在 Git 仓库。模型读不到这些信息,也就无法准确判断问题。所谓上下文不够,很大程度上不是模型窗口不够大,而是环境不在 Agent 的访问范围内。
由此给我们带来挑战:
- 开发需要在本地环境改代码、跑测试,再传回线上进行部署测试
- 运维需要在机器上执行命令、查看日志,然后把结果给 Agent 进行分析排查故障
人与 Agent 交替验证是现在普遍的 AI 工作流。
2. 将环境完全交给 AI Agent
目前常见的做法是在环境中安装 Agent 或者由中心的 Agent 通过 SSH 、MCP 执行 Job 进行操作。
前一种方式将 Agent 部署在每台机器上,泄漏模型访问凭证; 后一种方式使 Agent 能大面积访问主机,潜在风险巨大。
另外一个思路是,让 Agent 远程运行在环境上:
- 超级大脑只需要一个。模型调用、会话状态、插件、密钥、审计都保留在一处集中管理
- 更易管控。通过简单配置即可纳管目标机器,通过统一入口使用 Agent
- 成本更低。目标机器上只运行一个轻量的转发进程,资源占有少
- 接入更容易。通过上位端入口,可以轻松管控大批量机器
下面就是基于 deepseek-harness 实现的一个方案。
3. deepseek-harness-web
deepseek-harness-web 把 dsh 的 Web 界面打包成单个容器,一条命令就能起一个 Agent,自带 Web UI、模型路由和持久化存储。
简单说,它解决的是大脑在哪里,我的选择是 deepseek-harness + 容器 + 持久化。
┌─────────────────────────────────────────────────────────────┐
│ dsh-web 容器 │
│ │
│ entrypoint.sh ── PID 1,自动重启循环 │
│ ├─ sync-provider.sh 模型路由 → settings.yaml │
│ ├─ sync-workspace.sh 启动拉取 + 退出时落盘 │
│ ├─ s3-sync.mjs 双向同步守护进程 │
│ └─ dsh web DeepSeek Harness Web UI │
│ │
│ /root ⇄ ./home(bind mount) ⇄ S3 bucket(可选) │
└─────────────────────────────────────────────────────────────┘
对 remote-node 而言,它就是上位机,也是给人的操作入口。
通过配置环境变量,设置相关的配置文件。API_KEY 配置模型访问凭证;设置 BASE_URL 与 MODEL 将自定义模型写入 /root/.dsh/settings.yaml,MODEL 支持逗号分隔的多个模型 id,第一个为默认模型。工作区与 dsh 配置都落在 /root(bind mount ./home:/root),配置 S3 后再整体同步到 bucket。
deepseek harness 启动日志会打印带 token 的访问地址,替换为实际的域名或 IP 即可访问。若使用域名访问,需要把 TRUSTED_HOST 设置为浏览器请求携带的 Host 头(可包含端口)。
4. deepseek-harness-remote-node
deepseek-harness-remote-node 把一台远端机器变成 dsh 的执行环境。远端机器主动连接到 dsh 已有的 Web 入口,Agent 的文件读写、shell 命令、终端、语言服务器全部发生在那台机器上——而 Agent 循环、模型调用、会话状态、插件留在宿主。
| 正常 | deepseek-harness-remote-node | |
|---|---|---|
ctx.fs — 读、写、改 | dsh 宿主机的磁盘 | 节点的磁盘 |
ctx.subprocess — 命令、终端、LSP | dsh 宿主机的进程 | 节点的进程 |
| Agent 循环、模型调用、会话状态 | dsh 宿主机 | dsh 宿主机(不变) |
没有它,Agent 只能改宿主机上的文件;有了它,Agent 改的是你指定的那台机器。
例如,问 Agent:「工作机还剩多少磁盘空间?」它会在远端节点上执行 df 命令,而不是在宿主上。
4.1 架构
宿主仍然是一个 dsh 进程,对外只暴露一个 Web 入口;节点是一条从目标机器拨入的连接。
┌───────────────────────────────────────────────────────┐
│ dsh host │
│ │
│ webServer :3080 │
│ ├ /api/remote.mux ← 浏览器 mux │
│ └ /node/v1 ← 节点通道 │
│ │
│ ctx.nodeRegistry ── @shaowenchen/deepseek-harness-remote-node │
│ ctx.fs ........... @shaowenchen/deepseek-harness-remote-node/fs │
│ ctx.subprocess ... @shaowenchen/deepseek-harness-remote-node/subprocess │
└───────────────────────▲───────────────────────────────┘
│ wss,由节点主动连接
┌───────────────────────┴───────────────────────────────┐
│ 执行环境(Pod / VM / 宿主机) │
│ 文件系统 · 进程 · 终端 · 语言服务器 │
└───────────────────────────────────────────────────────┘
节点可以是各种形态的执行环境,比如一个 Pod、Container、VM、物理机。
4.2 原理
dsh 的架构里,Agent 的一切环境操作都收敛到两个接口(seam):
| 接口 | 覆盖范围 |
|---|---|
ctx.fs | 读、写、改、列目录、元信息 |
ctx.subprocess | 命令、终端、语言服务器 |
上层能力(bash 工具、PTY、LSP)都组合在这两个接口之上,不指定具体实现。两个接口合起来,就定义了一个执行环境。替换这两个 provider,等同于替换整个执行环境——bash、终端、LSP 一行都不用改,上层完全感知不到。
完整调用链:用户提问 → Agent 调用 bash 工具 → dsh-bash-local 调用 ctx.subprocess(它从不 import child_process)→ 本包的 subprocess 入口把调用转发过通道 → 节点上的 agent 执行。
4.3 安装
宿主和节点都要安装 Node 22+ 环境。
github.com 无法访问时,可以设置--proxy 标志代理两个安装脚本:
| |
- host
在 dsh-web 容器里执行脚本:
| |
或者也可以通过 --cwd 指定工作目录,默认 $HOME/.deepseek-harness-remote-node。
| |
patch 层会显式 disable 掉本机 provider。
装完重启 dsh。dsh-web 容器里:
```sh
dsh-restart
- node 侧
在执行环境上执行:
| |
wss:// 是 dsh web 暴露的地址:
| |
凭证由 host 安装脚本随机生成并打印,同时存在 $DSH_HOME/node-credential。dsh-node --describe 可以在不连接的情况下打印本机身份。
一台节点只跑一个 agent 进程。节点 id 默认取本机 hostname,同一个 id 的第二个 agent 会被当作 busy 拒掉。检查是否有重复进程。
- 卸载脚本
| |
4.5 使用
节点注册后,不需要调用任何东西。 像平时一样和 Agent 对话,它的工具就会在远端机器上运行。
节点上还剩多少磁盘空间?
在当前目录下生成一个
test.txt文件,内容是hello world。
下面是我接入的一些环境和运行截图。
K8s Pod:

Linux 虚拟机:

macOS 宿主机:

5. 与其他方案的比较
5.1 直接安装 Agent
在每台目标机器上分别安装 Claude Code 或 dsh,彼此独立工作。该方案在功能上成立——每台机器上都是一个完整的 Agent 。
主要问题是:
- 模型密钥扩散。N 台机器即 N 份密钥,泄露风险乘以 N,轮换需要改动 N 台。
- 会话历史分散。会话历史只保存在产生它的那台机器上,跨机器检索需要逐台翻查。
- 版本漂移。N 台各自升级,行为不一致,排查问题前先要确认目标机器的版本。
- 配置与权限各自为政。若要统一收口,需要额外引入一层配置管理。
如果借助 Job 平台分发 Agent,端到端链路还会更长,需要经历安装、启动、回收各一次。
5.2 SSH 到主机
最直观的做法是复用现有的 SSH 通道:人能够 SSH 到目标机,Agent 也可以。对命令行工具而言 SSH 够用,但对一个要读代码、改代码、跑测试的编码 Agent,SSH 只解决了一半:
| 问题 | 说明 |
|---|---|
| 密钥安全 | 宿主机要持有到 N 台目标机的私钥,宿主机失守则横向可达 |
| 联通性 | 目标机要能被宿主访问到,反向场景需要额外的跳板或穿透 |
| 文件读写 | 要么靠 cat / sed 拼命令,要么另建一条文件传输通道 |
| 终端与 LSP | SSH 给你一个 shell,但 PTY 语义、语言服务器的生命周期都要自己再搭一层 |
5.3 Web IDE
在目标机器上跑一个 Web IDE,人从浏览器进去写代码、做运维。这条路看起来轻量——IDE 自带文件树、终端、语言服务——但改造成本高,需要的资源多。
不只是需要大量开发、运维人员支撑平台,还要在每台目标机器上维护一套完整的 IDE 运行时:文件服务、终端网关、语言服务器进程管理、用户隔离、会话持久化,每一项都需要大量人力。
更关键的是,Web IDE 解决的是"人怎么访问远端环境",而不是"Agent 怎么执行在远端环境"。它把密钥、会话、权限的负担原封不动地搬到每台目标机器上——前面列出的模型密钥扩散、版本漂移、配置分散等问题一个也没少,只是又加了一层平台运维。
即使将 Agent 集成进 Web IDE,得到的也只是一个个孤立的工作空间。每个环境各自维护上下文,公共知识无法集中沉淀,跨机器协作仍然需要人去搬运。
6. 小结
回到最开始的问题:Agent 无法触达目标环境,人与 Agent 交替验证的闭环就不成立。这篇文章给出的方案是——让环境接入 Agent,而不是把 Agent 移动到环境。
具体来说:
- deepseek-harness-web 把 dsh 的 Web 工作区装进单个容器,一条命令即可自托管,模型调用、会话状态、密钥全部收口在一处。
- deepseek-harness-remote-node 让远端机器主动连接到 dsh,接管
ctx.fs和ctx.subprocess两个接缝,Agent 的文件读写、命令执行、终端、LSP 全部落在远端机器上。 - 凭证校验已落地,三个包合并为一个,全量
fs.*/proc.*/tty.*操作族已实现。 - fail-closed 绝不回退上位机,单槽注册防止冲突,对等测试保证与本地行为一致。
