>wangyr@context: ~$

作品三

OpenCanvas:Agent 思考画布

2026-08-28 起 · 十二天一百七十次提交 · 本月还在改

一块跟 Agent 一起想事情的画布:就着自己的文件,把一个问题展开成一棵树。

思考时,一个回答引出的几个子问题全留在图上,一条条追下去;探索时,把手上的笔记、代码、资料挂上来指着问,答案连着材料;学习时,一个题目问出几层、几条线,整棵树存在自己的文件夹里,改天接着长。

问题长成一棵树,每轮只给地址,整棵树存回文件夹 文件夹 笔记/货币.md 笔记/信用.md 资料/央行.md 笔记/物价.md 笔记/货币.md 笔记/信用.md 货币.canvas 改天打开 接着长 画布 1 货币是什么 只给地址,不给内容,它自己去读 Agent · 本机 1.1谁发行 1.2怎么定价 1.3信用哪来 1.3.1谁来担保 绑的是个人 AI 操作系统那个文件夹 导出后 Obsidian 直接打开
左边是本机一个文件夹,右边是画布。一个问题问出去,线指向本机的 Agent,Agent 指回文件夹里的一份文件,只拿到地址、自己去读,回答落回卡片;回答分出三个子问题,再从一张问下去,长出下一层;最后整棵树变成文件夹里的一份画布文件。自动循环。右边一格是它接上的两处,第一个标签能点过去。

它帮使用者做三件事,都在同一张图上。

  • 思考:一个回答引出几个子问题,每个都在画布上长成一张卡,编号写着它从哪来;追完一条回头追另一条,不用翻记录找岔口。见提问树一节。
  • 探索:从手上的材料出发。左边是文件树,文件挂到画布上指着它问,Agent 自己去读原文。见文件夹一节。
  • 学习:同一张画布上的提问接在同一段对话上,后面的问题能推翻前面的答案;画布存在自己的文件夹里,下次打开从任意一张卡接着问。见对话一节。
  • 三件事在同一张图上:卡片按意思摆,手画的线 Agent 认;乱了一键整理;能导出带走。见画布一节。

它是什么:加在本机 Agent 外面的一层交互,不是又一个 AI 产品。

  • 本机的 Claude Code、Codex 已经认识我的文件、技能和项目说明。它们的对话是线性的:一个回答引出几个子问题,只能追一条,其余的当场丢或记在别处;聊完,这棵树只活在脑子里。
  • 缺的只是一个能把思路铺开的界面。OpenCanvas 是那个界面:一个在本机跑的网页,绑一个文件夹;画布上的每一个动作传给 Agent,Agent 的每一句回答落在画布上。
  • 我先试过让 Agent 直接写画布文件。它能写,看不见画出来的样子;我能看见,画布上的动作传不回去。缺的不是画布,是那根回传的线。
  • 一套 harness:规则、闸门、校验、权限、测试这类不产出内容、只约束产出的工程,十五项,每项约束画布、本机服务、Agent 三样里的某一样。

为什么接本机 Agent:市面的 AI 画布接云端模型,不认识使用者的文件;这块画布接本机已装好的 Agent,文件、技能、项目说明全在场。

  • 不要 API key:已装好的 Claude Code 或 Codex 直接驱动画布,不另接模型。
  • 不上传:材料在本机文件夹,画布文件在本机文件夹,服务只听本机。
  • Agent 在终端里认识什么,在画布上就认识什么:同一份文件、同一套技能、同一份项目说明。
  • 它每一轮收到了什么、此刻在读哪个文件,界面上看得见。

它和什么连着:绑本机一个文件夹,接本机已装的 Agent,画布能导出给 Obsidian。

  • 输入是本机一个文件夹,我绑的是个人 AI 操作系统那个文件夹,项目说明和技能全在场。
  • 输出是画布文件,落在那个文件夹里,一键导出后 Obsidian 直接打开。
  • 用到系统里的会话钩子和技能目录;Agent 是本机的 Claude Code 或 Codex,代码在 GitHub 开源。
  • 「只给地址,不给内容」和个人 AI 操作系统的「索引只维护语义」是同一条判断。

