Skip to content

Visual SNN Design Studio

The Studio is a web-based research workbench for designing, simulating, and analysing spiking neurons interactively. It combines 118 built-in neuron models, custom ODE editing, 15 analysis views, and Verilog RTL generation in a single interface.

Installation

Bash
pip install sc-neurocore[studio]

This installs FastAPI and Uvicorn alongside the core package. A published wheel also carries the built user interface in sc_neurocore/studio/frontend_dist/, so no Node.js toolchain is needed to run the Studio.

An installation from a source checkout carries the interface only if studio/frontend/dist/ has been built first (see Development); without it the build still succeeds, and sc-neurocore studio says that there is no interface and opens the API documentation at /docs instead. Release builds set SC_NEUROCORE_STUDIO_UI=required, which makes a build without the interface fail. Before the wheel can be published, the release workflow installs it into a clean environment, launches sc-neurocore studio from that installation and requires the root redirect, every asset the interface names and the whole model catalogue to be served (tools/studio_installed_acceptance.py).

Quick Start

Bash
sc-neurocore studio

Opens http://127.0.0.1:8001/studios/sc-neurocore/ in your browser; the root http://127.0.0.1:8001/ redirects there. The Studio starts in Model mode with 185 neuron models browsable by category. Switch to ODE mode to write custom equations.

The first screen opens on an operator workbench rather than a landing page. It shows the saved-project/session state, selected model or ODE mode, simulation state, audit/capability evidence health, compile/synthesis readiness, and evidence-bundle export availability. The export card targets synthesis evidence first, then compile evidence, then saved-project evidence, and opens the owning surface when that scoped bundle is already available for artifact download. Each card links to the production Studio surface that owns the action. The next left-panel section is a readiness panel derived from /api/studio/operator/status; it turns route-policy, identity, audit, job root, runtime-limit, and capability gaps into ready, warning, and blocked rows before the operator opens Admin or runs release preflight.

To use a different port:

Bash
sc-neurocore studio --port 9000

Two Modes

Model Mode

Browse all sc-neurocore neuron models grouped by category (Conductance, IF, Oscillator, Bursting, Hardware, Network, Statistical, AI). Each model's parameters appear as sliders. Models are auto-classified by firing pattern (tonic, bursting, adapting, irregular, chaotic, silent) with colour-coded badges.

When a model has a canonical bundled schema, the sidebar also exposes its schema-declared, compiler-supported integrator choices and supported signed Q-formats. Generate RTL compiles the current selected model, parameter overrides, timestep, integrator and Q-format through the isolated schema-backed compiler job. The response includes studio.compile-traceability.v1 provenance with the exact model/schema and configuration used, including the canonical schema digest. Models without a canonical schema fail closed: Studio does not infer a fuzzy schema match or silently substitute an ODE.

For euler and map configurations, Run RTL co-sim advances the guided workflow through a real bit-exact parity gate before synthesis. The isolated job generates the maintained bit-true C reference and the same configured RTL, compiles them with GCC and Icarus Verilog, executes the RTL with VVP, and compares spike_out plus every state output as signed integers on every cycle. The path-free studio.cosim-parity.v1 report records the exact model/schema configuration, bounded stimulus, signal set, source and trace digests, tool versions, and the first mismatch when parity fails. Complete sources and traces remain path-confined job artifacts. exp_euler, rk4, and gauss_seidel are compile-only until a maintained bit-true reference lowerer exists; Studio disables the parity action and blocks model-mode guided synthesis rather than claiming an approximate pass.

Pattern filter: click a pattern badge in the model list to filter.

ODE Mode (custom equations)

Write ODEs in Brian2-style syntax in the Monaco editor:

Text Only
dv/dt = -(v - E_L) / tau_m + I / C

# threshold: v > -50
# reset: v = -65

Five built-in templates: LIF, Izhikevich, AdEx, Hodgkin-Huxley, FitzHugh-Nagumo.

Analysis Views

The Studio provides 15 view tabs, each showing a different analysis of the current simulation:

View Tab What it shows
Trace Trace Voltage + current + spike raster with zoom/pan
Phase portrait Phase v vs w trajectory with nullcline overlay
ISI histogram ISI Interspike interval distribution
f-I curve f-I Firing rate vs input current
Bifurcation Bif Parameter sweep → voltage attractor scatter
2D Heatmap 2D Two-parameter sweep → firing rate colour map
Sensitivity Sens Parameter importance bar chart
Spike-triggered average STA Average voltage shape around spikes
Frequency response Freq Bode plot: gain and phase lag of the rate vs the frequency of a modulated drive
Characterisation Char One-click dashboard: pattern, rheobase, f-I, sensitivity
Multi-model overlay Multi Compare 2-4 models in one plot
A/B Comparison A/B Split-view of two configurations
E-I Network E-I Excitatory-inhibitory network raster + population rates
Code generator Code Python script + one-liner for notebooks
Precision Q8.8 Float64 vs bit-true fixed-point kernel run + parameter-quantisation run, error traces
Verilog RTL RTL Generated Verilog from the selected schema-backed model or custom ODE

Trace View

The main view shows:

  • Voltage traces for all state variables (colour-coded)
  • Current injection subplot showing the input protocol
  • Spike raster with red tick marks
  • Zoom/pan: mouse wheel zooms the time axis centred on cursor, drag to pan, double-click to reset
  • Crosshair cursor with tooltip showing exact time and voltage values
  • Axis labels: mV for voltage, nA for current, ms for time
  • Imported data overlay: paste CSV data to compare with simulation

The canvas is pixels, so it names itself with a sentence a screen reader reads: the variables, the run's length and step, its spike count, and each variable's range and final value. Data table (bottom right of the plot) shows the same numbers as a table. Both are read from the display projection the server sends, whose bucket extrema keep every minimum and maximum and whose last sample is the run's last, so they are the run's own values. Other views name themselves by title and point to the CSV and JSON exports for their numbers.

A series with more samples than the plot has pixel columns is stroked as each column's first, lowest, highest and last sample, plus one sample either side of the visible window, so every visible extreme survives while the path the browser draws stays bounded. The result data are not changed; this applies wherever a series is longer than the server's own display projection, such as an imported trace. A series whose horizontal values do not increase (a phase portrait) is drawn in full. Every parameter slider carries its name and unit and states its value for assistive technology.

