Step-by-step tutorial · 控制面 · 跨窗口协作

分步教程:跨窗口协作 — 一个 agent,多个 KLayout

其它教程讲的是"在一个 KLayout 里画什么",这一篇讲的是控制面本身:一个 agent 可以同时连着多个 KLayout 窗口,每个窗口是一个独立端口(会话)。人和 agent 在这些窗口之间协作,靠的是 klink 插件工具栏上三个按钮——SEND(把你框选的东西变成 agent 能引用的持久记忆)、GFTGT(把 gdsfactory/klive 流量导向某个指定窗口)、以及跨会话 transfer(在窗口之间两阶段安全搬运几何)。这一篇每一张图都是真实 live 会话的窗口截图再叠标注,配的数字都是那次实际调用返回的。

这一篇和其它教程有一处不同:其它教程的插图是 view.screenshot 截的版图画布,而这一篇要讲的是工具栏和窗口本身,所以截的是整个 KLayout 应用窗口(含标题栏、工具栏)。演示全程在临时 tab(MW_SRC / MW_DST)里进行,不触碰任何已有的工作 tab。

前置条件

  • 至少两个 KLayout 窗口在运行,各自加载了 klink 插件、各占一个端口——默认从 8765 起,第二个 8766、第三个 8767,以此类推。每个窗口右上角工具栏会显示自己的端口标志 K876x
  • agent 一侧(Claude Code / Codex 等)通过 MCP 桥连接;调用时用 session 参数指定要操作哪个窗口,不指定则默认第一个。
  • 这一篇不需要任何工艺 PDK——演示用的是几个普通图层(10/0 金属、20/0 pad、1/0 边框、6/0 文字)上的示意几何。

1klink 工具栏解剖

每个加载了 klink 插件的 KLayout 窗口,都会在标准工具栏右侧多出一小组控件。这一小组就是人和 agent 协作的入口:

KLayout 窗口顶部工具栏,右侧的 K8765、SEND、GFTGT、REC 四个控件被红框圈出,各自连一条箭头指向说明标签
Stage 1 · klink 插件工具栏(红框为标注叠加,非 KLayout 原生):K8765 是端口标志,说明这个窗口就是会话 8765;SEND 把当前选区发给 agent 变成持久 id;GFTGT 把本窗口设成 gdsfactory/klive 目标(端口 8082);REC 录制成可回放脚本。窗口标题栏 [MW_SRC] 是当前显示的 cell 名。

后面三步分别把 SENDGFTGT 和跨窗口 transfer 走一遍——每一个既是人能点的按钮,也是 agent 能调的 typed 调用。

2一个 agent,多个窗口

klink 的会话模型很简单:每个 KLayout 窗口 = 一个端口 = 一个会话。一个 agent 通过 MCP 桥可以同时挂着多个窗口,靠端口区分该对谁下手。下面是同一个 agent 眼里的两个窗口——左边 K8765 放着源器件,右边 K8767 是一个空的着陆框:

两个 KLayout 窗口并排,左窗口标志 K8765 里有一个器件,右窗口标志 K8767 里只有一个空边框,两个端口标志各自被红圈圈出
Stage 2 · 两个 live KLayout 窗口并排(各自缩放拼合):左 K8765(源,含器件),右 K8767(目标,空框)。两个窗口靠工具栏 K876x 标志自我标识——agent 调用时传 session="8765""8767" 就能分别操作,互不干扰。

因为窗口靠端口而不是靠"当前焦点"来寻址,agent 可以在一个窗口里读、在另一个窗口里写,不需要来回切前台——这正是接下来 SEND / GFTGT / transfer 三步的基础。

3SEND:把选区变成 agent 的持久记忆

人在 KLayout 里框选一块东西、点一下工具栏的 SEND,这块选区就被记进 agent 侧的会话记忆,拿到一个稳定 id。之后你说"我刚发的那个"、"这块区域",agent 就能解析到确切的几何,而不用你再描述一遍坐标。agent 侧对应的 typed 调用是 selection.send_context

# 人的做法:框选 → 点工具栏 SEND
# agent 的等价做法(这次演示实际调用的):
client.selection_set_box("MW_SRC", [-12000, -2000, 12000, 2000])   # 选中整个器件(单位 dbu)
snd = client.call("selection.send_context", {"source": "tutorial_demo"})
# -> {"status": "sent", "count": 5, "send_seq": 5}
K8765 窗口,SEND 按钮被红框圈出并有箭头指向说明,画布里的器件被青色框圈出标为已选中的 5 个对象
Stage 3 · 在 K8765 里选中器件(青框,5 个对象已高亮)→ SEND。返回 status: sent · count 5 · send_seq 5——这块选区现在成了 agent 记忆里一条持久条目(形如 sel_xxxx),后续对话里"我刚发的那个"就指它。send_seq 单调递增,SEND 会先落盘 journal 再广播,所以就算此刻没有监听者也不会丢。