它由三样东西构成,下面先讲四节用途,再讲一轮提问怎么走。

  • 三样东西:
    • 浏览器里的画布,显示文件树、卡片、连线,接住每一个动作。
    • 本机服务,管画布文件、组装每轮发给 Agent 的东西、起 Agent 进程、把回答流回画布。
    • Agent,使用者已装好的 Claude Code 或 Codex,工作目录是画布指向的文件夹。
  • 顺序:提问树、文件夹、对话、画布四节讲用途;全图、上下文、Agent、本机、可见五节讲它怎么做到;harness 一节把散在各节的约束收成一张表;最后是演化和现状。
  1. 提问树:一个问题展开成一棵树,卡上的号就是来路
  2. 文件夹:从手上的材料出发,不从空白提示框出发
  3. 对话:一张画布是一段对话,整棵树接着长
  4. 画布:图按意思组织,铺在对话上
  5. 全图:三样东西,一轮提问
  6. 上下文:只发地址,不发内容
  7. Agent:两家都能接,画布上随时切
  8. 本机:一字不出本机,双击就开
  9. 可见:它收到了什么、在做什么,看得见
  10. Harness:十五项工程,每项约束一层
  11. 演化:十二天,画布与对话的关系画了三次
  12. 现状:分三档说

提问树:一个问题展开成一棵树,卡上的号就是来路

一个回答引出的几个子问题,每个都在画布上长成一张卡,全部留着,追完一条回头追另一条。每张卡左上角一个号,看见号就知道它从哪张卡问出来,不必追线。

号意思
1一张根卡
1.2从 1 问出来的第二张
[1&2].1把 1 和 2 放在一起问出来的第一张。「合问」指选中几张卡一起问
[1&2].1.1从那张合问卡再问下去的
[1&2.1].1把 1 和 2.1 放在一起问的。中括号让这两种分得开
OpenCanvas 界面:左侧文件夹列表,中间画布上从三份文件长出的提问树,每张卡片底部都有继续提问的输入框
就着「秩序重组是什么」这个问题,从三份笔记长出的一棵树。

合问的号原来把两种分隔符挤在同一根轴上,读不回来路。

卡片上只有三样:问、答、接着问。

文件夹:从手上的材料出发,不从空白提示框出发

每张画布指向本机一个文件夹,想事情从这个文件夹里的笔记、代码、资料出发:左边是文件树,文件挂到画布上,指着它问。

Agent 以这个文件夹为工作目录,文件夹里的东西它全认识。一个文件夹可以放任意多张画布。

打开应用看到的是画布清单,不是文件夹选择框。画布是主角,文件夹是它的属性。

OpenCanvas 首屏:绑定文件夹后的空画布与提问框
首屏。绑一个文件夹,从这里问第一个问题。

对话:一张画布是一段对话,整棵树接着长

同一张画布上的所有提问接在同一段对话上:后面的问题能看见、也能推翻前面的答案;画布文件存在文件夹里,下次打开从任意一张卡接着问。

两家 Agent 在同一张画布上各记各的对话。

两个已接受的代价,一个留作观察的未知。

画布:图按意思组织,铺在对话上

卡片按使用者的意思摆,线按使用者的判断连,手画的线 Agent 下一句就按它来。画布上因此有两种关系,必然分家:对话谱系是事实,手画的线是判断。

画布可编辑,交互交给一个开源的节点图库。

「整理」按来路排成树林,一步撤销。

沉淀与导出建在里面,不进首屏。

全图:三样东西,一轮提问

上面四节的事由三样东西完成,都在一台机器上。一轮提问从画布出发,经本机服务到 Agent,回答流回画布,画布文件落在使用者自己的文件夹里。

