Community Knowledge > Headless and Batch Automation
A Persistent Headless NX Session: the Socket-Dispatcher Pattern (What Replaces NXOpen Remoting)
The problem
Driving NX programmatically from another process (an AI agent, a web service, a test harness) hits two walls:
- Per-call batch is slow. Every
run_journal.exeinvocation boots "most of NX" — seconds at best, minutes under load. Interactive-feeling orchestration needs the session to persist between commands. - Official remoting is effectively dead for modern stacks. The
documented options are .NET Framework Remoting and Java RMI only —
never Python or C++ — and the new .NET Core stack (
NXBIN\managed_core, net10.0 in NX 2606) contains no remoting types at all (Microsoft removed System.Runtime.Remoting from .NET Core). There is no gRPC or replacement RPC layer in the install.
The pattern
Run a journal that never exits and speaks a trivial protocol:
- A launcher spawns
run_journal.exe worker.py(stdin detached — see the stdin-stall article) and waits for a ready file the worker writes (pid, port, release) — which also lets later client processes adopt a still-running session instead of booting a new one. - The worker binds
127.0.0.1:<port>, then loops: read one JSON line →exec()the code in a persistent namespace (pre-bound withNXOpen,session,theSession,ufs) → reply with one JSON line carrying captured stdout, an optionalresultvalue, or a full traceback. ~200 lines, stdlib-only (NX's embedded Python has no pip — by design the worker needs nothing installed). - State persists across calls (variables, imports, open parts), so the client can work incrementally, exactly like a person at a console.
The two rules that make it robust
1. One thread, one request at a time. NXOpen is not thread-safe; every
call must run on the journal's main thread. So the worker is deliberately
single-threaded — the accept loop, the exec, and the reply all live on the
main thread, and the client serializes requests. Don't be tempted to thread
the worker; queue on the client side instead.
2. Socket errors must never unwind the session. The corollary of
single-threading: a client that times out and closes its socket leaves the
worker mid-exec. When the worker finishes and its reply write (or next
read) raises ConnectionResetError, that exception must be caught and
answered by dropping the connection and re-accepting — in testing, a
reset that escaped the conversation loop unwound serve() and took the
whole NX session down with it. Wrap the entire per-connection conversation
in except OSError: pass. Timeout semantics then become clean: a timed-out
call is still running in NX; the client reconnects afterwards (or
force-restarts the worker if it's truly wedged).
The corollary: crash isolation for fragile calls
A persistent session is precious, so anything deprecated or crash-prone
(BatchShade image rendering being the proven example) belongs in a
disposable one-shot run_journal process, never in the live worker. With
DUP_GROUP licensing the extra session is free on the same machine.
Reference implementation
nx-mcp (private repo: chrisdamonmartini/nx-mcp) implements the full
pattern as an MCP server — session manager with spawn/adopt/timeout/restart,
the stdlib worker, one-shot isolation for journals and rendering, plus a
full-API index introspected from the installed release (25,792 classes /
215,526 methods on NX 2606) for discoverability.
Source: first-party design + verification against NX 2606, 2026-07-12; remoting status per NXOpen Programmer's Guide (executing_remote_processes) and a strings scan of NXBIN managed_core; reference implementation https://github.com/chrisdamonmartini/nx-mcp (worker/nx_worker.py) · retrieved 2026-07-12