4GFTGT:把 gdsfactory / klive 流量导向某个窗口

多窗口时有个现实问题:你在 Python 里用 gdsfactory 生成一份版图,想让它落进哪一个 KLayout 窗口?klive 的兼容端口是固定的 8082GFTGT 按钮就是用来把这条流量指到当前窗口——点一下,本窗口就成了 gdsfactory/klive 的目标,之后经 8082 推来的几何都落在这里。agent 侧对应 session.mark_klive_target

# 人的做法:在想接收 gf 版图的窗口上点 GFTGT
# agent 的等价做法(这次演示实际调用的):在 K8767 上执行
client.call("session.mark_klive_target", {})
# -> {"ok": True, "klive_target_session": "klayout-8767", ...}
K8767 窗口,GFTGT 按钮被红框圈出并有箭头指向说明标签
Stage 4 · 在 K8767 上点 GFTGTklive_target_session 被设成 klayout-8767。此后任何经 klive 兼容端口 8082 推来的 gdsfactory 版图,都会落进这个窗口,而不是默认的第一个。想换一个窗口接收,就在那个窗口上再点一次 GFTGT。

5跨窗口 transfer:两阶段搬运几何

最后一步把几何真正从一个窗口搬到另一个。klink 的跨会话 transfer 是两阶段的——先在目标窗口 dry-run(只算不写、给你一份 review),确认无误再 commit 真正写入。这样搬运永远是"先验证后变更",失败不留半成品。用的是 MCP 工具 klink.transfer_prepare(读源窗选区 → 打包 → 在目标窗口 dry-run)+ klink.transfer_commit(真正落盘):

# 源窗口 K8765 里已经 SEND / 选中了那 5 个对象(见 Stage 3)
# 阶段一:prepare —— 读源选区、打包、在目标窗口 K8767 的 MW_DST 上 dry-run
prep = transfer_prepare(source_session="8765", target_session="8767",
                        target_cell="MW_DST", copy_mode="flat_selection")
# target_dry_run.inserted == 0   (只是演算,没写任何东西)

# 阶段二:commit —— 确认后真正写入
commit = transfer_commit(package_id=prep["package_id"])
# write.inserted == 5   (10/0 × 3 + 20/0 × 2)
K8767 窗口,只有一个空的粉色着陆框和 DST·K8767 文字,框里没有器件
Stage 5a · transfer 之前:K8767MW_DST 里只有一个空着陆框。
同一个 K8767 窗口,空框里现在多了从 K8765 搬过来的器件,器件被青色框圈出
Stage 5b · commit 之后:器件从 K8765 落进了 K8767flat-selection 两阶段:dry-run inserted 0 → commit inserted 510/0 × 3 + 20/0 × 2),逐层对得上 Stage 3 选中的那 5 个对象。源窗口 K8765 原封不动——transfer 是拷贝,不是剪切。

验证,不是截图

核心思路说的一样,截图是给人看的,真正的完成依据是结构化返回。这次演示三步各自的实际返回:

{
  "send":     {"status": "sent", "count": 5, "send_seq": 5},
  "gftgt":    {"ok": true, "klive_target_session": "klayout-8767"},
  "transfer": {
    "prepare_dry_run": {"cell": "MW_DST", "requested": 5, "inserted": 0, "by_layer": {"10/0": 3, "20/0": 2}},
    "commit":          {"cell": "MW_DST", "requested": 5, "inserted": 5, "by_layer": {"10/0": 3, "20/0": 2}}
  }
}

requestedcommit.inserted 都是 5by_layer 逐层一致——说明源窗口那 5 个对象一个不多一个不少地落进了目标窗口,且 dry-run 阶段 inserted=0 证明"先验证后变更"确实没有在确认前写任何东西。这些数字才是判断成没成的依据,图只是让人一眼看懂发生了什么。

下一步

把这套控制面用到你自己的多窗口流程里:一个窗口当"参考/源"、另一个当"工作区",用 SEND 把源里的选区喂给 agent,用 transfer 两阶段搬过去;用 gdsfactory 生成版图时,先在目标窗口点 GFTGT 定向。这些能力的深入字段说明见工作流。想看 agent 真正把一句话变成版图 / DRC / LVS 的实战对话,去教程页顶部的实战对话案例