Characterisation Dashboard

Click Characterize to run a one-click analysis that produces:

  • Firing pattern classification
  • Threshold current (rheobase estimate)
  • f-I curve
  • Top sensitive parameters
  • State variable ranges

E-I Network

Simulates a balanced excitatory-inhibitory LIF network. When the Rust engine is installed, the entire simulation runs in compiled Rust — connectivity construction, Poisson input generation, Euler integration, spike detection, and rate binning happen in a single py_simulate_ei_network() call with zero Python per-timestep overhead. Falls back to NumPy if the engine is unavailable.

Parameters (adjustable via sidebar sliders):

  • Neuron counts (N exc, N inh)
  • Synaptic weights (E→E, E→I, I→E, I→I)
  • Connection probability
  • External Poisson drive rate (Hz)

Displays a spike raster (blue = excitatory, red = inhibitory) and population firing rate traces.

Current Injection Protocols

Five injection protocols for all simulations:

Protocol Description
Constant Steady current for full duration
Step I from 20 % to 80 % of the run, 0 before and after
Ramp Linear increase from 0 to I
Pulse train Five pulses per run at amplitude I, each on for a fifth of its period (at least 10 steps per period, 2 on)
Sine I · sin(2π f t) at frequency_hz; the frequency response adds a bias it oscillates about

Interactive Features

  • Auto-simulate: simulation reruns 250ms after any slider change
  • Keyboard shortcuts: Space=run, 1-5=switch between the first analysis views, arrow keys/Home/End=move within the view switcher, ?=help overlay
  • Session save/load: save named sessions to localStorage
  • Shareable URLs: state encoded in URL hash (click Share link)
  • 10 preset experiments: threshold exploration, adaptation, bursting, chaos, hardware comparison, and more
  • CSV export: download the full-resolution raw traces at their post-step sample times as comma-separated values
  • JSON export: download the complete simulation response (raw traces, snapshots and the display projection) with path-free studio.simulation-run.v2 reproducibility metadata
  • Evidence labels: trace and analysis plots surface evidence classification, source, input digest, and result digest labels
  • PNG export: screenshot the current plot
  • Code export: a standalone script built from the resolved experiment. It re-resolves the request, refuses if this installation would resolve a different experiment, and runs it through the public runner — so the time step, drive protocol, initial state, parameters and randomness travel with the export instead of being guessed from the model's constructor
  • Replay pack: a sealed studio.replay-pack.v1 document with the re-resolvable request, the specification, its identity digest, the complete expectation (every spike event, a digest per state trace, initial and final state, drive digest) and the environment that sealed it. Replay it with python -m sc_neurocore.studio.replay_pack <pack.json>; see Replay and code export

One effective experiment per run

The resolved Python specification owns immutable snapshots of its inputs. Editing a returned dictionary or a result's experiment metadata cannot change a subsequent execution or its cache identity.

Before anything runs, a simulation request (/api/simulate, /api/models/simulate, /api/multi-simulate, or the simulate kind of /api/analysis/jobs) is resolved into one experiment (studio.experiment-spec.v1) that the response returns and whose digest keys the result cache:

  • model — class, module SHA-256, descriptor SHA-256 and contract digest, canonical schema profile and its SHA-256 (or equations with the equation digest and the declared variables for the playground).
  • numerical — method, family, effective dt with its source (override, model_default, model_attribute, studio_default), sub-steps and time unit; steps — the exact step count and effective duration. An oversized synchronous run is refused with execution_mode = job_required and the job route, never shortened.
  • initial_state — typed initial values (descriptor-declared for models, explicit for every playground variable, undeclared ones at 0.0; an initial value for an undeclared variable is rejected).
  • protocol — kind, amplitude, the explicit sine frequency_hz, the fixed step/ramp/pulse fractions and the SHA-256 of the drive samples.
  • randomness — kind (none, seeded-model, diffusion-noise), the effective seed and its source (request, model-default, playground-default, drawn), the requested trial (replay or fresh) and the effective one. A seed on a deterministic model or on noise-free equations is rejected; a fresh trial draws a seed, reports it so the trial can be replayed, and is never cached. Playground diffusion noise (xi) is drawn from a per-run generator, not from the process-global stream.
  • backend — the selected backend and the rejected alternatives with their reasons; runtime — package, Python and NumPy versions and the equation builder digest.

Explicit request defaults resolve to the same experiment as omitted ones, so the GUI, the API and a replay from an exported response share one effective configuration; cache.hit says whether a replay came from the cache and the run manifest carries experiment_sha256 and trial.

Complete state and raw-result custody

Every simulation response (/api/simulate, /api/models/simulate, /api/multi-simulate) returns the complete run next to a bounded display projection:

  • state_layout — the state the model declares (its committed descriptor [state] table for catalogue models, the equations for the playground), each variable with its role from the canonical schema profile (biological, auxiliary or unassigned), unit, meaning, declared initial value, observed kind (scalar or vector) and shape. A declared variable the instance does not expose is listed with its reason; attributes that change without being declared are listed under undeclared_mutable. complete is true only when every declared variable was recorded at every step and nothing undeclared changed; incomplete_reasons says why not. Name heuristics are not a state source.
  • observation — the clock: the initial snapshot is at 0 ms, sample i is the state after step i at (i + 1) * dt, and drive sample i applies over [i * dt, (i + 1) * dt).
  • initial_state and final_state — exact snapshots of every observable declared variable (vectors as lists).
  • raw — full-resolution per-step traces of every recorded scalar state (states), vector states within the element budget (vector_states), the drive, spike step indices and spike times. When the scalar traces alone would exceed element_budget the block says so in reason instead of shortening anything; vectors beyond the budget are recorded in the snapshots only and named in vector_snapshots_only.
  • time, states, current_trace — the display projection, at most 5,000 points chosen as the per-bucket extrema of every scalar trace and the drive plus the first and the last sample, with display.sample_index mapping each point to its raw step. Peaks, resets and the final sample survive reduction; spikes are raw step indices and are never decimated. Analyses, exports and the spike-triggered average read raw, not the projection.
  • effective_inputs — the ST-01 receipt; state_recording lists the recorded and excluded declared variables and display_points the size of the projection.