部件做什么实际是什么
画布显示文件树、卡片、连线;接住提问、拖动、连线、框选一个网页,用浏览器的应用模式开成没有地址栏的窗口
本机服务管画布文件;组装每轮递给 Agent 的文字;起 Agent 进程;流式回传一个 Node 进程,只听本机回环地址,端口自动挑空的
Agent按地址读文件,回答使用者已装好的 Claude Code 或 Codex,每张画布选一家
文件夹放材料,也放画布文件使用者自己的任何一个文件夹。画布文件是普通 JSON,和材料放在一起
启动器双击:起服务、挑端口、开窗口;退出即停服务一段脚本装成应用图标,不是 Electron
人 在卡片上问 画布 浏览器 · 文件树 · 卡片 流式显示回答 动作 回答 本机服务 问题 + 地址 接在这张画布的对话上 Agent Claude Code 或 Codex 工作目录 = 文件夹 文件夹 · 使用者自己的 笔记 · 代码 · 资料 画布文件(普通 JSON) 项目说明 · 技能 · 设置 卡片落盘 Agent 按地址自己读 本机边界 不出去
红色实线:人在画布上的每一个动作,这是第一版缺的那根线。虚线箭头:Agent 按地址自己去读材料、画布文件和使用者的配置,服务不替它读。红色虚线:本机边界,没有任何东西越过它。

一轮提问走五步。

上下文:只发地址,不发内容

Agent 读到的是材料原文,不是我替它剪过的一段;资料从十篇涨到一万篇,每轮发出去的东西不变。做法:每一轮递给 Agent 的只有两样,问题的原话,和使用者此刻认为相关的每张卡一行地址;卡片正文一个字不进提示词,Agent 顺着地址自己去读。

类别例子Agent 自己拿得到吗所以
它自己产生的说过的话、读过的内容在对话里,本来就在不发
磁盘上的文件、笔记、代码、画布文件本身它能读、能搜给地址
只在使用者手上的这次问什么;点了哪张卡、划了哪一段、连了哪条线拿不到问题给原文,指点给地址

地址一行长什么样。

1「货币是什么」(我们之前聊过的那一轮)对话卡
3「先把两个概念分开」(我手写的便签,原话在画布文件里)便签
文件 笔记/某篇.md(我挂上来的文件,不在我们的对话里)文件卡
文件 笔记/某篇.md 中我划的一段(开头是「货币内生的意思是」,约多少字)划中的一段

三条理由,都不是省钱。

替它挑一段塞进去是在猜

我不知道使用者要用那份材料的哪一部分。猜错了,Agent 拿到的是被剪裁过的材料,它和使用者都看不出被剪掉了什么。

它定位比我准

它能在文件里搜,我只能猜。

地址的成本是常数

使用者的资料从十篇涨到一万篇,每轮发出去的东西一个字不变。给别人用的软件,每轮成本必须是常数。

代价:Agent 有权不去读那个地址,答案会浅。

手画的线在这一层算数。

Agent:两家都能接,画布上随时切

装了哪家就用哪家:Agent 是使用者已装好的 Claude Code 或 Codex,画布自己不带模型,不要 API key。

Agent 接口只有两个动作:起一段对话、问一句。

本机:一字不出本机,双击就开

材料、画布文件、Agent 全在本机,不上传任何东西。

服务只信本机的浏览器。

应用是一段启动脚本装成的图标,界面仍是网页。

可见:它收到了什么、在做什么,看得见

使用者能看见 Agent 每一轮收到了什么、此刻在做什么:发出去的东西短到一眼能看完,这才是可审计。

这个项目反复踩的一类错是出错时静悄悄。规矩:任何失败路径都要在界面上留下痕迹,宁可吵,不可静。

测试的规矩:先把代码改坏,看测试会不会红。

Harness:十五项工程,每项约束一层

harness 不产出内容,只让产出稳定:每一轮 Agent 收到什么、谁能连上服务、服务能读什么、画布上什么能改什么不能改、出错时怎么露出来,都定在一处,散在上面各节里;这里收成一张表。

