Interactive workflows

Human, agent, and live KLayout in one loop

A set of interaction capabilities that let a human and an agent collaborate on one live KLayout: SEND selection memory, one agent driving many KLayout windows, cross-session transfer, an 8082 klive-compatible display, and recording a whole session into a replayable script. Each is an ordinary tool — nothing here needs setup beyond the plugin.

One catalogue, two faces

klink's control surface has two faces: the Python client KLinkClient and the same tools exposed over MCP. Both come from the live plugin's method registry — no hand-maintained list that can drift. Every tool's function, parameters and examples are in the MCP Tool Reference; this page is about the interactive loop that ties them together.

from klink import KLinkClient
with KLinkClient() as c:
    print([m["name"] for m in c.methods()["methods"]])

SEND selection memory — what "this area" means

The biggest friction in human + agent collaboration is "which area do you mean?". klink solves it with SEND: you select geometry in KLayout and click the plugin's SEND toolbar action (or an agent calls selection.send_context), and the selection is recorded as a stable id such as sel_0006 in session-scoped memory (.klink/sessions/<id>/interaction_context.jsonl). From then on "just sent", "this area", "that one" resolve to exact geometry, not a screenshot.

ToolUse
interaction.selection.recentRecent SENDs (default latest 5, by order, not age).
interaction.selection.latestThe latest SEND.
interaction.selection.getRead an exact record by id.
interaction.selection.labelName/describe an important selection.
interaction.contextLive selection + recent SEND memory together.

selection.get is the live current selection; interaction.* is the durable memory of what was explicitly SENT. Because minutes of layout work can pass between a SEND and the message referring to it, memory is resolved by order/count, not age. interaction.context returns the current selection and recent SENDs together so the agent can tell which one the user means.

Two deliberate design choices: (1) explicit SEND rather than passively watching every click — only a selection the user actively sends becomes a sel_000N, filtering out exploratory-click and mis-select noise; (2) memory lives outside the plugin (on the bridge) — the plugin holds only "right-now" zero-layer facts (selection.get + the event stream), while session-scoped memory, id assignment and language resolution live in the MCP bridge's interaction.*, persisted per session to disk JSONL.

SENDs are durable and never lost. The plugin journals every SEND with a monotonic sequence number before broadcasting it, so a SEND made while no agent is listening is not lost — the bridge catches up from the journal and deduplicates when it next connects.
# user: select A → SEND, select B → SEND
interaction.selection.recent limit=2      # → [sel_0007(B), sel_0006(A)]
interaction.selection.label id="sel_0006" label="probe pad"

One agent, many KLayouts

Each KLayout window binds a port (the first free one in 87658799) and registers as a session. One MCP bridge can drive all of them at once — they are equal peers, no port has a "working" or "LVS" role; you always say which session you mean.

klayout-8765real working layout
klayout-8766scratch / test
klayout-8767reference / diff
one MCP bridgeswitches target on demand
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"

In practice: when a real working layout and a demo/test window are open at once, label each window first so the agent refers to "scratch" instead of a port number and destructive ops never hit the working tab.

Cross-session transfer — two-phase, confirmation-safe

Moving geometry between windows is two-phase: prepare first, dry-run on the target, then commit to actually write. That catches "wrong target" and "wrong package" before anything lands in the target window.

Select sourceSEND or name a cell/selection.
prepareBuilds a package, dry-runs on target.
Inspect reportTarget, cell, layers, shape/instance counts.
commitOnly then write into the target.
klink.transfer_prepare source_session="klayout-8765" target_session="klayout-8766" \
    copy_mode="flat_selection" target_cell="IMPORTED"     # dry-run built in
klink.transfer_commit package_id="pkg_0001"

copy_mode: flat_selection (default) flat-copies visible geometry without hierarchy; shallow_instance moves instance references — and if the target window lacks the child cell, it blocks the commit instead of silently making an empty shell. Nothing lands until commit, so a wrong target is caught at dry-run. Plus layer_map remapping and translate_um offset during transfer.

Port 8082: klive-compatible display, a full klive replacement

Besides RPC on 8765, the plugin runs a klive-protocol-compatible display server on 127.0.0.1:8082. It is a drop-in replacement for the original klive: any gdsfactory / external script that hard-codes localhost:8082 — especially Component.show() — pushes layout into KLayout with no changes. You do not install klive separately.

CapabilityDetail
Protocol compatibleByte-compatible with klive 0.4.1 request/response ({"gds":…, "keep_position":…, "libraries":…, "technology":…, "lyrdb":…, "l2n":…}) and replies {"version":"0.4.1", "type":"open"|"reload", …}.
c.show() out of the boxgdsfactory's Component.show() pushes straight into KLayout, reloads if open, keep_position keeps your view.
More than GDSThe klive protocol also carries lyrdb (DRC markers) and l2n (netlist extraction), shown in the browser.
Multi-session forwardSingle window loads via pya directly; with many windows, 8082 is a fixed entrypoint forwarding to the registered target session — pick it with klink.session_set_klive_target / session.mark_klive_target.
Never disturbs RPCIf 8082 can't bind, only display is affected; klink RPC on 8765 is unaffected.
import gdsfactory as gf
c = gf.components.mzi()
c.show()          # via 127.0.0.1:8082 → klink's klive-compat server → shown in KLayout
klink.session_set_klive_target session_id="klayout-8766"   # scratch window receives c.show()
This is also the display channel for the photonics flow: after photonics.import_gf takes over a finished gdsfactory script, later photonics.reroute refreshes the display through the same path.

Record → replayable script

The recorder turns a whole working session — manual GUI edits and agent RPC edits alike — into a replayable script. It is a replay-script generator, not a literal call log: it records whatever rebuilds the final layout state, so a bulk RPC or an exec.python snippet may expand into per-object actions.

ToolUse
recorder.startBegin recording (optional output_path). Idempotent.
recorder.statusRecording? how many events/actions so far, output path. Safe anytime.
recorder.stopStop and write; returns stats + wrote. Idempotent.

Stopping writes two artifacts:

  • <name>.py — a KLinkClient replay script, annotated with # user command: lines naming each menu action.
  • <name>_pya.py — a standalone pya companion that runs inside KLayout with no klink installed.
recorder.status
recorder.start
# … typed RPC edits + manual GUI edits …
recorder.stop        # writes .py and _pya.py
Check recorder.status before starting your own recording so you never clobber one already in progress.

So a one-off manual fix becomes a script you can re-run and re-parameterize, and an agent can regenerate from it.

Profiles & tool discovery

--profile filters by intent (read/write/verify/escape/all) and domain (one of 11 tokens). Default read,write,verify,escape; klink.find_tools navigates narrower tools mid-task. Full detail on MCP Reference · Profiles.

python -m klink.mcp --profile read,write,verify,escape
klink.find_tools domain="routing_backends"
klink.status

Escape hatch

exec.python runs controlled pya for cases no typed RPC covers; it still schedules recorder + layout-diff detection. Prefer typed RPCs — they validate input, return structured errors with a next_action, and appear in the recorder as intent rather than opaque code. See MCP Reference · Escape hatch.