diff --git a/miniapp/CMakeLists.txt b/miniapp/CMakeLists.txt
index a5957854ea21feafa89eb63abcf1eb6410739ecc..682adefe1d04b057cb330dc9468485b276b797b2 100644
--- a/miniapp/CMakeLists.txt
+++ b/miniapp/CMakeLists.txt
@@ -4,8 +4,6 @@ set(MINIAPP_SOURCES
     io.cpp
     miniapp.cpp
     miniapp_recipes.cpp
-    model.cpp
-    trace_sampler.cpp
 )
 
 add_executable(miniapp.exe ${MINIAPP_SOURCES} ${HEADERS})
diff --git a/miniapp/miniapp.cpp b/miniapp/miniapp.cpp
index 32c8bc2c53d71e12b787a06a0abe2e8e8becf9c2..1a3065ec1c8d3178ccd0a2374a51948d2bc7be65 100644
--- a/miniapp/miniapp.cpp
+++ b/miniapp/miniapp.cpp
@@ -1,13 +1,17 @@
 #include <cmath>
 #include <exception>
 #include <iostream>
+#include <fstream>
 #include <memory>
 
+#include <json/src/json.hpp>
+
 #include <catypes.hpp>
 #include <cell.hpp>
 #include <cell_group.hpp>
 #include <fvm_cell.hpp>
 #include <mechanism_catalogue.hpp>
+#include <model.hpp>
 #include <threading/threading.hpp>
 #include <profiling/profiler.hpp>
 #include <communication/communicator.hpp>
@@ -17,17 +21,24 @@
 
 #include "io.hpp"
 #include "miniapp_recipes.hpp"
-#include "model.hpp"
+#include "trace_sampler.hpp"
 
 using namespace nest::mc;
 
 using global_policy = communication::global_policy;
 using communicator_type = communication::communicator<global_policy>;
 
+using lowered_cell = fvm::fvm_cell<double, cell_local_size_type>;
+using model_type = model<lowered_cell>;
+using sample_trace_type = sample_trace<model_type::time_type, model_type::value_type>;
+
 void banner();
 std::unique_ptr<recipe> make_recipe(const io::cl_options&);
+std::unique_ptr<sample_trace_type> make_trace(cell_member_type probe_id, probe_spec probe);
 std::pair<cell_gid_type, cell_gid_type> distribute_cells(cell_size_type ncells);
 
