Nexus, Sigils, and Weaves
This page describes how the three concepts work together from an integrator perspective: what to construct first, what each type is responsible for, and how HTTP, MCP, and OPC UA line up. It intentionally stays at the public API and behavior level—no SDK source listings.
Warning
Registration depends on import-time setup. Follow these rules or routes, MCP tools, and OPC UA nodes may be missing or duplicated.
- Nexus first — Construct and configure
Nexusbefore you create anySigilorWeave.- Sigils before Weaves, at module scope — Instantiate every
Sigilat module level (top level of your entry module), before anyWeave, and beforenexus.start(). Do not create Sigils inside functions, methods, conditional paths that might not run, or weave callbacks; lazy or dynamic creation can skip registration against the running Nexus.- Weaves at module scope only — Instantiate every
Weaveat module level as well. Do not create Weaves inside functions, inside another Weave’s callback, or while constructing another Weave in a way that hides registration from startup; only module-level Weaves are guaranteed to be scheduled when Nexus starts.
Mental model
| Piece | Role |
|---|---|
| Nexus | One process-wide singleton runtime: embedded OPC UA server, FastAPI app, MCP server, certificate handling, tracing, and lifecycle for all Sigils and Weaves. |
| Sigil | One logical OPC UA variable (and matching REST/MCP surface): either hosted on Nexus’s embedded server or bound to a node on another server (for example a PLC). |
| Weave | A soft real-time loop: a labeled periodic task that runs your async (or sync) callback and is meant to read/write Sigils or call other async code. |
You typically: construct Nexus once with your listen URL and options, instantiate every Sigil (registration happens at construction time), register every Weave, then call start() so the HTTP server and OPC UA stack come up with everything already wired.
Nexus
Singleton and initialization
Nexus is a singleton: repeated construction returns the same instance; only the first initialization applies. Treat your application as owning one Nexus for its lifetime.
Constructor arguments you will use in practice:
opc_ua_url— Endpoint string the embedded OPC UA server binds to (for exampleopc.tcp://localhost:4840for local tools, oropc.tcp://0.0.0.0:4840when you publish port 4840 from Docker so the host can reach it).namespace— OPC UA namespace URI registered for embedded nodes (you normally keep the default unless you are standardizing NodeIds across fleets).device_id— Object under the server’s Objects folder where embedded variables are created; useful when browsing the address space in a client.otlp_endpoint— Optional OpenTelemetry OTLP/gRPC exporter endpoint. If omitted, traces go to the console instead.token— License JWT string. A valid JWT is always required beforestart(): pass it here astoken=or setVEILNET_AETHER_TOKENin the environment (you cannot omit both). Nexus verifies the JWT locally (signature and expiry); no internet is required for license validation. See Overview — Licensing.
What starts when you call start()
start() brings up Uvicorn on 0.0.0.0:8000 with:
GET /health— JSON liveness (status: ok)./docs— Interactive OpenAPI for the auto-generated routes (Sigils and Weaves each contribute endpoints; see below)./nexus— MCP over HTTP (stateless HTTP app mounted here).
The embedded OPC UA server is configured during application lifespan: certificate material under certs/ (self-signed files are created when missing), security policies enabled for both open and signed/encrypted profiles, namespace registration, then Sigil initialization, Weave task startup, and finally OPC UA listen. On shutdown, Weaves are cancelled, Sigils torn down (including disconnecting any external OPC UA clients), and the server stops.
Certificates
Expect certs/cert.pem and certs/key.pem on the working directory (or let Nexus generate them on first run). Containers usually mount a host certs/ volume so you can pin trust material in production.
Sigil
Construction and registration
Creating Sigil(...) parses node_id as a standard OPC UA NodeId string, validates initial value against optional minimum / maximum, and registers the Sigil with the singleton Nexus. That registration is what causes embedded variables to appear under the configured device and what attaches REST and MCP handlers.
So the order matters: build Nexus (first call wins), then create Sigils before nexus.start(), so startup can create OPC UA nodes and connect external clients.
Embedded vs external
- Embedded (default) — Omit
opc_ua_url. Nexus creates a variable on the embedded server at your NodeId. Reads and writes from Python go through that server node. REST and MCP are generated from this Sigil’s metadata. - External — Pass
opc_ua_url(and optionaluser_name/password). Nexus builds an OPC UA client to that server. For signing/encryption you can supply security policy, certificate, private_key, server_certificate, and message_security_mode consistent with how you already connect with asyncua.
For external-backed Sigils, initialization can apply client security and connect during Nexus startup.
Writable vs read-only
writable defaults to true. When writable=False:
- REST POST and MCP write tools are not registered for that Sigil (operators and agents read only).
- The embedded OPC UA variable is created non-writable for clients.
- Your Python logic (for example inside a Weave) can still call
writeon embedded nodes to update simulated state, subject to min/max checks. Use this pattern for operator setpoints (writable) vs simulated plant outputs (read-only from tools, writable from the model).
Limits
Optional minimum_value and maximum_value constrain write (and validate initial_value at construction).
REST shape (embedded or mirrored)
Each Sigil contributes a GET route to read its value and, if writable, a POST route whose JSON body is a small object with a value field. Paths are derived from the NodeId’s namespace index and identifier so they stay stable for automation.
MCP
Each Sigil registers read tools; write tools appear only when the Sigil is writable. Tool names are derived from the NodeId in a way that keeps MCP identifiers safe.
Read path with an external client
When a Sigil uses an external client, read() can refresh from the remote server and keep the embedded mirror (if present) aligned for a single logical value—think “PLC is source of truth for PV, twin still exposes it on Nexus.”
Weave
Registration and parameters
Construct Weave(...) after Nexus exists and before start(). Parameters:
label— Stable human-facing name; used in logs, tracing, and MCP.cycle_time— Period in seconds between invocations (for example0.1for 100 ms).callback— Callable run each cycle. May be async or ordinary sync (sync work is offloaded so it does not block the event loop).depends_on— Optional list of other Weave instances. Before each cycle, Nexus waits until every dependency has finished its previous cycle (completion synchronization). Use this to enforce ordering when one loop must always run after another.description— Documentation string surfaced on introspection routes and MCP.
Soft real-time behavior
Weaves are soft real-time: each cycle has a timeout tied to the schedule. If your callback exceeds the budget, the weave can be marked failed for that cycle. If the loop falls behind real time, Nexus can record an overrun state and continue without blocking the process indefinitely—design callbacks to stay well under cycle_time for deterministic plants.
REST and MCP for weaves
Each weave exposes GET routes (under the same router family as other OPC UA–related HTTP) for label, description, cycle_time, completed cycle count, and next scheduled cycle time in Nexus time coordinates—useful for dashboards and health checks.
MCP exposes a structured tool that returns status, timing, and metadata for a weave so agents can reason about whether the control loop is healthy.
End-to-end order of operations
- First reference to Nexus — Construct with your
opc_ua_url, a license JWT viatoken=and/orVEILNET_AETHER_TOKEN, optionalotlp_endpoint, and optional namespace/device overrides. - Sigils — Instantiate every variable (embedded and external). Ensure NodeId strings are valid and initial values respect bounds.
- Weaves — Instantiate loops; set
depends_onif you need ordering between loops. nexus.start()— Blocks in the usual “run the ASGI server” sense; your process is now serving HTTP, MCP, and OPC UA until interrupted.
Minimal example
The snippet below is application code (a tiny twin), not the SDK. It shows one writable setpoint, one read-only simulated value, and a weave that integrates a simple lag every 100 ms.
A license JWT is always required, including for this minimal twin: set VEILNET_AETHER_TOKEN before you run the script, or pass token="..." into Nexus(...) (see Overview — Licensing). The snippet only passes opc_ua_url so the token is expected from the environment.
from aether.nexus import Nexus
from aether.sigil import Sigil
from aether.weave import Weave
nexus = Nexus(opc_ua_url="opc.tcp://localhost:4840")
setpoint = Sigil(
node_id="ns=2;s=MyTwin.Setpoint",
initial_value=50.0,
description="Operator setpoint (writable).",
)
temperature = Sigil(
node_id="ns=2;s=MyTwin.Temperature",
initial_value=20.0,
writable=False,
description="Simulated temperature (updated by the weave).",
)
async def step_physics():
sp = float(await setpoint.read())
t = float(await temperature.read())
t += (sp - t) * 0.01
await temperature.write(t)
Weave(
label="physics",
cycle_time=0.1,
callback=step_physics,
description="First-order lag toward setpoint every 100 ms.",
)
if __name__ == "__main__":
nexus.start()
Surfaces while running
- Liveness:
GET /health - OpenAPI:
/docs(includes Sigil and weave helper routes) - MCP:
/nexus - OPC UA: the
opc_ua_urlyou configured for the embedded server