一份目录,两张脸
klink 的控制面有两张脸:Python client KLinkClient,以及 MCP server 暴露的同名工具。二者都来自 live plugin 的 method registry,不维护一份会漂移的手写清单。全部工具的功能、参数与示例见 MCP 工具参考;本页讲的是把这些工具串成人和 agent 一起协作的交互流程——每一节都是"是什么 → 人怎么点 → agent 调什么、真实返回什么",配真实截图,当页看完,不用再跳去别处找例子。
from klink import KLinkClient
with KLinkClient() as c:
print([m["name"] for m in c.methods()["methods"]])
SEND 选择记忆 —— "这块区域"到底指哪
人和 agent 协作最大的摩擦,是"我说的'这块'是哪块"。klink 用 SEND 解决它:你在 KLayout 里框选一块几何,点插件工具栏的 SEND 按钮,这次选择就被记成一个稳定 id(如 sel_0006),存进 session 级记忆 .klink/sessions/<id>/interaction_context.jsonl。从此"刚发的""这里""那一个"都能解析到精确几何,而不是截图。
# 人的做法:框选一块几何 → 点工具栏 SEND
# agent 的等价调用:
selection.send_context(source="tutorial_demo")
# → {"status": "sent", "count": 5, "send_seq": 5}
SEND。返回 status: sent · count 5 · send_seq 5——这块选区现在是 agent 记忆里一条持久条目(形如 sel_0005),之后"我刚发的那个"就指它。| 工具 | 用途 |
|---|---|
interaction.selection.recent | 最近若干次 SEND(默认最新 5,按顺序不按时间)。 |
interaction.selection.latest | 最新一次 SEND。 |
interaction.selection.get | 按 id 读精确记录。 |
interaction.selection.label | 给重要选择加名字/描述。 |
interaction.context | 同时返回 live 选择 + 最近 SEND 记忆。 |
关键区别:selection.get 是当前 live 选择;interaction.* 是明确 SEND 过的持久记忆。因为从 SEND 到那句引用它的话,中间可能隔了好几分钟的版图操作,所以记忆按序号/数量解析,而不是按时间。用 interaction.context 可以同时拿到"当前选择"和"最近 SEND",让 agent 判断用户指的是哪个。
两个刻意的设计取舍:一是用显式 SEND,而不是被动监听每次点选——只有用户主动发出的选择才记一条 sel_000N,过滤掉探索性点击和误选的噪音;二是记忆放在插件外部(桥侧),插件只保持"此刻"的零层事实(selection.get 和事件流),会话级记忆、id 分配、语言解析都在 MCP 桥的 interaction.*,按会话持久化到磁盘 JSONL。
send_seq 把每次 SEND 写进 journal,所以即使当时没有 agent 在监听也不会丢——返回 status: journaled_no_listener 同样算成功;桥下次连上时会从 journal 补齐并按 send_seq 去重。
第二个例子,同一个按钮:硅光端口。 选中 net link0 上的两个端口(自定义器件的 P0、MMI 的 o1)→ SEND,字段一样,只是数字跟着换:
selection.send_context(source="gf_tutorial")
# → {"status": "sent", "count": 2, "send_seq": 2}
SEND 把两个 link0 端口发给 agent,返回 count 2 · send_seq 2。左侧 cell 树能看到 klink_port.P0 (link0) 和 MMI 的端口。一个 agent 控制多个 KLayout
每个 KLayout 窗口启动时绑定一个端口(第一个空闲的 8765–8799)并注册为一个 session,工具栏上的 K876x 标志就是这个窗口的自我标识。一个 MCP 桥可以同时驱动全部这些 session——它们是平等 peer,没有"工作端口"或"LVS 端口"的特殊角色,agent 靠显式的 session 参数寻址,不靠"当前前台窗口"。
K8765 是端口标志,说明这个窗口是会话 8765;SEND、GFTGT、REC(录制)是人和 agent 共用的三个入口,后两个分别在下面的"跨会话搬运/klive"节和"录制"节里讲。因为窗口靠端口而不是靠"当前焦点"寻址,agent 可以在一个窗口里读、在另一个窗口里写,不用来回切前台:
K8765(源,含器件),右 K8767(目标,空框)。agent 调用时传 session="8765" 或 "8767" 就能分别操作,互不干扰。| 工具 | 用途 |
|---|---|
klink.session_list | 枚举正在运行的 session。 |
klink.session_label | 给某个 session 打人类 label / 别名。 |
klink.session_resolve | 把 label / 别名 / active cell / top cell 解析成 session id。 |
klink.session_use | 把桥的主 RPC 目标切到某个 session。 |
klink.session_status | 看某个 session 的记录。 |
klink.session_list
klink.session_label session_id="klayout-8766" label="scratch" aliases=["test"]
klink.session_resolve query="scratch" # → klayout-8766
klink.session_use session_id="klayout-8766"
实践建议:同时开着真实工作版图和 demo/测试窗口时,先给每个窗口打 label,agent 就能用"scratch"这种名字而不是端口号来指代,破坏性操作也不会误伤工作 tab。
跨会话搬运 —— 两阶段、确认安全
在窗口之间搬几何是两阶段的:先 prepare,对目标 session dry-run 通过后,再 commit 才真正写入。这样在写进目标窗口之前就能发现"搬错窗口""包内容不对"。
# 源窗口 K8765 里已经 SEND / 选中了 5 个对象(见上一节)
# 阶段一:prepare —— 读源选区、打包、在目标窗口 K8767 的 MW_DST 上 dry-run
klink.transfer_prepare source_session="8765" target_session="8767" \
target_cell="MW_DST" copy_mode="flat_selection"
# → prepare_dry_run: {"cell":"MW_DST","requested":5,"inserted":0,"by_layer":{"10/0":3,"20/0":2}}
# 阶段二:commit —— 确认后真正写入
klink.transfer_commit package_id="pkg_0001"
# → commit: {"cell":"MW_DST","requested":5,"inserted":5,"by_layer":{"10/0":3,"20/0":2}}
K8767 的 MW_DST 里只有一个空着陆框。
K8765 落进了 K8767。requested 与 commit.inserted 都是 5、by_layer 逐层一致(10/0 × 3 + 20/0 × 2),跟上一节 SEND 选中的那 5 个对象对得上;源窗口 K8765 原封不动——transfer 是拷贝,不是剪切。copy_mode:flat_selection(默认)只拍平复制可见几何、不带层级;shallow_instance 只搬实例引用——若目标窗口缺对应子 cell,它会阻断提交而不是静默造一个空壳。目标窗口写入前什么都不会真正写进去,所以搬错目标会在 dry-run 阶段被发现。搬运中还可用 layer_map 重映射图层、translate_um 平移。
想看工具栏解剖 + 两窗口 + SEND + GFTGT + transfer 连成一整套的完整实战,见分步教程:跨窗口协作。
8082 端口:klive 兼容显示,完全替代 klive
插件除了 8765 的 RPC,还在 127.0.0.1:8082 起了一个 klive 协议兼容的显示服务。它是原版 klive 的直接替代(drop-in replacement):任何硬编码 localhost:8082 的 gdsfactory / 外部脚本——尤其是 Component.show()——无需改动就能把版图推进 KLayout。你不需要再单独装 klive。
多窗口时有个现实问题:Component.show() 该落进哪一个 KLayout 窗口?klive 兼容端口是固定的 8082,工具栏的 GFTGT 按钮就是用来把这条流量指到当前窗口——人怎么点:在想接收 gdsfactory 版图的窗口上点一下 GFTGT;agent 等价调用是 session.mark_klive_target:
# 人的做法:在想接收 gf 版图的窗口上点 GFTGT
# agent 的等价调用:在 K8767 上执行
session.mark_klive_target()
# → {"ok": true, "klive_target_session": "klayout-8767"}
K8767 上点 GFTGT:klive_target_session 被设成 klayout-8767。此后经 8082 推来的 gdsfactory 版图都会落进这个窗口,而不是默认的第一个;想换一个窗口接收,就在那个窗口上再点一次 GFTGT。| 能力 | 说明 |
|---|---|
| 协议兼容 | 逐字节兼容 klive 0.4.1 的请求/响应({"gds":…, "keep_position":…, "libraries":…, "technology":…, "lyrdb":…, "l2n":…}),并回 {"version":"0.4.1", "type":"open"|"reload", …}。 |
c.show() 开箱即用 | gdsfactory 的 Component.show() 直接把组件推进 KLayout,已开则 reload、可 keep_position 保住视角。 |
| 不止 GDS | 随 klive 协议一并推送 lyrdb(DRC 标记)和 l2n(网表抽取),一并在浏览器里显示。 |
| 多会话转发 | 单窗口时直接走 pya 载入;多窗口时 8082 是固定入口,把请求转发给注册的目标 session——用 klink.session_set_klive_target / session.mark_klive_target(就是 GFTGT 按钮的等价调用)选定哪个 KLayout 接收。 |
| 不干扰 RPC | 即使 8082 被占用起不来,也只影响显示;8765 的 klink RPC 完全不受影响。 |
import gdsfactory as gf
c = gf.components.mzi()
c.show() # 走 127.0.0.1:8082 → klink 的 klive 兼容服务 → 显示在 GFTGT 标记的窗口
录制 → 可回放脚本
是什么:recorder 把一整段工作会话——手工 GUI 编辑和 agent 的 RPC 编辑一视同仁——变成可回放脚本。它不是逐条 RPC 日志,而是 replay-script 生成器:记录足以重建最终 layout 状态的动作,所以一个批量 RPC 或一段 exec.python 可能会展开成逐对象的动作。
人怎么点:工具栏的 REC 按钮开始/停止录制,和下面的 recorder.* 调用是同一件事的两个入口。
| 工具 | 用途 |
|---|---|
recorder.start | 开始录制(可选 output_path 指定输出)。幂等。 |
recorder.status | 是否在录、已记录多少事件/动作、输出路径。随时可调。 |
recorder.stop | 停止并写脚本,返回统计 + wrote。幂等。 |
调用什么、拿到什么:停止时写出两个产物——
<name>.py—— 基于KLinkClient的回放脚本,并用# user command:注释标出每步对应的菜单动作。<name>_pya.py—— 独立pya版,可在没装 klink 的 KLayout 里直接跑。
recorder.status # 先确认没在录别人的
recorder.start
# …做一些 typed RPC 编辑 + 手工 GUI 编辑…
recorder.stop # 产出 .py 和 _pya.py
recorder.status,避免覆盖一个正在进行的录制。这样一来,原本一次性的手工修改就变成了能重复运行、能改参数的脚本,agent 也能在此基础上重新生成。
Profiles 与工具发现
是什么:--profile 同时按意图(read/write/verify/escape/all)和领域(11 个 token 之一)过滤 MCP 工具列表,避免一次性把几百个工具全塞给 agent。
怎么用:启动 MCP 桥时用 --profile 指定;工具列表定下来之后,任务中途还可以调 klink.find_tools 在这份列表里按 domain 或关键词导航到更窄的一组。
python -m klink.mcp --profile read,write,verify,escape # 默认
python -m klink.mcp --profile read,device_photonics
klink.find_tools domain="routing_backends"
klink.status # 解释器 / 能力 / 连接状态
完整说明见 MCP 参考 · Profiles。
Escape hatch
是什么:exec.python 可在 KLayout 内跑受控 pya 片段,适合 typed RPC 暂未覆盖的边角场景。
怎么用、要注意什么:调用方式和普通 RPC 一样,但优先级应低于 typed RPC——后者有输入验证、结构化错误、next_action,且在 recorder 里表现为"意图"而非不透明代码。exec.python 仍会触发 recorder + layout-diff 检测,所以事后能看出改了什么,但事前拿不到 typed RPC 那样的输入校验。详见 MCP 参考 · Escape hatch。