MCP / RPC reference

Complete MCP tool reference

klink's control plane is one tool catalogue with two faces: a typed Python client (KLinkClient) and the same methods exposed as MCP tools. Both are generated from the live plugin's method registry — there is no hand-maintained list that can drift. This page lists all 127 plugin RPCs + 68 MCP local tools (195 total) across 15 domains: function, parameters, examples.

Overview & conventions

Tool names are stable namespace.verb. Each maps to exactly one domain; that domain token is both a klink.find_tools domain=<token> navigation key and a --profile <token> filter.

Reading conventions

  • name* in a parameter list means required.
  • Coordinates: _um = microns (most natural); _dbu = integer database units. micron = dbu × layout.dbu, where dbu comes from layout.info.
  • Layers: layer index int, "L/D" string (e.g. "1/0"), or {layer, datatype} object.
  • The Kind column tells you where a tool runs: rpc executes inside the KLayout plugin; local executes in the MCP process. Other badges: read write verify escape mutates (undoable) long (separate timeout).
Batch first. Never author generated layout one RPC per object — the loop pays TCP + JSON + transaction + GUI bookkeeping per call and is often hundreds of times slower. Use shape.insert_boxes / shape.insert_many / instance.insert_many / instance.insert_pcell_many. Singleton inserts are for debugging one object.

Discover tools live

The catalogue is queried live, never memorized. It is generated from the live plugin's method registry — there is no hand-maintained list that can drift.

from klink import KLinkClient
with KLinkClient() as c:
    print([m["name"] for m in c.methods()["methods"]])   # meta.methods
klink.find_tools                          # no args → domain index
klink.find_tools domain="routing_backends"  # that area's tools + usage
klink.find_tools query="lvs route"          # ranked matches across all tools

klink.guide reports what is open, the on-disk intent state (declared nets / LVS reports / spec files), and the literal next call for each available intention.

Profiles

--profile filters along two orthogonal axes — intent and domain. Default is read,write,verify,escape.

IntentExposes
readRead-only: layout.info, cell.list, shape.query, view.*, pcell.*, recorder.
writeEditing: shape.insert_*, cell.create, layer.ensure, instance.insert*, edit.undo.
verifyChecks: drc.run, lvs.run.
escapeEscape hatches: exec.python, exec.reset, events.*.
allEverything, no filtering.
python -m klink.mcp --profile read,write,verify,escape   # default
python -m klink.mcp --profile read,device_photonics      # mix both axes
python -m klink.mcp --profile routing_backends           # one domain only

All local tools are always included under any intent profile; klink.find_tools / klink.status / klink.reconnect are always on. Legacy aliases: basic→read, draw→write, advanced→escape, drc→verify.

1 · Connection, self-check, discovery & view connection_and_view · 30 tools

klink.find_tools domain="connection_and_view"

Start here when unsure. klink.status reports connection, active session, interpreter and optional capabilities; klink.guide reports what is open + on-disk intent state + the literal next call; klink.find_tools navigates the rest; klink.reconnect recovers a dropped link. This domain also carries the MCP-side session registry helpers (klink.session_*, klink.transfer_*) — klink drives many KLayout sessions from one MCP bridge; sessions are equal peers, pass the one you mean explicitly. View tools are read-mostly navigation: a freshly created cell is invisible until view.show_cell; view.new_tab opens a disposable scratch tab; view.show_25d opens the native 2.5D viewer from a display list; view.hier_levels raises displayed hierarchy depth if child instances render as name-label boxes. Screenshots (view.screenshot) are a user-requested artifact only — never an agent verification step; prefer geometry queries. Destructive here: view.close_tab — disposable test tabs only.