项做了什么约束哪一层
只发地址,不发内容每轮递给 Agent 的只有问题的原话和选中卡片的一行地址,卡片正文一个字不进提示词,Agent 顺着地址自己去读本机服务递给 Agent 的输入
地址的写法标注贴在每一行,不设全局抬头;「范围」只在多选时说,写成可判定的一句加反例,不枚举;划中的一段给开头那句话,不给行号;标题只取首行,不做内容特判;画布文件的路径只在列出的卡里有便签、地图块或另一家答的卡时才带本机服务组装的地址清单
谱系不可改,手画的线是附加层每张卡只有一个来处,画成派生线,不可改;手画的线标成「我连的」,进地址清单,不改写对话历史画布上的两种关系
号是唯一钥匙号分配一次不再变,空号如实留着;只有提问时选中的卡算来源;计数器丢了从现有的号重建;合问用中括号表达嵌套,已发出的号一个不动画布上的卡片与 Agent 指认卡片的方式
一张画布一段对话同一张画布的所有提问接在同一段对话上;只用公开的续接参数,不依赖没写进帮助文档的参数;对话满了交给 Agent 压缩,不自造兜底本机服务与 Agent 之间的对话
Agent 接口只有两个动作起一段对话、问一句;每家一个文件,把自家的输出映射成同一套事件,服务其余部分不知道背后是谁;两家各存各的对话,另一家答过的卡改标注,不走「接着问」的捷径Agent 的接入边界
不替使用者批准写操作Agent 发来的审批请求一律拒绝Agent 的写权限
使用者的配置默认全加载项目说明、技能、开工脚本全加载,不替使用者判断他的配置重不重要,曾有的精简开关整个删掉;等待时长写在卡片上,让使用者自己看见自己的配置多贵Agent 的启动与画布的显示
服务只信本机的浏览器只听本机回环地址;连接握手时校验来源,不是本机的一律拒绝;以后加任何入口,沿用同一前提本机服务的入口
图片三道门请求来自本页面、那个文件夹开过画布、文件找得到,三道都过才给;只读本机服务读文件的出口
每轮发出的原文落盘这一轮实际发出去的原文一字不差存在画布文件里,卡片能展开看;等待期间卡片说出它此刻在读哪个文件、搜哪个词画布文件与画布的可审计
失败路径必须留痕任何失败在界面上留痕,宁可吵,不可静:断线自动重连,发送失败如实返回并禁用按钮,自检失败在界面和系统通知上都看得见,找不到 Agent 就明说画布、本机服务、启动器的出错路径
运行时格式与交换格式拆开运行时用自己的格式,和别的工具互通靠导出,不为了直接打开而绑死运行时画布文件
不自动动使用者的排布不做回答后自动整理,使用者拖过的位置是他的,只有他点「整理」才动;沉淀不自动触发画布的自动动作边界
测试先改坏每条防回归测试先故意改坏代码,确认真会报红,再装回去;前端手势类改动一律真浏览器实测;「它没做成」不能证明防线有效,要去看防线本身全部代码的变更

十五项里最多的一类是「不」:不发内容、不改谱系、不批准、不砍配置、不信外部来源、不自动整理。理由是同一条:判断权在使用者和 Agent 手上,画布不替任何一方拿主意。

演化:十二天,画布与对话的关系画了三次

一百七十次提交里,反转过的是画布与对话的关系、每轮给 Agent 什么、怎么分发;删掉的有一本账、一个开关、一条隐藏参数依赖、一条横幅。功能增长不列。

