agentleFS
imchangchangimchangchang/dsh-one/.agents/skills/session-fork-parallel/SKILL.md

session-fork-parallel

Skill1 starsChanged 53 days agoMITView on GitHub

主 session 通过网关 HTTP RPC(curl 直接调 /api/*,与 dsh-one 插件 dshRpc.ts 同一套协议)生成多个独立顶层 session 并行开发:每个任务一个 session,任务说明作为第一个 prompt 发出后主 session 撒手不管,人直接在 GUI 会话列表里点开每个 session 继续交互(审批、提问都弹给用户,不经主 session 转达)。当用户要并行开发多个任务、要求"每个 session 独立、人直接与每个 session 对话"、不想用子代理(子代理藏在主 session 内,人无法直接参与)时使用。

The file

Files
SKILL.md

独立 session 并行开发(RPC 生成,人直接交互)

解决的问题

子代理(subagent / workflow)的对话都在主 session 内部:人看不到也插不进话,结果靠主 session 转达。当并行开发的每个任务需要人直接与那个 session 对话(追问、改需求、中途接手)时,要用真正的顶层 session——它会出现在 GUI 会话列表里,人可以随时点开。

dsh 网关暴露 HTTP RPC(POST /api/<method>),与 dsh-one 插件 src/server/dshRpc.ts 用的是同一套协议。用 curl 就能生成、驱动、读取独立 session。

两个 dsh 版本的 wire 差异(先判版本再写请求)

dsh 0.1.1(legacy)dsh 0.1.2+(modern,实测过 0.1.2-rc.1 / 0.1.6-alpha.1)
认证无,直连browser-session auth:必须换票,见「前置条件」
methoddot 名:session.createnamespace/path:session/create
payload直接 {...}包一层:{"args":{"request":{...}}}(session/list 等无参方法用 {"args":{"_request":{}}};session.prompt 还要 mint requestId,见方法表)
探测方法host.describe(rpcId 回声即无认证 dsh)host.describe 用不了:0.1.2+ 的认证层先拦下未带 cookie 的 /api/*(401 + 正文 unauthorized,实测 0.1.2-rc.1);换票后用 session/list 探测。扩展侧的端口探测代码见 src/server/portProbe.ts
版本判定无~/.dsh/dsh-owned.json 的 version 字段(0.1.2+);拿不到就两个形态都试

legacy 一列只在核对旧实例时用:装配对话区支持的 dsh 版本范围是 [0.1.6-alpha.1, 0.2.0)(见 README.md 的 dsh version tracking),0.1.1 的 wire 代码还留在仓库里(src/server/dshRpc.ts 的 legacy 分支),但已不在支持范围内。正常开发走 modern 一列。

dsh-one 的完整映射表与 TS 封装在 src/server/dshRpc.ts 的 MODERN_WIRE,以它为准。

前置条件

1. 确定实例地址(端口不固定)

cat ~/.dsh/dsh-owned.json
# {"pid":33411,"port":3080,"token":"...","version":"0.1.2-rc.1",...}

端口 = port;0.1.2+ 的 launch token 也在里面(每次进程启动 mint,只此一份)。

2. 0.1.2+ 换认证 cookie(0.1.1 跳过)

token 不能直接调 API,要先换票:GET /?token=<token> 换回一张 cookie(同一 token 在它所属进程存活期间可以反复换,扩展侧就是这么做的)。

TOKEN="<dsh-owned.json 里的 token>"
curl -s -m 5 -D /tmp/dsh-cookie-headers.txt -o /dev/null "http://127.0.0.1:3080/?token=${TOKEN}"
COOKIE=$(grep -i "set-cookie" /tmp/dsh-cookie-headers.txt | sed 's/^[Ss]et-[Cc]ookie: //' | sed 's/;.*//')

之后的每个 RPC 都带 -H "cookie: $COOKIE"。

3. 探测(验证认证 + 拿方法口径)

curl -s -m 5 -X POST http://127.0.0.1:3080/api/session/list \
  -H 'content-type: application/json' -H "cookie: $COOKIE" \
  -d '{"type":"client-request","rpcId":"probe-1","method":"session/list","payload":{"args":{"_request":{}}}}'

返回 "result":{"ok":true,...}(rpcId 必须回显)即通。未经认证时返回纯文本 unauthorized。

RPC 信封

{"type":"client-request","rpcId":"<任意唯一串>","method":"<方法>","payload":<形态>}

响应里要看的只有两样:rpcId 与请求一致(不一致/缺失即视为无效),以及 result.ok——true 时结果在 result.value,false 时是 "result":{"ok":false,"error":{"code","message"}}。扩展侧的判定见 src/server/dshRpc.ts 的 callRpc,test/mock-dsh/server.ts 按同一口径作答。

常用方法(modern 形态;legacy 去掉 args 包装、method 用 dot 名)

methodmodern payload(args 内)说明
session/list{"_request":{}}会话列表(items[]);行里 cwd、running、projections.values.title 可用。也用它确认 turn 在跑(running + updatedAt 变化)
session/create{"request":{"workspaceId":"..."}}建空白顶层 session,返回 sessionId。必须用 workspaceId,不要用 cwd(见「注意」第一个)
session/rename{"request":{"sessionId","title"}}起标题,GUI 列表里好认;空标题服务端拒绝
session/prompt{"request":{"requestId":"<uuid>","sessionId","mode":"queue"|"steer","content":[{"type":"text","text":"..."}]}}发消息;requestId 必须客户端 mint(crypto.randomUUID()),落进用户消息头;content 是块数组,文本块 {type:"text",text},图片块 {type:"image",mediaType,data}
session/fork{"request":{"sessionId","atSeq?"}}带历史 fork 出子 session(继承到 atSeq,省略=尾部)。需要源 session 至少有一个完成的 turn(turn/end),否则服务端拒绝;一般并行开发用 create + 任务 prompt 更干净
session/cancel{"request":{"sessionId"}}停当前 turn
workspace/create{"request":{"path":"/abs/path"}}幂等注册 workspace,返回 workspace(含 workspaceId、sessionIds)。既是拿 workspaceId 的手段,也是验证 session 归属的手段(见流程)
workspace/archiveSession{"request":{"sessionId"}}归档,从 GUI 列表隐藏。界面文案写的是「归档(不可恢复)」「只有归档是终点动作」,所以只对测试/一次性的 session 用
session/historymodern 没有这个端点只有 legacy 有:payload {sessionId, beforeSeq?, maxMessages?}(缺 beforeSeq 读尾页),返回 {events, hasMore, projections},看里面的 turn/end 判断回合完成。modern 下历史与事件都走 $events / session/control 两条 WebSocket 流(扩展侧实现在 src/server/modernStreams.ts);只想判「turn 还在跑」就用 session/list 的 running/updatedAt

流程

  1. 判版本 + 探测:读 ~/.dsh/dsh-owned.json → 0.1.2+ 换 cookie → session/list 探测。
  2. 拿目标仓库的 workspaceId:workspace/create 幂等注册({"request":{"path":"<仓库绝对路径>"}}),从返回的 workspace.workspaceId 取。
  3. 每个任务一个 session:
    • session/create 建 session(带 workspaceId)
    • session/rename 起标题(任务名,用户能在 GUI 里认出)
    • session/prompt 把任务说明作为第一个 prompt 发过去(任务要写完整:目标、约束、仓库约定如 AGENTS.md、验收标准——这是这个 session 唯一能拿到的"交接文档")
    • 验证归属:立刻再调一次 workspace/create 幂等查询,确认新 sessionId 在返回的 sessionIds 里;不在 = 没挂上 workspace,GUI 主列表看不到——把它 cancel + archiveSession 清掉,重新 create(attach 偶发失败但重试即成功,见「注意」)。
  4. 把 session 清单(标题 + 短 id)交给用户:这些是顶层 session,GUI 会话列表可见,直接点开交互,不再经主 session 转达。
  5. 可选:session/list 确认 running=true 即可撒手。不要长期轮询等结果——agent 没有完成回调,后续跟进是人在 GUI 里的事;如果主 session 需要统一收结果、编排、汇总,那是子代理/workflow 的场景,不是本 skill。

复用脚本(推荐:批量建 session 直接用)

references/scripts/ 下有按 dsh 版本分的便捷脚本(python3 标准库,零依赖),把「换票/探测/拿 workspaceId/create+rename+prompt/attach 验证」整条链路做完,返回 session 清单:

脚本适用备注
references/scripts/mk-sessions-modern.pydsh 0.1.2+自动读 ~/.dsh/dsh-owned.json 换票;可 --base/--token/--owned 覆盖;attach 验证不通过自动 cancel+archive 重建一次
references/scripts/mk-sessions-legacy.pydsh 0.1.1无认证;workspace.list 按 path 匹配 workspace,未注册则 workspace.create

两者输入一致:--tasks 指向 JSON 文件 [{"title": "...", "prompt": "任务说明..."}, ...],--repo <仓库绝对路径>(默认 cwd)。加 --dry-run 只解析与验证不建 session。跑完把输出清单交给用户即可。同目录的 test_mk_sessions_bridge.py 是 --tag 那条桥客户端的单测(在 references/scripts/ 里跑 python3 -m unittest test_mk_sessions_bridge -v)。

--tag <组名>(仅 modern 脚本):一次派生 = 侧栏一个标签组(Kimi WebBridge 那种一个任务一个组)。建完 session 后经扩展 loopback 桥归组:脚本读 ~/.dsh/dsh-one/bridge.json(扩展激活时写的 {port, token},127.0.0.1 随机端口 + 每进程随机 token)→ POST /tag,body {"action":"assign","group":<组名>,"sessionIds":[...]}(显式 action,无默认行为)。tags.json 的找/建组、颜色轮换、原子写全部由扩展进程完成(sessionsStore 的归组/读归属/清归属 → dshStateStore.updateTags),脚本不再直写文件——规避了 agent 进程写工作区外 ~/.dsh/dsh-one/ 被文件沙箱拦截的问题,--tag 不再弹审批。dsh-one 插件 watch 该目录,写完侧栏自动聚出这个组,不用重启窗口。不加 --tag 则完全不动 tags.json。

桥对 session 级标签归属提供完整增删改查(POST /tag,显式 action,无默认行为;域收敛到标签组/会话归属,只绑 127.0.0.1 + Bearer token,不接任意路径/内容):

actionbody语义返回
assign{action, sessionIds, group}按组名找/建组,把这些 session 归进去(单组语义覆盖旧组){ok, tag:{name,id}, sessionCount}
assign{action, sessionIds, tagId}归到指定已存在组(tagId 必须存在,未知拒绝)同上
get{action, sessionId}查该 session 当前在哪个组(无组返回 null){ok, group:{id,name,color,preset}|null}
unassign{action, sessionIds}把这些 session 移出组(组定义保留,不删组){ok, sessionCount}

派生惯例(用户约定 2026-09-07):批量派生默认带 --tag <批次/任务组名>——一次派发的所有 session 归进一个标签组(同一次派发 = 一组,不同任务用不同组名)。已由 #18 落地:--tag 走桥、无沙箱审批;桥记录缺失 / token 陈旧 / 扩展未加载时脚本报错指路(「DSH One 扩展未加载?先在 VS Code 里启动扩展(或 reload 窗口)」),不静默。多窗口注意:多个 VS Code 窗口各自起端点、写同一份 bridge.json,晚激活的窗口覆盖注册,早激活的失效——派生时以当前 bridge.json 为准,若刚 reload 过窗口(或扩展重启过)桥记录会重建,脚本照常生效。

python3 .agents/skills/session-fork-parallel/references/scripts/mk-sessions-modern.py \
  --tasks /tmp/tasks.json --repo /Users/me/workspace --tag "2026-09 批量改造"

注意

  • session/create 用 cwd 建的 session 归入「未分组」,不出现在 workspace 组的会话列表——用户会以为没建成功(实际在跑,只是列表看不到)。务必带 workspaceId 建(cwd 语义是「不注册 workspace 的裸会话」,只有建「未分组对话」时才用,且那也要预分配临时 cwd)。
  • attach 偶发失败:session/create 返回 ok、session 在跑,但 workspace.sessionIds 里没有它——表现是 GUI 里「一瞬间出现又消失」(前端推送过、快照重建时按注册表过滤掉)。判定:create 后立刻幂等查 sessionIds;失败则清掉重建,重试即成功。
  • authorization 状态:dsh-owned.json 的 token 与实例进程绑死——网关重启过(pid/port 变了)就重读该文件、用新 token 重新换票,旧 cookie 随之作废。
  • 新 session 继承网关默认配置(预设、沙箱、审批策略都由该实例自己的设置决定)。审批/提问帧会在 GUI 弹给用户,由人应答——这正是本方案的目的。
  • session 挂哪个 workspace 就干哪个 workspace 的活(cwd = workspace 路径)。
  • 并行改同一仓库的代码仍要按 AGENTS.md 走 worktree(worktree-dev-flow),RPC 只管会话,不隔离代码,多个 session 直接改同一目录会互相踩。
  • 测试/一次性 session 用完归档(先 session/cancel 再 workspace/archiveSession),别堆列表。
  • 网关探测/调用的完整 TS 封装见 src/server/dshRpc.ts(createSession / forkSession / renameSession / promptSession),curl 示例和它行为一致;历史窗口的 payload 构造在 src/pure/historyWindow.ts(legacy 的 session.history)。脚本化批量创建时小心来源脚本的顶层副作用:把来源文件当模块 import 会连它顶层的建 session 代码一起执行。

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.