ToolKindParamsFunction
hellorpcclient, protocolIntroduce the client and receive server info + capability list. Recommended as the first call on every new connection. Not required (the server works without it), but useful for version-gating features.
klink.find_toolslocaldomain, queryDiscover klink tools by domain or keyword. Call with NO args for the domain index; domain=<token> for that domain's tools + detailed usage; query=<keywords> to rank matching tools (optionally within one domain). tools/list already contains every tool and they are all callable — this is for NAVIGATION and on-demand detailed usage, not a gate. Use it whenever you are unsure which tool to use.
klink.guidelocalSTART HERE if you do not know this stack: reports what is open, what intent state already exists on disk (declared nets, LVS reports, spec files), the literal call for each available user intention, and a suggested next action. Call it whenever you are unsure what to do next — the workflow lives in tool results, not in your memory.
klink.reconnectlocalClose any stale klink client and try to reconnect to KLayout.
klink.session_labellocalaliases, description, label*, session_id*Attach a human label and aliases to a registered KLayout session.
klink.session_listlocalinclude_staleList discoverable KLayout/klink sessions from the local session registry.
klink.session_resolvelocalquery*Resolve a session id, human label, alias, active cell, or top cell to a KLayout session.
klink.session_set_klive_targetlocalsession_id*Set the registered KLayout session used by the klive-compatible 8082 entrypoint.
klink.session_statuslocalinclude_stale, session_idReturn one registered KLayout session record, defaulting to the active MCP session.
klink.session_uselocalsession_id*Switch this MCP bridge's active KLayout RPC target to a registered session.
klink.statuslocalReturn MCP bridge connection status and last klink connection error.
klink.transfer_commitlocaldry_run, package_id*Commit a package previously created by klink.transfer_prepare.
klink.transfer_preparelocalcopy_mode, layer_map, source_session*, target_cell, target_session*, translate_umPrepare a flat-selection transfer package between two registered KLayout sessions and dry-run it on the target.
meta.debug_signalsrpcfireReturn SignalHub diagnostic log and optionally fire a synthetic event on a channel to verify the subscribe->emit->deliver path. Pass {'fire': 'selection_changed'} to test event delivery.
meta.methodsrpcReturn the full RPC method catalogue with descriptions and JSON schemas. Designed to be directly consumable by LLM function-calling layers (tool definitions, MCP, OpenAI/Anthropic tools, etc.).
meta.pingrpcLiveness probe. Echoes the supplied params back and includes the server-side trace id. Use this to measure round-trip time or check a connection is healthy.
view.activate_tabrpcindex*Switch the current KLayout tab (view) by index from view.list_tabs. After switching, all single-layout RPCs (layout.info, cell.list, shape.query, ...) operate on that tab's layout.
view.close_tabrpcview_indexClose a layout view tab by index. Closes the active tab if no index is specified.
view.hier_levelsrpcmax, minRead or set the view's displayed hierarchy depth (pya min_hier_levels/max_hier_levels). With no params, reports the current levels. Pass min and/or max to change them; the view is refreshed via update_content() so following screenshots see the change. If dense child-cell instances render as name-label boxes instead of geometry, max is too shallow - raise it (the KLayout default is 1).
view.highlightrpcboxes_um, circles_um, clear, color, expire_s, halo, line_width, polygons_umDraw transient highlight markers on the current view to point the user at locations — view-layer overlays that do NOT touch the layout, the selection, or undo. Pass boxes_um, polygons_um and/or circles_um (microns, top-cell coordinates; circles_um takes the SAME {center, radius, start/end_angle_deg} specs as cell.fill_region, so a highlighted circle/sector is exactly the fillable one — never hand-approximate an arc). Style: color ('#RRGGBB', default red), line_width px, halo. By default REPLACES previous highlights (clear: false accumulates, e.g. different colors per category). expire_s auto-removes this batch after N seconds. Use this instead of selection.set_box when you only want to POINT — set_box clobbers the user's real selection. view.highlight_clear removes everything.
view.highlight_clearrpcRemove ALL klink highlight markers (from view.highlight) immediately. Never touches the layout, selection, or undo.
view.list_tabsrpcList all layout tabs (views) in this KLayout window: index, title, file path, active cell, and which tab is current. Use together with view.activate_tab to inspect non-active layouts through the ordinary single-layout RPCs.
view.new_tabrpccell_name, dbuOpen a new, empty layout tab with a fresh top cell and make it the current tab (pya MainWindow.create_layout mode 1). Returns index (the new tab) and previous_current_index so scratch-tab workflows can restore the user's tab afterwards via view.activate_tab. previous_current_index is -1 when there was no tab open at all before this call -- in that case there is nothing to restore, so skip the view.activate_tab restore step. Use this instead of exec.python.
view.screenshotrpcbbox_dbu, bbox_um, height_px, mode, path, width_pxRender a PNG screenshot of the active layout view. Two modes: 'base64' embeds the PNG in the response as a data URL (great for LLMs with vision support); 'path' saves to disk and returns the absolute path (use for large images). Width/height are in pixels; defaults match what the user sees on screen. You can also clip to a region via bbox_um=[x1,y1,x2,y2] in microns (preferred) or bbox_dbu in integer database units; the clip box is rendered exactly (no viewport aspect-ratio expansion), so match width_px/height_px to its aspect ratio for a linear micron-to-pixel mapping.
view.show_25drpccell, displays*, generatorOpen KLayout's native 2.5d (extruded 3D) viewer and feed it a display list: one entry per material with a layer, a z range in microns, and optional name/colors. Layers are read from a cell of the active layout (default: the current cell). klink ships no z heights - thickness/elevation are process facts the caller owns (derive the list with klink.stack25d.stack_displays from your StackSpec + z table). Requires a KLayout build with OpenGL.
view.show_cellrpccell*, zoom_fitSet the active cellview's displayed (top) cell. KLayout shows a single cell at a time per view; if you just created a cell with cell.create and want to actually see its contents, you must call this (or insert an instance of it into the current top). Also zoom-fits by default. Returns the cell that is now being shown.
view.show_lvsdbrpckind, path*Load a saved KLayout LVS/netlist database from disk into the current view's Netlist Browser and SHOW it, so you can cross-probe layout<->netlist interactively (click a net/device -> highlight in layout). Like DRC's marker browser, but for LVS/connectivity. kind='lvs' (default) reads a .lvsdb (LayoutVsSchematic, with the matched/unmatched cross-reference); kind='l2n' reads a .l2n (extraction only). Pair with structdevice.lvs_check mode='lvsdb', which writes the .lvsdb and returns its path. Read-only (loads a file; does not modify the layout).
view.viewportrpcReport the current viewport: visible bbox in microns (bbox_um, klink's standard unit) and in integer database units (bbox_dbu, converted via the active layout's dbu), pixel size of the view widget, and cellview index. Call this to align an external coordinate with what the user sees.
view.zoom_boxrpcbbox_dbu, bbox_umZoom the viewport to show exactly the given bbox. Provide bbox_um=[x1,y1,x2,y2] in microns (preferred - klink's standard user-facing unit, matching boxes_um/points_um/center_um used elsewhere) or bbox_dbu=[x1,y1,x2,y2] in integer database units (converted using the active layout's dbu). Provide exactly one of the two.
view.zoom_fitrpcFit the entire layout into the viewport (equivalent to GUI's 'Zoom Fit').

Example.

klink.status
klink.guide
view.list_tabs

# Python client equivalent
from klink import KLinkClient
with KLinkClient() as c:
    print(c.hello(client="my-script"))
    print(c.layout_info(verbosity="summary"))
    print(c.ping(nonce=42))

# session registry + two-phase transfer (prepare + dry-run, then commit)
klink.session_list
klink.session_label session_id="klayout-8766" label="scratch" aliases=["test"]
klink.session_resolve query="scratch"
klink.transfer_prepare source_session="klayout-8765" target_session="klayout-8766" copy_mode="flat_selection"
klink.transfer_commit package_id="pkg_0001"

2 · Multi-session registry & cross-session transfer (plugin side) multi_session_transfer · 7 tools

klink.find_tools domain="multi_session_transfer"

The plugin-side half of session/transfer: session.label_set and session.mark_klive_target manage the shared registry directly from a KLayout window; transfer.pending_set/status/clear and transfer.paste_pending hold and apply a reviewed flat-selection package in this window; transfer.import_cell_tree_package imports a cell tree from a GDS/OAS package via native Cell.copy_tree. The MCP-side orchestration (klink.session_*, klink.transfer_prepare/commit) lives in connection_and_view — start there for the two-phase, confirmation-safe transfer flow.

ToolKindParamsFunction
session.label_setrpcaliases, description, label*, session_id*Set a human label and aliases for a registered KLayout session in the shared registry.
session.mark_klive_targetrpcMark this KLayout window as the klive/gdsfactory-compatible 8082 target.
transfer.import_cell_tree_packagerpcdry_run, path*, source_cell*Import one cell tree from a GDS/OAS package into this KLayout window using KLayout's native Cell.copy_tree behavior. Name conflicts are resolved by KLayout with '$N' suffixes.
transfer.paste_pendingrpcclear_after, dry_runPaste the currently pending flat-selection transfer package into this KLayout window. The package must already contain final target layers and coordinates.
transfer.pending_clearrpcClear this KLayout window's pending transfer package without writing layout geometry.
transfer.pending_setrpcpackage*Store an already-reviewed flat-selection transfer package in this KLayout window.
transfer.pending_statusrpcReturn the pending transfer package status for this KLayout window.

3 · Geometry & cell-structure authoring geometry_authoring · 51 tools

klink.find_tools domain="geometry_authoring"

The core drawing surface. Read first: layout.info, cell.list/cell.tree, layer.list/layer.display_list, shape.query, instance.query, pcell.*, library.list. Author with BATCH RPCs for anything generated — never one RPC per object. layer.ensure before drawing. edit.undo/redo/status wrap edits in transactions. Beyond basic authoring this domain also carries: geometry.boolean/cell_xor/density (diff and coverage checks), cell.fill_region (dummy fill / test-structure tiling), cell.flatten, layer.load_lyp/save_lyp/set_style/set_visible (view styling), library.list/refresh/register_file, pcell.convert_to_static, shape.change_layer/transform, instance.transform. Destructive: layout.clear, cell.delete — disposable/test cells only; do NOT touch the user's working tab/layout UNLESS the user explicitly instructs it.

Read

ToolKindParamsFunction
layout.inforpcverbositySnapshot of the currently active layout view: number of open views, active cellview index, top cell name, source file path, database unit, the full top-cell list and the registered layer/datatype pairs. Safe to call often - this is the method an LLM agent should use to refresh its world view.
cell.listrpclimit, name_prefix, offset, top_only, with_bboxFlat, paginated list of cells in the active layout. Use this to discover what cells exist. For hierarchy use cell.tree instead. Filtering: 'name_prefix' is a case-sensitive prefix match; 'top_only' restricts to top cells. Pagination: 'offset' + 'limit'.
cell.treerpcmax_depth, max_nodes, rootHierarchical cell tree rooted at a given cell (default: first top cell). Bounded by max_depth and max_nodes. Use this to understand how a layout is composed; the 'instances' count on each node tells you how many times its parent instances it.
layer.listrpcList all layers currently defined in the active layout. Each entry has layer_index (the runtime handle used by other RPCs), layer/datatype (GDS numbers), and optional name. Also returns dbu_um so the client can convert between microns and database units.
layer.display_listrpcList the current view's layer DISPLAY entries: layer/datatype, visible, fill_color/frame_color ('#RRGGBB'), dither_pattern index and name. This is the view-side counterpart of layer.list (which lists data layers).
shape.queryrpcbbox_dbu, cell*, kinds, layers, limitRead shapes from ONE cell (no recursion) as JSON. Strongly recommended: narrow with 'layers' and 'bbox_dbu', and respect pagination via 'limit' (default 500, max 5000). Coordinates are in database units (dbu); multiply by layout dbu (from layout.info) to get microns. The 'truncated' flag means more shapes matched than 'limit' - call again with a tighter bbox.
instance.queryrpcbbox_dbu, bbox_um, child, limit, parent*List direct child instances in a parent cell. Returns child name, bbox, transform, optional array, PCell metadata, and the child cell's direct shape counts by layer. This is a read primitive for clients that need to inspect or clean up instance/PCell geometry without using exec.python.
pcell.librariesrpcList available KLayout PCell libraries (Basic is always there; PDKs register their own). Use pcell.list next to enumerate PCells in a specific library.
pcell.listrpclibraryList all PCells in library (default 'Basic'). Returned entries are just names - call pcell.info for parameter details on a specific one.
pcell.inforpclibrary, pcell*Describe the parameters of one PCell so the caller can build a valid params dict for instance.insert_pcell. Each entry reports {name, type, default, description, choices?}. 'type' is one of: int, double, string, boolean, layer, list, shape, none.
library.listrpcList all libraries registered in this KLayout process (Basic, salt/PDK libraries, runtime-registered device or file libraries). Each entry has name, id, description, technologies, cell_count, pcell_count and up to 20 top_cells (PCell-only libraries like Basic legitimately have cell_count 0); libraries created by library.register_file also carry source_file. Use pcell.list/pcell.info to inspect PCells inside a library.

Write · cells & layers

ToolKindParamsFunction
cell.createrpcnameCreate a new cell in the active layout. If name is given and already taken, KLayout appends '$1', '$2', ... to keep names unique (the effective name is returned). If omitted, an anonymous auto-named cell ('$N') is created. Idempotent is NOT guaranteed - each call creates a fresh cell.
cell.renamerpcallow_suffix, cell*, new_name*Rename a cell. Fails if new_name is already taken (KLayout would otherwise silently append '$1'; we surface that as an error so callers can decide). Pass allow_suffix=true to opt into KLayout's auto-suffix behaviour.
cell.deleterpccell*, recursiveDelete a cell from the active layout. Use recursive=true to also delete child cells that become orphaned (no longer referenced by any other cell). Default is to delete only this cell; remaining instances are turned into 'ghost' references.
cell.flattenrpccell*, dry_run, levels, pruneFlatten a cell's hierarchy: child instances are dissolved into plain shapes inside the cell. levels (default -1 = all levels), prune (default true: child cells left orphaned are deleted), dry_run (default false: only report what would happen). DESTRUCTIVE for the cell's hierarchy — the flatten itself is one undo step; use dry_run first when unsure.
cell.fill_regionrpcboxes_um, cell*, circles_um, column_step_um, exclude_layers, exclude_margin_um, fc_bbox_um, fill_cell*, origin_um, polygons_um, region_layers, row_step_umTile a fill cell across a region (KLayout's built-in Fill Utility, pya.Cell#fill_region): dummy fill, device arrays, test-structure tiling. Region = union of boxes_um, polygons_um, circles_um (full circles or angular sectors), and region_layers (fill wherever the target cell has geometry on those layers — e.g. a hand-drawn blob on a scratch layer), minus geometry of exclude_layers (grown by exclude_margin_um). Only tiles that fit ENTIRELY inside the region are placed, so curved boundaries leave an unfilled rim — reported in remaining_area_um2. The fill footprint defaults to the fill cell's bbox (fc_bbox_um overrides); raster steps default to that footprint (row_step_um/column_step_um override, e.g. for gaps between tiles). Single pass, one raster — check remaining_area_um2 in the result for uncovered leftovers. Placed instances are one undo step (Ctrl+Z reverts the whole fill).
layer.ensurerpcdatatype, layer*, nameEnsure a GDS layer (layer/datatype) exists in the active layout. If missing, it is created inside an undo-able transaction. Returns the layer_index handle and whether the layer was just created. Safe to call repeatedly - it's a pure upsert.
layer.set_stylerpccolor, dither_pattern, fill_color, frame_color, layer*, line_widthStyle one layer's display in the current view: color sets fill AND frame ('#RRGGBB'); or set fill_color / frame_color separately; dither_pattern (KLayout stipple index, 0..) and line_width optionally. Display only — layout data untouched.
layer.set_visiblerpcexclusive, layers*, visibleShow or hide layers in the current view (display only — layout data untouched, no undo involved). layers = ['L/D', ...]; visible (default true); exclusive: true = show ONLY the listed layers and hide every other one (the 'let me see just 1/0 and 3/0' debugging call). Unknown layers are reported, not silently ignored.
layer.load_lyprpcpath*Load a KLayout .lyp layer-properties file into the current view (colors/stipples/visibility for the whole stack in one call).
layer.save_lyprpcpath*Save the current view's layer properties (colors/stipples/visibility) to a KLayout .lyp file.
library.refreshrpclibraryRe-evaluate library content in every layout that uses it (official pya.Library refresh). Pass library to refresh one library by name, or omit it to refresh ALL registered libraries. Call this after a library changed (e.g. a PCell was re-registered or a salt library was updated) instead of asking the user to press the GUI refresh button. Read-only layouts are left untouched by KLayout itself.
library.register_filerpcdescription, name, path*, technologyRegister a layout file (GDS/OASIS/anything KLayout reads) as a runtime library, so its cells become placeable by name via instance.insert / instance.insert_many with library set. name defaults to the file stem. The library lives for this KLayout session only (re-register after restart). Refuses a name that is already registered — pick a new name; there is no in-session replace.

Write · shapes (batch-first)

ToolKindParamsFunction
shape.insert_boxesrpcboxes_dbu, boxes_um, cell*, datatype, dry_run, layer, layer_indexInsert many axis-aligned rectangles into one cell/layer in a single RPC and one undo transaction. Provide boxes_um or boxes_dbu as a list of [x1,y1,x2,y2] boxes. Use this instead of many shape.insert_box calls for large generated layouts.
shape.insert_manyrpccell*, dry_run, items*Insert a mixed list of shapes into one cell in a single RPC and one undo transaction. Each item has kind/type = box, polygon, path, or text, its own layer selector, and the same geometry fields used by the corresponding single-shape RPC.
shape.insert_boxrpcbbox_dbu, bbox_um, cell*, datatype, layer, layer_indexInsert an axis-aligned rectangle into cell on the given layer. Provide the box as bbox_um=[x1,y1,x2,y2] (microns, most natural) or bbox_dbu (integer database units). The edit is wrapped in a single-step transaction so Ctrl+Z undoes it.
shape.insert_polygonrpccell*, datatype, layer, layer_index, points_dbu, points_umInsert a polygon (hull only; no holes yet) into cell. Points are given as points_um=[[x,y],...] (microns) or points_dbu (dbu). At least 3 points required. The polygon is closed automatically.
shape.insert_pathrpcbegin_ext_dbu, begin_ext_um, cell*, datatype, end_ext_dbu, end_ext_um, layer, layer_index, points_dbu, points_um, round_ends, width_dbu, width_umInsert a path (center line with width) into cell. Points via points_um/points_dbu, width via width_um/width_dbu. Optional begin_ext/end_ext extensions (defaults to width/2 - flush) and round_ends for rounded caps.
shape.insert_textrpccell*, datatype, layer, layer_index, position_dbu, position_um, size_dbu, size_um, string*Insert a text label into cell. Position via position_um/position_dbu. Optional size_um maps to KLayout text size (pya.Text.size). Labels are non-geometric annotations - they snap to integer coordinates but do not produce mask geometry.
shape.deleterpcall_layers, bbox_dbu, bbox_um, cell*, datatype, dry_run, kinds, layer, layer_index, layers, limitDelete shapes from ONE cell that match a declarative selector. Select by layer (layer_index / layer+datatype / layers=[...] / all_layers=true), optional bbox_dbu|bbox_um (touching), and optional kinds=['boxes','polygons','paths','texts']. Use dry_run=true to preview the count before actually deleting. The whole erase runs inside a single transaction so Ctrl+Z / edit.undo rolls it all back at once. Returns {deleted, per_layer, truncated}. If nothing matches, deleted=0 and the call succeeds without error.
shape.change_layerrpcbbox_um, cell*, from_layer*, to_layer*Move shapes from one layer to another within a cell (optionally only those touching bbox_um). The one-intention version of 'redraw this on the right layer'. One undo step; zero matches is an error.
shape.transformrpcbbox_um, cell*, layers, limit, mirror, move_um, rotationMove/rotate/mirror EXISTING shapes in place (no delete+redraw). Filter: layers (['L/D', ...]) and/or bbox_um (touching) — at least one is required so a bare call cannot silently rewrite the whole cell. Action: move_um [dx, dy], rotation (degrees CCW), mirror. Rotation/mirror happen about the matched set's combined bbox center (GUI-like), then the move applies. One undo step; zero matches is an error, not a silent no-op.

Write · instances & PCells (batch-first)

ToolKindParamsFunction
instance.insert_manyrpcdry_run, items*, parent*Insert many existing child-cell instances into one parent cell in a single RPC and one undo transaction. Each item has child plus the same transform/array fields accepted by instance.insert.
instance.insert_pcell_manyrpcdry_run, items*, parent*Insert many PCell instances into one parent cell in a single RPC and one undo transaction. Each item accepts library, pcell, params, transform fields, and optional array, matching instance.insert_pcell.
instance.insertrpcarray, child*, klink_id, library, magnification, mirror, parent*, position_dbu, position_um, rotationPlace child inside parent. Position is in microns (position_um) or dbu (position_dbu); rotation is degrees (0/90/180/270 stay as integer transforms, else promoted to a complex transform with magnification). Optional array creates a grid. Two array shapes are accepted: orthogonal {rows, cols, pitch_x_um/dbu, pitch_y_um/dbu} and general {na, nb, a_dbu|a_um:[x,y], b_dbu|b_um:[x,y]} (the general form survives rotated/sheared arrays). Without an array you get a single instance. Cyclic hierarchy (child already contains parent) is rejected.
instance.insert_pcellrpcarray, klink_id, library, magnification, mirror, params, parent*, pcell*, position_dbu, position_um, rotationBuild a PCell variant cell from a library (e.g. Basic.CIRCLE, Basic.TEXT) using the supplied params dict, then insert one instance of it in parent. Call pcell.info(library, pcell) first to discover the exact parameter names and types. Layer parameters accept {'layer': int, 'datatype': int} or 'L/D'. Identical params reuse the same variant cell (KLayout merges on value-identity).
instance.deleterpcall, bbox_dbu, bbox_um, child, dry_run, limit, parent*Delete instances from parent that match a declarative selector. Optional filters: child (cell name or cell_index), bbox_dbu/bbox_um (touching). Removing an instance is NON-destructive to the child cell itself - only the reference in this parent goes away. Wrapped in one transaction so undo rolls the whole batch back. With dry_run=true only the count is reported. With no filters you must pass all=true to confirm deleting every instance in the parent.
instance.transformrpcbbox_um, child, mirror, move_um, parent*, rotationMove/rotate/mirror PLACED instances (arrays move as one object). Filter: child (cell name) and/or bbox_um (touching) — at least one required. Action: move_um, rotation (degrees CCW), mirror; rotation/mirror about the matched set's combined bbox center. One undo step; zero matches is an error.
pcell.register_fittedrpcfit_table*, name*Register a fitted-device PCell at runtime from a fit table (format klink_transistor_pcell_fit_v1, produced by the klink fitter from a user exemplar family). The plugin ships only the generic machinery; device definitions come from outside via this call - a new device family needs zero plugin changes and zero reloads. The PCell lands in library 'klink_structdevice' and is immediately instantiable via instance.insert_pcell.
pcell.convert_to_staticrpccell*, prune_variantConvert a PCell variant into an ordinary static cell (Layout#convert_cell_to_static) and — because KLayout leaves existing placements pointing at the old variant — retarget every instance in the layout to the new static cell, then delete the orphaned variant (prune_variant, default true). Afterwards the geometry is frozen: parameter editing no longer applies. One undo step.

Write · layout-level & edit history

ToolKindParamsFunction
layout.file_inforpcdetail, path*Answer "what is inside this layout FILE?" without touching the session: the file is read into a throwaway layout and discarded, so open tabs cannot contaminate the answer (a measured agent failure mode: importing merged the file into a dirty layout, then session queries reported leftover cells/layers as the file's). Returns dbu, top cells, total cell count, the file's own layer list, and each top cell's bbox in dbu; detail='counts' adds per-layer stored-shape counts split by kind (boxes/polygons/paths/texts/others + total; slower on big files) -- a question about 'boxes' means the 'boxes' entry, not 'total'. Use THIS to inspect a file; use layout.show_file to open one in a tab; layout.import_file MERGES into the active layout.
layout.show_filerpckeep_position, mode, path*, technologyLoad a GDS/OAS file into KLayout. If the file is already open in a tab, reload it. Otherwise open it in the current view (mode='replace') or a new tab (mode='new'). The return's file_info block reports what the FILE itself contains (read separately from the file, immune to session state) -- answer file-content questions from it, not from session queries. When recording is active, all shape/cell events triggered by the file load are merged into a single layout_show_file() line.
layout.save_filerpccellview_index, path*Save the active layout to a GDS or OASIS file on disk. Extension determines format: .gds/.gds2 for GDSII, .oas/.oasis for OASIS.
layout.export_cleanrpcallowlist_layers*, cells, cellview_index, path*Fail-closed delivery export: write ONLY an explicit process-layer allowlist to a new GDS/OASIS file, with every klink marker (Port/Anchor/Region PCell instance) removed and PCell context stripped. Works on a scratch copy -- the live layout is never modified. The output is re-read and verified (no reserved layers, no marker cells) before being atomically promoted; on any verification failure the temp file is deleted and the call errors. Refuses allowlists that include a reserved marker layer. Pass cells to export ONLY those top cells (+ their hierarchy) -- without it the WHOLE layout's top cells go into the file, including unrelated ones from a shared session. Use layout.save_file for full working archives; use THIS for masks and hand-offs.
layout.import_filerpccreate_other_layers, layer_map, on_conflict, path*MERGE a layout file (GDS/OASIS/...) into the ACTIVE layout -- the load-time mapping workflow (official LoadLayoutOptions), unlike layout.show_file which opens a file in its own tab. layer_map remaps layers while reading ([{from: 'L/D', to: 'L/D'}, ...]); create_other_layers (default true) controls whether unlisted layers are read too; on_conflict decides same-name cells: 'rename' (default, new cells get a $1-style suffix), 'add' (content merged into the existing cell), 'overwrite' (old cell replaced), 'skip' (new cell dropped). One undo step. Returns cells/layers added, the new top cells, and a file_info block describing what the FILE itself contained -- after a merge, session queries (cell.list/layer.list) describe the MIXED layout, never the file; answer file-content questions from file_info or layout.file_info instead. To merely INSPECT a file, do not import it: use layout.file_info (session untouched) or layout.show_file (own tab).
layout.clearrpccellview_indexClear the entire layout: removes all cells, shapes, and hierarchy in one operation. Leaves an empty layout ready for new content. Useful before restoring a version-control snapshot.
edit.undorpcUndo the most recent undoable operation (a klink mutating RPC, a Macro IDE edit, or a GUI edit). This is the programmatic equivalent of Edit > Undo. Returns before/after stack snapshots so callers can detect no-op undos. Non-undoable things (e.g. view zoom) are skipped automatically by KLayout.
edit.redorpcRedo the most recently undone operation. Pair with edit.undo. Returns before/after stack snapshots so callers can verify the stack actually advanced.
edit.statusrpcdebugReport current undo/redo availability. Use this to decide whether an edit.undo / edit.redo call would actually do anything. Pass debug=true to surface KLayout Manager introspection fields (useful when diagnosing a build where has_undo / has_redo come back absent).

Geometry checks (read-only reports)

ToolKindParamsFunction
geometry.booleanrpca*, b*, op*, write_toBoolean between two layer sources: op is one of and/or/xor/not (not = a minus b). a and b are {cell, layer} — cells may differ (defaults: b.cell = a.cell), hierarchy is included and inputs are merged. Returns polygon_count / area_um2 / bbox_um of the result; pass write_to {cell?, layer} to ALSO write the (target cell is created if missing; the result says cell_created) result polygons (one undo step; default target cell = a.cell). Typical checks: overlap between two layers (op=and, area>0 means a short/contact), difference against an intent region (op=xor, area==0 means exact match).
geometry.cell_xorrpccell_a*, cell_b*, layers, only_differingGeometric diff between two cells, per layer (pure report, writes nothing): for every layer present in either cell, XOR the merged hierarchical geometry and report diff polygon_count / area_um2. layers restricts the comparison; only_differing (default true) omits identical layers from the listing. equal==true means byte-level geometric identity on every compared layer. THE tool for 'did my edit change only what I intended' — compare a backup/reference cell against the edited one.
geometry.densityrpccell*, layer*, window_umCovered-area density of one layer in a cell: merged hierarchical geometry area divided by the window area. window_um [l,b,r,t] defaults to the cell's bbox on that layer. Returns area_um2, window_area_um2 and density (0..1). The pre-check for dummy-fill decisions (pair with cell.fill_region).

Example.

# 1) read
layout.info verbosity="summary"
cell.list top_only=true

# 2) create cell + ensure layer
cell.create name="MYBLOCK"
layer.ensure layer=1 datatype=0 name="M1"

# 3) batch-draw a row of rectangles (microns) -- never loop single inserts!
shape.insert_boxes cell="MYBLOCK" layer="1/0" boxes_um=[[0,0,10,4],[20,0,30,4],[40,0,50,4]]

# 4) mixed shapes in one call
shape.insert_many cell="MYBLOCK" items=[
  {"kind":"box","layer":"1/0","bbox_um":[0,10,50,14]},
  {"kind":"path","layer":"2/0","points_um":[[0,20],[50,20]],"width_um":2},
  {"kind":"text","layer":"63/0","position_um":[0,26],"text":"MYBLOCK","size_um":4}
]

# 5) make it visible (a new cell is invisible until show_cell)
view.show_cell cell="MYBLOCK"

# PCell placement (check params first)
pcell.info library="Basic" pcell="CIRCLE"
instance.insert_pcell parent="MYBLOCK" library="Basic" pcell="CIRCLE" \
    params={"l":"1/0","r":5.0,"n":64} position_um=[100,0]

# one call places an 8x8 grid
instance.insert parent="TOP" child="MYBLOCK" position_um=[0,0] \
    array={"rows":8,"cols":8,"pitch_x_um":60,"pitch_y_um":40}

4 · Selection & SEND interaction memory selection_and_send_memory · 10 tools

klink.find_tools domain="selection_and_send_memory"

Two distinct things. selection.* is the LIVE current selection in KLayout: selection.get, selection.set_box (replaces current selection), selection.clear, selection.send_context (agent-side explicit SEND). interaction.* is durable session memory of selections the user explicitly SENT (toolbar SEND, recorded as ids like sel_0006): interaction.selection.latest/recent (default latest 5, ordered NOT time-pruned)/get/label, and interaction.context (current selection + recent memory together). Use these whenever the user says "just sent", "this area", "here", "that one". Resolve by order/count, not age. Bind user phrases to these ids/queries, NOT to screenshots.

ToolKindParamsFunction
interaction.contextlocalinclude_current_selectionReturn current KLayout selection plus recent persisted interaction selections.
interaction.selection.clear_sessionlocalconfirm*Clear this MCP session's persisted interaction context after explicit confirmation.
interaction.selection.getlocalid*Return one stored selection by stable selection id.
interaction.selection.labellocaldescription, id*, labelAttach or update a label and description for a stored selection.
interaction.selection.latestlocalReturn the latest explicit sent selection from this MCP session memory.
interaction.selection.recentlocallimitReturn recent explicit sent selections by order, defaulting to the latest five.
selection.clearrpcClear the current object selection in the active view.
selection.getrpclimitReturn the current object selection in the active view. Each entry is either a shape (with layer + bbox) or an instance (with target cell + bbox). Empty selection returns an empty list - not an error.
selection.send_contextrpcmax_items, sourceExplicitly send the current non-empty KLayout selection as a selection_sent event for external AI interaction context. Selected RULERS are captured too (event keys ruler_count/rulers, ascending id), so 'the ruler I sent' resolves via interaction.selection.*. This does not store memory in the plugin.
selection.set_boxrpcbbox_dbu, bbox_um, cell*, include_instances, layers, limitSelect every shape in 'cell' whose bbox intersects the given box (pass bbox_um in microns — preferred — or bbox_dbu) on any of the given 'layers' (default: all layers). Set include_instances: true to ALSO select placed child instances overlapping the box (GUI box-select parity; an array instance selects as one object). Replaces the current selection and shows KLayout's native selection rendering. Returns the number of objects selected.

Example.

# user selects A -> SEND, selects B -> SEND
interaction.selection.recent limit=2       # -> [sel_0007(B), sel_0006(A)]
interaction.selection.label id="sel_0006" label="probe pad"
# now act by id, not by guessing position from a screenshot

5 · Rulers (annotations) & on-canvas measurement rulers_and_measurement · 7 tools

klink.find_tools domain="rulers_and_measurement"

A ruler is a pya.Annotation living in the view, not in the layout: selection.get cannot see it and no saved GDS carries it — annotation.* is the only channel. It is the line-shaped pointing channel between user and agent: the user drags a ruler to say "cut a section along here" and the agent reads it with annotation.list; the agent draws one back with annotation.insert to propose a cut or preview a route, and re-reads after the user drags it. annotation.measure answers "how wide is this gap" with no coordinates at all. SEND captures selected rulers too (previous section); turning rulers into a Region belongs to region.claim (next section).

ToolKindParamsFunction
annotation.listrpccategory, limit, selected_onlyList the rulers (annotations) in the current view — including the ones the USER drew by hand. Rulers live in the view, not in the layout: they are invisible to selection.get and to any saved GDS, so this is the only way to read them. Each entry carries the FULL points_um list and the segment count; a multi-segment ruler is not reducible to a start/end pair.
annotation.getrpcid*One ruler by id (see annotation.list). Errors if the id is not a live ruler in the current view.
annotation.insertrpcangle_constraint, category, fmt, outline, points_um*, snap, style, textDraw a ruler in the current view. This is the agent->user pointing channel for a LINE (view.highlight points at an AREA): propose a cross-section cut, mark a spacing, show where a route will go — the user can then drag the ruler and you re-read it with annotation.list.
annotation.updaterpcangle_constraint, category, fmt, id*, outline, points_um, snap, style, textChange an existing ruler in place (only the properties you pass). Use annotation.list first to get the id.
annotation.deleterpccategory, idDelete rulers by id or by category tag. Pass exactly one of id / category. To wipe every ruler use annotation.clear.
annotation.clearrpcconfirmDelete EVERY ruler in the current view, the user's included. Destructive: requires confirm=true. Prefer annotation.delete{category:'klink'} to clear only agent-drawn ones.
annotation.measurerpcangle_constraint, category, keep_failed, point_um*Auto-measure ruler at a seed point: KLayout looks outward from the point along the allowed directions, finds the nearest edges on VISIBLE layers, and pulls a ruler between them. This is 'how wide is this gap / this line' without knowing any coordinates. If nothing is found nearby the measurement fails and the degenerate zero-length ruler is removed again (measured=false).

Example.

annotation.list                                    # list rulers in the current view
annotation.insert points_um=[[0,0],[10,0]] category="klink" text="cut here"
annotation.measure point_um=[5,5]                  # auto-measure the nearest gap/line

6 · Ports, anchors & regions (marker PCells) ports_and_anchors · 26 tools

klink.find_tools domain="ports_and_anchors"

Ports are net endpoints (klink_Port PCells: net + orientation + width). Anchors are routing constraints (klink_Anchor PCells) whose kind is waypoint_region, bend_region, or corridor (a plain corridor is a REQUIRED pass-through; label it choice_group=BUS for an OPTIONAL channel). port.mark/list/update/transform/set_layer/unmark/delete_all/repair_names; the same verbs on anchor.* (+ anchor.repair_ids). port.mark_many marks many ports in one cell with a single RPC/undo step (validate-before-mutate) — use it instead of looping port.mark for generated port arrays. port.harvest_blackbox derives Ports from LIVE gdsfactory/PDK blackbox instance positions via the waveguide stub convention — a photonics-adjacent tool that lives in this domain. Routing tools default port_layer=999/99, anchor_layer=999/1. Keepouts are NOT an anchor kind — pass your OWN design's obstacle layer(s) to routing tools as obstacle_layers; klink ships no default keepout layer (900/0 is klink's reserved keepout layer, used internally by structdevice). These are the INPUT to routing_backends: mark Ports+Anchors, then call a routing.* tool.

Regions are claimed areas (klink_Region PCells, default layer 999/10): the user drags box/ellipse rulers around a spot, then region.claim converts them into ONE Region PCell and consumes the rulers. Roles: include = union, clip = intersect, exclude = subtract (half-disc = ellipse clipped by a box; annulus = circle minus circle). The result must be one connected component — disconnected islands are rejected, claim them separately. region.list/get/unclaim/set_layer; region.get returns the composed polygon (hull_um) — feed it to cell.fill_region to fill the area, or its bbox_um to view.zoom_box to navigate there. The user can also click a Region and SEND it; resolve via interaction.selection.*. To turn a Region into a validated, uniquely-numbered array, see the layout_intent domain.

ToolKindParamsFunction
port.set_layerrpclayer*Configure the default Port PCell marker layer for this layout.
port.markrpcaccess_mode, cell*, center_dbu, center_um, label, layer, name, net, orientation, port_type, show_label, slide_allowed, slide_edge, target_layer, width_umCreate one klink_Port PCell instance in a cell.
port.mark_manyrpcaccess_mode, cell*, items*, label, layer, net, orientation, port_type, show_label, slide_allowed, slide_edge, target_layer, width_umCreate many klink_Port PCell instances in one cell in a single RPC and one undo transaction. items is a list of per-port objects accepting the same fields as port.mark's params (name, label, center_um/center_dbu, orientation, width_um, port_type, net, target_layer, show_label, access_mode, slide_allowed, slide_edge); each item must set center_um or center_dbu. Any of layer, label, orientation, width_um, port_type, net, target_layer, show_label, access_mode, slide_allowed, slide_edge may also be given ONCE at the top level as a default every item inherits unless it overrides it. Every item is validated BEFORE any port is inserted -- if one item is invalid (missing center, duplicate name), nothing is created and the error names the offending items[i] index.
port.listrpccell*, layer, sortList klink_Port PCell instances in a cell. When layer is given, only ports whose marker layer matches it are returned (it also selects the repair layer); without it all ports are returned.
port.updaterpcaccess_mode, cell*, label, layer, name*, net, orientation, port_type, show_label, slide_allowed, slide_edge, target_layer, width_umUpdate a single Port PCell instance by immutable name.
port.transformrpcaccess_mode, cell*, label, layer, names, net, orientation, port_type, rotate_delta, selection, show_label, slide_allowed, slide_edge, target_layer, width_umBatch-update Port PCell parameters by names or GUI selection.
port.repair_namesrpccell*, layer, prefixRepair duplicate or empty Port names in a cell. This is primarily for ports inserted manually through the KLayout PCell GUI, which bypasses port.mark's uniqueness check.
port.harvest_blackboxlocalcell*, clear, nets, port_layer, stub_size_um*, tags*, wg_layer*Harvest optical ports from PDK blackbox instances in a cell via the waveguide stub convention (small stub boxes on the waveguide layer) and mark them as klink Ports. Ports are derived from LIVE instance positions: re-run after moving instances in the GUI to refresh them, then route. Net intent keys on identity-stable names {tag}{ordinal}_{stubIndex}.
port.unmarkrpccell*, name*Delete one Port PCell instance by name.
port.delete_allrpccell*, layerDelete all Port PCell instances in a cell.
anchor.set_layerrpclayer*Configure the default Anchor PCell marker layer for this layout.
anchor.markrpccell*, center_dbu, center_um, height_um, id, kind, label, layer, mode, name, net, orientation, path_points, priority, radius_um, required, show_label, width_umCreate one klink_Anchor PCell instance in a cell.
anchor.listrpccell*, layer, sortList klink_Anchor PCell instances in a cell. When layer is given, only anchors whose marker layer matches it are returned (it also selects the repair layer); without it all anchors are returned.
anchor.updaterpccell*, height_um, id*, kind, label, layer, mode, net, new_id, orientation, path_points, priority, radius_um, required, show_label, width_umUpdate a single Anchor PCell instance by immutable id.
anchor.transformrpccell*, height_um, ids, kind, label, layer, mode, names, net, orientation, path_points, priority, radius_um, required, selection, show_label, width_umBatch-update Anchor PCell parameters by ids or GUI selection.
anchor.repair_idsrpccell*, layer, prefixRepair duplicate or empty Anchor IDs in a cell. This is primarily for anchors inserted manually through the KLayout PCell GUI, which bypasses anchor.mark's uniqueness check.
anchor.unmarkrpccell*, id*Delete one Anchor PCell instance by id.
anchor.delete_allrpccell*, layerDelete all Anchor PCell instances in a cell.
region.set_layerrpclayer*Configure the default Region PCell marker layer for this layout (default 999/10).
region.claim_previewrpclimit, npoints, rulersDry-run for region.claim, NO mutation: lists EVERY ruler in the current view as a claim candidate, newest first (recency_rank 1 = drawn last), each with how claim would read it (kind: box | ellipse | polygon for 3+ point rulers | line = not claimable), point count, bbox_um, label, selected, and labeled_role when the ruler's label is the express-lane token 'region' / 'region:exclude'. Pass rulers:[{id, role}] to also compose that exact set and get the would-be result (bbox/area/holes) or the same errors claim would raise. Use it to NARRATE the candidates to the user and get a confirmation before claiming -- the view mixes this-moment intent with old measurement leftovers.
region.claimrpccell, keep_rulers, layer, name, npoints, rulers*Convert rulers into ONE klink_Region PCell (reserved layer, default 999/10) and consume the rulers. Ruler kinds: 2-point box/ellipse rulers, and 3+ point rulers read as EXACT closed polygons (auto-closed first..last..first; self-intersecting outlines are refused naming the crossing segments). Roles: include (union), clip (intersect), exclude (subtract; holes are never writable). The result must be a single connected component; disconnected islands are rejected with per-island bboxes -- claim them separately. Ellipses are discretized safely: include/clip inscribed, exclude circumscribed. PICKING rulers is the hazard (the view mixes intent with old measurement lines): take ids from interaction.selection.latest.rulers when the user SENT them, else region.claim_preview + narrate + user confirmation; rulers labeled 'region' may be taken without asking. The result echoes each consumed ruler (consumed[]).
region.listrpccellList every klink_Region PCell instance (name, cell, layer, bbox, area, klink_id). Regions are the durable claimed areas; rulers are only drafts.
region.getrpcname*One Region by name: composed polygon (instance transform x local contours) in target-cell DBU and um, area, holes. Refuses magnification != 1.
region.occupancyrpcexclude_cells, layers, name*, obstacle_cellsRecursive occupancy facts for one Region: merged obstacle polygons per requested layer AND/OR per named obstacle CELL (every occurrence bbox of those cells inside the region -- for custom devices/blackboxes), clipped to the region polygon, plus the remaining free area. Everything is explicit -- klink assumes nothing about which layers or cells are obstacles. truncated: true means the result is incomplete and MUST NOT be used for planning.
region.repair_idsrpckeeper*, name*Keeper repair for copy-paste duplicated Regions (same name / klink_id on several instances). The USER designates the keeper (parent cell + anchor position); the keeper retains the original name, klink_id, and any Intent binding untouched. Every other duplicate gets a fresh auto R### name + klink_id and NO intent -- bind one later with intent.rebind. Never guesses which copy is the original; ambiguous keeper specs are refused.
region.unclaimrpcname*Delete one Region PCell instance by name. Deletes ONLY the region marker -- never any generated output or user geometry.

Example.

port.mark cell="NET1" name="A" center_um=[0,0]   orientation="E" width_um=2 net="sig"
port.mark cell="NET1" name="B" center_um=[80,20] orientation="W" width_um=2 net="sig"
anchor.mark cell="NET1" kind="waypoint_region" center_um=[40,40] radius_um=6 net="sig"
port.list cell="NET1"

routing.tapered_hybrid_cell cell="NET1" angle_mode="manhattan" obstacle_layers=["10/0"]

# Region: drag rulers around a spot, claim them into one Region marker
region.claim cell="TOP" layer="999/10" rulers=[{"id":12,"role":"include"}]
region.get name="R001"

7 · Executable layout intent (Region → deterministic array) layout_intent · 10 tools

klink.find_tools domain="layout_intent"

The Region-driven generation loop (needs a claimed Region — see ports_and_anchors's region.claim). intent.prepare analyzes the region's occupancy against YOUR declared obstacles (layers, named device/blackbox cells counted by bbox per instance occurrence, free-form polygons, plus an optional clearance_um), plans a pitch grid of ANY existing source cell (rotation_deg/mirror supported) with unique polygon-text number labels (real geometry, TextGenerator), validates containment + obstacles + overlaps, and returns a preview + plan_id + plan_hash — nothing is written. intent.apply (plan_id + plan_hash + confirm=plan_id) commits it as one transaction into a fresh KLINK_I_* container cell (one Ctrl+Z undo; klink_id-stamped root). intent.regenerate re-plans with parameter patches (e.g. numbering.start), and the next apply atomically swaps ONLY this intent's container — hand-edited outputs are detected (diverged) and never silently overwritten. intent.list/get show stored intents + live condition; intent.rebind explicitly re-points an intent at a region (the only path after a region was deleted/repaired); intent.retire decides the output's fate via output_policy (preserve/detach/remove). Layers, pitches, and sizes are all required inputs: klink ships no process defaults. Low-level primitives intent.apply_managed_plan/intent.managed_digest/intent.remove_managed_output are the plugin RPCs the orchestrator tools call — don't call them directly in normal flows.

Orchestration (MCP local tools)

ToolKindParamsFunction
intent.preparelocalallow_empty_obstacles, clearance_um, extra_obstacles_um, instruction, intent_id, label*, mirror, numbering*, obstacle_cells, obstacle_layers, pitch_um*, project_root, region*, rotation_deg, source_cell*Plan an array_labeled intent for a claimed Region: analyze occupancy, place a pitch grid of an existing source cell (any custom cell; rotation/mirror supported) with UNIQUE physical number labels (real polygon text), validate containment/obstacles, and return a preview + plan_id + plan_hash. Writes NOTHING to the layout. Obstacles are whatever YOU declare: layers, named device/blackbox cells, free-form polygons, plus an optional clearance margin -- klink ships no process defaults and assumes nothing. Apply with intent.apply after reviewing.
intent.applylocalconfirm*, plan_hash*, plan_id*, project_rootCommit a previewed plan in ONE KLayout transaction (fresh container cell + klink_id root; regenerate swaps the container). Requires the plan_id AND plan_hash from intent.prepare plus confirm=plan_id. Refuses stale plans (layout changed since prepare), diverged outputs, and plans with problems. One Ctrl+Z undoes the whole apply.
intent.regeneratelocalintent_id*, parameters_patch, project_rootRe-plan an applied intent (optionally patching parameters, e.g. a new numbering start or pitch) and return a fresh preview; the next intent.apply atomically replaces ONLY this intent's container. Refuses if the output was hand-edited (diverged) -- never a silent overwrite.
intent.listlocalproject_rootList stored intents (id, region, executor, revision, output container) with the lazy output condition: applied | diverged | undone_or_deleted | never_applied.
intent.getlocalintent_id*, project_rootOne stored intent in full (parameters, output, lazy condition).
intent.rebindlocalintent_id*, project_root, region*Explicitly bind an intent to a (new) Region name -- the ONLY path after a region was deleted/repaired. Verifies the region exists; never guesses.
intent.retirelocalconfirm, intent_id*, output_policy, project_rootRetire an intent. output_policy decides the geometry's fate: 'preserve' (default) keeps the container as ordinary layout and just closes the intent; 'detach' additionally forgets the output binding (container stays, no longer managed); 'remove' deletes the container via the digest-guarded plugin primitive -- refused if the output was hand-edited (diverged). Never deletes user geometry.

Low-level primitives (plugin RPC, called by the orchestrators above)

ToolKindParamsFunction
intent.apply_managed_planrpccontainer_cell*, expected_managed_digest, expected_root_klink_id, mode*, parent_cell*, payload*, plan_hash*, root_klink_id*, scope_checkMechanically commit a fully-validated managed plan in ONE transaction: create a fresh container cell from the typed payload, place its root instance (klink_id-stamped) in the parent, and on mode=replace swap out the previous container. All business validation must happen BEFORE this call; TOCTOU is guarded by scope_check (re-run + compare) and expected_managed_digest. Refuses 0 or multiple root matches, digest mismatches, and stale scope. One undo step reverts the whole apply.
intent.managed_digestrpccontainer_cell*Managed digest of a container cell's own content (canonicalized shapes + child instances). Used for the lazy undone/diverged check: compare against the digest recorded by the last apply.
intent.remove_managed_outputrpcexpected_managed_digest*, parent_cell*, root_klink_id*Delete ONE managed output (root instance + container cell) after verifying identity and digest. Refuses 0/multiple root matches and diverged containers (digest mismatch) -- hand-edited output is never silently destroyed. One transaction, one undo step.

Example.

# 1) claim a Region (see ports_and_anchors' region.claim)
region.claim cell="TOP" rulers=[{"id":12,"role":"include"}]

# 2) plan: tile any existing cell on a pitch grid, uniquely numbered
intent.prepare region="R001" source_cell="SENSOR" pitch_um=[20,20] \
    obstacle_layers=["10/0"] numbering={"prefix":"S","width":3,"start":1} \
    label={"layer":"20/0","slot_region":"R00X"}
# -> preview + plan_id + plan_hash -- review it before anything is written

# 3) apply: one transaction writes it
intent.apply plan_id="..." plan_hash="..." confirm="..."

# 4) change your mind: regenerate with a new numbering start, apply swaps the old container
intent.regenerate intent_id="..." parameters_patch={"numbering":{"start":201}}
intent.apply plan_id="..." plan_hash="..." confirm="..."

8 · Routing backends routing_backends · 9 tools

klink.find_tools domain="routing_backends"

All read Port/Anchor PCells in a cell and write routes. Pick by topology/quality: routing.tapered_hybrid_cell is the main path+patch backend; routing.tapered_polygon_cell writes continuous taper polygons (first-class, not a fallback); routing.steiner_cell handles multi-terminal nets (>2 ports); the routing.damped_* family adds explicit extra obstacle clearance; routing.global_channel_cell is a global-decision router (candidate-sink assignment + corridor-capacity load-balancing) on top of tapered hybrid geometry; routing.multilayer_escape_cell routes wall-blocked nets via a bridge layer + vias; routing.gdsfactory_ports routes Port markers with a named gdsfactory strategy (needs gdsfactory in the interpreter). Always inspect the structured result: ok=false, obstacle_hit_count>0, sibling overlaps, short route_count all mean failure. Pass your own design's obstacle_layers (no default).

ToolKindParamsFunction
routing.damped_polygon_celllocalanchor_layer, angle_mode, cell*, clear, corner_style, damping_distance_um, obstacle_layers, port_layer, route_layer, spacing_umRoute one KLayout cell with the explicit damped polygon backend. Uses continuous taper polygons and keeps extra distance from obstacles.
routing.damped_segment_celllocalanchor_layer, angle_mode, cell*, clear, damping_distance_um, obstacle_layers, port_layer, spacing_umRoute one KLayout cell with the explicit damped segment backend. Uses tapered hybrid output and keeps extra distance from obstacles.
routing.damped_steiner_celllocalanchor_layer, angle_mode, cell*, clear, damping_distance_um, obstacle_layers, port_layer, root_ports, route_layer, spacing_umRoute one KLayout cell with the explicit damped Steiner backend. Uses multi-terminal trunk/branch topology and damped obstacle clearance.
routing.gdsfactory_portslocalall_two_port_nets, allow_crossing, auto_taper, backbone_um, bundle_gather_um, cell*, clear, collision_check_layers, cross_section, distance_um, end_straight_um, gf_route_layer, min_straight_taper_um, net, obstacle_bboxes_um, output_mode, pair_by, path_length_match, port_layer, radius_um, resolution_um, route_layer*, route_width_um, router, sbend_fallback, separation_um, sort_ports, source, source_orientation, source_prefix, start_straight_um, steps, taper, target, target_orientation, target_prefix, waypoints_umRoute KLayout Port markers with a named gdsfactory routing strategy. router: bundle=Manhattan river routing with separation (DEFAULT; also honors waypoints/steps, radius_um, start/end_straight_um, path_length_match, collision_check_layers); electrical=bundle with metal defaults and sharp corners; sbend=smooth S-transitions for offset facing ports; all_angle=non-Manhattan bundle (optional backbone_um spine); single=independent Manhattan route per pair; dubins=arc-based any-heading per pair; astar=EXPERIMENTAL grid A* per pair around obstacle_bboxes_um (+resolution_um/distance_um) — gf's astar is fragile, so klink verifies the result and ERRORS instead of returning a wall-crossing route; for reliable obstacle avoidance prefer klink's own routing.tapered_hybrid_cell / routing.damped_* with obstacle_layers. A parameter the chosen router cannot honor returns an error naming the routers that honor it. Requires gdsfactory in the MCP interpreter.
routing.global_channel_celllocalanchor_layer, angle_mode, cell*, clear, obstacle_layers, port_layer, safe_distance_um, spacing_umRoute one KLayout cell with the stronger global channel backend. It performs obstacle-aware candidate assignment and capacity-aware corridor assignment before using tapered hybrid geometry.
routing.multilayer_escape_celllocalbridge_layer*, cell*, clear, obstacle_layers, port_layer, route_layer*, spacing_um, via_layer*Route wall-blocked pairwise nets by using a primary route layer, bridge layer, and via boxes.
routing.steiner_celllocalanchor_layer, cell*, clear, obstacle_layers, port_layer, root_ports, route_layerRoute multi-terminal nets in one KLayout cell using klink's rectilinear Steiner/bus tree router. Use for nets with more than two ports.
routing.tapered_hybrid_celllocalanchor_layer, angle_mode, cell*, clear, obstacle_layers, port_layer, spacing_umRoute one KLayout cell using klink's tapered hybrid cell router. Reads Port/Anchor PCells, plans routes, validates, and writes results. Pass obstacle_layers=[...] with YOUR design's keepout layers (no default).
routing.tapered_polygon_celllocalanchor_layer, angle_mode, cell*, clear, corner_style, obstacle_layers, port_layer, route_layer, spacing_umRoute one KLayout cell using klink's continuous tapered polygon backend. Supports the same Port/Anchor semantics as hybrid routing, but writes continuous taper polygons.

routing.gdsfactory_ports routers (a parameter the chosen router can't honor is an error naming the routers that honor it, never silently ignored):

routerUse
bundle (default)Manhattan river routing with separation; also honors waypoints/steps, radius_um, start/end_straight_um, path_length_match, collision_check_layers.
electricalbundle + metal defaults + sharp corners + electrical port typing.
sbendSmooth S-transition for laterally offset, facing ports.
all_angleNon-Manhattan bundle (optional backbone_um spine).
singleIndependent Manhattan route per pair.
dubinsArc-based any-heading route per pair.
astar (experimental)Grid A* around obstacle_bboxes_um; gf's astar is fragile, so klink verifies the result and errors instead of returning a wall-crossing route. For reliable avoidance use klink's own tapered_hybrid/damped + obstacle_layers.

Example.

routing.tapered_hybrid_cell cell="BLOCK" angle_mode="manhattan" spacing_um=20 obstacle_layers=["900/0"]
routing.damped_segment_cell cell="BLOCK" damping_distance_um=15 obstacle_layers=["900/0"]
routing.steiner_cell cell="BLOCK" route_layer="1/0"
routing.gdsfactory_ports cell="BLOCK" route_layer="1/0" router="bundle" \
    separation_um=5 radius_um=10 path_length_match=true
"Done" for a route/P&R stage is a live LVS match=True — marker counts and "looks routed" never substitute. Check ok, obstacle_hit_count, sibling overlap and route_count in the structured result.

9 · DRC & LVS verification drc_and_lvs_verification · 2 tools

klink.find_tools domain="drc_and_lvs_verification"

Both are long-running, pure pya, domain-agnostic. drc.run runs arbitrary DRC DSL (Ruby) you supply against the layout — exceptions inside the script come back as results, they do NOT fail the RPC. lvs.run is the connectivity counterpart: extracts the live layout into a device netlist and compares it against a REFERENCE netlist you supply, writes a native .lvsdb and (default) opens it in the Netlist/LVS browser. A P&R/device stage counts as DONE only on a real live LVS match=True — offline fixtures and marker counts never substitute. For the structdevice flow prefer structdevice.lvs_check.

ToolKindParamsFunction
drc.runrpccode*, input_layout, output_rdb, result_mode, stderr_limit, stdout_limit, top_cellEscape hatch: run arbitrary DRC DSL script code in KLayout's integrated Ruby DRC engine. Accepts DRC DSL source (Ruby-like syntax with source()/input()/report()/etc.) and executes it. report() TAKES A SECOND ARGUMENT: report("title", $output_rdb). Without it the RDB is never written and rdb_summary comes back empty with no error -- the run looks clean because nothing was recorded, not because nothing was found. If the script includes source() it runs in standalone mode (against the specified file). If source() is omitted it runs in interactive mode against the currently loaded layout. Optional variables ($input_layout, $output_rdb, $topcell) are injected into the Ruby interpreter so the script can reference them without hardcoding paths. stdout/stderr are captured and returned as strings. If output_rdb is specified and the script generates it, the RDB is parsed and a violation summary is returned. Exceptions in DRC scripts do NOT fail the RPC - they come back inside result.exception with any stdout/stderr captured before the error. Only malformed requests (missing code, oversized) return ok=false.
lvs.runrpccell*, conductors*, devices*, out_lvsdb, reference*, show, viasGeneral LVS (like the DRC escape hatch, for connectivity): extract the live layout into a device netlist (per-cell device extractors from the 'devices' config) and compare against a REFERENCE netlist you pull in -- an external SPICE file (reference.spice) OR a structured netlist (reference.netlist). Writes a native .lvsdb and (show=true, default) opens it in the Netlist/LVS browser for layout<->netlist cross-probe. Pure pya; domain-agnostic; terminal names/layers are all parameters. Read-only (adds temp marker layers to the in-memory layout; does not alter saved geometry).

Example.

# DRC: M1 minimum spacing 0.2um (interactive, against the current layout)
drc.run script="""
m1 = input(1, 0)
m1.space(0.2.um).output("M1_space", "M1 spacing < 0.2um")
"""

# LVS: extract conductor layers, compare to a reference SPICE, open the browser
lvs.run cell="BLOCK" conductors=["1/0","3/0"] vias=["2/0"] \
    devices={...} reference={"spice":"ref.spice"} out_lvsdb="block.lvsdb" show=true

10 · Custom-device netlist → auto P&R → LVS device_structdevice · 6 tools

klink.find_tools domain="device_structdevice"

This is the device-AGNOSTIC custom-device P&R flow. A "device" is any cell with an arbitrary parameter set + terminals; klink assumes no parameter names/count and no device vocabulary. The device library, process profile, and terminal source are EXAMPLE/PDK data passed in explicitly — the tools ship none and return an instructive "write/run an example" error. structdevice.build_from_netlist is the headline one-call flow (confirmation-gated: call once for a proposal, again with the confirm token to build); routing runs on the flexdr engine using a compact physical model where the device's own metal layers double as routing layers. structdevice.declare_nets / connect_nets / lvs_check / spec_write are the SEND-driven interactive path. structdevice.register_pcell wraps the lower-level plugin RPC pcell.register_fitted (listed under geometry_authoring).

ToolKindParamsFunction
structdevice.build_from_netlistlocalcell*, cols, confirm, mode, netlist*, rows, sessionBuild a circuit cell from a device-level netlist, fully algorithmic and confirmation-gated. TWO calls: (1) call WITHOUT confirm -> it returns needs_confirmation, a proposal (grid rows x cols, derived row pitch, routing layers, device mix) and next_action; READ the proposal to the user. (2) If they approve, call again with the SAME arguments plus the confirm token from next_action -> it places (derived floorplan), single-pass multilayer routes, draws, and device-LVS-verifies a FRESH cell. Netlist format: {instances: [{instance_id, device_cell}], nets: [{net_id, terminals: ['X1.D', ...]}], groups: [{instances: [...]}]}. NOTHING is hand-tuned: layers/vias/spacing come from the process profile, the floorplan from demand. DO NOT place/route/draw yourself; DO NOT change rows/cols/mode unless the user asks; relay problems to the user VERBATIM. Every result carries next_action -- follow it.
structdevice.connect_netslocalcell*, conductors, min_spacing_um, min_width_um, route_layer, route_width_um, session, via_cell, viasWire every declared-but-unconnected net of a device cell in one call and verify it: recipe-derived attach points, automatic via placement/reuse, automatic keepouts (everything not on the net is an obstacle), damped routing, then LVS — on any mismatch ALL mutations are undone. Call AFTER structdevice.declare_nets and BEFORE structdevice.spec_write. Results carry next_action; relay problems verbatim, never improvise wiring. EXAMPLE-DRIVEN, NOT standalone: attach points need a recipe from your project (klink ships none), so called as-is it returns an instructive error.
structdevice.declare_netslocalcell*, conductors, recent_sends*, viasDeclare electrical nets from the user's SENDs: ONE SEND framing two or more device terminals = ONE declared net; declarations persist to <cell>.elec_nets.json and feed structdevice.lvs_check / spec_write. EXAMPLE-DRIVEN, NOT standalone: reading device terminals needs a recipe injected from your project (klink ships none), so called as-is it returns an instructive error naming the recipe to provide -- never a guess.
structdevice.lvs_checklocalcell*, conductors, mode, session, viasLVS in one call: derive device terminals (recipe), extract the nets the drawn wiring actually makes (KLayout-native extraction on a saved snapshot), and reconcile against the declared nets persisted by structdevice.declare_nets. mode='net' (default) = net-level LVS-lite; mode='device'/'both' ALSO runs device-level LVS (build reference + extracted netlists, compare with native NetlistComparer) -> result under device_lvs. Findings are instructions with terminal-level evidence; report persists to <cell>.lvs.json. EXAMPLE-DRIVEN, NOT standalone: deriving device terminals needs a recipe from your project (klink ships none), so called as-is it returns an instructive error.
structdevice.register_pcelllocaldiff_report, fit_table*, name*, sessionRegister a fitted-device PCell at runtime from a fit table (produced by the exemplar fitter). One call, zero plugin changes, zero reloads: the PCell lands in library 'klink_structdevice' and is immediately usable in the GUI and via instance.insert_pcell. Call AFTER the fitter produced the table; the table encodes user geometry and stays local. Recommended protocol: run the byte-exact differential harness (klink.domains.structdevice.pcell_diff.verify_differential) against ground truth FIRST and pass its outcome as diff_report so the registration carries its acceptance evidence.
structdevice.spec_writelocalcell*, conductors, device_class, layer_roles*, session, viasProject a live cell into a klink.spec.json v1 fact file in one call: devices (recipe terminals), instances, declared nets (from structdevice.declare_nets), derived nets, and their reconciliation. layer_roles maps 'L/D' to a role name and is recorded as user_declared. The spec lands in <cell>.klink.spec.json next to the net table.

Example.

# step 1: no confirm -> proposal (grid rows x cols, row pitch, routing layers, device mix)
structdevice.build_from_netlist cell="RINGOSC" netlist={
  "instances":[{"name":"INV0","device":"inv_x1"}, ...],
  "nets":[{"name":"a","terminals":["INV0/in","INV2/out"]}, ...],
  "groups":[]
} mode="3L"
# -> needs_confirmation + proposal + next_action(confirm=...)

# step 2: same args + confirm token -> actually place/route/draw/LVS
structdevice.build_from_netlist cell="RINGOSC" netlist={...} mode="3L" confirm="CONFIRM-xyz"

# SEND-driven interactive path
structdevice.declare_nets recent_sends=3 cell="BLOCK" conductors=["1/0","3/0"] vias=["2/0"]
structdevice.connect_nets cell="BLOCK" route_layer="3/0" route_width_um=0.5
structdevice.lvs_check cell="BLOCK" mode="both"
structdevice.spec_write cell="BLOCK" layer_roles={"1/0":"gate","3/0":"metal1"}

11 · Imaging (cross-section / 3D / SEM / Blender) imaging · 4 tools

klink.find_tools domain="imaging"

Everything runs klink-side (no plugin involvement); heavy deps are optional and the error names the exact pip install command. Recipes/VisualStack instances are example-owned — klink ships mechanism only. imaging.xsection_run makes a process cross-section along an explicit cut line from a .pyxs recipe (engine klayout_pyxs); steps=true + '# klink-step: <name>' recipe markers give a per-step film. imaging.render3d builds a GLB plus a self-contained interactive viewer HTML by extruding each visual-stack layer into a 3D mask (a circle stays a smooth prism); a layer's sidewall_deg declares wall tilt, and cutaway_um boolean-cuts the finished model open to expose a section; process curvature (LOCOS, CMP) stays where it's real: the 2D sections of imaging.xsection_run. imaging.sem_top renders deterministic SEM-style top-view PNGs (greyscale + false color). imaging.blender renders a paper-grade image via a headless bpy subprocess — mode=die polishes a render3d GLB, mode=figure builds a device figure from GDS+stack at 1:1 layout coordinates (lattice layers become atomic-structure motifs).

ToolKindParamsFunction
imaging.blenderlocalbasename, camera, cell, gds, glb, lattice_a_um, mode*, output_dir*, overwrite, samples, session, slabs, stack, style*, timeout_s, transparent, weld_slits_dbuPaper-grade Blender render (headless bpy, executed in a SUBPROCESS — bpy never lives in the server). mode='die': import a GLB from imaging.render3d, stand it flat, filmic + transparent film + shadow catcher. mode='figure': GDS-driven device figure at 1:1 layout coordinates — kind='lattice' stack layers render as the material's ATOMIC STRUCTURE (graphene/MoS2 motifs), solids as exact layout prisms, plus explicit substrate/oxide 'slabs'. Both write <basename>.png (RGBA) + <basename>.blend (open in desktop Blender to adjust by hand) + sidecar. Needs pip install bpy (~300MB; instructive error if missing). Renders are not byte-deterministic (Cycles).
imaging.render3dlocalbasename, cell, cutaway_um, gds, output_dir*, overwrite, session, stack*, style*, weld_slits_dbuBuild a 3D model (GLB) of the layout plus a SELF-CONTAINED interactive viewer page (html: embedded model + vendored viewer JS + manual color/metal/rough panel + PNG export; opens by double-click, offline). Extrudes each layer of a klink_visual_stack_v1 declaration between its z0_um/z1_um — the model IS the layout (a circle stays a smooth prism). For process TRUTH (etch profiles, bird's beaks, conformal films) use the 2D cross-section tool imaging.xsection_run instead; klink does not fake 3D process simulation. Layers may declare sidewall_deg in the stack for smooth tilted walls; cutaway_um cuts the finished model to a region. Deterministic outputs + klink_imaging_result_v1 sidecar; never overwrites unless overwrite=true.
imaging.sem_toplocalbasename, cell, corner_radius_um, gds, layers, output_dir*, overwrite, session, stack*, style*, width_px, window_umSEM-style top view of the layout: per-layer grey levels (sem_grey) + bright topography rims (edge_glow) from a klink_visual_stack_v1 declaration, with beam blur, film grain, scanlines and vignette (deterministic, seeded). Writes <basename>_sem.png (greyscale) and <basename>_sem_color.png (false color from layer colors) + a klink_imaging_result_v1 sidecar. layers=[...] restricts to a subset — e.g. the masks printed up to a given process step. Needs numpy/scipy/pillow.
imaging.xsection_runlocalauto_layer_base, axis, basename, below_um, cell, cut_from_ruler, cut_um, delta_dbu, depth_um, exclude, extend_um, gds, height_um, output_dir*, overwrite, recipe*, render, ruler_id, ruler_segment, session, show, stack, steps, style, weld_slits_dbu, z_window_umProcess cross-section from a .pyxs recipe along an explicit cut line — headless, deterministic, engine=klayout_pyxs (pinned; instructive install error if missing). Writes <basename>.gds (or per-step files with steps=true using '# klink-step: <name>' recipe markers) + a klink_imaging_result_v1 sidecar into output_dir; never overwrites unless overwrite=true. Source is either a gds path OR the live KLayout session (saved via layout.save_file). show=true opens the section in a new tab. NOTE: .pyxs recipes are trusted Python code executed in-process.

12 · Nanodevices (Hall bar / EBL / flake) device_nanodevice · 2 tools

klink.find_tools domain="device_nanodevice"

nanodevice.hallbar is a one-call closed loop: from a HallBarSpec (bar length/width, contact_count, contact/pad dims, pitch, gaps) it computes and draws the whole device (bar + N symmetric contact arms + pads + Port markers + labels), then delegates routing to the generic router (overlap validation on, optional EBL writefield walls as keepouts), committing to a disposable cell (dry_run supported). Failures return problems/next_action and change nothing. nanodevice.detect_commit commits flake traces as polygons from a precomputed traces.json, or runs live detection from a microscope image (needs cv2 + numpy).

ToolKindParamsFunction
nanodevice.detect_commitlocalcell, coordinate, dry_run, image, pixel_size_um, session, traces_pathLoad (or detect) nanodevice flake traces and commit them as polygons into a live KLayout cell, in one call. Pass traces_path for a precomputed traces.json, or image (+pixel_size_um) to run detection (requires cv2/opencv + numpy in this interpreter). State persists on disk; failures return instructions and change nothing.
nanodevice.hallbarlocalcell, dry_run, route_layer, session, spacing_um, spec, writefieldBuild, route, validate, and commit one Hall bar device in one call: generates geometry + Ports/Anchors from a spec, routes contacts to pads with klink's existing router (overlap validation ON, optional writefield walls), writes into a disposable cell, persists state. On problems it returns instructions (problems/next_action); a failed call changes nothing.

Example.

nanodevice.hallbar cell="HB1" spec={
  "bar_length_um":60, "bar_width_um":8, "contact_count":6,
  "contact_width_um":4, "pad_size_um":40, "pitch_um":24, "gap_um":6
} route_layer="1/0" dry_run=true

nanodevice.detect_commit cell="FLAKE" traces_path="out/traces.json"

13 · Photonics (gdsfactory import / connect / reroute) device_photonics · 3 tools

klink.find_tools domain="device_photonics"

Photonic circuit flow. Needs gdsfactory in the MCP interpreter. Two port sources, one interactive loop: photonics.import_gf takes a finished user gdsfactory script over into the loop in one call — device instances become real KLayout cells+instances, routed connections collapse to device-level nets, per-device port templates persist in the spec, and nets are routed by klink. port.harvest_blackbox (listed under ports_and_anchors — it derives Ports from live blackbox instance positions) is the other port source; re-run it after moving instances, then route. photonics.connect reads the latest N SENDs as port pairs, auto-names nets, persists, re-harvests, and routes with gdsfactory. photonics.reroute re-routes a cell after the user moved components (reads the persisted net table). Multi-port optical nets are not routed as stars — insert an explicit splitter/MMI/Y-branch first and route the resulting two-port nets.

ToolKindParamsFunction
photonics.connectlocalcell, radius_um, recent_sends*, route_layer, separation_um, stub_size_um, wg_layer, width_umConnect ports the user just SENT, in one call: reads the latest N explicit SEND selections, turns them into port pairs (one SEND framing two klink Port markers = one pair; single-marker SENDs pair consecutively), auto-names nets, persists the net table, re-harvests ports from live instance positions, and routes with gdsfactory. Workflow: user presses SEND for each pair -> call this with recent_sends. The KLayout session is derived from the SENDs automatically. On problems it returns instructions, never guesses.
photonics.import_gflocalcell, component, port_layer, route, route_layer, script_path*, sessionTake over a FINISHED gdsfactory script into klink's interactive loop in one call: runs the user's .py in this (gdsfactory-capable) interpreter, takes the Component it builds, imports its DEVICE instances as real KLayout cells+instances (batch RPC), collapses its routed/snapped connections to device-level nets, persists per-device port templates + the net table in the spec, and routes the nets with klink (the script's own routes are replaced by klink-owned ones). Afterwards the user can DRAG components in KLayout and photonics.reroute (just the cell name) re-routes from live positions. The script is executed — only run files the user asked you to import.
photonics.reroutelocalcell*, route_layer, session, stub_size_um, wg_layerRe-route a cell whose connections were made with photonics.connect, after the user moved components. Reads the persisted net table, re-harvests ports from live instance positions, routes, writes back. Needs only the cell name (plus session when not the primary one).

Example.

# 1) take over a finished script (c = build_mzi() inside it)
photonics.import_gf script_path="my_mzi.py" cell="MZI" route_layer="1/0"

# 2) drag a phase shifter in the KLayout GUI ... then:
photonics.reroute cell="MZI"     # optics + metal redrawn together, your drag is kept

# ports can also come from the blackbox stub convention
port.harvest_blackbox cell="MZI" tags=["gc","mmi"] wg_layer="1/0" stub_size_um=0.5
photonics.connect recent_sends=4 cell="MZI" radius_um=10 separation_um=5

14 · L-Edit bridge (file-exchange RPC) bridge_ledit · 18 tools

klink.find_tools domain="bridge_ledit"

Requires L-Edit running with the bridge macro loaded as SOURCE (example_template/ledit_bridge/ledit_bridge.cpp via Tools > Macro > Load Macro..., zero compile). Transport is a JSON file exchange under %LOCALAPPDATA%\klink\ledit_bridge\<namespace>; one namespace per L-Edit instance, only ONE L-Edit may hold a namespace at a time. ledit.status is discovery + handshake — call it first when anything misbehaves; errors name the exact fix. ledit.import_selection does a fresh GET of the user's current L-Edit selection into a new KLayout landing cell. ledit.push_cell pushes a flat KLayout cell back into L-Edit (append-only; use a fresh target cell to regenerate). ledit.import_cell_tree imports a cell and its whole hierarchy into KLayout, children-first, instances staying instances. ledit.push_cell_tree pushes the other way, a whole subtree back into L-Edit with hierarchy intact, reporting placements it can't express rather than approximating them. Deeper T-Cell workflows live in the Python API klink.bridges.ledit — see the L-Edit Bridge guide. 0.5.7 adds 13 more tools, turning the bridge from a drawing transport into a full editor control surface: six navigation tools (show_cell/set_cell_hidden/list_windows/close_window/layout_view/save_image), four destructive commands with explicit targets that refuse (delete_cell/rename_cell/delete_objects/close_design), and three verification tools (run_drc/drc_summary/export_gds); macro 0.5.6 → 0.5.8 — reload the macro in L-Edit after upgrading.

ToolKindParamsFunction
ledit.statuslocalnamespaceDiscover L-Edit bridge namespaces and report liveness + handshake for one: hello heartbeat age, macro version/capabilities, current .tdb file and cell. When the macro supports it, also lists every open design (designs, with the visible/changed flags) and the active design's cells (with T-Cell flags) and, with macro >= 0.5.6, the open windows (windows[]) -- one call answers "what is in L-Edit right now". Start here when any ledit.* call misbehaves; errors name the exact fix (load/reload the macro, close a modal dialog, ...).
ledit.import_selectionlocalnamespace, session, target_cellImport the CURRENT L-Edit selection into a fresh KLayout landing cell (fresh GET each call -- never stale geometry). Generic capability matching: box->box, wire->path, circle->Basic.CIRCLE PCell (stays parametric), any other outline->polygon; non-convertible objects are reported, never silently dropped. Layers migrate by NAME + GDS number (L-Edit's own table; unmapped layers get auto-assigned numbers, reported).
ledit.push_celllocalcell*, ledit_cell, namespace, sessionPush a KLayout cell's flat geometry into an L-Edit cell through the bridge: boxes, paths (->wires) and polygons transfer; text and sub-instances are counted and reported, not silently dropped. Layers are created in L-Edit with the KLayout layer NAME when one exists (else L<gds>D<dt>) plus the GDS numbers. Draw is append-only on the L-Edit side -- pass a fresh ledit_cell to regenerate.
ledit.import_cell_treelocalcell*, namespace, sessionImport an L-Edit cell AND ITS HIERARCHY into KLayout as real cells + instances (ledit.import_selection reads the current SELECTION and drops instances by design). Cells are rebuilt children-first, layer identity travels by NAME + GDS number, and each target cell is recreated so re-importing is idempotent. Shapes L-Edit exposes without a convertible outline are reported in not_convertible.
ledit.push_cell_treelocalcell*, clear, expect_file, namespace, sessionPush a KLayout cell AND EVERY CELL BELOW IT into L-Edit, keeping the hierarchy: cells are created children-first and instances are rebuilt as real instances (ledit.push_cell is the FLAT one -- it refuses sub-instances). Idempotent by default (each cell is cleared before redraw, since L-Edit's draw only appends). The whole tree goes over as ONE ordered batch. Instances L-Edit placement cannot express exactly (magnification, non-orthogonal rotation, skewed array) are REPORTED in unsupported_instances, never approximated. For a whole design rather than a subtree, a GDS file via import_gds is cheaper.
ledit.show_celllocalcell*, namespaceOpen (or raise) a layout window on an L-Edit cell and make it the visible cell -- the answer to 'show me X in L-Edit' / 'open cell X'. Reports window_opened (a new window was created) and via. Cell names come from ledit.status cells[]. Read-only on the design: no geometry is touched.
ledit.set_cell_hiddenlocalcell*, hidden*, namespaceHide an L-Edit cell from the cell lists (the 'Hide In Lists' flag that auto-generated T-Cell variants carry) or show it again -- 'hide cell X' / 'unhide X'. Round-trips through L-Edit's own flag (LCell_SetShowInLists) and reports the value read back; if the stored property disagrees with the flag both are reported (hidden, hidden_property), never one picked silently. ledit.status cells[] reads the same flag.
ledit.list_windowslocalnamespaceList every open L-Edit window (layout, text, log, ...) with its index, file, cell and whether it is the visible one. Works with no design open. The index is the handle ledit.close_window takes.
ledit.close_windowlocalcell, file, index, namespaceClose L-Edit window(s): by cell name (all layout windows on that cell; pass file when two open designs share the name) or by index from ledit.list_windows. Reports matched/closed. No last-window guard: closing a design's last window may close that design, so confirm with the user before closing windows you did not open.
ledit.layout_viewlocalcell, home, namespace, rect_umOne verb for an L-Edit cell's view: with neither rect_um nor home it READS the current view; rect_um=[left,bottom,right,top] (microns) SETS it (zoom to that area -- 'zoom to the device' / 'look at this region'); home=true resets to the cell's home view. Always returns the view AFTER the call plus has_window (without an open window the rect is not meaningful -- ledit.show_cell first). Default cell = the visible cell.
ledit.save_imagelocalcell*, dpi, height_px, namespace, path*, rect_um, width_pxRender an L-Edit cell to an image file (PNG/BMP/JPG by extension) via LCell_SaveImageToFile -- whole cell by default, or rect_um for an area. USER-REQUESTED ARTIFACT ONLY: call it when the user asks for a picture/screenshot of the L-Edit cell, never as verification evidence (verify with ledit.status / get_cell geometry, same rule as KLayout view.screenshot). The folder must already exist. Reports path and bytes.
ledit.delete_celllocalcell*, force, namespaceDESTRUCTIVE: delete an L-Edit cell by explicit name. Refused (unless force=true) when the cell is the visible cell, is instanced by other cells (referenced_by names them -- deleting it deletes those instances too), or is a T-Cell generator. Confirm with the user before deleting anything they drew; klink's own scratch cells (push targets, probes) are fair game.
ledit.rename_celllocalcell*, namespace, new_name*Rename an L-Edit cell; refuses when new_name is already taken (ledit.status cells[] shows what exists).
ledit.delete_objectslocalcell*, layer, namespace, rect_umDESTRUCTIVE: delete shapes inside an L-Edit cell by layer and/or area. rect_um=[left,bottom,right,top] (microns) deletes only objects whose bounding box lies entirely INSIDE the rect (a route merely crossing it stays); layer restricts to one layer; give at least one. Instances are never deleted here (clear_cell resets a whole cell). Reports deleted and by_layer. Confirm with the user first unless the cell is klink's own.
ledit.close_designlocaldiscard, file*, namespaceClose an OPEN L-Edit design by name (from ledit.status designs[]). A design with unsaved changes is refused unless discard=true. Closing a design's last WINDOW does not close it -- this does. Use it to drop scratch designs klink created (new_design); confirm with the user before closing theirs.
ledit.run_drclocalcell, namespace, rect_umRun L-Edit's own DRC on a cell (whole cell, or rect_um=[left,bottom,right,top] microns for an area) with the design's loaded rule set; reports the error COUNT and status only -- L-Edit v16.3 does not expose the violation geometry through the UPI; for violation geometry use export_gds and klink's KLayout-side drc tools. Also reports the rule count. Refused when the design has no DRC rules.
ledit.drc_summarylocalcell, namespaceRead the last DRC result of an L-Edit cell without re-running: errors and status (needed = never run or stale, passed, failed). errors is null until a run has happened.
ledit.export_gdslocalcell, cell_name_length, include_hierarchy, log_path, namespace, path*Write an L-Edit design (or one cell with its hierarchy) to a GDS file with LFile_ExportGDSII -- the cheap L-Edit -> KLayout return path: then layout.file_info / layout.import_file in KLayout read it as-is (no padding needed in this direction). GDS limits cell names to cell_name_length (32 standard; KLayout accepts longer, raise it for a round trip of long klink names). The folder must already exist; the export log is written to log_path (default next to the bridge inbox) and scanned for errors.

15 · Escape hatch (pya exec, events, recorder) escape_hatch · 9 tools

klink.find_tools domain="escape_hatch"

Prefer typed RPCs. exec.python runs raw pya for operations no typed RPC covers / debugging / compact one-offs (exec.reset clears its namespace); it still schedules recorder + layout-diff detection. events.* (channels/status/subscribe/unsubscribe) is the live event stream the bridge subscribes to for SEND memory — usually you read interaction.* instead. recorder.* (start/stop/status) generates a replay SCRIPT (not a literal RPC log); a bulk RPC may expand into replay actions. Check recorder.status before tests so you never clobber a user recording.

ToolKindParamsFunction
events.channelsrpcList event channels the server can push. Subscribe to a subset via events.subscribe. Events are delivered as NDJSON frames with {"event": "<name>", "data": {...}}.
events.statusrpcReturn event subscription and SignalHub diagnostics for the calling connection. Use this to debug whether selection_changed and other interaction events are bound and subscribed.
events.subscriberpcchannels*Subscribe the calling connection to one or more event channels. Unknown channels are silently ignored (check 'accepted' in the response). Call events.channels for the full list.
events.unsubscriberpcchannelsUnsubscribe the calling connection from one or more channels. Pass an empty list or omit to keep state unchanged; pass '*' to drop all subscriptions.
exec.pythonrpccode*, protect_cellview, reset, result_mode, stderr_limit, stdout_limitEscape hatch: run arbitrary Python code in KLayout's Qt main thread. Full pya access, full filesystem access - equivalent to running a macro from the IDE. Pre-bound globals: pya, mw (main window), view (current LayoutView), layout (active Layout). State persists across calls ON THE SAME CONNECTION. Pass reset=true to wipe the namespace first. stdout/stderr are captured and returned as strings. If the last top-level statement is an expression, its value comes back as return_value (Jupyter-style); otherwise had_result=false. Exceptions in user code do NOT fail the RPC - they come back inside result.exception along with any stdout/stderr captured before the raise. Only malformed requests (missing code, oversized, syntax error) return ok=false. Callers writing LLM feedback loops should branch on result.exception.
exec.resetrpcClear the per-connection Python namespace used by exec.python. Equivalent to calling exec.python with reset=true and no code. Useful when a client wants a fresh sandbox without running anything else.
recorder.startrpcoutput_pathStart recording all layout-mutating events and translating them into a replayable Python script. Idempotent: calling while already recording returns the current status without starting a new session. Pass output_path to override the default location (~/Documents/klink_recordings/klink_record_YYYYMMDD_HHMMSS.py).
recorder.statusrpcReturn current recorder state (is it recording, how many events and translated actions so far, configured output path). Safe to call at any time.
recorder.stoprpcoutput_pathStop the active recording and write the replayable script to disk. Returns the final stats plus wrote (bool) indicating whether the file was successfully written. Idempotent: calling when not recording returns wrote=false with the last known status.

Example.

recorder.status                 # confirm nobody else is recording first
recorder.start
# ... some typed-RPC edits + manual GUI edits ...
recorder.stop                   # produces <name>.py and standalone <name>_pya.py

# only use the escape hatch when no typed RPC covers it
exec.python code="print(layout.top_cell().name); print(len(list(layout.each_cell())))"

recorder produces two files: <name>.py (KLinkClient-based replay) and <name>_pya.py (a standalone pya version runnable inside KLayout).

See these tools composed into real loops and runnable demos in the Tutorials.