Spike-driven relevance routing for modular neural systems
TemporalFocus.jl is a small Julia library for computing per-component relevance scores from spike activity and readout change over time. The core abstraction is a routing loop that updates component weights from:
- spike density
- readout surprise relative to an exponential moving average
- routing momentum
- lateral inhibition between components
The library is intentionally narrow. It does not try to be a full SNN runtime, an LLM integration layer, or a hardware supervisor.
TemporalFocus is an extracted, early-stage library. It is useful today, but it still needs a lot of work before it reaches the broader long-term shape rmems wants for it.
What this means in practice:
- the current API is small and focused
- several defaults still reflect the original research/runtime context
- documentation and boundaries are improving, but the package is not yet the final form
- downstream integrations should treat this as an evolving library rather than a finished platform
TemporalFocus owns spike-driven relevance routing logic:
ActivityRegionas a compact per-region summary (Float32rate in[0,1], readout of lengthn_out)RegionRouteras the mutable routing state (routing_weightslengthn_regions, sum ~1)update_routing!as the per-tick routing updaterouting_diagnosticsfor lightweight inspection/loggingadapt_leak!as a small optional helper for stress-aware leak adaptation
The frozen interop shapes (and what the package deliberately does not own — e.g. spike
event lists / full trains) are documented in docs/interop.md.
TemporalFocus does not own:
- spike event lists or full spike trains
- full neuron or reservoir simulation
- training loops or plasticity pipelines
- token embeddings or transformer execution
- hardware telemetry ingestion
- deployment/runtime supervision
- model-specific ANN/LLM adapters
If a workflow needs those pieces, they should live in surrounding libraries or applications that feed compact readouts into TemporalFocus.
using Pkg
Pkg.add("TemporalFocus")using TemporalFocus
router = RegionRouter(
n_regions = 4,
n_out = 8,
region_names = ["sensor", "reservoir", "memory", "decoder"],
)
regions = [
ActivityRegion(0.82f0, Float32[0.9, 0.7, 0.2, 0.1, 0.0, 0.1, 0.3, 0.5]),
ActivityRegion(0.28f0, Float32[0.3, 0.2, 0.1, 0.0, 0.0, 0.0, 0.2, 0.2]),
ActivityRegion(0.41f0, Float32[0.4, 0.6, 0.5, 0.2, 0.1, 0.1, 0.0, 0.1]),
ActivityRegion(0.12f0, Float32[0.1, 0.1, 0.0, 0.0, 0.4, 0.6, 0.8, 0.9]),
]
update_routing!(router, regions)
routing_weights = router.routing_weights
println(routing_weights)
println(routing_diagnostics(router))Worked examples live in examples/:
examples/three_region.jl— minimal 3-region layoutexamples/six_region.jl— larger layout with custom region namesexamples/reservoir_integration.jl— pattern for feeding compact reservoir readouts into TemporalFocus
Run any example from the repository root:
julia --project=. examples/three_region.jlThe old NERO/lobe names still work as backward-compatible aliases:
# These are equivalent:
LobeState == ActivityRegion
NeroOrchestrator == RegionRouter
update_relevance! == update_routing!
nero_diagnostics == routing_diagnosticsAt each tick, TemporalFocus computes a raw score for each component:
score_i = α · density_i + β · surprise_i + γ · momentum_i
with:
density_i: current normalized spike activitysurprise_i: deviation from the component's EMA readoutmomentum_i: change in routing weight relative to the previous tick
The raw scores are then:
- reduced by cross-component inhibition
- clamped with a floor so components do not go fully silent
- normalized with a softmax-like pass to produce routing weights that sum to 1
ActivityRegion(last_spike_rate::Float32, output::Vector{Float32})
ActivityRegion(n_out::Int)
RegionRouter(; n_regions=4, n_out=16, region_names=DEFAULT_REGION_NAMES)
update_routing!(router::RegionRouter, regions::Vector{ActivityRegion})
routing_diagnostics(router::RegionRouter)
adapt_leak!(leak_rate::Ref{Float32}, stress::Real; min_leak=0.01f0, max_leak=0.25f0, stress_adapter=nothing)Legacy aliases (LobeState, NeroOrchestrator, update_relevance!, nero_diagnostics)
resolve to the same types/functions; use the preferred names above for new code.
A few defaults still reflect the package's original extraction context:
- the default lobe names are
Attention,FFN,Memory, andOutput - the default inhibition matrix is tuned for a 4-component example layout
adapt_leak!default stress scale is percent-like in[0, 100](customstress_adapterallowed)- the package currently exposes NERO terminology directly in type/function names
Those defaults are serviceable, but they are not the final abstraction boundary.
- Dev docs — updates from
main(Documenterdeploydocs) - Stable docs (
/stable) appear only after the first version tag is pushed; until then use dev
Source markdown lives in docs/ (Documenter pages under docs/src/):
docs/src/overview.md— architecture, scope, and intended usagedocs/src/api.md— exported types/functions and behavior notesdocs/src/interop.md— frozen data-shape / interop contract (rates, readouts, routing weights)docs/src/roadmap.md— gaps, next cleanup targets, and candid project status
(Root copies under docs/*.md may exist for GitHub browsing; Documenter builds from docs/src/.)
Build locally with:
julia --project=docs -e 'using Pkg; Pkg.develop(path="."); Pkg.instantiate()'
julia --project=docs docs/make.jlThe Julia package / project identity changed from NeuroPulse (and earlier
SpikenautAttention / SpikenautNero) to TemporalFocus. The GitHub
repository and GitHub Pages path remain Limen-Neural/NeuroPulse.jl
(https://limen-neural.github.io/NeuroPulse.jl/...).
Migration steps for downstream users:
- replace
Pkg.add("NeuroPulse")(orSpikenautAttention) withPkg.add("TemporalFocus") - replace
using NeuroPulse(orusing SpikenautAttention) withusing TemporalFocus - update any package metadata or examples that still reference the old package name
The NERO algorithm name remains in the current public API via NeroOrchestrator and
nero_diagnostics, but the package identity is now TemporalFocus.
Run tests with:
julia --project -e 'using Pkg; Pkg.instantiate(); Pkg.test()'This project is licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE-2.0 or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Contributions intentionally submitted for inclusion in this package by you, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.