run_metadata carries the studio.simulation-run.v2 manifest: the digests of the request, of the returned payload and of the raw block, the layout source, whether custody is complete, whether raw traces are included and the observation clock. Evidence bundles accept both v1 (history) and v2 manifests.

The catalogue routes run on the Python custody backend so every declared variable is observed. The optional Rust batch backend, still used by analysis sweeps that need rates only, exports the membrane voltage and no initial snapshot; its result says so in state_layout and is never presented as complete-state custody.

Analysis validity and metric contracts

Every analysis response (/api/fi-curve, /api/bifurcation, /api/sensitivity, /api/heatmap, /api/freq-response, /api/precision, /api/nullclines, /api/ir/cosim and the analysis job kinds) carries a contract block (studio.metric-contract.v1): the kind of metric, its definition, the units of every reported quantity (model-defined where the equation system declares none), the applicability conditions, the limitations of the computation and a domain verdict — complete when every requested point was evaluated, partial when some points were invalid and are reported, empty when none could be. The analysis_metadata manifest repeats the contract kind and the domain so an evidence bundle can tell a partial result apart without opening it. An invalid point is never a zero:

  • f-I curve, heatmap — the rate is the spike count over the whole simulated duration (transient included); the contract says so, together with the drive protocol.
  • Frequency response — measured over the largest whole number of drive cycles in the run (transient included); a frequency without one whole cycle is not measured (null, domain partial).
  • Sensitivity — the dimensionless rate elasticity |rate(p+δ) − rate(p−δ)| / (2δ) · |p| / rate(p) with δ = 10 % of p. A zero base rate or a zero parameter makes the elasticity undefined; the row reports sensitivity: null with a reason and the domain is partial or empty.
  • Bifurcation — labelled numerical-extrema-sweep: the late-run extrema of the analysed variable (request field variable, first trace by default) under the route's drive protocol. It is not a bifurcation continuation: no branch is followed and no stability is computed; attractor_kinds says per point whether extrema, a fixed-point mean or an insufficient trace was found.
  • Nullclines — each component of the drift field is evaluated at every grid sample with NumPy floating-point errors raised; a sample that raises or is non-finite is invalid and reported in validity_0 / validity_1 (rows follow y, columns x). A contour cell needs four valid corners. The field is evaluated at the request's current (default 0) with every variable outside the two swept ones held at held (default: its initial value) and the diffusion-noise symbol at zero; domain.status and the invalid fractions are returned, and the phase view labels a partial domain.

Precision comparison

The Python comparison rejects invalid timing and runs exceeding its step limit; it never labels a shortened prefix as a complete comparison.

/api/precision (and /api/ir/cosim) compares three runs of the same experiment and keeps them apart:

  • float_result — the float64 explicit-Euler playground run;
  • fixed_result — the bit-true fixed-point run: the generated C kernel of the equation system (sc_neurocore.compiler.intelligence.bit_true_kernel, proven bit-identical to the emitted RTL by the Icarus co-simulation), compiled with the host C compiler and driven with one encoded input word per step. Its words block holds the raw integer state words; arithmetic states the value encoding, multiply width collapse and rounding, accumulate overflow policy, division/modulo/LUT lowering, threshold/reset sequencing and the kernel and compiler digests. backend is bit-true-kernel.
  • parameter_quantisation_result — float64 with parameters and initial state rounded to the word resolution, so parameter rounding can be told apart from the fixed-point operations (wrap-truncate multiplies, saturation, look-up tables) that only the bit-true run performs. The time-step word the kernel applies is reported under encoding.dt; all three runs receive the same drive samples.

