Altium - the Altium Bridge
Public Made by Adomby adom
Adom Bridge for Altium Designer: a resident DelphiScript agent gives the AI eval, the 1,100-command process launcher, all 8 EDA surfaces with screenshots, compile/ERC, DRC, OutJob outputs, native SchLib/PcbLib authoring with STEP 3D bodies, and dialog handling by PID.
main
| Name | Last updated |
|---|---|
| .cargo | 1mo ago |
| fixtures | 1mo ago |
| src | 8h ago |
| Cargo.lock | 1mo ago |
| Cargo.toml | 1mo ago |
| README.md | 1mo ago |
altium-bridge (Rust) — the bridge process, rewritten
The next major version of the bridge process (the part ab spawns on the Windows box). Decisions on record (John, 2026-08-31):
- The bridge calls ab's verbs for everything desktop-shaped — window enumeration, dialogs,
screenshots, UIA, shell, taskbar — instead of re-implementing Win32. No
windows-rs, no ctypes. - The DelphiScript agent shrinks to generic primitives; the bridge becomes the brain. Only ~213 of the agent's 3,675 lines touch Altium's API; the rest (JSON assembly, parsing, tallies, filters) moves here where it is typed, testable against recorded board reads, and never needs an Altium restart to change.
- No standalone MCP server. External agents reach Altium through
adom-bridge-clion PATH (the Adom Anywhere lane), same as this container does.
Status — 2026-09-01: cold bootstrap proven, board-read primitive landed
26 of the Python bridge's 95 verbs (27%). The Python bridge (../src, 0.1.14) stays the shipped
runtime until Rust covers the paths users actually hit.
Proven live on the box this session:
- Cold bootstrap through the
-Rlane: 1.59 s, new generation installed, heartbeat up, zero dialogs. This was the last unproven path. Two bugs it flushed out: ab's direct API refusesshell_execute(412 — CLI-only, with pull_file/send_files/hd_send_files), so the bridge owns its child processes directly; andCommand::arg()re-quotes an argument containing a space, which Altium cannot parse —raw_argis required (the Python bridge hit the identical trap in August). - Board reads.
read_boardis ONE agent round trip for components + nets + layers + copper- rules, composed in DelphiScript from the EXISTING readers (no new Altium API surface, so it
cannot introduce an engine-halting identifier).
src/board.rsderives every view from it:board_summary232 ms,rules_by_kind152 ms, raw read 157 ms.
- rules, composed in DelphiScript from the EXISTING readers (no new Altium API surface, so it
cannot introduce an engine-halting identifier).
- The shaping is now tested with no Altium anywhere.
fixtures/read_board.jsonis a recorded live board read;cargo testruns 8 tests against it on any machine. They have already earned their keep: one caught a dispatch bug (an innermatchon the un-stripped verb name, soaltium_board_summarysilently returned the raw read), and one caught a wrong assumption of mine about the Fanout rule group. - A real view the agent could not give cheaply: Altium emits
TRuleKindas a bare ordinal.rule_kind_name()maps 31 ordinals to names, read off a live default rule set (each default rule is named after its own kind — observed, not guessed); unknown ordinals render askind <n>rather than being invented, and a test fails loudly if a board carries an ordinal we do not know.
Original v0 vertical slice
Speaks the existing agent's file-IPC protocol unchanged (request.txt / request.payload /
response_<id>.json / hb.txt), so it serves these verbs with zero template changes:
status, readiness, agent_status, eval, run_command, open_document, describe (ab's
Verbs tab renders it verbatim — one entry per verb with input/output schemas, hints and pitfalls),
uninstall (the cascade verb: drops the agent's stop file and clears the agent workspace, never a
user design file), and the Phase 1 introspection surface — list_designators, component_data, component_pins, list_nets,
net_connections, layers, copper_layers, read_rules, set_layer_visibility,
footprint_primitives, schematic_data.
/health detects Altium through ab's desktop_find_window and parses the title the same way the
Python detector does (edition word without version parens — issue #2 — included).
Not yet ported: launch/close, agent bootstrap/recover (X2 -R lanes), dialogs, surfaces with
frames, checks/outputs, library authoring, boards, licence driving, web-control bundle install.
Until parity, the Python bridge (../src) stays the shipped runtime.
The gate is OPEN — the runtime kind is "exe" (ab, on #987)
ab shipped the compiled-binary runtime kind; it is spelled spawn.kind: "exe" (there is no
"binary" alias). ab spawns spawn.entrypoint directly with --port <n> appended, same env
contract as node/python bridges (ADOM_BIND_HOST, ADOM_DIRECT_API_URL + bridge token, forwarded
X-Adom-Caller-*), same health probing, PID tracking, log capture and reap/respawn supervision.
bridge_readiness reports runtime: "none", runtimeReady: true, so no spurious needs-runtime state.
Three things to know:
- Integrity is gated at the zip, not the exe. ab sha256-verifies the release zip against the
manifest before extracting. A spawn-time re-hash was deliberately not added (the reference hash
would live in
bridge.jsonnext to the exe — same tamper domain, no trust boundary). - Manifests are single-zip today, so a compiled bridge is per-OS. Fine here: Altium is Windows-only.
- Set
killImageNameto the exe basename so stop/kill is image-verified. Ours is inbridge.jsonalongside this README (the shipped Python bridge keeps../src/bridge.jsonuntil parity).
Reference implementation: scripts/sample-bridges/hello-rust in adom-inc/adom-bridge — page
adom/hello-rust-bridge, v1.3.1. ~290 lines covering --port, ADOM_BIND_HOST, the /status
led/summary/tooltip self-report, caller-header logging, describe, and the uninstall cascade.
Build (cross-compile from the container; the box has no MSVC linker)
# one-time: rustup + the windows-gnu target + mingw cross linker
curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
rustup target add x86_64-pc-windows-gnu && sudo apt-get install -y mingw-w64
# build
cd bridge-rs && cargo build --release --target x86_64-pc-windows-gnu
# -> target/x86_64-pc-windows-gnu/release/altium-bridge.exe
Test on the box (manual, until ab spawns it)
adom-bridge --ai-thread "<t>" send_files '{"filePaths":["<repo>/bridge-rs/target/x86_64-pc-windows-gnu/release/altium-bridge.exe"]}'
# on the box (PowerShell), with the Python bridge's agent already resident:
# $env:ADOM_DIRECT_API_URL='http://127.0.0.1:24680'; & "$HOME\Downloads\altium-bridge.exe" --port 8991
curl -s http://127.0.0.1:8991/health
curl -s -X POST http://127.0.0.1:8991/command -H 'content-type: application/json' -d '{"command":"altium_list_designators","args":{}}'
Both bridges can read the same resident agent (the inbox is serialized by the agent, one request at a time) — but only ONE session should drive it at a time; see the dev skill's one-driver rule.
Naming
Verbs say what the caller wants, in the caller's words: read_* to get data out, write_* /
apply_* to put it back. The board-wide read is read_board, not dump_board — "dump" describes
the implementation (a blob out of memory) rather than the intent, and it has no counterpart, so it
breaks the symmetry the moment writes arrive. (John, 2026-09-01, on the first draft: "that's not a
great verb name ... we should probably pick a better verb thats more like a read or write*"*.)
Layout
| File | Role |
|---|---|
src/main.rs |
--port, ADOM_BIND_HOST, /health /status /verbs /command, envelope timing |
src/ab.rs |
loopback client to ab's direct API (token badge, forwarded X-Adom-Caller-*, delegate) |
src/agent.rs |
the file-drop protocol to the resident DelphiScript agent |
src/health.rs |
Altium detection via ab + the title parser |
src/verbs.rs |
dispatch; v0 views over existing agent commands |
Next
- Port lifecycle (launch/close, bootstrap, recover with the busy-notice reaper via ab).
- Extend the read primitives: give
read_boardthe stack-in-use flag it still lacks, plus aread_schematiccompanion, and re-express the remaining introspection verbs as views over them. Writes get the mirror-image naming (write_*/apply_*). - Surfaces + frames (all via ab screenshots), checks/outputs, library authoring, boards.
- Swap: publish the zip (
altium-bridge.exe+bridge.json+BRIDGE_VERSION+ README) as a release asset, repoint the manifest, and retire../src.
Two script-engine laws this design depends on
Measured on the box 2026-08-31, and the reason the agent shrinks to dumb primitives:
Breakinside aTry..Exceptdoes not break — the engine raises it, theExcepteats it, and a 20-iteration retry loop always runs all 20 with itsSleep(10). That was the entire mysterious ~300 ms per agent file write. Flag-based loops (While (Not Done)) fix it.hb.txtmust be tmp+rename; responses can be written directly — readers hold the heartbeat's final path open, so a directSaveToFileonto it raisesEFCreateErrorand halts the engine with a modal. A freshresponse_<id>.jsonhas no readers and writes in 0 ms.
With both applied, in-engine file writes measure 0 ms and the round-trip floor is the poll interval, not I/O — which is exactly the budget this bridge's read-primitive design assumes.