+void write_trace_json(const sample_trace_type& trace, const std::string& prefix = "trace_");
+
 int main(int argc, char** argv) {
     nest::mc::communication::global_policy_guard global_guard(argc, argv);
 
@@ -47,7 +58,7 @@ int main(int argc, char** argv) {
         auto cell_range = distribute_cells(recipe->num_cells());
 
         // build model from recipe
-        model m(*recipe, cell_range.first, cell_range.second, 0.1);
+        model_type m(*recipe, cell_range.first, cell_range.second);
 
         // inject some artificial spikes, 1 per 20 neurons.
         cell_gid_type spike_cell = 20*((cell_range.first+19)/20);
@@ -55,13 +66,24 @@ int main(int argc, char** argv) {
             m.add_artificial_spike({spike_cell,0u});
         }
 
+        // attach samplers to all probes
+        std::vector<std::unique_ptr<sample_trace_type>> traces;
+        const model_type::time_type sample_dt = 0.1;
+        for (auto probe: m.probes()) {
+            traces.push_back(make_trace(probe.id, probe.probe));
+            m.attach_sampler(probe.id, make_trace_sampler(traces.back().get(),sample_dt));
+        }
+
         // run model
         m.run(options.tfinal, options.dt);
         util::profiler_output(0.001);
 
         std::cout << "there were " << m.num_spikes() << " spikes\n";
 
-        m.write_traces();
+        // save traces
+        for (const auto& trace: traces) {
+            write_trace_json(*trace.get());
+        }
     }
     catch (io::usage_error& e) {
         // only print usage/startup errors on master
@@ -109,3 +131,46 @@ std::unique_ptr<recipe> make_recipe(const io::cl_options& options) {
         return make_basic_rgraph_recipe(options.cells, p);
     }
 }
+
+std::unique_ptr<sample_trace_type> make_trace(cell_member_type probe_id, probe_spec probe) {
+    std::string name = "";
+    std::string units = "";
+    
+    switch (probe.kind) {
+    case probeKind::membrane_voltage:
+        name = "v";
+        units = "mV";
+        break;
+    case probeKind::membrane_current:
+        name = "i";
+        units = "mA/cm²";
+        break; 
+    default: ;
+    }
+    name += probe.location.segment? "dend" : "soma";
+
+    return util::make_unique<sample_trace_type>(probe_id, name, units);
+}
+
+void write_trace_json(const sample_trace_type& trace, const std::string& prefix) {
+    auto path = prefix + std::to_string(trace.probe_id.gid) +
+                "." + std::to_string(trace.probe_id.index) + "_" + trace.name + ".json";
+
+    nlohmann::json jrep;
+    jrep["name"] = trace.name;
+    jrep["units"] = trace.units;
+    jrep["cell"] = trace.probe_id.gid;
+    jrep["probe"] = trace.probe_id.index;
+
+    auto& jt = jrep["data"]["time"];
+    auto& jy = jrep["data"][trace.name];
+
+    for (const auto& sample: trace.samples) {
+        jt.push_back(sample.time);
+        jy.push_back(sample.value);
+    }
+    std::ofstream file(path);
+    file << std::setw(1) << jrep << std::endl;
+}
+
+
diff --git a/miniapp/miniapp_recipes.cpp b/miniapp/miniapp_recipes.cpp
index 322090332b3a27f88371959b8b7c2f0d0dd37688..f472f60c9d66e0157b17fedc7ae96feb024cd659 100644
--- a/miniapp/miniapp_recipes.cpp
+++ b/miniapp/miniapp_recipes.cpp
@@ -74,10 +74,10 @@ public:
         unsigned n_probe_segs = pdist_.all_segments? basic_cell_segments: 1u;
         for (unsigned i = 0; i<n_probe_segs; ++i) {
             if (pdist_.membrane_voltage) {
-                cell.add_probe({i, i? 0.5: 0.0}, mc::probeKind::membrane_voltage);
+                cell.add_probe({{i, i? 0.5: 0.0}, mc::probeKind::membrane_voltage});
             }
             if (pdist_.membrane_current) {
-                cell.add_probe({i, i? 0.5: 0.0}, mc::probeKind::membrane_current);
+                cell.add_probe({{i, i? 0.5: 0.0}, mc::probeKind::membrane_current});
             }
         }
         EXPECTS(cell.probes().size()==cc.num_probes);
diff --git a/miniapp/model.cpp b/miniapp/model.cpp
deleted file mode 100644
index a7dca18ccc7bdbc05272f0bca0b52fdd4a3f4d3e..0000000000000000000000000000000000000000
--- a/miniapp/model.cpp
+++ /dev/null
@@ -1,95 +0,0 @@
-#include <cstdlib>
-#include <vector>
-
-#include <catypes.hpp>
-#include <cell.hpp>
-#include <cell_group.hpp>
-#include <communication/communicator.hpp>
-#include <communication/global_policy.hpp>
-#include <fvm_cell.hpp>
-#include <profiling/profiler.hpp>
-#include <recipe.hpp>
-#include <threading/threading.hpp>
-
-#include "model.hpp"
-#include "trace_sampler.hpp"
-
-namespace nest {
-namespace mc {
-
-model::model(const recipe &rec, cell_gid_type cell_from, cell_gid_type cell_to, time_type sample_dt) {
-    // construct cell groups (one cell per group) and attach samplers
-    cell_groups_ = std::vector<cell_group_type>{cell_to-cell_from};
-    samplers_.resize(cell_to-cell_from);
-
-    threading::parallel_for::apply(cell_from, cell_to, 
-        [&](cell_gid_type i) {
-            PE("setup", "cells");
-            auto cell = rec.get_cell(i);
-            auto idx = i-cell_from;
-            cell_groups_[idx] = cell_group_type(i, cell);
-
-            cell_local_index_type j = 0;
-            for (const auto& probe: cell.probes()) {
-                cell_member_type probe_id{i,j++};
-                samplers_[idx].emplace_back(probe_id, probe.kind, probe.location, sample_dt);
-                const auto &sampler = samplers_[idx].back();
-                cell_groups_[idx].add_sampler(probe_id, sampler, sampler.next_sample_t());
-            }
-            PL(2);
-        });
-
-    // initialise communicator
-    communicator_ = communicator_type(cell_from, cell_to);
-}
-
-void model::reset() {
-    t_ = 0.;
-    // otherwise unimplemented
-    std::abort();
-}
-
-model::time_type model::run(time_type tfinal, time_type dt) {
-    time_type min_delay = communicator_.min_delay();
-    while (t_<tfinal) {
-        auto tuntil = std::min(t_+min_delay, tfinal);
-        threading::parallel_for::apply(
-            0u, cell_groups_.size(),
-            [&](unsigned i) {
-                auto &group = cell_groups_[i];
-
-                PE("stepping","events");
-                group.enqueue_events(communicator_.queue(i));
-                PL();
-
-                group.advance(tuntil, dt);
-
-                PE("events");
-                communicator_.add_spikes(group.spikes());
-                group.clear_spikes();
-                PL(2);
-            });
-
-        PE("stepping", "exchange");
-        communicator_.exchange();
-        PL(2);
-
-        t_ = tuntil;
-    }
-    return t_;
-}
-
-void model::write_traces() const {
-    for (auto& gid_samplers: samplers_) {
-        for (auto& s: gid_samplers) {
-            s.write_trace();
-        }
-    }
-}
-
-void model::add_artificial_spike(cell_member_type source, time_type tspike) {
-    communicator_.add_spike({source, tspike});
-}
-
-} // namespace mc
-} // namespace nest
diff --git a/miniapp/model.hpp b/miniapp/model.hpp
deleted file mode 100644
index 8388ffb4eada65fab9d1f8d288bd70abdf25bbe3..0000000000000000000000000000000000000000
--- a/miniapp/model.hpp
+++ /dev/null
@@ -1,45 +0,0 @@
-#include <cstdlib>
-#include <vector>
-
-#include <catypes.hpp>
-#include <cell_group.hpp>
-#include <communication/communicator.hpp>
-#include <communication/global_policy.hpp>
-#include <fvm_cell.hpp>
-#include <recipe.hpp>
-
-#include "trace_sampler.hpp"
-
-namespace nest {
-namespace mc {
-
-struct model {
-    using cell_group_type = cell_group<fvm::fvm_cell<double, cell_local_size_type>>;
-    using time_type = cell_group_type::time_type;
-    using communicator_type = communication::communicator<communication::global_policy>;
-
-    model(const recipe &rec, cell_gid_type cell_from, cell_gid_type cell_to, time_type sample_dt);
-
-    void reset();
-
-    time_type run(time_type tfinal, time_type dt);
-
-    void write_traces() const;
-
-    void add_artificial_spike(cell_member_type source) {
-        add_artificial_spike(source, t_);
-    }
-
-    void add_artificial_spike(cell_member_type source, time_type tspike);
-
-    std::size_t num_spikes() const { return communicator_.num_spikes(); }
-
-private:
-    time_type t_ = 0.;
-    std::vector<cell_group_type> cell_groups_;
-    std::vector<std::vector<sample_to_trace>> samplers_;
-    communicator_type communicator_;
-};
-
-} // namespace mc
-} // namespace nest
diff --git a/miniapp/trace_sampler.cpp b/miniapp/trace_sampler.cpp
deleted file mode 100644
index 079adda8be19dc26cfb1f18696c2d5c98e414312..0000000000000000000000000000000000000000
--- a/miniapp/trace_sampler.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-#include <fstream>
-#include <string>
-
-#include <json/src/json.hpp>
-
-#include "trace_sampler.hpp"
-
-namespace nest {
-namespace mc {
-
-sample_to_trace::sample_to_trace(cell_member_type probe_id,
-                const std::string &name,
-                const std::string &units,
-                float dt,
-                float t_start):
-    trace_{name, units, probe_id.gid, probe_id.index},
-    sample_dt_(dt),
-    t_next_sample_(t_start)
-{}
-
-sample_to_trace::sample_to_trace(cell_member_type probe_id,
-                probeKind kind,
-                segment_location loc,
-                float dt,
-                float t_start):
-    sample_to_trace(probe_id, "", "", dt, t_start)
-{
-    std::string name = "";
-    std::string units = "";
-
-    switch (kind) {
-    case probeKind::membrane_voltage:
-        name = "v";
-        units = "mV";
-        break;
-    case probeKind::membrane_current:
-        name = "i";
-        units = "mA/cm^2";
-        break;
-    default: ;
-    }
-
-    trace_.name = name + (loc.segment? "dend": "soma");
-    trace_.units = units;
-}
-
-void sample_to_trace::write_trace(const std::string& prefix) const {
-    auto path = prefix + std::to_string(trace_.cell_gid) +
-                "." + std::to_string(trace_.probe_index) + ".json";
-
-    nlohmann::json jrep;
-    jrep["name"] = trace_.name;
-    jrep["units"] = trace_.units;
-    jrep["cell"] = trace_.cell_gid;
-    jrep["probe"] = trace_.probe_index;
-
-    auto& jt = jrep["data"]["time"];
-    auto& jy = jrep["data"][trace_.name];
-
-    for (const auto& sample: trace_.samples) {
-        jt.push_back(sample.time);
-        jy.push_back(sample.value);
-    }
-    std::ofstream file(path);
-    file << std::setw(1) << jrep << std::endl;
-}
-
-} // namespace mc
-} // namespace nest
diff --git a/miniapp/trace_sampler.hpp b/miniapp/trace_sampler.hpp
index e62c93cb1380787899ed09c54ecbd2902a52a1ed..b0ea4dcb01a44294c409bed1ea2cd4dbd7eeafa6 100644
--- a/miniapp/trace_sampler.hpp
+++ b/miniapp/trace_sampler.hpp
@@ -7,58 +7,65 @@
 #include <cell.hpp>
 #include <util/optional.hpp>
 
+#include <iostream>
+
 namespace nest {
 namespace mc {
 
 // move sampler code to another source file...
+template <typename TimeT=float, typename ValueT=double>
 struct sample_trace {
+    using time_type = TimeT;
+    using value_type = ValueT;
+
     struct sample_type {
-        float time;
-        double value;
+        time_type time;
+        value_type value;
     };
 
     std::string name;
     std::string units;
-    cell_gid_type cell_gid;
-    cell_local_index_type probe_index;
+    cell_member_type probe_id;
     std::vector<sample_type> samples;
+
+    sample_trace() =default;
+    sample_trace(cell_member_type probe_id, const std::string &name, const std::string &units):
+        name(name), units(units), probe_id(probe_id)
+    {}
 };
 
-struct sample_to_trace {
+template <typename TimeT=float, typename ValueT=double>
+struct trace_sampler {
+    using time_type = TimeT;
+    using value_type = ValueT;
+
     float next_sample_t() const { return t_next_sample_; }
 
-    util::optional<float> operator()(float t, double v) {
+    util::optional<time_type> operator()(time_type t, value_type v) {
         if (t<t_next_sample_) {
             return t_next_sample_;
         }
 
-        trace_.samples.push_back({t,v});
+        trace_->samples.push_back({t,v});
         return t_next_sample_+=sample_dt_;
     }
 
-    sample_to_trace(cell_member_type probe_id,
-                    const std::string &name,
-                    const std::string &units,
-                    float dt,
-                    float t_start=0);
-
-    sample_to_trace(cell_member_type probe_id,
-                    probeKind kind,
-                    segment_location loc,
-                    float dt,
-                    float t_start=0);
-
-    void write_trace(const std::string& prefix = "trace_") const;
-
-    const sample_trace& trace() const { return trace_; }
+    trace_sampler(sample_trace<time_type, value_type> *trace, time_type sample_dt, time_type tfrom=0):
+       trace_(trace), sample_dt_(sample_dt), t_next_sample_(tfrom)
+    {}
 
 private:
-    sample_trace trace_;
-
-    float sample_dt_;
-    float t_next_sample_;
+    sample_trace<time_type, value_type> *trace_;
 
+    time_type sample_dt_;
+    time_type t_next_sample_;
 };
 
+// with type deduction ...
+template <typename TimeT, typename ValueT>
+trace_sampler<TimeT, ValueT> make_trace_sampler(sample_trace<TimeT, ValueT> *trace, TimeT sample_dt, TimeT tfrom=0) {
+    return trace_sampler<TimeT, ValueT>(trace, sample_dt, tfrom);
+}
+
 } // namespace mc
 } // namespace nest
diff --git a/src/cell.hpp b/src/cell.hpp
index 137ea339eaaeef83a2a3b26206c98a017e87a181..5d01241c5d3d002daff5662d8cd68db6da650bd1 100644
--- a/src/cell.hpp
+++ b/src/cell.hpp
@@ -45,6 +45,11 @@ enum class probeKind {
     membrane_current
 };
 
+struct probe_spec {
+    segment_location location;
+    probeKind kind;
+};
+
 /// high-level abstract representation of a cell and its segments
 class cell {
 public:
@@ -59,14 +64,12 @@ public:
         segment_location location;
         parameter_list mechanism;
     };
-    struct probe_instance {
-        segment_location location;
-        probeKind kind;
-    };
+
     struct stimulus_instance {
         segment_location location;
         i_clamp clamp;
     };
+
     struct detector_instance {
         segment_location location;
         double threshold;
@@ -171,12 +174,12 @@ public:
     //////////////////
     // probes
     //////////////////
-    index_type add_probe(segment_location loc, probeKind kind) {
-        probes_.push_back({loc, kind});
+    index_type add_probe(probe_spec p) {
+        probes_.push_back(p);
         return probes_.size()-1;
     }
 
-    const std::vector<probe_instance>&
+    const std::vector<probe_spec>&
     probes() const { return probes_; }
 
 private:
@@ -197,7 +200,7 @@ private:
     std::vector<detector_instance> spike_detectors_;
 
     // the probes
-    std::vector<probe_instance> probes_;
+    std::vector<probe_spec> probes_;
 };
 
 // Checks that two cells have the same
diff --git a/src/cell_group.hpp b/src/cell_group.hpp
index 10212123728e00492d6861241947aa6af92de019..37e97d43376b44e5d795b2216eef7288351eac6f 100644
--- a/src/cell_group.hpp
+++ b/src/cell_group.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include <cstdint>
+#include <functional>
 #include <vector>
 
 #include <catypes.hpp>
@@ -37,8 +38,7 @@ public:
     cell_group(cell_gid_type gid, const cell& c) :
         gid_base_{gid}, cell_{c}
     {
-        cell_.voltage()(memory::all) = -65.;
-        cell_.initialize();
+        initialize_cells();
 
         source_id_type source_id={gid_base_,0};
         for (auto& d : c.detectors()) {
@@ -49,6 +49,14 @@ public:
         }
     }
 
+    void reset() {
+        remove_samplers();
+        initialize_cells();
+        for (auto& spike_source: spike_sources) {
+            spike_source.source.reset(cell_, 0.f);
+        }
+    }
+
     void advance(time_type tfinal, time_type dt) {
         while (cell_.time()<tfinal) {
             // take any pending samples
@@ -132,7 +140,17 @@ public:
         sample_events_.push({sampler_index, start_time});
     }
 
+    void remove_samplers() {
+        sample_events_.clear();
+        samplers_.clear();
+    }
+
 private:
+    void initialize_cells() {
+        cell_.voltage()(memory::all) = -65.;
+        cell_.initialize();
+    }
+
     /// gid of first cell in group
     cell_gid_type gid_base_;
 
diff --git a/src/event_queue.hpp b/src/event_queue.hpp
index 792fed34da681b9a4e4a3e1b8058eed860f2588b..8d04f9db0a1ecddaf24680c2e75ce00b56a31590 100644
--- a/src/event_queue.hpp
+++ b/src/event_queue.hpp
@@ -67,6 +67,11 @@ public :
          }
     }
 
+    // clear everything
+    void clear() {
+        queue_ = decltype(queue_){};
+    }
+
 private:
     struct event_greater {
         bool operator()(const Event &a, const Event &b) {
diff --git a/src/model.hpp b/src/model.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..83ca129895571408d059ceb13fabb0c21b6206f8
--- /dev/null
+++ b/src/model.hpp
@@ -0,0 +1,123 @@
+#include <cstdlib>
+#include <vector>
+
+#include <catypes.hpp>
+#include <cell.hpp>
+#include <cell_group.hpp>
+#include <communication/communicator.hpp>
+#include <communication/global_policy.hpp>
+#include <fvm_cell.hpp>
+#include <recipe.hpp>
+#include <profiling/profiler.hpp>
+
+#include "trace_sampler.hpp"
+
+namespace nest {
+namespace mc {
+
+template <typename Cell>
+struct model {
+    using cell_group_type = cell_group<Cell>;
+    using time_type = typename cell_group_type::time_type;
+    using value_type = typename cell_group_type::value_type;
+    using communicator_type = communication::communicator<communication::global_policy>;
+    using sampler_function = typename cell_group_type::sampler_function;
+
+    struct probe_record {
+        cell_member_type id;
+        probe_spec probe;
+    };
+
+    model(const recipe &rec, cell_gid_type cell_from, cell_gid_type cell_to):
+        cell_from_(cell_from), cell_to_(cell_to)
+    {
+        cell_groups_ = std::vector<cell_group_type>{cell_to_-cell_from_};
+
+        threading::parallel_vector<probe_record> probes;
+        threading::parallel_for::apply(cell_from_, cell_to_, 
+            [&](cell_gid_type i) {
+                PE("setup", "cells");
+                auto cell = rec.get_cell(i);
+                auto idx = i-cell_from_;
+                cell_groups_[idx] = cell_group_type(i, cell);
+
+                cell_local_index_type j = 0;
+                for (const auto& probe: cell.probes()) {
+                    cell_member_type probe_id{i,j++};
+                    probes.push_back({probe_id, probe});
+                }
+                PL(2);
+            });
+
+        probes_.assign(probes.begin(), probes.end());
+        communicator_ = communicator_type(cell_from_, cell_to_);
+    }
+
+    void reset() {
+        t_ = 0.;
+        for (auto& group: cell_groups_) {
+            group.reset();
+        }
+    }
+
+    time_type run(time_type tfinal, time_type dt) {
+        time_type min_delay = communicator_.min_delay();
+        while (t_<tfinal) {
+            auto tuntil = std::min(t_+min_delay, tfinal);
+            threading::parallel_for::apply(
+                0u, cell_groups_.size(),
+                [&](unsigned i) {
+                    auto &group = cell_groups_[i];
+
+                    PE("stepping","events");
+                    group.enqueue_events(communicator_.queue(i));
+                    PL();
+
+                    group.advance(tuntil, dt);
+
+                    PE("events");
+                    communicator_.add_spikes(group.spikes());
+                    group.clear_spikes();
+                    PL(2);
+                });
+
+            PE("stepping", "exchange");
+            communicator_.exchange();
+            PL(2);
+
+            t_ = tuntil;
+        }
+        return t_;
+    }
+
+    void add_artificial_spike(cell_member_type source) {
+        add_artificial_spike(source, t_);
+    }
+
+    void add_artificial_spike(cell_member_type source, time_type tspike) {
+        communicator_.add_spike({source, tspike});
+    }
+
+    void attach_sampler(cell_member_type probe_id, sampler_function f, time_type tfrom = 0) {
+        // TODO: translate probe_id.gid to appropriate group, but for now 1-1.
+        if (probe_id.gid<cell_from_ || probe_id.gid>=cell_to_) {
+            return;
+        }
+        cell_groups_[probe_id.gid-cell_from_].add_sampler(probe_id, f, tfrom);
+    }
+
+    const std::vector<probe_record>& probes() const { return probes_; }
+
+    std::size_t num_spikes() const { return communicator_.num_spikes(); }
+
+private:
+    cell_gid_type cell_from_;
+    cell_gid_type cell_to_;
+    time_type t_ = 0.;
+    std::vector<cell_group_type> cell_groups_;
+    communicator_type communicator_;
+    std::vector<probe_record> probes_;
+};
+
+} // namespace mc
+} // namespace nest
diff --git a/src/spike_source.hpp b/src/spike_source.hpp
index 95099cb547de24e76386c2253960b8fffe4bf569..0537b6800a19534d23aedd7de2edae3d022b53e6 100644
--- a/src/spike_source.hpp
+++ b/src/spike_source.hpp
@@ -13,13 +13,11 @@ class spike_detector
 public:
     using cell_type = Cell;
 
-    spike_detector( const cell_type& cell, segment_location loc, double thresh, float t_init) :
+    spike_detector(const cell_type& cell, segment_location loc, double thresh, float t_init) :
         location_(loc),
-        threshold_(thresh),
-        previous_t_(t_init)
+        threshold_(thresh)
     {
-        previous_v_ = cell.voltage(location_);
-        is_spiking_ = previous_v_ >= thresh ? true : false;
+        reset(cell, t_init);
     }
 
     util::optional<float> test(const cell_type& cell, float t) {
@@ -58,6 +56,12 @@ public:
 
     float v() const { return previous_v_; }
 
+    void reset(const cell_type& cell, float t_init) {
+        previous_t_ = t_init;
+        previous_v_ = cell.voltage(location_);
+        is_spiking_ = previous_v_ >= threshold_;
+    }
+
 private:
 
     // parameters/data
diff --git a/src/threading/serial.hpp b/src/threading/serial.hpp
index fb66e6e2ff78ec7eb638091ded2ac874583227e6..297f8266410ef334327856178346edb8a0d66607 100644
--- a/src/threading/serial.hpp
+++ b/src/threading/serial.hpp
@@ -56,6 +56,10 @@ struct parallel_for {
     }
 };
 
+template <typename T>
+using parallel_vector = std::vector<T>;
+
+
 inline std::string description() {
     return "serial";
 }
diff --git a/src/threading/tbb.hpp b/src/threading/tbb.hpp
index eae6b112bdb75b697709379b41f00cfbc249a3b1..8e0086741cac1d51e02e9fa148e9eaa3f3f837a3 100644
--- a/src/threading/tbb.hpp
+++ b/src/threading/tbb.hpp
@@ -46,6 +46,9 @@ struct timer {
 
 constexpr bool multithreaded() { return true; }
 
+template <typename T>
+using parallel_vector = tbb::concurrent_vector<T>;
+
 } // threading
 } // mc
 } // nest
diff --git a/tests/unit/test_probe.cpp b/tests/unit/test_probe.cpp
index 289e5929c909d19732e9d7bae7ba43a05b2ccdcb..888e710eec377143b6ac6684e41cc222d0b0c107 100644
--- a/tests/unit/test_probe.cpp
+++ b/tests/unit/test_probe.cpp
@@ -13,8 +13,8 @@ TEST(probe, instantiation)
     segment_location loc1{0, 0};
     segment_location loc2{1, 0.6};
 
-    auto p1 = c1.add_probe(loc1, probeKind::membrane_voltage);
-    auto p2 = c1.add_probe(loc2, probeKind::membrane_current);
+    auto p1 = c1.add_probe({loc1, probeKind::membrane_voltage});
+    auto p2 = c1.add_probe({loc2, probeKind::membrane_current});
 
     // expect locally provided probe ids to be numbered sequentially from zero.
 
@@ -49,9 +49,9 @@ TEST(probe, fvm_cell)
     segment_location loc1{1, 1};
     segment_location loc2{1, 0.5};
 
-    auto pv0 = bs.add_probe(loc0, probeKind::membrane_voltage);
-    auto pv1 = bs.add_probe(loc1, probeKind::membrane_voltage);
-    auto pi2 = bs.add_probe(loc2, probeKind::membrane_current);
+    auto pv0 = bs.add_probe({loc0, probeKind::membrane_voltage});
+    auto pv1 = bs.add_probe({loc1, probeKind::membrane_voltage});
+    auto pi2 = bs.add_probe({loc2, probeKind::membrane_current});
 
     i_clamp stim(0, 100, 0.3);
     bs.add_stimulus({1, 1}, stim);
diff --git a/tests/validation/validate_synapses.cpp b/tests/validation/validate_synapses.cpp
index 9607f416a9c49a9344a45ffd5c1a4366241738fc..3dbf7fbb62efc44d128f58222b22f3f2ae437eaf 100644
--- a/tests/validation/validate_synapses.cpp
+++ b/tests/validation/validate_synapses.cpp
@@ -71,8 +71,8 @@ void run_neuron_baseline(const char* syn_type, const char* data_file)
     cell.add_synapse({1, 0.5}, syn_default);
 
     // add probes
-    auto probe_soma = cell.add_probe({0,0}, probeKind::membrane_voltage);
-    auto probe_dend = cell.add_probe({1,0.5}, probeKind::membrane_voltage);
+    auto probe_soma = cell.add_probe({{0,0}, probeKind::membrane_voltage});
+    auto probe_dend = cell.add_probe({{1,0.5}, probeKind::membrane_voltage});
 
     // injected spike events
     postsynaptic_spike_event synthetic_events[] = {