让 Claude Code 直接写画布文件。卡片互相盖住。根因是单向盲写:它能写看不见,我能看传不回。立项理由从这里来2026-08-27
形态从 Obsidian 插件改成本机网页加本地文件夹:Obsidian 的画布交互接口未公开,现有插件全靠硬扒内部接口。定下画布是对话现场,不是对话的沉淀地。第一版砍掉沉淀层2026-08-28
画布引擎不自造,改用开源节点图库;画布从只读改成可编辑;运行时格式与交换格式拆开。同日沉淀层以最小形态加回:一次综合加一个输出契约2026-08-28
架构变更:从「每个分支一段对话」改成「一张画布一段对话」。理由:谱系是树,理解不是树;累积让后面的能推翻前面的;不再依赖没写进帮助文档的分叉参数2026-08-28
入口反了:从空白提问框改成从文件出发。补文件树、文件卡、阅读栏。查出每轮首字等待的大头在我个人配置里一个开工脚本,不在画布2026-08-28
上下文从「塞内容」改成「给地址」。取消「一张卡的正文一辈子只进去一次」那本账;不自造压缩兜底;只用公开参数。新前提是这东西要给别人用:不替使用者砍配置,默认全加载加精简开关加显示秒数;分发用图标加网页,不用 Electron;定位不写死成学习工具2026-08-29
首屏从文件夹选择框改成画布清单;文件夹用系统选择框;画布名等第一问答完再起2026-08-29
编号带层级,号本身就是来路。提示词重做:标注贴每一行,不设全局抬头;范围只在多选时说2026-08-31
安装包从 131MB 缩到约 1MB,只拷运行时真用得上的东西。同日查出打包的应用从没真正跑起来:入口判断把路径里的中文比错了2026-09-01
公开前收口:服务只听本机回环地址,握手校验来源;对话记录与本机路径不进仓库。仓库改名 OpenCanvas,先建成私有2026-09-02
接 Codex:每张画布一个开关,两家各存各的对话。精简模式整个删掉2026-09-03 – 04
合问的号加中括号:扁平字符串表达不了嵌套。已发出的号不动,只改我自己一张老画布2026-09-05 – 07
画面加「刷新」;重连后自动重开画布。首页显示两家 Agent 状态,删掉那条假横幅2026-09-05 – 07
一键整理;本地图片显示;模型下拉第一项改成「默认」,只改字不改行为2026-09-07
点选修复:受控模式下单击选卡从来没生效过,框选靠库的内部改动才生效。真浏览器逐事件复现才查到2026-09-08

画布与对话的关系画了三次:分叉的树,一段对话,地图铺在一段对话上。变的是给 Agent 什么,没变的是 Agent 在本机、材料在本机、画布是对话现场。

现状:分三档说

写这一页的日期是 2026-09-17。我自己在用,代码已开源。

在跑,有真实使用记录
  • 绑文件夹、文件树、阅读栏、提问树、合问、层级编号、地址式上下文、一张画布一段对话,以 Claude Code 为 Agent。我自己文件夹里的画布都用这一套长出来。
  • 双击启动的应用、画布清单、刷新与重连、Shift 多选、一键整理、本地图片显示,都在我正在用的版本里。
  • 服务只听本机、握手校验来源,有测试守着,两条做过改坏代码的验证。
建了,不常跑或没走通
  • Codex:集成测试真跑过,我的真实画布里没有一张卡由它答过。
  • 便签、手画的线、在文件里划一段挂成卡:有测试,我的真实画布里没有出现过。
  • 沉淀成地图块、图层切换、导出 JSON Canvas:代码保留,不进首屏,我的真实画布里没有地图块。
  • 启动器不检查本机有没有 Node;说明里「右键打开未签名应用」的办法在新版 macOS 上无效。
设计中或未测得
  • 对话被压缩之后画布会不会变笨,没验过。
  • 删掉的卡片仍留在对话里,要不要让 Agent 知道哪些卡还在,待定。
  • Windows 与 Linux 的启动器,只有 macOS 这一个。
  • 自动更新不做,自带运行时的单文件不做,等真有人撞上再说。

数字只报能证明系统真实存在的。

提交,2026-08-28 到 09-08一百七十次
自动化测试,服务端四百三十七条
自动化测试,前端一百一十四条
服务端与前端源码六千六百四十六行
安装包,第一版 131MB844KB
我自己文件夹里的真实画布,最大一张十三张卡、十四条线三张
画布清单最近一次打开2026-09-11

代码开源在 GitHub。画布连的是我的个人 AI 操作系统里那个文件夹,见作品一。「只给地址,不给内容」和作品一里「索引只维护语义,不维护清单」是同一条判断。它背后的想法怎么来的,见思考。