JITX AI Agent Integration and Interactive Design Sessions#
An AI agent with terminal and project-file access can inspect and edit a live JITX board through the JITX CLI commands jitx design session and jitx design exec. The session keeps the Python design connected to the runtime; each exec runs a short Python script against its current layout. An engineer can view the same design in the board editor while the agent works.
This note walks through connection, layout inspection, a placement change, and result inspection. It then covers batches, callbacks, and recovery. The jitx.run API is experimental. Pin the versions used for a project and recheck the examples when upgrading.
1. Prepare the project and agent#
Use an existing JITX Python project with a discoverable Design subclass. You need Python 3.12 or newer, the project’s dependencies, an installed and licensed native runtime, and an agent allowed to read and write project files and run local commands. The terminal commands below assume the project root is the working directory.
Run the agent’s commands, Python session, and runtime on the same host for this procedure; the model may run elsewhere. Install the JITX skills in your agent and give it this note: the skills provide design guidance, while terminal access provides the connection to the running session. This workflow uses the JITX CLI directly and requires no additional MCP server.
Use the JITX Python packages and a compatible native runtime. Keep the resolved package versions in the project lockfile.
Layer |
Name |
Version |
|---|---|---|
Python API and CLI |
|
|
Protocol support |
|
|
Standard library |
|
|
Native runtime |
Installed by |
|
Agent guidance |
|
|
For a project managed with uv, review the existing dependency constraints and add the packages from PyPI or your organization’s approved mirror:
uv add "jitx~=4.6.0" "jitxlib-standard~=4.6.0"
uv sync
The ~= constraints allow compatible patch updates; the lockfile records the exact installed versions. Keep that lockfile with the design. If your organization uses an offline bundle, obtain these packages and their dependencies through its approved distribution route.
Activate the project environment in each terminal, including the agent’s command environment:
Shell |
Command |
|---|---|
macOS or Linux |
|
Windows PowerShell |
|
An agent that starts a fresh shell for every command should use uv run jitx ..., or the project environment’s full executable path, consistently. A global jitx may be a different version.
Check the CLI, install the native runtime if necessary, and inspect the version actually selected:
jitx --version
jitx runtime resolve-version --prod
jitx runtime install --prod
jitx runtime introspect
jitx design session --help
jitx design exec --help
The installer selects the production runtime compatible with the project’s Python package. Confirm that runtime introspect reports the intended version. Complete JITX authentication and licensing before starting a design session; use your JITX contact’s instructions if authorization is missing.
2. Start a persistent design session#
Find the exact design name, then start the project runtime:
jitx design find
jitx runtime start --background
jitx runtime status
In Terminal A, start the session and leave it running:
jitx design session mypkg.main.MyDesign
Replace mypkg.main.MyDesign throughout this note with the discovered name. The session submits the design, captures the layout, and listens for scripts. It is a foreground process; use a dedicated terminal or a persistent process facility in your agent host.
In Terminal B, run a read-only readiness check:
jitx --format json design exec mypkg.main.MyDesign -c "def main(rd): return rd.name"
A successful response has this shape:
{"result": "mypkg.main.MyDesign"}
If the probe reports exec_failed with “message could not be delivered” immediately after startup, the initial capture may still be running. Read Terminal A and retry this read-only probe after startup finishes. Do not use a layout-changing script as a readiness probe.
Open the editor if you want to follow the work visually:
jitx ui open --board --design mypkg.main.MyDesign
The editor is optional for CLI execution. design exec addresses the existing session by name; it does not load a new copy of the design on every request.
Agent writes script and runs design exec
|
v
Persistent Python session <----> JITX runtime <----> Board editor
current design objects layout and checks engineer
|
v
JSON result and streamed output
3. Read before changing the layout#
The example scripts accompany this note. Copy the five .py files into the project root so the filenames in the commands below resolve. Adapt the component paths before running the edit scripts. The first script, inspect_layout.py, reads the layout and issue report:
from jitx.component import Component
from jitx.run.runtime import SyncRuntimeDesign
def main(rd: SyncRuntimeDesign) -> dict[str, object]:
if not rd.layout.sync(timeout=30):
raise TimeoutError("Layout work did not settle within 30 seconds")
rd.capture()
report = rd.issues()
components = []
for trace, component in rd.query(Component):
placement = (
trace.transform * component.transform
if component.transform is not None else None
)
components.append({
"path": str(trace.path),
"placement": None if placement is None else {
"position_mm": list(placement.translation),
"rotation_deg": placement.rotation,
"side": placement.side.name,
},
})
return {
"design": rd.name,
"components": components,
"checks_complete": report.complete,
"error_count": len(report.errors),
"issues": report.text(),
}
The result reports each component’s board-absolute position_mm, rotation_deg, and side (Top or Bottom). An unplaced component has a null placement. Run the script and retain the baseline result:
jitx --format json design exec mypkg.main.MyDesign inspect_layout.py
RuntimeDesign is the session’s Python view of the runtime design. rd.root provides its captured object hierarchy; rd.query(...) traverses it, and rd.layout sends physical-layout operations. A capture replaces rd.root, so retrieve components and other objects again after each rd.capture(). Capturing can also assign remaining pins in a partially assigned design; account for that when inspecting one.
Inspection paths are relative to rd.root: an output path of circuit.u1 corresponds to the Python expression rd.root.circuit.u1. Here circuit and u1 are attributes defined by the project source. A board reference designator such as U1 is not necessarily the Python attribute u1. Use the inspection output and source together to find the correct object.
Choose a wait bound appropriate for the design; the example uses 30 seconds. sync() returns False on timeout; a successful command exit alone does not prove that routing finished. Also check report.complete: an empty error list with unfinished checks is not a clean result. Physical-design checks do not replace schematic review, signal-integrity validation, or manufacturing review.
4. Make one placement change and check the result#
Read the project source and the inspection output to identify the intended component. Adapt the supplied place_component.py to the actual object path; this example uses the full path rd.root.circuit.u1 throughout:
from jitx.layerindex import Side
from jitx.run.runtime import SyncRuntimeDesign
def main(rd: SyncRuntimeDesign) -> dict[str, object]:
rd.capture()
print("Place circuit.u1 at (10, 5) mm, 90 degrees, top side")
placed = rd.layout.place(rd.root.circuit.u1, (10, 5), 90, on=Side.Top)
if not rd.layout.sync(timeout=30):
raise TimeoutError("Placement applied; background work is still pending")
rd.capture()
report = rd.issues()
return {
"placement": str(placed),
"checks_complete": report.complete,
"error_count": len(report.errors),
"issues": report.text(),
}
Execute the reviewed script:
jitx --format json design exec mypkg.main.MyDesign place_component.py
jitx --format json design exec mypkg.main.MyDesign inspect_layout.py
Positions are board-absolute millimeters; rotations are degrees. The examples specify position, rotation, and side explicitly because omitted placement settings retain their current runtime values. The returned placement describes the operation response. Use the second inspection to compare the captured placement and issue report with the baseline and inspect the board editor. The return value is JSON-compatible data; convert JITX objects to explicit fields or a string before returning them.
A Python exception does not roll back earlier successful requests. In the example, a timeout occurs after placement has been applied. Inspect the current layout before retrying a failed edit. These operations change the runtime layout; they do not rewrite the Python source. Keep source edits and the project’s saved layout state with the design through your normal save and version-control workflow.
5. Give the agent a bounded task#
Copy and adapt this prompt after the read-only probe succeeds:
Use this project's pinned environment and the JITX skills.
The runtime and persistent session are already running in this project.
Design name: mypkg.main.MyDesign
Read the supplied application note and the project source.
First run inspect_layout.py and report the current components,
check completeness, and physical-design errors. Identify the exact component
path for the requested change.
Task: place circuit.u1 at board position (10, 5) mm, 90 degrees, top side.
Write a short script with main(rd) and use jitx --format json design exec.
Check sync()'s return value and capture again. Then run inspect_layout.py to
verify the captured position, rotation, and side and compare issues with baseline.
Report the actual change, remaining errors, and any unfinished checks.
Keep the existing session. Do not change topology, stackup, constraints,
or unrelated placements. If the command fails after a change, inspect current
state before retrying. Stop when the requested placement and checks are reported.
The task author chooses the allowed changes. Session scripts execute with the session process’s filesystem and environment access; they execute ordinary Python. Give the agent the same project permissions and data-handling controls you would use for other code execution. Design source and script output included in a model conversation follow the selected AI service’s deployment and data policies.
6. Understand command results and script lifetime#
Item |
Behavior |
|---|---|
Entry point |
The file runs afresh; then |
Synchronous function |
|
Asynchronous function |
|
Successful JSON output |
stdout contains one |
Script output in JSON mode |
stderr carries newline-delimited JSON events with |
Script failure |
Nonzero exit with an |
Script file |
The command sends an absolute file path; the session must be able to read that path. |
Environment |
The script uses the session’s working directory and environment, not changes made only in the caller’s shell. |
Check exit status before parsing a successful-result envelope. Do not merge stdout and stderr if another program consumes the JSON. Use print() for diagnostic output and return for the result.
Define components, circuits, and designs in the project package before starting the session. Defining new JITX classes inside an executed script can fail because design instantiation is already active. Modules first imported by a script are removed from the import cache after execution; modules loaded with the design remain loaded. Restart the session after editing design code or a module it already imported. Use a design-loaded helper module for intentional state that must remain accessible between requests.
7. Use batches and callbacks when needed#
A batch sends several operations as one request. If the runtime rejects the request, it rejects the operations together. Batching does not guarantee that requested routing is feasible or that later checks pass.
The following examples use the same capture, bounded wait, and issue-report steps as the single-placement script. Replace rd.root.circuit.u1 and rd.root.circuit.u2 with your design’s full object paths. Each example changes placement; run it only after reviewing its target positions.
Batch placement#
Save this as batch_place.py. It places circuit.u1 at (10, 5) mm, 90 degrees, top side, and circuit.u2 at (0, -12) mm, 0 degrees, bottom side:
from jitx.layerindex import Side
from jitx.run.runtime import SyncRuntimeDesign
def main(rd: SyncRuntimeDesign) -> dict[str, object]:
rd.capture()
with rd.layout.batch() as batch:
first = batch.place(rd.root.circuit.u1, (10, 5), 90, on=Side.Top)
second = batch.place(rd.root.circuit.u2, (0, -12), 0, on=Side.Bottom)
if not rd.layout.sync(timeout=30):
raise TimeoutError("Batch applied; background work is still pending")
rd.capture()
report = rd.issues()
return {
"placements": {
"circuit.u1": str(first.placement),
"circuit.u2": str(second.placement),
},
"checks_complete": report.complete,
"error_count": len(report.errors),
"issues": report.text(),
}
Run the batch and inspect the captured result:
jitx --format json design exec mypkg.main.MyDesign batch_place.py
jitx --format json design exec mypkg.main.MyDesign inspect_layout.py
Batch handles resolve after the batch is sent. A via or control point created in a batch cannot be a route endpoint in that same batch; create it first, then use its returned handle in a subsequent request. Routing, vias, control points, pours, and regions require valid objects, nets, layers, and via definitions from the actual design. Consult the installed jitx.run.layout API before extending these examples.
Asynchronous placement#
Save this as async_place.py. It places circuit.u1 at (-10, 0) mm, 0 degrees, top side. Await the placement, wait, capture, and issue-report methods:
from jitx.layerindex import Side
from jitx.run.runtime import AsyncRuntimeDesign
async def main(rd: AsyncRuntimeDesign) -> dict[str, object]:
await rd.capture()
placed = await rd.layout.place(
rd.root.circuit.u1, (-10, 0), 0, on=Side.Top
)
if not await rd.layout.sync(timeout=30):
raise TimeoutError("Placement applied; background work is still pending")
await rd.capture()
report = await rd.issues()
return {
"placement": str(placed),
"checks_complete": report.complete,
"error_count": len(report.errors),
"issues": report.text(),
}
Run the asynchronous example and inspect the captured result:
jitx --format json design exec mypkg.main.MyDesign async_place.py
jitx --format json design exec mypkg.main.MyDesign inspect_layout.py
Observe a placement change#
Layout monitors observe changes from scripts and the UI. Save this as monitor_move.py. It moves circuit.u1 to (5, 5) mm, 0 degrees, top side, and prints placement notifications. The monitor is scoped to one operation so rerunning the script does not accumulate listeners:
from jitx.layerindex import Side
from jitx.run.runtime import SyncRuntimeDesign
def main(rd: SyncRuntimeDesign) -> dict[str, object]:
rd.capture()
def moved(instances) -> None:
print("moved:", [str(instance) for instance in instances])
with rd.layout.monitor.placements(moved):
placed = rd.layout.place(rd.root.circuit.u1, (5, 5), 0, on=Side.Top)
if not rd.layout.sync(timeout=30):
raise TimeoutError("Move applied; background work is still pending")
rd.capture()
report = rd.issues()
return {
"placement": str(placed),
"checks_complete": report.complete,
"error_count": len(report.errors),
"issues": report.text(),
}
Run the monitored move and inspect the captured result:
jitx --format json design exec mypkg.main.MyDesign monitor_move.py
jitx --format json design exec mypkg.main.MyDesign inspect_layout.py
The callback output is diagnostic; use the fresh inspection to verify the result even if no notification is printed.
For ongoing observation, retain the returned monitor handle in a design-loaded module and call close() when finished. An unclosed registration stays active after exec returns; running the registration script again adds another callback. Routes, vias, controls, regions, and pours have monitors that receive Changes objects with added, changed, and removed collections.
Callbacks run on the session event loop. Keep them short and nonblocking. A blocking request or capture through a synchronous design from a callback raises an error. Use an asynchronous callback with an asynchronous design, or queue work for a later request. Events have no cause field: guard callbacks that modify the layout against reacting to their own changes, and make duplicate notifications harmless. Callback output is forwarded to an active exec; otherwise it goes to the session terminal.
Advanced clients can exchange typed UserEvent messages through rd.layout.emit(...) and rd.layout.monitor.user(...). Define the event class in an importable shared module. Event data must be JSON-compatible; referenced design objects are resolved in each receiving client’s tree. The sending session does not receive its own event.
8. Recover and finish#
Symptom |
Action |
|---|---|
|
Check the project CLI version and environment; use the project’s JITX environment. |
|
Run |
|
Check the exact session name, Terminal A, and completion of the initial capture. |
Component path fails |
Inspect the design hierarchy; example names such as |
|
Read the rejection, inspect current layout and constraints, and revise the operation. |
Second session starts |
The previous session ends with |
An edit times out |
Inspect current state before retrying; the edit may already have applied. |
A script runs indefinitely |
Requests are serialized, so later |
Fresh source changes are absent |
Stop and restart the session to reload the design’s modules. |
Runtime events do not refresh every captured field. Capture before relying on reference designators, control-point shapes, region input polygons, route-owned pours, or final pour connectivity. Orphaned pour islands and unresolved pin-assignment nets can differ between an event update and a fresh capture. A rerouting route retains its old event geometry until routing finishes or becomes blocked. Use a settled capture for checks that depend on final geometry, and refetch object references afterward.
If a capture fails, retain the traceback and restart the session before continuing. Use only a successful capture for subsequent inspection.
Stop the session when finished:
jitx design session mypkg.main.MyDesign --stop
The runtime and editor have separate lifetimes. Stop the project runtime only when other users and tools no longer need it:
jitx runtime stop
For work on another machine, run the agent’s JITX commands on the runtime host, for example through its remote terminal. Forwarding the editor port alone does not give a remote agent the project filesystem or session-file paths. See remote runtime access.