NXKnowledge

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:

  1. Per-call batch is slow. Every run_journal.exe invocation boots "most of NX" — seconds at best, minutes under load. Interactive-feeling orchestration needs the session to persist between commands.
  2. 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 with NXOpen, session, theSession, ufs) → reply with one JSON line carrying captured stdout, an optional result value, 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