36 deterministic checks, no LLM in the loop, no false positives from token salad. 5 of them block the agent from applying broken edits. The other 30 lint live as you build. Every rule has a trigger condition you can verify against your code.
These run on every action the agent proposes, before anything touches the canvas.
A blocking finding requires explicit user override. Source:
src/utils/agentGuardrails.ts.
delete_component, delete_components_matching, rename_components_matching, clear_canvas, replace_model) where downstream impact > 4 layers, or any of them are shape-changing.threshold × baseline (default 5×, slider 2-20×). Blocks at 2× threshold, warns below.useProviderStore.paramExplosionThreshold(). Settings → "Param explosion threshold".add_connection closes a cycle in the existing DAG. DFS check on the union of current edges and proposed edges.nn.Module.forward semantics. torch.autograd docs note that gradient computation requires a DAG.add_component without afterName, and no follow-up add_connection referencing the new component as either endpoint.embedDim % numHeads, GQA numHeads % numKVHeads, elementwise merge parent equality, concat axis compatibility, explicit linear inFeatures vs upstream, computeOutputShape throw, and outputs with NaN / 0 / negative dims at the first layer to introduce them.componentRegistry.computeOutputShape. Head-dim convention from Vaswani et al. 2017 (Attention Is All You Need). GQA ratio from Ainslie et al. 2023 (GQA).embedDim=384, numHeads=5 until the GPU rejects the kernel. This gate fires sub-millisecond, pre-apply.add_component or update_params: e.g. dropout above 1, stride of 0, numHeads of 0. Uses the same convention-based ranges the inspector shows inline.src/utils/paramConstraints.ts::validateParamValue, shared with the inspector so CI and the canvas never disagree.
These run live as you build, every model change, no agent involvement.
They surface as inline warnings on the canvas and in the Advisor panel.
Source: src/utils/architectureAdvisor.ts.
input.output.nn.CrossEntropyLoss applies LogSoftmax internally; an explicit Softmax before it double-applies and slows training. BCEWithLogitsLoss has the same issue with Sigmoid.p > 0.65.groupedQueryAttention component has params where numHeads % numKVHeads != 0.swiGLU / gated FFN has intermediateSize that doesn't follow the common ~(2/3) × 4 × hidden convention used in LLaMA / Mistral.conv1d/2d/3d or a depthwise/separable/transpose variant) connects directly into a linear layer.Linear expects a flat [batch, features] tensor. The forward pass raises a shape error, or silently mis-multiplies the spatial dims. Insert a Flatten or a Global Average Pool between them.relu/gelu/silu/sigmoid/tanh/softmax/…) connects directly into another activation node with no Linear/Conv/Norm between them.ReLU → Softmax, which clips logits to ≥ 0 and distorts the output distribution.linear layer connects directly into another linear layer with no activation between them.dropout layer is the last node before the output node.conv1d/2d/3d, depthwise, or separable) has stride > kernelSize.stride == kernel (e.g. ViT 16/16); stride > kernel is almost always a typo.flatten or linear layer connects directly into a (non-transpose) convolution.[channels, …spatial] feature map, but Flatten / Linear emit a flat [batch, features] vector. The forward pass raises a shape error unless the spatial dims are rebuilt with a Reshape / Unflatten first.nn.Conv*d input-shape contract.LayerNorm → BatchNorm).positionalEncoding, learnedPositionalEmbedding, rope, alibi) in the model.maxpool, avgpool, adaptive pool) connects directly into a linear layer. Global pools are exempt: they already collapse spatial dims.[channels, …spatial] map, but Linear expects a flat [batch, features] tensor, so the forward pass raises a shape error. Insert a Flatten or a Global Average Pool first.flatten layer connects directly into an attention layer.[sequence, dim] layout and flatten only after the attention stack.ConvTranspose2d has kernelSize not divisible by stride (with stride > 1).GroupNorm layer's channel count is not an exact multiple of numGroups.Linear exceeds ~1B parameters (inFeatures × outFeatures).numKVHeads below numHeads, or switch to mla.sigmoid or tanh activation directly.N(0, 0.02 / √(2L)).embedding with a numeric vocabSize and attention layers, but the final Linear feeding the Output projects to a different width.numKVHeads, skips MLA) exceeds 4 GB for a single 8,192-token sequence.emaTarget node and no branch carrying stopGradient.matryoshkaHead lists a nestedDims entry greater than its own embedDim.embedding[:d] is usable on its own for each listed d. A d larger than the width names a prefix that does not exist, so a caller who truncates to it silently gets the full width instead and indexes at a size nobody chose. There is no reading of the graph under which a prefix longer than the whole was meant.The neurarch-lint GitHub Action (report-corpus: true) and the neurarch-mcp server (NEURARCH_REPORT=1) can share one anonymous structure+verdict row per linted model. Both are off by default.
Sent: the structural fingerprint (an 8-character hash of the layer-type histogram and edge count), the histogram and edge count themselves (which let the server verify the fingerprint is real), and the finding pairs, rule id plus severity.
Never sent: the model graph, parameter values, layer names, code, file paths, the repository identity, or any user or machine identifier. The rows are unauthenticated by design; there is no way to attribute one to a person. The server rejects any row whose fingerprint cannot be recomputed from the histogram it came with.
Reliability: reporting has a 5-second timeout and swallows every error. It can never fail a build or a tool call.
Paste a HuggingFace ID or drop a .py file. The same 40 rules run client-side, and now also run in CI on raw .py via the neurarch-lint GitHub Action, sub-millisecond.