comparison.bit_true and comparison.parameter_quantisation report, for every state variable, the per-step absolute error (trace, full resolution, and display aligned with the float result's display samples), its maximum, mean, RMS and final value and the first step beyond half a word resolution; the spike trains are compared in order (events); saturation counts the steps each word spent at the format limits. error and quantized_params keep the former summary for the first declared variable (bit-true error).

The request names the word (q_format, Q8.8 = 16 bits with 8 fractional, 8 to 32 bits), the accumulate overflow (saturate or wrap) and product rounding (truncate or nearest), the protocol and frequency_hz. A parameter, expression constant, initial value, time step or drive sample the word cannot hold is rejected with its field (HTTP 422 invalid_model_input) instead of being clamped — a clamped parameter is a different model, not a precision effect — as are diffusion noise (xi) and integrators the kernel does not mirror. When the host has no C compiler the route answers HTTP 503 native_tool_unavailable; nothing is estimated in its place.

API Reference

The Studio backend exposes a REST API. All POST endpoints accept JSON. Replayable simulations are cached (LRU, 64 slots) by the digest of their resolved experiment; fresh stochastic trials are never cached.

Core Simulation

Method Endpoint Description
POST /api/simulate Run ODE simulation
POST /api/models/simulate Run named model simulation
POST /api/compare A/B comparison of two configs
POST /api/multi-simulate Simulate 2-4 models in parallel
POST /api/network/ei E-I balanced network simulation

Model-run contract (/api/models/simulate, /api/export/svg)

A catalogue model run is fail-closed. The request body rejects unknown keys, non-numeric or non-finite params values and dt, non-positive dt and duration, and any protocol outside constant, step, ramp, pulse, sine. The run contract then validates the request against the model class itself and never substitutes a default:

  • every params key must be a numerically overridable constructor field of the model; private state, derived (init=False) fields, factory-initialised fields and non-numeric fields (integrator or profile literals, arrays) are rejected with the reason;
  • integer fields reject fractional values; integer-drive models reject protocols whose samples are not integral;
  • a model with a fixed class-level step (for example IntegerQIFNeuron, 1.0 ms) accepts only that dt; a model without any timestep accepts only the Studio default of 0.1 ms per step;
  • a model whose profile declares a macro step (HodgkinHuxleyNeuron and ConnorStevensNeuron: 1 ms of 100 sub-steps of 0.01 ms; WangBuzsakiNeuron: 0.5 ms of 50) is clocked by that macro step: one sample per step() call, duration / macro step calls, and the response dt is the macro step. Its dt is the sub-step, accepted only at the profile's value, because the class chooses its own sub-step count from dt and nothing declares how long a call lasts at another one. Such a model cannot form a network population, since a network advances every population once per dt;
  • a model whose step needs inputs the current protocol cannot supply (for example DendriticNMDANeuron, which needs glutamate) is rejected before any step runs;
  • a constructor failure is reported as-is instead of falling back to defaults.

Rejections return HTTP 422 with detail = {"error": "invalid_model_input", "model", "field", "reason"}; body-schema failures return the usual list of validation errors, including for NaN or Infinity in the body. A validated run that raises or produces a non-finite or non-scalar state at some step returns HTTP 422 with detail = {"error": "model_simulation_failed", "model", "backend", "step", "time_ms", "diagnostic"} and no partial payload; failed requests are never cached. A successful run carries an effective_inputs receipt (studio.model-run-inputs.v1): backend (python or rust), effective dt and its source, step_ms (the simulated time of one step() call, which is dt except for a macro-stepping model), every effective parameter, the applied overrides, the step drive parameter and kind, the protocol, the requested duration, the step count with a steps_truncated flag, the plot stride, and the recorded and excluded state variables with the exclusion reason (non-scalar, absent, or not exported by the Rust batch backend). Non-finite values are never rewritten to zero.

Analysis

Method Endpoint Description
POST /api/fi-curve Firing rate vs current sweep
POST /api/bifurcation Parameter sweep attractor map
POST /api/sensitivity Parameter sensitivity analysis
POST /api/heatmap Two-parameter firing rate heatmap
POST /api/characterize Full model characterisation
POST /api/freq-response Frequency response curve
POST /api/precision Float64 vs bit-true fixed-point compare (q_format, overflow, rounding, protocol)
POST /api/nullclines Nullclines of a 2D section with validity masks (current, held)

Analysis responses for /api/compare, /api/fi-curve, /api/bifurcation, /api/sensitivity, /api/heatmap, /api/freq-response, /api/precision, /api/nullclines, and /api/characterize include analysis_metadata with the studio.analysis-result.v1 schema. The manifest records the analysis type, evidence classification, source (ode, model, mixed, or unknown), input and result SHA-256 digests, the returned result keys, and the metric contract kind and domain verdict of the payload (contract, domain; null for a payload without a contract) without exposing host-local paths. The corresponding plot views surface the evidence class, source, input digest, and result digest next to the rendered analysis. /api/multi-simulate attaches per-result run_metadata with the studio.simulation-run.v2 schema so each overlaid trace carries the same reproducibility provenance as /api/simulate.

The frequency-response endpoint drives the model about an operating point: I(t) = bias * (1 + depth * sin(2*pi*frequency_hz*t)) (bias is the Studio's current, depth 0.5 by default and below 1, so the current never changes sign). From the spike times t_k over the whole drive cycles it reports, per frequency, the mean rate, the rate's first-harmonic amplitude 2|Σ exp(−2πi f t_k)| / window, the gain (that amplitude per unit of modulating current, depth * |bias|), the phase by which the rate lags the drive, and the vector strength. It used to drive a mean-zero sine amplitude * sin(...) and report the mean rate alone; the default model left its safety bounds under it. A request that still sends amplitude is refused with that reason.

Resources

Method Endpoint Description
GET /api/templates List ODE templates
GET /api/templates/{name} Get template by name
GET /api/models List every catalogue model
GET /api/models/scan Classify all models by firing pattern under the synchronous analysis budget and return studio.model-scan.v1 evidence metadata
GET /api/models/{name} Get model detail (params, state vars)
GET /api/presets List preset experiments
GET /api/presets/{id} Get preset detail
POST /api/codegen Export a script that reproduces the resolved experiment
POST /api/export/replay-pack Export a sealed pack another installation can run and compare
POST /api/export/replay-notebook Export a cited Jupyter notebook that carries and replays a sealed pack
POST /api/compile Compile ODE to Verilog with source-to-RTL traceability
POST /api/models/compile Compile a selected catalogue model with explicit timestep, integrator and Q-format
POST /api/models/cosim Run selected-model bit-exact C-reference versus real Icarus RTL parity
POST /api/candidates/validate Locate every problem with a candidate model package (Candidate Models)
POST /api/candidates/diff Diff a candidate against its parent, mathematically and semantically
POST /api/candidates/simulate Run a candidate under its own profile, bounded
POST /api/candidates/review-packet Run a candidate's reference tests and return its review packet
POST /api/fits Fit a model's parameters to a split cohort, with hold-out error and identifiability (Parameter Fitting)
POST /api/fits/replay Run an exported fit again and report whether it reproduced
GET /api/project/{name}/comments Review comments, each checked against its revision (Workspaces)
POST /api/project/{name}/revisions/{revision}/comments Comment on one immutable revision, or reply
GET /api/cache/stats Cache hit/miss statistics
GET /api/health Health check

Network Canvas

Method Endpoint Description
GET /api/graph/models List neuron models available for graph populations
POST /api/graph/population Create a population node
POST /api/graph/projection Create a projection edge
POST /api/graph/validate Validate graph JSON and return structured errors
POST /api/graph/simulate Run a graph through the public Network runtime
POST /api/graph/notebook Export a tutorial notebook that rebuilds the graph with the public API and checks its spikes
POST /api/graph/export-nir Write the validated graph as a NIR (HDF5) file, returned as base64 with notes
POST /api/graph/import-nir Read a NIR file or a legacy graph envelope into Studio graph JSON

A graph runs what it declares. Every population names a catalogue model whose constructor contract accepts the parameters and the graph timestep; every projection carries a signed weight that agrees with its source population's type, an explicit connection rule (random with a probability or all_to_all), a delay that is a whole number of timesteps and a seed that is given or derived from the graph seed; populations declare their external input (none, constant, poisson). The server resolves the graph into studio.network-graph-spec.v1, lowers it to public Population, Projection, SpikeMonitor and stimulus objects and runs the reference Python loop; the result (studio.network-graph-result.v1) reports the executed specification, the loop's semantics (previous-step propagation, delay buffering), a topology artefact with CSR digests, every spike event per population and a metric contract. Validation reports every error of a graph at once; nothing is clamped, rounded or replaced by a template default, and an oversized run is refused rather than shortened. See Network Canvas.

Example: POST /api/simulate

JSON
{
  "equations": ["dv/dt = -(v - E_L) / tau_m + I / C"],
  "threshold": "v > -50",
  "reset": "v = -65",
  "params": {"E_L": -65.0, "tau_m": 20.0, "C": 10.0},
  "init": {"v": -65.0},
  "dt": 0.1,
  "duration": 100.0,
  "current": 10.0
}

Response includes time, states, spikes, spike_count, dt, n_steps, stats (rate_hz, isi_mean_ms, isi_cv, isi_histogram), current_trace, and pattern (auto-classified firing behaviour), plus the custody fields (state_layout, observation, initial_state, final_state, raw, display) described above. Simulation responses also include run_metadata with the studio.simulation-run.v2 schema, simulation evidence classification, source (ode or model), input, result and raw SHA-256 digests, effective dt, executed step count, returned display sample count, spike count, recorded state variable names, layout source, custody verdict and observation clock.

Analysis responses include analysis_metadata with path-free result provenance. The frontend displays the analysis type, source, and shortened input/result digests beside the active analysis plot so exported JSON can be matched back to the API result contract.

Development

Bash
# Terminal 1: backend
sc-neurocore studio --port 8001

# Terminal 2: frontend dev server (hot reload)
cd studio/frontend
npm install
npm run dev

The Vite dev server proxies /api/* to http://127.0.0.1:8001.

Production build:

Bash
cd studio/frontend
npm run build   # → studio/frontend/dist/

A checkout's sc-neurocore studio serves this build once it exists, and the package build copies it into the wheel as sc_neurocore/studio/frontend_dist/; an installation serves its own copy first.

The production build serves two entry surfaces from the same source tree:

  • the standalone application at /studios/sc-neurocore/;
  • the Module Federation 2.x remote at /studios/sc-neurocore/remoteEntry.js, with federation identity sc_neurocore and expose ./SnnStudioPanel.

The remote and the SCPN Studio host share react and react-dom as singleton dependencies pinned to 19.2.7. Keep the object-form share declaration in vite.config.ts; array shorthand permits an independent React fallback and can produce invalid hook calls when the host renders the panel. The corresponding schema-A ui_module contract is:

JSON
{
  "remote_entry": "/studios/sc-neurocore/remoteEntry.js",
  "exposes": ["./SnnStudioPanel"],
  "federation": "module-federation-2"
}

npm run build fails unless the emitted remote exports the get/init container contract, retains the own identity and expose, contains no demo_studio reference, and resolves its entry imports inside dist/.

Frontend contract tests:

Bash
cd studio/frontend
npm test -- src/capabilityShell.test.ts
npm run test:e2e

The frontend capability shell is fail-closed for registered panels. If the backend capability registry omits a panel contract, that panel is disabled instead of assuming the underlying API or external tool is available.

The Playwright e2e suite first exercises the standalone Vite application and its backend contracts. It then builds the deployable output, serves the real remoteEntry.js, and loads ./SnnStudioPanel through a separate Module Federation host with the production singleton-share contract. This second browser test fails on missing chunks, incorrect public paths, federation-name drift, expose drift, duplicate-React hook failures, or a panel that cannot render through the host boundary.

The Admin panel loads path-free audit health at startup and can request the admin-gated audit export endpoint. Development-preview policy mode can still use X-Studio-Principal plus the studio.admin role, but production deployments should set SC_NEUROCORE_STUDIO_ALLOW_HEADER_PRINCIPAL=false and configure SC_NEUROCORE_STUDIO_IDENTITY_FILE with sc-neurocore.studio.identity.v1 service accounts. Store only SHA-256 token hashes in that file, authenticate API calls with Authorization: Bearer <token>, and give admin export accounts the studio.admin role. Persistent audit rows are written as canonical JSONL with previous_event_hash and event_hash fields. The audit status and export endpoints verify the retained hash chain and report integrity_verified, integrity_error, and the latest retained event hash without exposing filesystem paths or secret material. Legacy or unverifiable retained rows are not silently trusted: status and export payloads include retained_event_count, quarantined_event_count, and quarantine_reason so operators can separate verified evidence from rows that must be migrated, quarantined, or reviewed during incident reconstruction. Administrators can export only the quarantined retained rows through GET /api/studio/audit/quarantine/export; the payload is path-free, includes per-row quarantine_reason values, and is intended for incident handoff or offline archive migration. Administrators can also persist that quarantine export as confined Studio job artifacts through POST /api/studio/audit/quarantine/archive; the response returns a path-free archive manifest, reason counts, job ID, and downloadable artifact metadata under the existing /api/studio/jobs/{job_id}/artifacts/... surface. Before importing or restoring a saved archive, administrators can submit the archive payload and optional companion manifest to POST /api/studio/audit/quarantine/archive/validate; Studio recomputes the reason counts and verifies the manifest linkage without reading arbitrary server-side paths. Administrators can review archive retention through GET /api/studio/audit/quarantine/archive/retention?retain_latest=10; the path-free response lists valid quarantine archive jobs newest first, marks the newest archives as retain, and marks older archives as prune_candidate without deleting job artifacts. The Admin panel exposes the same archive lifecycle through the Audit archive section: operators can create a quarantine archive, review the retention plan with a bounded retain-latest value, paste path-free archive JSON plus an optional companion manifest for validation, materialize a restore artifact job, and execute the retention purge without copying host paths or raw audit files into the browser. Administrators can materialize a validated quarantine archive into confined restore artifacts through POST /api/studio/audit/quarantine/archive/restore. The route validates the archive and optional manifest, then writes evidence/audit-quarantine/restore.jsonl and evidence/audit-quarantine/restore-manifest.json as Studio job artifacts. It does not append restored rows to the active audit chain; operators can inspect, download, and hand off the restore artifact before any destructive or chain-mutating action. After reviewing retention state, administrators can execute the retention purge through POST /api/studio/audit/quarantine/archive/purge with {"retain_latest": 10}. The route recomputes the retention plan and removes only archive jobs marked as prune_candidate; retained archives and non-archive jobs are left untouched. When SC_NEUROCORE_STUDIO_AUDIT_ROTATION_BYTES is set, the retained-file count must be a positive integer so rotation always keeps at least one archived JSONL segment for incident review and retained-chain verification.

Create the first local service account before enabling the production profile:

Bash
sc-neurocore studio-bootstrap-admin \
  --identity-file /etc/sc-neurocore/studio-identities.json \
  --principal-id svc-studio-admin

The bootstrap command prints the bearer token once and stores only its SHA-256 digest in the identity file. Capture that token in the deployment secret manager, set SC_NEUROCORE_STUDIO_IDENTITY_FILE to the written path, and do not copy the token into repository files or shell history.

Administrators can inspect and update persistent service-account metadata from the Admin panel Identity section or through /api/studio/identity/service-accounts. The list and detail endpoints return principal IDs, role lists, active state, and optional UTC expiry only; they do not expose bearer-token hashes or filesystem paths. The PATCH endpoint updates roles, active state, and expiry atomically, preserves the stored token hash, reloads the backend authenticator after success, and records a dedicated studio.identity.service_account.update audit event in addition to the normal route-policy audit decision. Lifecycle updates fail with 409 if the change would leave the identity file without any active unexpired studio.admin principal.

Interactive operators can use persistent browser users from the same identity file. Add them offline through the maintained provisioning command:

Bash
printf '%s\n' "$STUDIO_OPERATOR_PASSWORD" | sc-neurocore studio-add-browser-user \
  --identity-file /etc/sc-neurocore/studio-identities.json \
  --username operator \
  --principal-id human-operator \
  --role studio.viewer \
  --password-stdin

The command writes username, principal ID, roles, active state, optional UTC expiry, and a PBKDF2-HMAC-SHA256 password verifier while preserving existing service accounts. The browser login form calls POST /api/studio/auth/login; a successful login stores the returned bearer token in sessionStorage, sends it as an Authorization header for subsequent API calls, and can revoke it with POST /api/studio/auth/logout. Studio does not use auth cookies for this session mode, so cookie CSRF tokens are intentionally not part of the current contract. Set SC_NEUROCORE_STUDIO_BROWSER_SESSION_TTL_SECONDS to tune the server-side session expiry; the default is 12 hours.

Repeated invalid browser-login attempts are throttled before another password check is performed. The default policy permits five invalid attempts within five minutes and then returns 429 browser_login_throttled with Retry-After for a 15-minute cooldown. Tune this deployment policy with SC_NEUROCORE_STUDIO_BROWSER_LOGIN_MAX_FAILURES, SC_NEUROCORE_STUDIO_BROWSER_LOGIN_FAILURE_WINDOW_SECONDS, and SC_NEUROCORE_STUDIO_BROWSER_LOGIN_COOLDOWN_SECONDS. A successful login clears prior invalid-attempt state for that username; disabled or expired users keep their explicit failure reason and are not converted into throttle events. The Admin panel Operator section reports the active lockout threshold, failure window, and cooldown without exposing identity-file paths or secret material.

Administrators can inspect and update browser-user lifecycle metadata from the Admin panel Identity section or through /api/studio/identity/browser-users. The Admin panel also creates browser users through the same route. Create, detail, and PATCH responses return only username, principal ID, roles, active state, and optional UTC expiry. Creation stores only a PBKDF2-HMAC-SHA256 verifier for the submitted password, reloads backend authentication immediately, rejects duplicate usernames with 409, and records studio.identity.browser_user.create without password material. Role, active-state, and expiry updates preserve the stored password verifier, reload backend authentication immediately, and record both the route-policy audit decision and a dedicated studio.identity.browser_user.update audit event. Browser-user lifecycle updates use the same last-admin guard as service-account updates.

The Admin panel Audit section derives an identity-lifecycle count and latest identity-lifecycle action from exported audit rows whose action starts with studio.identity.. This gives operators a compact confirmation that account creation, role changes, active-state changes, expiry changes, and password rotation are present in the audit trail without exposing token hashes, password verifiers, or local identity-file paths.

Use POST /api/studio/identity/browser-users/{username}/password or the Admin panel per-user secret field to rotate a browser user's password verifier. The route preserves public metadata, writes a fresh PBKDF2-HMAC-SHA256 verifier, reloads backend authentication, clears the login throttle bucket for that username, revokes active browser sessions for the user's principal, and records studio.identity.browser_user.password.rotate without password material.

The same Admin surface displays local worker health from /api/studio/jobs/status. Configure SC_NEUROCORE_STUDIO_JOB_ROOT to keep per-job working directories on an operator-selected disk, and tune SC_NEUROCORE_STUDIO_JOB_TIMEOUT_SECONDS for cooperative worker timeouts. Use SC_NEUROCORE_STUDIO_JOB_MAX_ARTIFACT_BYTES to cap each declared artifact written by a worker. Use SC_NEUROCORE_STUDIO_EDA_PROCESS_CPU_SECONDS and SC_NEUROCORE_STUDIO_EDA_PROCESS_MEMORY_BYTES to apply host-supported CPU and memory ceilings to Yosys and nextpnr child processes. The status payload is path-free and reports allowed job kinds plus active/completed/failed/timed-out counts. It also includes one resource_profiles entry per allowed job kind, recording the default timeout, per-artifact size ceiling, and supported execution models (thread and process) without exposing the job-root path.

Synchronous analysis routes (/api/simulate, /api/fi-curve, /api/bifurcation, /api/sensitivity, /api/freq-response, /api/heatmap, /api/nullclines, /api/precision, /api/characterize, /api/multi-simulate, /api/compare) execute in the request worker, so a fail-closed synchronous analysis budget bounds their cost. The budget projects each request's integration steps (simulation_count * ceil(duration / dt)) or nullcline grid points and returns HTTP 422 before running when the request exceeds SC_NEUROCORE_STUDIO_MAX_SYNC_ANALYSIS_STEPS_PER_SIMULATION, SC_NEUROCORE_STUDIO_MAX_SYNC_ANALYSIS_TOTAL_STEPS, or SC_NEUROCORE_STUDIO_MAX_SYNC_ANALYSIS_SIMULATIONS. The 422 detail names the violated limit, the projected cost, and the allowed ceiling without local paths; a non-positive timestep is rejected the same way. Operator status echoes the three ceilings in resource_limits.

Training start now submits work through the process-backed local worker manager; stop, status, and SSE stream routes retain the parent-process control and observation surface. The training monitor's SSE stream remains the live metric channel, while the panel surfaces the path-free action-evidence contract for the active run: evidence classification, action kind, job ID, terminal status, replay route, terminal artifact names, configuration summary, and latest epoch. The Admin queue records the bounded training job and its path-free terminal artifact manifest. Terminal training jobs write training/status.json and training/evidence.json; the latter uses the studio.action-evidence.v1 contract to record the action kind, replay route, job ID, terminal status, evidence classification, status payload SHA-256, and status artifact metadata without exposing host-local paths or secrets. After the terminal evidence artifact is available, /api/training/status/{job_id} returns a studio.training.evidence-summary.v1 operator summary containing the verified action kind, evidence classification, replay route, evidence artifact digest, and result artifact metadata. Training checkpoint controls export studio.training.checkpoint.v1 JSON from /api/training/checkpoint/{job_id} and import it through /api/training/checkpoint/import, validating both the config digest and the full checkpoint digest before restoring the training configuration.

Compile, synthesis, PnR, and full-pipeline routes also submit through the bounded worker manager. Their HTTP responses remain synchronous for existing UI flows, and the Admin queue records path-free artifacts at compiler/result.json, cosim/report.json, cosim/traces.json, synthesis/result.json, synthesis/multi-target-result.json, synthesis/pnr-result.json, and pipeline/result.json. The Network Canvas also surfaces the pipeline action-evidence contract beside the terminal result, including evidence classification, action kind, status, target, step, replay route, and the pipeline/result.json plus pipeline/evidence.json artifact names.

Compile responses include path-free studio.compile-traceability.v1 metadata. Both /api/compile and /api/ir/emit-sv-direct compile every supplied equation and preserve init and module_name. The direct route defaults to sc_ode_neuron; the worker route defaults to sc_neuron. These ODE export routes use the equation compiler's fixed default timestep of 0.1. They reject simulation-only fields such as dt, duration, and current with HTTP 422; they do not silently apply a different simulation configuration. Selected-model compilation has its own explicit timestep contract.

The manifest records the source equation payload, emitted RTL module metadata, source and RTL SHA-256 digests, and evidence_classification: "compile" without exposing host-local paths. The Compiler Inspector displays shortened source, RTL, and manifest digests beside direct equation-to-Verilog output. The same strip can export a compile evidence bundle with replay metadata for /api/ir/emit-sv-direct, using the compile input digest as the replay request fingerprint and the traceability digest as the operator note. After export, it lists the compile bundle's path-confined artifacts with size and SHA-256 labels and downloads each file through the compile-scoped authenticated job-artifact route.

Synthesis results include path-free studio.synthesis-target-provenance.v1 metadata for the selected target. Multi-target runs include the studio.synthesis-target-provenance-matrix.v1 matrix with a stable SHA-256 digest across every supported target. These records capture target capacity, Yosys command, optional nextpnr command/device, tool availability, and tool version strings when available. The frontend renders the all-target matrix as device, synthesis-readiness, PnR-readiness, tool, evidence-class, and digest rows so operators can inspect target support without opening raw JSON. After a single-target or all-target synthesis run, the FPGA panel can export a synthesis-scoped evidence bundle anchored on the captured worker job ID. The bundle includes the worker record plus the validated synthesis/evidence.json or synthesis/multi-target-evidence.json artifact, and the panel downloads bundle artefacts through the authenticated job-artifact route.

Each worker-backed compile, model co-simulation, synthesis, PnR, and pipeline action also writes a normalized studio.action-evidence.v1 manifest next to the result artifact: compiler/evidence.json, cosim/evidence.json, synthesis/evidence.json, synthesis/multi-target-evidence.json, synthesis/pnr-evidence.json, or pipeline/evidence.json. The manifest records the action kind, replay route, job ID, evidence classification, result payload SHA-256, and result artifact metadata without exposing host-local paths or secrets. Evidence bundle export validates selected job evidence artifacts (evidence.json and *-evidence.json) against this contract and classifies them as action_evidence entries in the bundle manifest.

Job artifacts are served through the admin-gated /api/studio/jobs/{job_id}/artifacts/{artifact_path} endpoint. The endpoint only serves manifest-declared artifacts, revalidates the recorded size and SHA-256 digest before returning bytes, and uses generic error details when an artifact is missing or fails integrity checks.

The Admin panel queue uses /api/studio/jobs and /api/studio/jobs/{job_id} for path-free job records. Those endpoints are admin-gated and expose status, owner, request ID, timestamps, result metadata, and artifact manifests without revealing the configured job-root path. The Admin Jobs section also displays the resource profiles published by /api/studio/jobs/status, so operators can see per-kind timeout, artifact, and execution-model limits before launching long-running work.

Administrators can create reproducible handoff bundles with POST /api/studio/evidence/bundle. The route runs as a bounded studio-evidence worker job and can include one saved project payload, selected simulation responses carrying studio.simulation-run.v1 or .v2 run metadata, selected analysis responses carrying studio.analysis-result.v1 analysis metadata, selected default-flow run and attestation responses, selected job records, verified copies of selected job artifacts, a bounded audit export, and command replay metadata such as method, route, and request body digest. Bundle files are declared job artifacts under evidence/, with simulation payloads stored under evidence/simulations/, analysis payloads stored under evidence/analyses/, selected studio.model-scan.v1 model-scan payloads (classified as analysis evidence) stored under evidence/model-scans/, selected studio.training.weight-restore.v1 weight-restore payloads (classified as training evidence) stored under evidence/training-weight-restores/, selected studio.training.weight-restore-attach.v1 weight-restore attach payloads (classified as training evidence) stored under evidence/training-weight-restore-attaches/, default-flow payloads stored under evidence/default-flows/, and a studio.evidence-bundle.v1 manifest at evidence/manifest.json. The Admin evidence form exposes Model Scan JSON, Weight Restore JSON, and Weight Restore Attach JSON fields alongside the simulation and analysis inputs. Selected job action-evidence artifacts are copied under evidence/jobs/{job_id}/artifacts/ and classified as action_evidence only after studio.action-evidence.v1 validation. The bundle manifest is path-free and omits bearer tokens, token hashes, password material, and host-local filesystem paths. The response and manifest also include a path-free summary with artifact-path count, manifest-entry count, entry-type counts, action-evidence classification counts, and selected source-job counts by kind and owner.

The Admin panel exposes the same evidence-bundle workflow. Operators can enter an optional saved project name, simulation-result JSON, analysis-result JSON, default-flow run JSON, default-flow attestation JSON, comma-separated job IDs, audit export settings, and replay metadata. Recent job rows display declared artifact paths plus evidence-manifest counts and can seed the job ID field for bundle export. After export, the panel refreshes the worker queue and shows the bundle ID, evidence job ID, artifact count, and manifest entry count. The panel also surfaces entry-type, evidence-class, and source-job summaries so operators can verify bundle content without opening the raw manifest JSON. It also lists manifest entries with entry type, evidence classification, source, and artifact or replay detail before the downloadable file rows. Bundle artifact rows expose path, size, and SHA-256 labels and download through the authenticated job artifact route, preserving bearer-session access control for browser users.

Studio also exposes a process-backed job-manager path for new backend work that can be expressed as an importable module:function task with a JSON-serializable payload and result. Process jobs use the same path-confined artifact context and public manifest shape as thread-backed jobs, but they run in a separate Python process so timeout or cancellation can terminate the worker instead of leaving a long-running Python callable alive in the backend process. /api/training/start, /api/compile, /api/models/compile, /api/models/cosim, /api/synth/run, /api/synth/multi-target, /api/synth/pnr, and /api/pipeline/run use this process-backed path for training, ODE/model-to-RTL compilation, selected-model real RTL parity, synthesis, PnR, target comparison, and graph-to-synthesis execution while preserving their response contracts and evidence artifacts. Remaining route closures stay on the thread-backed path until each workflow is migrated to importable process tasks with explicit payload contracts.

The Admin panel also uses the admin-gated /api/studio/operator/status aggregate when available. That endpoint reports deployment profile, route-policy enforcement, route inventory counts, protected-route audit coverage, identity mode, audit health plus retained-chain integrity, worker health, resource-limit posture, browser-login lockout limits, and capability counts without exposing local paths or token material.

The first-screen readiness panel and the Admin Operator section render the same operator-status posture. Browser readiness is an operator activation aid: it shows disabled route-policy enforcement, header-principal fallback, memory-only audit sinks, missing job roots, incomplete runtime ceilings, unhealthy jobs, and unavailable capabilities early, while sc-neurocore studio-preflight remains the release promotion gate.

For production deployments, set SC_NEUROCORE_STUDIO_DEPLOYMENT_PROFILE=production. That profile fails closed unless route policies are enforced, header principals are disabled, and the identity file, audit log, and job root are all configured.

Studio ships deployment-profile packages for the three supported operator contexts:

Bash
sc-neurocore studio-deployment-profile --studio-profile local
sc-neurocore studio-deployment-profile --studio-profile lab --output studio-lab-profile.json
sc-neurocore studio-deployment-profile --studio-profile server --format env --output studio-server.env

The package schema is studio.deployment-profile.v1. The local package keeps the runtime profile in development with loopback-only hosts and origins for a single workstation. The lab and server packages set the runtime profile to production, require route-policy enforcement, disable header principals, and include placeholders for the durable identity file, audit log, job root, saved project workspace, allowed hosts, allowed origins, preflight command, launch command, and backup items. Package output contains placeholders and safe defaults only; bearer tokens, password material, token hashes, and host-local paths must be supplied outside the repository by the operator.

Generate the durable-state backup and restore plan from the same runtime environment:

Bash
sc-neurocore studio-backup-plan --output studio-backup-plan.json

The plan schema is studio.backup-plan.v1. By default it is safe for deployment logs: it lists the identity file, audit log, job root, and saved project workspace by stable item IDs and source labels without resolved local paths or secret material. Use --include-local-paths only for an internal host-local handoff that must name the exact paths to capture and restore.

Saved project writes use the studio.project-save.v1 response schema. The backend persists the full project JSON for later restore, while the API returns only the project name, saved timestamp, Studio payload version, project-state SHA-256, full-project SHA-256, and project_workspace evidence classification. The response is path-free so deployment logs and UI state do not expose operator-local workspace roots.

The Projects panel keeps the latest save response in UI state and renders the classification, project name, state digest, project digest, and schema version as the operator-visible confirmation for the persisted workspace. The same strip can export a project evidence bundle through the bounded evidence worker, using the saved project name and project SHA-256 as replay metadata while refreshing the worker queue after export. When the bundle is available, the Projects strip lists the path-confined artifacts with size and SHA-256 labels and downloads each file through the authenticated Studio job-artifact endpoint. The frontend keeps Admin, Projects, Compiler, and Synthesis evidence-bundle state in separate slots so one workflow cannot display or download another workflow's last exported bundle by accident. The operator workbench uses those same scoped slots for its first-screen export action: it routes to synthesis evidence after a synthesis job, compile evidence after compile traceability, and project evidence after a saved project.

Before promoting a Studio deployment, run the release preflight from the same environment that will launch the backend:

Bash
sc-neurocore studio-preflight --output studio-preflight.json

The command exits with status 0 when no check fails; non-blocking warnings do not change the exit status. It checks runtime settings, route-policy enforcement, disabled development header principals, required admin route policies, a valid identity store with at least one active unexpired studio.admin principal, browser-login lockout settings, audit-log readiness, job-root readiness, and bounded EDA process and job-artifact resource limits. The required route-policy inventory includes service account list/detail/update routes and browser-user list/detail/create/update and password-rotation routes, job list/detail/artifact routes, and the evidence bundle export route. The resource_limits check reports the configured EDA CPU and memory ceilings and the per-job artifact byte limit; it returns a warn status, rather than failing, when the host cannot enforce the ceilings (non-POSIX) or when they are left unbounded, so operators can still see the gap in deployment logs. The JSON report uses schema studio.preflight.v1 and is safe for deployment logs: it reports booleans, counts, stable check IDs, a top-level warned flag, and path-free remediation steps without local filesystem paths, bearer tokens, token hashes, passwords, or password verifiers.

Additional Panels (Blocks 2–6)

The Studio includes five additional panels beyond the core research workbench:

  • Compiler Inspector — build SC IR, verify, emit SystemVerilog. Details
  • Synthesis Dashboard — worker-backed Yosys synthesis for 4 FPGA targets, multi-target comparison, target provenance, resource estimation. Details
  • Training Monitor — live SNN training with 6 surrogate gradients, SSE metric streaming. Details
  • Network Canvas — drag-and-drop populations and projections with React Flow, NIR export/import. Details
  • Integration — worker-backed full pipeline (graph → compile → synthesise), project save/load. Details

Full documentation: Studio Hub