Version: 0.26.2 Status: ✅ Production Ready Last Updated: September 5, 2026 Project: Juniper - Cascade Correlation Neural Network Monitoring
- Introduction
- Getting Started
- Dashboard Overview
- Training Controls
- Visualization Tabs
- Configuration
- Troubleshooting
- Advanced Features
Juniper Canopy is a real-time monitoring and diagnostic frontend for Cascade Correlation (CasCor) Neural Networks. It provides:
- Real-time Training Visualization - Monitor loss, accuracy, and training progress
- Network Topology Viewer - Visualize network structure as it evolves
- Decision Boundary Plotting - See how the network classifies data
- Dataset Explorer - View and analyze training data
- Training Controls - Start, pause, resume, and reset training sessions
✅ Zero Configuration - Works out of the box with sensible defaults ✅ Demo Mode - Test and explore without a CasCor backend ✅ Real-time Updates - WebSocket-based push updates (<100ms latency) ✅ Responsive UI - Modern Bootstrap-based interface ✅ Production Ready - Comprehensive testing and CI/CD pipeline
-
Clone the repository:
cd ~/Development/python/Juniper/juniper-canopy
-
Activate the conda environment:
The env name is versioned: each rebuild increments the suffix and renames the old env
*-DEPRECATED. Discover yours withconda env list | grep JuniperCanopy.conda activate JuniperCanopy1 # live env; see the note above -
Verify dependencies:
pip install -r conf/requirements.txt
Demo mode runs a simulated training session without requiring a CasCor backend:
./demoThis will:
- Start the FastAPI server at http://127.0.0.1:8050
- Initialize demo mode with spiral dataset
- Begin simulated training automatically
- Open the dashboard at http://127.0.0.1:8050/dashboard/
To monitor real training, point canopy at a running juniper-cascor service (and at juniper-data, which supplies the dataset generators):
# The juniper-cascor service: host port 8201 in the standard local layout
# (8200 inside the juniper-deploy containers)
export JUNIPER_CANOPY_CASCOR_SERVICE_URL=http://localhost:8201
# The juniper-data service (shared, unprefixed name)
export JUNIPER_DATA_URL=http://localhost:8100
# Run application (the env name is versioned — see Installation)
cd src
/opt/miniforge3/envs/JuniperCanopy1/bin/python main.pyWithout JUNIPER_CANOPY_CASCOR_SERVICE_URL canopy logs
No CasCor service URL configured — falling back to demo mode and runs the demo backend.
Once running, open your browser to:
http://127.0.0.1:8050/dashboard/You should see:
- Status Indicator (Green ● Active / Blue ● Training)
- WebSocket Connection Status
- Training Controls Panel (left sidebar)
- Network Information Panel (left sidebar)
- Visualization Tabs (main area)
┌─────────────────────────────────────────────────────────┐
│ Juniper Canopy Monitor │
│ Real-time monitoring for Cascade Correlation NNs │
├──────────────┬──────────────────────────────────────────┤
│ Status: ● │ WebSocket: 1 connection(s) │
├──────────────┴──────────────────────────────────────────┤
│ Training │ ┌─────────────────────────────────────┐ │
│ Controls │ │ [Training Metrics Tab] │ │
│ ┌─────────┐ │ │ │ │
│ │ Start │ │ │ Loss and Accuracy plots │ │
│ │ Pause │ │ │ │ │
│ │ Stop │ │ └─────────────────────────────────────┘ │
│ └─────────┘ │ │
│ │ │
│ Network │ │
│ Info │ │
│ ┌─────────┐ │ │
│ │Input: 2 │ │ │
│ │Hidden:3 │ │ │
│ │Output:1 │ │ │
│ └─────────┘ │ │
└──────────────┴──────────────────────────────────────────┘- 🟢 Green ● Active - Server running, idle
- 🔵 Blue ● Training - Training in progress
- 🟠 Orange ● Standby - Server connected, not healthy
- 🔴 Red ● Error - Connection or server error
N connection(s)(Green) - Active WebSocket connections- Disconnected (Gray) - No WebSocket connection
- Fast Updates (1 second): Status indicators, training metrics
- Slow Updates (5 seconds): Network topology, decision boundaries, dataset
The Training Controls Panel (left sidebar) provides real-time control over training:
┌─────────────────┐
│ Start Training │ (Green)
└─────────────────┘- Action: Starts or restarts training from beginning
- Demo Mode: Begins simulated training with automatic epoch progression
- Production Mode: Initiates training on real CasCor backend
- Note: Automatically resets state on start
┌─────────────────┐
│ Pause Training │ (Yellow/Warning)
└─────────────────┘- Action: Pauses training without losing state
- Demo Mode: Freezes epoch progression while maintaining current state
- Production Mode: Sends pause command to CasCor backend
- Resume: Click again to resume (button text changes)
┌─────────────────┐
│ Stop Training │ (Red/Danger)
└─────────────────┘- Action: Stops training completely
- Demo Mode: Halts simulation thread cleanly
- Production Mode: Sends stop command to CasCor backend
- Warning: State is preserved but training cannot be resumed (use Start to restart)
The sidebar's Meta Parameters cards (Neural Network, Dataset, Candidate Nodes) hold the full
parameter set — epochs, learning rate, hidden units, multi-node layers, optimizer, activation,
output-weight initialisation, dataset generator fields and the candidate-pool settings. The two
below are the ones most often changed. Their bounds come from TrainingSettings in
src/settings.py, and their defaults can be overridden per deployment with
JUNIPER_CANOPY_TRAINING__LEARNING_RATE__DEFAULT / JUNIPER_CANOPY_TRAINING__HIDDEN_UNITS__DEFAULT
(see Configuration). Edits are staged until you click Apply — the button
enables only when a value differs from the last applied set — and the Parameters tab shows the
applied values.
- Type: Decimal number (any step)
- Default: 0.01
- Range: 0.0001 - 1.0
- Effect: Controls training speed and convergence
Maximum Hidden Units
- Type: Integer (step: 1)
- Default: 1000
- Range: 0 - 10000
- Effect: Caps the number of cascade units added during training
Note: These controls are active in both demo and service modes. In service mode, current values are also exposed through
GET /api/statusandGET /api/state.
The dashboard ships 15 tabs, in tab-bar order: Training Metrics, Candidate Metrics, Network Topology, Network Evolution, Decision Boundary, Dataset View, Workers, Parameters, Snapshots, Replay, Network Editor, Redis, Cassandra, Tutorial and About. The five cascade-only tabs — Candidate Metrics, Network Topology, Network Evolution, Decision Boundary and Workers — are hidden while a one-shot model such as Recurrence (LMU) is selected.
Purpose: Monitor real-time training progress with loss and accuracy curves
Plots Displayed:
- Training & Validation Loss (Blue/Red lines)
- Training & Validation Accuracy (Green/Orange lines)
Features:
- Auto-scaling Y-axis - Adjusts to data range
- Real-time Updates - Store updated every second (REST + WebSocket-assisted buffering)
- Smoothing - 10-point rolling average (configurable)
- Hover Information - Epoch, exact metric values
- Legend Toggle - Click legend items to show/hide series
- Validation Overlays - Dashed
val_lossandval_accuracytraces are shown when available - Learning Rate Card - Displays current
learning_ratefrom training state (--if unavailable) - Hidden Units Ratio - Displays
hidden_units / max_hidden_unitswhen max is known - Phase Duration - Shows elapsed time from
phase_started_atduring active phases - Progress Bars - Shows grow iteration and candidate epoch progress when state includes:
grow_iteration,grow_maxcandidate_epoch,candidate_total_epochs
Interpreting the Plots:
✅ Good Training:
- Loss decreases smoothly
- Accuracy increases steadily
- Validation metrics track training metrics closely
- No sudden spikes or divergence
- Validation loss increases while training loss decreases (overfitting)
- Erratic fluctuations (learning rate too high)
- Flat curves (learning rate too low, or convergence)
Metric Layouts: name the current zoom/pan view and click Save Layout
(POST /api/v1/metrics/layouts); the dropdown lists the saved layouts (GET /api/v1/metrics/layouts),
Load re-applies one and Delete removes it without a confirmation dialog.
In-metrics replay: when training is stopped, paused, completed or failed (never during a live
run) a replay bar appears under the plots: ⏮ start, ◀ step back, ▶ play / ⏸ pause, step forward,
⏭ end, a position slider and a current / max readout. Playback advances one recorded epoch per
tick. The base tick is 1000 ms; the 1x / 2x / 4x buttons set it to 1000 / 500 / 250 ms.
Data Source:
- Metrics history:
GET /api/metrics/history?limit=... - Training state:
GET /api/state - WebSocket stream:
/ws/training(metrics,state,topology,event)
Purpose: Visualize network architecture and connection weights
Display Elements:
-
Input Nodes (Green circles, left)
- One node per input feature
- Default: 2 nodes for (x, y) coordinates
-
Hidden Nodes (Blue circles, middle)
- Cascade units added during training
- Count increases as training progresses
- Maximum: 8 in demo mode (configurable)
-
Output Nodes (Orange circles, right)
- One node per output class
- Default: 1 node for binary classification
-
Connection Lines
- Color Intensity - Represents weight magnitude
- Red - Negative weights
- Blue - Positive weights
- Thickness - Proportional to |weight| value
Layout Algorithm:
- Spring Layout - Nodes positioned by force-directed graph
- Layered - Input (layer 0) → Hidden (layer 1) → Output (layer 2)
Interactive Features:
- Zoom - Scroll to zoom in/out
- Pan - Click and drag to pan view
- Hover - Show node ID and connection details
- Hidden depth slider - Once at least one cascade unit exists, filter the graph to the first
Khidden units (see below)
Hidden Depth Filter:
The Hidden depth slider is a view filter, not a training control. CasCor adds hidden units in cascade order (hidden_0 first), so the slider shows a prefix of that history.
| Slider / label | What you see |
|---|---|
Label reads all |
No filter. Every hidden unit and every connection is drawn. |
Label reads K of N |
Only the first K of N hidden units. Edges that touch a hidden unit at or past K are dropped. |
Slider at rest (0) |
Same as all. 0 means "no filter", not "show zero units." |
Slider at max (N) |
Same as all. |
The slider is hidden until the network has at least one hidden unit. Dragging commits on mouse-up (not on every tick). A chosen K survives later cascade_add events unless it is now past the new max, in which case the control snaps to "show all." The stats bar's Hidden Units readout shows K of N while a filter is active.
The first-run walkthrough highlights this control (network-visualizer-depth-slider-container).
If the label stays at "0 of N" while the graph is clearly showing every unit, that is F-CANOPY-042 (the label was wired to topology changes, not to the slider, and treated rest-state 0 as a real depth). The graph and stats bar can still be correct. canopy#570 splits the label onto its own clientside callback so it tracks the slider and matches the filter. See AGENTS_REFERENCE.md § Hierarchy Depth Filter.
- Click a node - Opens the selection panel (
Selected: …,Layer: Input|Hidden|Output). Clicking the same node again clears the selection. - Box / lasso - Mode-bar
select2d/lasso2dselect several nodes. The panel lists up to five.
Node Selection:
The selection panel is view state. It highlights nodes on the graph; it does not change the trained network.
| Gesture | What happens |
|---|---|
| Click a node (or the edge vertex that sits on it) | Panel opens. Layer is taken from the node label (Hidden 0 → Hidden), not from the Plotly curve index. |
| Click the selected node again | Selection clears. This is the only canvas click gesture that clears; the Clear selection button clears too. |
| Click any member of a box/lasso set | The whole set clears (same toggle). |
| Click empty canvas | Nothing. Plotly emits plotly_click only when a point is hit. The callback never runs. |
The panel used to say "(Click again or elsewhere to deselect)" after a click and "(Click elsewhere to deselect)" after a box select. The "elsewhere" half was described but never implemented. canopy#573 fixed it: a Clear selection button appears only while something is selected, the click hint reads "(Click again to deselect)", and the box/lasso panel carries no hint, because the button is the affordance. See AGENTS_REFERENCE.md § Topology Node Selection.
- Camera (modebar) — download the current figure as PNG
(
canopy_network_<YYYYmmdd>_<HHMMSS>.png, 2× scale). SVG from the same menu is a different path and does not needblob:. If the camera button does nothing, the browser console will show a CSPimg-srcviolation — see troubleshooting #6.
Data Source:
- API:
GET /api/topology - WebSocket:
/ws/training(type:topology)
Example Topology Response:
{
"input_units": 2,
"hidden_units": 3,
"output_units": 1,
"nodes": [
{"id": "input_0", "type": "input", "layer": 0},
{"id": "input_1", "type": "input", "layer": 0},
{"id": "hidden_0", "type": "hidden", "layer": 1},
{"id": "hidden_1", "type": "hidden", "layer": 1},
{"id": "hidden_2", "type": "hidden", "layer": 1},
{"id": "output_0", "type": "output", "layer": 2}
],
"connections": [
{"from": "input_0", "to": "output_0", "weight": 0.234},
{"from": "input_1", "to": "output_0", "weight": -0.156},
{"from": "hidden_0", "to": "output_0", "weight": 0.678}
],
"total_connections": 9
}Purpose: Make surgical edits to a restored CasCor network snapshot.
The Network Editor is for service-mode investigation workflows, not normal training. It is disabled until the CasCor lifecycle reports the Investigating state, which is entered by restoring a snapshot from the Snapshots tab. The panel polls GET /api/status every 2 seconds and unlocks when state_machine.status is Investigating.
Available Operations:
-
Append Hidden Unit
- Adds one hidden unit at the cascade tail.
- The weight vector length must match
input_size + existing_hidden_units. biasdefaults to0.0.- Supported UI activation choices are
Tanh,Sigmoid,ReLU, andLinear. - CasCor initializes the new unit's output column to zero, so the unit has no output-layer effect until weights are patched or training is resumed.
-
Remove Hidden Unit
- Deletes the selected zero-based hidden unit index.
- CasCor rebuilds downstream cascade weights so the forward-pass shape invariant still holds.
- The optimizer state is dropped on the CasCor side and rebuilt by fresh training.
-
Patch Weights
- Patches one parameter group:
output_weightsoutput_biashidden_unit_weightshidden_unit_bias
hidden_unit_*targets require a hidden unit index.- Values can be entered as comma-, semicolon-, or newline-separated floats.
- Canopy sends
dtype: "float32"and lets CasCor validate the exact target shape.
- Patches one parameter group:
Workflow:
- Run Canopy in service mode against a live CasCor backend.
- Restore a snapshot from the Snapshots tab.
- Wait for the Network Editor badge to show
FSM: Investigating. - Review the topology readout and hidden unit indexes.
- Apply the smallest mutation needed.
- Use the API response shown in the status alert to confirm the edit.
- Resume or retrain from the edited snapshot when ready.
Constraints and Failure Modes:
- Demo mode cannot apply network mutations; Canopy returns
501 Not Implementedfor the proxy routes. - CasCor enforces the
Investigatinglifecycle gate and rejects mutation attempts from other states. - CasCor owns exact tensor shape checks, out-of-range hidden unit handling, NaN/Inf validation, and activation validation.
- Canopy currently surfaces adapter failures as status alerts using the backend
detailtext. - Destructive operations should be treated as snapshot-scoped edits; keep an original snapshot available before removing units or replacing large weight groups.
Data Sources:
- Gating status:
GET /api/status - Public topology API:
GET /api/topology - Mutations:
PATCH /api/v1/network/weightsPOST /api/v1/network/hidden-unitsDELETE /api/v1/network/hidden-units/{idx}
Troubleshooting:
- If the topology readout does not refresh after the editor unlocks, verify the panel's internal topology request path in
src/frontend/components/network_editor_panel.pymatches the publicGET /api/topologyroute exposed bysrc/main.py.
Example Patch Request:
{
"target": "hidden_unit_weights",
"field": "weights",
"values": [0.12, -0.04, 0.31],
"hidden_unit_index": 2,
"dtype": "float32"
}Purpose: Visualize how the network classifies the input space
Display Components:
-
Contour Plot (Background)
- Color Gradient - Represents classification confidence
- Viridis Colorscale - Purple (class 0) → Yellow (class 1)
- Resolution - 100x100 grid (configurable)
-
Data Points (Scatter overlay)
- Color - Actual class label
- Size - Fixed (5 pixels)
- Opacity - 70% for background visibility
Interpreting the Visualization:
✅ Good Classification:
- Clear separation between color regions
- Data points clustered in correct color regions
- Smooth decision boundaries
- Mixed colors in data point regions
- Erratic, noisy boundaries
- Points in wrong color regions (misclassifications)
Configuration Options (app_config.yaml):
frontend:
decision_boundary:
resolution: 100 # Grid resolution
opacity: 0.7 # Contour opacity
contour_levels: 20 # Number of contour lines
color_scale: Viridis # Colormap
show_data_points: true
show_misclassified: trueData Source:
- API:
GET /api/decision_boundary - Computed on-demand from network forward pass
Example Response:
{
"xx": [[...], [...], ...], // X-coordinate meshgrid
"yy": [[...], [...], ...], // Y-coordinate meshgrid
"Z": [[...], [...], ...], // Predictions (100x100)
"bounds": {
"x_min": -1.2,
"x_max": 1.2,
"y_min": -1.2,
"y_max": 1.2
}
}Purpose: Explore the training dataset structure and distribution
Display Elements:
-
Scatter Plot (2D data points)
- X-axis - Feature 1 (e.g., x-coordinate)
- Y-axis - Feature 2 (e.g., y-coordinate)
- Color - Class label
- Marker Size - 5 pixels
-
Statistics Panel (if enabled)
- Sample count
- Feature count
- Class distribution
Default Dataset (Demo Mode):
- Name: Two-Class Spiral
- Samples: 200 (100 per class)
- Features: 2 (x, y coordinates)
- Classes: 2 (binary)
- Noise: Gaussian (σ=0.1)
Loading a dataset:
- Service mode (juniper-cascor backend): datasets come from the juniper-data service. In the
sidebar's Dataset section pick a generator (the list is
GET /api/dataset/generators, proxied from juniper-data), fill in its parameters and click Apply (POST /api/stage_dataset). The staged dataset takes effect through Restart with new dataset (POST /api/train/restart) or — with Experimental Functions enabled and a run in progress — the Live Switch (POST /api/live_dataset_swap). - Demo mode only: this tab's Generate / Upload / URL modal —
POST /api/dataset/generate(built-in generators),POST /api/dataset/import-file(CSV upload) andPOST /api/dataset/import-url(a CSV fetched by the server; disabled unlessJUNIPER_CANOPY_DATASET_IMPORT_URL_ENABLED=true). Both import paths accept CSV only: one sample per row, the last column is the integer class label, a header row is auto-detected, and files are capped at 10 MB / 50 000 rows / 100 features. JSON, NumPy.npyand HDF5.h5files are not accepted. In service mode all three actions are refused with HTTP400.
Data Source:
- API:
GET /api/dataset
Service Mode Behavior:
- Service-mode responses may include metadata only (
num_samples,num_features,num_classes) when arrays are not available yet. - In that case, the dataset tab renders empty plots with summary stats and class distribution shown as
N/A. - When arrays are available,
inputsandtargetsare rendered normally.
Example Response:
{
"inputs": [[0.12, 0.34], [-0.56, 0.78], ...],
"targets": [0, 1, 0, 1, ...],
"num_samples": 200,
"num_features": 2,
"num_classes": 2
}Purpose: Follow the candidate pool while cascor trains candidate units (the phase between output-training passes)
Display Elements:
- Pool Status Badge, Phase (
Idleuntil a pool is active) and Pool Size - Candidate Epoch Progress —
epoch / total, shown only while the training state carriescandidate_epochandcandidate_total_epochs - Current Pool (collapsible) — "No active candidate pool" or the per-candidate view
- Candidate Loss Plot — the candidate epochs of the current run, read from the same metrics store as the Training Metrics tab (no extra polling)
- Pool History (collapsible) — one read-only card per finished pool, capped at the most recent entries
Data Source:
- The training-state and metrics stores shared with the Training Metrics tab; the tab's own refresh runs only while it is selected
Purpose: A gallery of the network's cascade growth — one card per hidden unit added during a run
Display Elements:
- Stats line — "No snapshots yet" until the first unit is added
- Snapshot grid — one card per growth step, captured in the browser from the
cascade_addWebSocket events and the current topology; a step with an unchanged hidden-unit count is skipped, and the gallery clears itself when the input width changes or the hidden-unit count shrinks (a dataset change or a reset) - Clear — empties the gallery
- Weight norms — per-unit weight-norm traces, revealed only while a snapshot replay streams weight samples; none do today (see Replay Tab)
The sidebar is hidden on this tab.
Data Source:
- WebSocket stream
/ws/training(cascade_addevents) and the topology store — no polling
Purpose: Read-only view of the cascor worker registry
Display Elements:
- Status Badge —
LOADING, thenNO WORKERS,DEGRADED(stale workers present) orHEALTHY - Degradation alert — "Worker data degraded: …" when the upstream call fails (dismissable)
- Six tiles — total, idle, busy, stale, tasks done (
done / failed fail) and average health - Roster table — id, kind (
local/remote), status, health, last heartbeat, current task — or "No workers connected" - Local-workers note — cascor's registry reports remote WebSocket workers only; the in-process candidate pool is not listed individually, and the panel says so rather than inventing rows
Data Source:
GET /api/v1/workers/list(roster) plus best-effort aggregate stats, polled on the slow interval only while this tab is selected
Purpose: At-a-glance, read-only summary of the applied meta-parameters
Display Elements:
- Network Training, Dataset and Candidate Training tables — Pin / Parameter / Current /
Min / Max / Default; booleans render as
Enabled/Disabled - Pin checkboxes — pinned parameters appear as read-only name + value rows in the sidebar's Pinned card; the pin set lives in browser local storage, so it survives a reload
Editing happens in the sidebar; the tables re-render after every Apply.
Data Source:
- The applied-parameters store (filled by the sidebar's Apply round-trip) — no polling
Purpose: Create, browse and act on HDF5 training-state snapshots
Display Elements and Controls:
- Create — optional name and description, then Create Snapshot (
POST /api/v1/snapshots); the form clears on success and the table refreshes - Snapshot table — auto-refreshes every 10 s (
JUNIPER_CANOPY_SNAPSHOTS_REFRESH_INTERVAL_MS) and on Refresh; in service mode the list comes from the cascor service that created the snapshots, with canopy's local snapshot directory as the fallback - View — opens the detail panel (
GET /api/v1/snapshots/{id}) - Restore / Replay / Resume / Retrain — per row, or from the right-click context menu; each
opens a confirmation modal, and Confirm posts
POST /api/v1/snapshots/{id}/{op}. Training must be paused or stopped first: a running run answers409, an unknown id404, an operation the backend does not support501 - Replay additionally loads the session into the Replay Tab and switches to it
- History (collapsible) —
GET /api/v1/snapshots/history - Dataset swaps — paired before / after cards for every live dataset swap recorded in the run
Data Source:
GET /api/v1/snapshots,GET /api/v1/snapshots/{id},GET /api/v1/snapshots/history,POST /api/v1/snapshots,POST /api/v1/snapshots/{id}/{restore|replay|resume|retrain}
Purpose: Drive a snapshot replay session opened from the Snapshots tab
Display Elements and Controls:
- Idle placeholder — "No active replay session" until a replay is confirmed on the Snapshots tab
- Session header — snapshot id, FSM badge, and a weights badge (
V2 ✓ weightswhen the snapshot carries weights, otherwiseV1 (metrics only)) - Transport — ▶ Play, ⏸ Pause, ⏹ Stop
- Epoch scrubber — drag and release to seek; readout
current / end - Speed slider — −10× … 10×; negative values play backwards, 0 pauses (
Paused (0×)) - Time range — restrict playback to a sub-window of epochs
- Status block — the result of the last control action
- Dataset-swap events — markers on a wall-clock axis with a count; hover for details
Every control posts POST /api/v1/snapshots/{id}/replay/control, which canopy proxies to the cascor
service. By design, weight samples streamed during playback are drained every 500 ms into the buffer
that feeds the Network Evolution weight-norm traces. Today no weight sample
reaches the page, because cascor's replay frames carry none, so those traces stay hidden during a replay.
The drain starts only once the page has started a replay.
Data Source:
POST /api/v1/snapshots/{id}/replay/control; the loaded snapshot's swap history fromGET /api/snapshots/{id}/history/dataset_swaps
Purpose: Read-only monitoring of the optional Redis cache integration
Display Elements:
- Status and Mode badges (
DEMO/LIVE/DISABLED) - Unavailable / error display — when no Redis is deployed the tab shows an unavailable state and leaves the rest of the dashboard untouched
- Eight tiles — version, uptime, connected clients, latency, memory, ops/sec, hit rate and keyspace (placeholders when unavailable)
Data Source:
GET /api/v1/redis/statusandGET /api/v1/redis/metrics, every 5 s
Purpose: Read-only monitoring of the optional Cassandra persistence integration
Display Elements:
- Status (
UP/DOWN/DISABLED/UNAVAILABLE) and Mode (DEMO/LIVE/DISABLED) badges - Error area — an unavailable render, never a crash, when no cluster is configured
- Cluster overview — contact points, keyspace, a hosts table, keyspace count, table count and replication strategies (placeholders when unavailable)
Data Source:
GET /api/v1/cassandra/statusandGET /api/v1/cassandra/metrics, every 10 s
Purpose: In-app orientation
Display Elements and Controls:
- ▶ Take a guided tour — launches the walkthrough overlay (Skip / Done to leave it)
- Accordion — CasCor overview, workflow, UI guide, parameter reference and keyboard shortcuts; sections open independently of each other
- Right-clicking a control that has a tooltip offers View tutorial, which jumps to this tab
Purpose: Application information
Display Elements:
- App Version — the installed package version, the same value
GET /v1/healthreports - Licence, credits, documentation links and contact information
- System Information (toggle) — Python version and platform details, built locally without a request
Settings are typed pydantic-settings models in src/settings.py; the only configuration file the
application reads is an optional .env in the working directory (.env.example documents every
key). Precedence, highest first:
- Environment variables (
JUNIPER_CANOPY_*, see below) - The
.envfile - The defaults in
src/settings.py
conf/app_config.yaml is legacy: the application settings no longer come from it (only the
optional Redis client still reads it, through config_manager.py).
Every setting has a JUNIPER_CANOPY_-prefixed environment variable; nested sections use a double
underscore:
# Server configuration
export JUNIPER_CANOPY_SERVER__PORT=8051
export JUNIPER_CANOPY_SERVER__HOST=0.0.0.0 # a non-loopback host needs a SEC-F22 attestation — see .env.example
# Demo mode
export JUNIPER_CANOPY_DEMO_MODE=1
# Backend path
export JUNIPER_CANOPY_CASCOR_SERVICE_URL=http://localhost:8201 # juniper-cascor service (service mode)
export JUNIPER_DATA_URL=http://localhost:8100 # juniper-data (shared, unprefixed)
export JUNIPER_CANOPY_BACKEND_PATH=/custom/path/to/juniper-cascor # in-process cascor checkout (legacy path)
# Debug mode
export JUNIPER_CANOPY_SERVER__DEBUG=true
export JUNIPER_CANOPY_LOG_LEVEL=DEBUG
export JUNIPER_CANOPY_LOG_FORMAT=json
# Sidebar defaults for the training parameters
export JUNIPER_CANOPY_TRAINING__LEARNING_RATE__DEFAULT=0.01
export JUNIPER_CANOPY_TRAINING__HIDDEN_UNITS__DEFAULT=1000
export JUNIPER_CANOPY_TRAINING__EPOCHS__DEFAULT=1000000
# Demo-mode pacing
export JUNIPER_CANOPY_DEMO_CASCADE_EVERY=30 # add a cascade unit every N epochs
# JUNIPER_CANOPY_DEMO_UPDATE_INTERVAL is declared in settings but not applied: the backend
# factory creates the demo backend with a fixed 1.0 s epoch interval (a tracked divergence).Legacy CASCOR_* names. CASCOR_DEMO_MODE, CASCOR_SERVICE_URL, CASCOR_BACKEND_PATH,
CASCOR_LOG_LEVEL, CASCOR_DEMO_UPDATE_INTERVAL and CASCOR_DEMO_CASCADE_EVERY are still honoured,
with a deprecation warning at startup. CASCOR_SERVER_PORT, CASCOR_SERVER_HOST and CASCOR_DEBUG
are read by nothing — exporting them has no effect; use the JUNIPER_CANOPY_SERVER__* names above.
The sidebar's training defaults additionally honour the unprefixed CASCOR_TRAINING_LEARNING_RATE,
CASCOR_TRAINING_HIDDEN_UNITS and CASCOR_TRAINING_EPOCHS.
Every setting below lives on Settings in src/settings.py; the environment name is the field name
upper-cased under the JUNIPER_CANOPY_ prefix, with __ between a nested section and its key.
Setting (src/settings.py) |
Environment variable | Default |
|---|---|---|
server.host / server.port / server.debug |
JUNIPER_CANOPY_SERVER__HOST / __PORT / __DEBUG |
127.0.0.1 / 8050 / false |
demo_mode |
JUNIPER_CANOPY_DEMO_MODE |
false |
cascor_service_url |
JUNIPER_CANOPY_CASCOR_SERVICE_URL |
unset (demo fallback) |
juniper_data_url |
JUNIPER_DATA_URL (or JUNIPER_CANOPY_JUNIPER_DATA_URL) |
http://localhost:8100 |
recurrence_service_url |
JUNIPER_CANOPY_RECURRENCE_SERVICE_URL |
unset |
training.<param>.{min,max,default} |
JUNIPER_CANOPY_TRAINING__<PARAM>__{MIN,MAX,DEFAULT} |
see TrainingSettings |
demo_cascade_every |
JUNIPER_CANOPY_DEMO_CASCADE_EVERY |
30 |
demo_update_interval |
JUNIPER_CANOPY_DEMO_UPDATE_INTERVAL — declared, not applied (fixed 1.0 s) |
1.0 |
| Setting | Environment variable | Default |
|---|---|---|
enable_ws_control_buttons |
JUNIPER_CANOPY_ENABLE_WS_CONTROL_BUTTONS |
true |
dataset_import_url_enabled |
JUNIPER_CANOPY_DATASET_IMPORT_URL_ENABLED |
false |
| Snapshots-table refresh (panel-local) | JUNIPER_CANOPY_SNAPSHOTS_REFRESH_INTERVAL_MS |
10000 |
| Setting | Environment variable | Default |
|---|---|---|
log_level / log_format |
JUNIPER_CANOPY_LOG_LEVEL / JUNIPER_CANOPY_LOG_FORMAT |
INFO / text |
sentry_dsn |
JUNIPER_CANOPY_SENTRY_DSN |
unset |
metrics_enabled |
JUNIPER_CANOPY_METRICS_ENABLED |
false |
- Export the variable (or edit
.env) in the shell that will launch canopy. - Restart the application (
./demo, or the service-mode command above) — settings are read once at startup. - Verify: the startup log carries a deprecation warning for every legacy name in use, and
GET /v1/healthreportsdemo_mode.
Symptoms:
- All tabs show "No data available"
- Metrics, topology, dataset views are empty
Causes:
- Demo mode not started
- WebSocket not connected
- API endpoint errors
Solutions:
✅ Check demo mode is running:
# In logs/system.log, look for:
"Demo mode started with simulated training"
"Demo training simulation started"✅ Verify WebSocket connection:
- Check "WebSocket: 1 connection(s)" in dashboard header
- If "Disconnected", refresh browser page
✅ Check API endpoints:
curl http://127.0.0.1:8050/api/health
curl http://127.0.0.1:8050/api/metrics?limit=10✅ Review logs for errors:
tail -f logs/system.log | grep -i errorSymptoms:
ModuleNotFoundError: No module named 'uvicorn'Cause:
- Using system Python instead of conda environment
Solutions:
✅ Use conda environment Python explicitly: (the env name is versioned;
discover yours with conda env list | grep JuniperCanopy)
/opt/miniforge3/envs/JuniperCanopy1/bin/python main.py✅ Or activate environment first:
conda activate JuniperCanopy1
python main.py✅ Use demo script (automatically activates):
./demoSymptoms:
Error: Address already in use: 127.0.0.1:8050Cause:
- Another instance running on same port
- Previous instance didn't shut down cleanly
Solutions:
✅ Find and kill existing process:
# Find process using port 8050
lsof -i :8050
# Kill process
kill -9 <PID>✅ Use different port:
export JUNIPER_CANOPY_SERVER__PORT=8051
./demoSymptoms:
- Ctrl+C doesn't stop application
- Training continues after stop button
Cause:
- Background thread not stopping cleanly
Solutions:
✅ Force kill:
# Find Python process
ps aux | grep python | grep main.py
# Kill
kill -9 <PID>✅ Check Event-based stopping:
# In demo_mode.py, verify:
while not self._stop.is_set():
# ... workSymptoms:
- "WebSocket: Disconnected" in dashboard
- No real-time updates
- Browser console shows WebSocket errors
Causes:
- FastAPI event loop not set
- Network/firewall issues
- CORS configuration
Solutions:
✅ Verify event loop setup (in logs):
"Event loop captured for thread-safe broadcasting"
✅ Check browser console:
// Should see:
WebSocket connection to 'ws://127.0.0.1:8050/ws/training' opened✅ Test WebSocket manually:
// In browser console:
const ws = new WebSocket('ws://127.0.0.1:8050/ws/training');
ws.onopen = () => console.log('Connected');
ws.onmessage = (e) => console.log('Message:', e.data);Kept for anyone running a build older than canopy#570. On current main the
label follows the slider and reads "all" at rest.
Symptoms (pre-#570):
- Network Topology tab shows a loaded cascade (for example 40 hidden units)
- The Hidden depth label reads
"0 of 40"at rest, or does not change when you drag the slider - The graph and the Hidden Units stats count still follow the filter
Cause:
- The label was a fourth output of the clientside slider-bounds sync. That callback fires only when the topology store changes; the slider value was read as State. Rest-state
0was also rendered as"0 of N"even though the filter treats0as "show all."
What to do:
- Trust the graph and the stats bar for what is actually drawn.
"all"and a slider at max are the no-filter states. - Do not "fix" the label by adding the slider value as an Input of the bounds-sync callback — Dash rejects that as a circular dependency (
-depth-slider.valueis already an Output there). - Do not route the label through the topology rebuild (
update_network_graph); that callback's measured paint is 1.5–31 s. - The repair, canopy#570, is on
main: a dedicated clientside label callback whose rule matches_apply_hierarchy_filter. Developer contract: AGENTS_REFERENCE.md § Hierarchy Depth Filter.
Kept for anyone running a build older than canopy#573. Clicking blank canvas still does nothing — plotly emits no event — but the panel no longer promises that it will, and a Clear selection button now carries the gesture.
Symptoms (pre-#573):
-
After a click the panel read "(Click again or elsewhere to deselect)"
-
After a box/lasso it read "(Click elsewhere to deselect)"
-
Clicking the blank canvas left the highlight in place
-
Plotly emits
plotly_clickonly when a point is hit. A click on empty canvas produces no event, soclickDatanever changes andhandle_node_selection(prevent_initial_call=True) never runs. The "elsewhere" sentence was never implemented.
Solutions:
✅ Click the selected node again — that toggle does clear (including every member of a box/lasso set).
✅ Use the Clear selection button. A clientside click-on-empty handler was considered and rejected (it races Plotly and starves the 1.5–31 s rebuild family); canopy#573 shipped an explicit Clear selection button instead, visible only while something is selected.
See AGENTS_REFERENCE.md § Topology Node Selection.
- Topology (or any Plotly) camera button is present and clickable
- No file is offered; the figure does not change
- Browser console reports a Content-Security-Policy
img-srcviolation for ablob:http://…URL
Plotly's PNG export rasterises SVG → Blob → <img> → canvas. That
image load is a blob: URL. The shipped policy is
img-src 'self' data: blob: (SecurityConstants.DEFAULT_CSP_POLICY,
served by SecurityHeadersMiddleware). Without blob:, the promise
rejects with [object Event] and no download is offered. SVG export
from the same menu still works.
✅ Confirm the console names img-src, not a missing figure.
✅ Confirm the constant and the middleware still match:
cd src
pytest tests/regression/test_csp_plotly_image_export.py \
tests/regression/test_csp_bootstrap_cdn.py -vDo not "fix" this by adding blob: to script-src or by replacing
data: (Bootstrap icons need data:). Developer contract:
AGENTS_REFERENCE.md § Plotly PNG Export.
Check server health:
curl http://127.0.0.1:8050/api/healthGet current status:
curl http://127.0.0.1:8050/api/statusGet metrics:
curl http://127.0.0.1:8050/api/metrics?limit=5View logs:
# System logs
tail -f logs/system.log
# Training logs
tail -f logs/training.log
# UI logs
tail -f logs/ui.logCheck processes:
ps aux | grep -i cascor
ps aux | grep -i python.*main.pyVersion 0.25.0 introduces asynchronous training capabilities via CascorIntegration:
fit_async()- Non-blocking training withThreadPoolExecutorstart_training_background()- Background training with real-time status updates- RemoteWorkerClient integration - Distributed training support
- Thread-safe status tracking - Monitor training progress from any thread
Example Usage:
from backend.cascor_integration import CascorIntegration
integration = CascorIntegration()
future = integration.fit_async(X_train, y_train, max_epochs=200)
# Training runs in background - check status
while not future.done():
status = integration.get_training_status()
print(f"Epoch: {status['current_epoch']}")Note: Async training requires a real CasCor backend (not available in demo mode).
Juniper Canopy uses WebSocket push updates for real-time data streaming:
Connection:
const ws = new WebSocket('ws://127.0.0.1:8050/ws/training');
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
console.log('Type:', msg.type, 'Data:', msg.data);
};Message Types:
connection_established- Initial connection confirmationtraining_metrics- Epoch metrics (loss, accuracy)topology_update- Network structure changescascade_add- New hidden unit addedstatus- Training status changes (paused, running, stopped)
Latency: <100ms for metric updates
To use your own dataset in demo mode:
-
Prepare data format:
dataset = { "inputs": np.array([[x1, x2], [x1, x2], ...]), # Shape: (N, 2) "targets": np.array([0, 1, 0, 1, ...]), # Shape: (N,) "num_samples": N, "num_features": 2, "num_classes": 2 }
-
Modify demo_mode.py:
def _generate_custom_dataset(self): # Load your data inputs = np.loadtxt('data/my_dataset.csv', delimiter=',') targets = np.loadtxt('data/my_labels.csv') return { "inputs": inputs, "targets": targets, # ... etc }
-
Update initialization:
self.dataset = self._generate_custom_dataset()
Reduce Update Frequency:
frontend:
dashboard:
update_interval_ms: 2000 # Slower updates (less CPU)Limit Data Points:
frontend:
training_metrics:
buffer_size: 1000 # Smaller buffer (less memory)Disable Features:
frontend:
decision_boundary:
enabled: false # Skip boundary computationTraining Logs:
logs/training.log # All training metricsSystem Logs:
logs/system.log # Application eventsUI Interaction Logs:
logs/ui.log # User interactionsLog Rotation:
- Daily rotation at midnight
- 30-day retention
- Automatic compression
- README.md - Quick start guide
- API_REFERENCE.md - Complete API documentation
- DEVELOPMENT_ROADMAP.md - Planned features
- CHANGELOG.md - Release history
- Issues: Report bugs via GitHub Issues
- Questions: Check AGENTS.md for developer guidelines
- Email: paul.calnon@example.com (replace with actual contact)
The running version comes from the installed package metadata and is shown by GET /v1/health
and the About tab (both read the same source); the release history is in CHANGELOG.md.
Python: 3.11+ (requires-python in pyproject.toml)
License: MIT
*(Future feature - currently not implemented)*- CasCor - Cascade Correlation neural network architecture
- Epoch - One complete pass through the training dataset
- Cascade Unit - Hidden unit added dynamically during training
- Hidden depth - View filter on the Network Topology tab; keeps the first
Kcascade units. Slider0and slider-at-max both mean "all" (no filter). - Decision Boundary - Regions where network changes classification
- WebSocket - Bidirectional real-time communication protocol
- FastAPI - Modern Python web framework for APIs
- Dash - Python framework for interactive web dashboards
- Node selection - Topology click or box/lasso highlight. Clicking empty canvas does not clear (F-CANOPY-046). Click the selected node again, or (after canopy#573) use Clear selection.
Minimum:
- Python 3.12+
- 2GB RAM
- Modern web browser (Chrome, Firefox, Safari, Edge)
Recommended:
- Python 3.13+
- 4GB RAM
- 2+ CPU cores
- Fast network connection (for remote access)