Skip to main content

Executor Architecture Overview

Source files
  • src/qtg/infrastructure/executors/__init__.py
  • src/qtg/infrastructure/executors/registry.py
  • src/qtg/infrastructure/executors/local_adapter.py
  • src/qtg/infrastructure/executors/remote_proxy.py
  • src/qtg/infrastructure/bootstrap.py

1. Architecture Overview

The Movement Guard (MG) v3 executor is a node-level execution engine. Each node in a movement plan is bound to one executor, and the executor advances node state through five lifecycle methods (preflight / prepare / submit / observe / recover). Depending on the executor deployment model, they are divided into two categories: Both classes implement the same ExecutorProtocol interface, so the dispatcher/observer can use them without distinguishing between local and remote.

2. ExecutorProtocol

@runtime_checkable protocol defined in domain/protocols.py:
Role of each method: In addition, there are cancel and close methods:
  • cancel: supported only by some executors (for example, CEX withdrawal cancellation)
  • close: cleans up resources such as internal HTTP clients

3. LocalExecutorAdapter

File: infrastructure/executors/local_adapter.py Wraps an in-process handler object with the ExecutorProtocol interface.
All methods delegate directly to self._handler. Core behavior: Note that the health check does not call the handler and always returns ok: True.

4. RemoteExecutorProxy

File: infrastructure/executors/remote_proxy.py A proxy that delegates executor calls to an external HTTP service.

4.1 Constructor

4.2 HTTP JSON Protocol

All calls are JSON requests in the form POST {base_url}/{method}. Note that the submit body shape differs from the other methods. context and prepared_action are sent as separate keys, and additional kwargs such as sign_result are merged at the top level. Auth header: if auth_token is configured, every request includes Authorization: Bearer {token}.

4.3 close behavior

If client is injected externally, _owns_client = False and aclose() is not called on close. Cleanup happens only for internally created clients.

5. Executor Registry

File: infrastructure/executors/registry.py Registers and retrieves executors from a module-level dict[str, object].

5.1 executor_binding and registry lookup

MovementRequestNode.executor_binding is stored in the DB as JSON and has the following shape:
When get_executor(binding) receives this dict, it extracts binding["executor_key"] and looks it up in the registry. A plain string is also allowed.

5.2 Built-in executor key list

The Stargate executor ships in the Pro distribution and is registered through the package/loader gate — it is excluded from the OSS distribution. Lighter registration for lighter_secure_withdraw requires the Pro loader, an AwsKmsEvmSigner, configured L1 owner, contract, and account index, successful KMS address resolution with owner == KMS address, and L1 RPC via an explicit Lighter endpoint or EVM endpoints.

6. Health Check Protocol

Local

Always returns success. It does not inspect the handler’s actual state.

Remote

Calls the remote service’s /health endpoint and returns the response JSON as-is. If the connection fails, the exception propagates.

Registry-level health check

In application/services/registry.py, the system iterates over all executors via iter_executors() and calls health() on each one. It records the status in the DB as ExecutorRegistryEntry rows.

7. Bootstrap Process

File: infrastructure/bootstrap.py When bootstrap_runtime(settings=settings) is called, executors and probes are registered in the following order:
CCTP / EVM / Gateway / USDT0 / Hyperliquid / Stargate executors and the EVM probes are not registered when MG_EVM_RPC_ENDPOINTS_JSON is empty (no EVM endpoints). CEX and observe executors are always registered. CCIP is gated by ccip_enabled and registered before the EVM-endpoint guard. Stargate additionally requires MG_STARGATE_ENABLED and the Pro package/loader gate; Lighter uses the same package/loader gate. Both are absent from the OSS distribution.

8. Guide to Adding a New Executor

8.1 Local Executor

  1. Implement a handler class. At minimum, five methods are required:
  1. Wrap it with LocalExecutorAdapter and register it in the registry:
  1. Add the registration call to bootstrap_runtime() in bootstrap.py.

8.2 Remote Executor

  1. Implement six endpoints in the remote service:
    • POST /preflight, POST /prepare, POST /submit, POST /observe, POST /recover, GET /health
  2. Register it in the registry with RemoteExecutorProxy: