站在电车里, 我在手机上改代码
2026-09-29 16:03
对于我而言, 这是另外一个Aha时刻. 18个月前, 当我第一次开始用Claude Code的时候有这样的感觉. 而今天早上当我在电车上, 在手机上让deepseek harness(后面简称为dsh)帮我去改一段代码, 然后切app去读书, 一会儿后回到浏览器, 发现活已经干完了. 这个感觉就是很开心.
首先说下dsh是什么, 以及广义上harness是什么. harness(直译是挽具)是用来让人能更好地和LLM协作的外围工具. 从最宽泛的意义上讲, 你在和AI对话的时候, 已经使用过挽具了. 比如, 你在和AI对话的时候, 一般的聊天窗口都会把所有已有的聊天记录全部发到LLM, 这样LLM才能更好地在一定上下文下跟你好好说话. 这个把所有已有对话记录全部一次性合并起来发给LLM, 当然也可以被认为是挽具的一种. 对于软件工程而言, harness有更多的意义. 比如harness去硬盘上读了一段代码后发给LLM, 同时附上适当的提示词, 让LLM可以继续跟去下一个文件或者去执行某个命令等等. 这些都不属于LLM的直接范畴(就好比不是马身体的一部分), 但是对于结果来说又是很重要的. 配了好的挽具, 牛马才能多快好省地干活, 为资本家创造价值. 而在软件工程上, 配了更好的harness, 你的LLM才能更好地发挥自己的作用: 省token, 并生成出更好的代码. deepseek harness顾名思义, 就是deepseek家出的harness. 八月中发布以后我也一直在使用.
我为什么会看好dsh呢? 因为它太hackable了! 手上一个相对较新的dsh安装, 里面有28个会话层面的插件, 还有165个核心层面的插件. 比如会话层面的插件有叫tool-subagent, 有叫tool-todo, 还有叫tool-web; 而核心层面的插件比如session-title, settings-file, storage, tool-bash, skill, goal等等. 你所能想到的功能都被实现成了插件, 这也意味着, 至少在理论上, 你可以替换掉所有这些组件的功能, 意即, 你可以用非常低的成本来定制dsh的行为. 说个具体的例子, 我希望我的dsh里面主界面上应该只有一个聊天窗口, 不绑定任何workspace, 我可以在里面下达命令, 然后就会有一个LLM来判断我的意图, 并启用另外一个agent在某个合适的目录里开始这个工作, 并在需要我介入的时候找我. 这个东西如果我要在claude code里面实现, 大概率只有把claude code包起来. 但是在dsh里面, 几个prompt之后, 这个功能就出来了. 又比如, 我实际上只有Codex的订阅, 没办法直接接入到dsh, 于是装了一个插件就实现了codex auth.
接下来开始重头戏, 我是怎么样在手机上跑dsh的. 首先, 要让dsh直接在手机上运行没有任何意义, 因为dsh很可能会执行编译, 测试或者运行其他系统命令的工作. 在一个iPhone的沙盒里面, 这些都很难做到. 所以, 我们会需要让dsh跑在一台服务器上, 而且更重要的, 我们要确认这个dsh能够被安全地访问.
服务器方面, 我用了一台Oracle Cloud里的免费服务器, 4核心, 24G内存, 100G硬盘, 服务器运行在悉尼, 访问速度挺不错. 对于一个免费服务而言, 挺物超所值的(就不要钱!). 顺便说下, 现在注册Oracle账号要薅羊毛, 注意要用Pay as you go, 否则会和我一样, 重试API请求一整晚都排不到空闲机器.
然后是服务器管理, 我用的是自己相对比较熟悉的Ansible. 之前我维护我的VPS也都是用这样的模式. ssh连上去, 然后用Ansible来初始化并保证配置收敛. 好处在于某天如果服务器故障, 我可以起一台新的, 在Cloudflare里面改一下域名到IP的映射, 然后一切如旧. 缺点是配置收敛有时候需要靠习惯. 比如今天早上是我正式远程使用这个解决方案的第一天, 路上发现服务器上没装golang, 我通过ish连上了服务器, 安装了这些包, 但是我回头得记得改Ansible, 把golang的安装加到服务器初始化的配置里面去.
接下来是相对比较困难的部分了, 你如何保证这个服务是安全的? 如果你就直接打开服务, 暴露到公网, 那么有恶意用户访问的时候, 直接就有了这台服务器的远程命令执行权限. 我们可以依赖HTTPS+basic auth的形式来保证这个安装的安全, 但是这样在网络层面上仍然是有攻击点的. 所以, 我采用的方案是使用ZeroTier, 我也用这个服务有一段时间了, 目前很满意. 比如, 我上个月在香港转机的时候, 要使用OpenAI时发现被地域限制了, 当时就是通过ZeroTier提供的内网IP来ssh起socks代理来解决的.
使用了ZeroTier后, 虽然这台机器本身有公网IP, DSH本身只监听服务器本机, 再由入口层限制只有ZeroTier网段内的设备可以访问. 我可以在连上ZeroTier的前提下, 用一个类似172.16.35.45的内网IP来访问dsh了. 但是显然把这个IP绑到一个域名会更好, 反正我自己有一个域名, 加一个子域名也就是几分钟的事情, 所以就顺手添加了. 另外需要指出的是, 服务器上安装ZeroTier启动后加入指定的网络时, 会需要在ZeroTier的界面里允许这台新服务器加入网络.
到这个时候, 我们的服务器安全已经做好了网络层面的防护, 只有被信任的设备可以访问这个dsh服务. 在应用层面, dsh本身还提供了一层防护, 启动dsh进程后, 标准输出里面会带上一个类似token的URL, 如果直接访问dsh的服务而不带上这个token, 会被认为是非法访问. 所以安全防护来说, 我们在外部访问层面的防护已经差不多了. 但是, 这只是防护工作的一半. 和传统的应用不一样, 对于LLM应用, 我们不仅要防外部入侵者, 也需要防范随时可能发疯的LLM.
接下来是系统层面的隔离. 我没有打算把dsh关在一个容器里面, 因为这台服务器已经被我视为一个不安全的边界了, 在里面增加一层docker只会让AI和系统有更多的摩擦, 在核心能力上, 没有特别大的收益(至少和可能的摩擦比). 但是, 我还是可以把这个dsh进程关在一个不能登录的用户(dsh)里面. 这个在Linux下实现起来很简单. 而且一个额外的好处是, 我可以更好地限制这个用户能获得到的Github权限. 我在这台服务器上安装dsh, 一定会在上面有比较多的代码层面的交互. 为此, 我如何限制这台机器不能做我能在github上的所有操作, 就很重要. 推荐的办法是用一个github app, 给这个repo有限的权限(只能对特定的repo去pull/push等), 然后将这个app的相关认证密钥信息交给这台机器的另外一个用户, 并且通过不同用户的隔离来实现dsh用户能够按需获得有一个小时生命周期的token, 能够去做它需要做的事情, 但是没法去做限定范围外的操作. 在最坏的情况下, dsh用户可以获得这个app的全部权限, 但是其能造成的不良影响仍相对有限.
折腾完这些后, 还有一些额外的小功能需要折腾. 前面提到了, 我需要使用Codex Auth, 所以需要在新服务器上登录一次. 又比如, 我需要改变dsh的默认行为, 让访问dsh时使用的域名不是localhost的时候, 也能有所有的功能(而不是所有配置存在内存, 刷新页面就失效), 官方没有这个功能, 自己让LLM写插件也就几分钟的事情. 最后, 默认的dsh网页没有对手机端做任何优化, 这也是需要修的.
做完了所有这些, 我终于可以在电车上让LLM好好写代码了. 最棒的事情是, 所有的事情都是远程完成的, 不依赖任何客户端的连接. 不管你在干啥, 只要任务已经派发出去, LLM就会在服务器端孜孜不倦地干活.