• Nodes in the C++ stream-processing framework behind Nova Sonic register themselves in a global registry through static initialization. No central list to edit, no explicit dependency from tools on the nodes they might load.
  • Registry is a thread-safe, function-local static constructed on first use, so registration is safe regardless of static initialization order across translation units.
  • Registration works by linking a node library or by LD_PRELOADing it. The development CLI can assemble a graph from a JSON definition using nodes it was never compiled against.
  • Pulled double duty as a dependency-inversion mechanism: consumers depend on the node interface, not on implementing libraries. Adopted across the organization and later picked up for embedded speech processing.

The C++ framework that Amazon’s speech services are built on is a node graph. Teams implement nodes for individual audio-processing operations, a log-mel filterbank transform, Kaldi feature operations, and so on, and a stream-processing pipeline is a graph of those nodes assembled from a JSON definition. The assembly step looks node names up in a dictionary of node builders.

The ask, and the actual problem

The original request was small: make the development CLI accept shared objects on the command line, so an engineer could try a new node in a graph without rebuilding the tool. Looking at why that was hard turned up the real problem. The builder dictionary was populated by hand. To make a node available, someone imported it in C++ and added its builder to the lookup, which meant the graph-building tool had to declare an explicit dependency on every node anyone might want to use. Every new node was a change to the tool, and the tool’s dependency graph grew to the size of the organization’s node inventory.

The design I landed on is a statically initialized global registry. Each node adds a short stub, a static object whose constructor registers the node’s builder under its name through the existing node interface. The registry itself is a function-local static, constructed on first use and guarded for thread safety, which sidesteps the static-initialization-order problem across translation units: a stub in one library can register before or after any other and the registry is always there when it runs.

Because registration happens when the node’s library is loaded, there are two ways to make a node available and neither involves editing anything. Link the library, or LD_PRELOAD it. The development CLI got its shared-object flag more or less for free, and graph assembly became a pure function of configuration: the JSON names the nodes, the registry resolves them, and the tool needs no knowledge of what nodes exist.

The second win

The part that made it spread was the dependency inversion. Nodes register through the framework’s existing node base class, so anything that builds graphs depends only on that interface. The graph builder, and later the production services built on it, stopped carrying explicit dependencies on the node libraries. That simplified the organization’s dependency graph in a way the original CLI request never asked for, and it is why the pattern was adopted across the org rather than staying a development convenience. It has since been picked up for embedded speech processing, where the same “assemble from configuration, depend on the interface” property matters even more.