diff --git a/miniapp/miniapp.cpp b/miniapp/miniapp.cpp
index fdac119896bdeadf9b9d40fffd05bb3a6345bb5b..549386abd307db798127666913558ecf905a212d 100644
--- a/miniapp/miniapp.cpp
+++ b/miniapp/miniapp.cpp
@@ -1,4 +1,6 @@
 #include <iostream>
+#include <fstream>
+#include <sstream>
 
 #include <cell.hpp>
 #include <cell_group.hpp>
@@ -11,6 +13,7 @@
 #include "communication/communicator.hpp"
 #include "communication/serial_global_policy.hpp"
 #include "communication/mpi_global_policy.hpp"
+#include "util/optional.hpp"
 
 using namespace nest;
 
@@ -27,6 +30,8 @@ using communicator_type =
     mc::communication::communicator<mc::communication::serial_global_policy>;
 #endif
 
+using nest::mc::util::optional;
+
 struct model {
     communicator_type communicator;
     std::vector<cell_group> cell_groups;
@@ -102,18 +107,62 @@ struct model {
     std::vector<id_type> source_map;
     std::vector<id_type> target_map;
 
-    // sampling // WIP COME BACK HERE
-    struct simple_sampler {
-        index_type group_gid;
+    // traces from probes
+    struct trace_data {
+        using sample_type = std::pair<float,double>;
         std::string name;
-        double dt;
+        index_type id;
+        std::vector<sample_type> samples;
+    };
+
+    // different traces may be written to by different threads;
+    // during simulation, each trace_sampler will be responsible for its
+    // corresponding element in the traces vector.
+
+    std::vector<trace_data> traces;
+
+    // make a sampler that records to traces
+    struct simple_sampler_functor {
+        std::vector<trace_data> &traces_;
+        size_t trace_index_ = 0;
+        float requested_sample_time_ = 0;
+        float dt_ = 0;
+
+        simple_sampler_functor(std::vector<trace_data> &traces, size_t index, float dt) :
+            traces_(traces), trace_index_(index), dt_(dt) {}
+
+        optional<float> operator()(float t, double v) {
+            traces_[trace_index_].samples.push_back({t,v});
+            return requested_sample_time_ += dt_;
+        }
+    };
 
-        std::vector<std::pair<float,double>> tv;
-        util::optional<float> operator()(float t, double v) {
-            define
+    mc::sampler make_simple_sampler(index_type probe_gid, const std::string name,
+                                       index_type id, float dt)
+    {
+        traces.push_back(trace_data{name, id});
+        return {probe_gid, simple_sampler_functor(traces, traces.size()-1, dt)};
+    }
+
+    void reset_traces() {
+        // do not call during simulation: thread-unsafe access to traces.
+        traces.clear();
+    }
+
+    void dump_traces() {
+        // do not call during simulation: thread-unsafe access to traces.
+        for (const auto& trace: traces) {
+            std::stringstream path;
+            path << "trace_" << trace.id << "_" << trace.name << ".dat";
+
+            std::ofstream file(path.str());
+            file << "time\t" << trace.name << "\n";
+            for (const auto& sample: trace.samples) {
+                file << sample.first << "\t" << sample.second << "\n";
+            }
+            file.close();
         }
-        
-        
+    }
 };
 
 // define some global model parameters
@@ -185,6 +234,7 @@ int main(int argc, char** argv) {
         m.print_times();
         std::cout << "there were " << m.communicator.num_spikes() << " spikes\n";
     }
+    m.dump_traces();
 
 #ifdef SPLAT
     if (!mc::mpi::rank()) {
@@ -284,21 +334,18 @@ void all_to_all_model(nest::mc::io::options& opt, model& m) {
 
     // monitor soma and dendrite on a few cells
     float sample_dt = 0.1;
-    index_type monitor_group_gids = { 0, 1, 2 };
+    index_type monitor_group_gids[] = { 0, 1, 2 };
     for (auto gid : monitor_group_gids) {
         if (!m.communicator.is_local_group(gid)) {
             continue;
         }
 
-        lid = m.communicator.group_lid(gid);
-        index_type probe_soma = m.cell_groups[lid].probe_gid_range().first;
-        index_type probe_dend = probe_soma+1;
+        auto lid = m.communicator.group_lid(gid);
+        auto probe_soma = m.cell_groups[lid].probe_gid_range().first;
+        auto probe_dend = probe_soma+1;
 
-        // WIP COME BACK HERE
-        m.cell_groups[lid].samplers = {
-            { probe_soma, m.make_simple_sampler(gid, "vsoma", sample_dt) },
-            { probe_dend, m.make_simple_sampler(gid, "vdend", sample_dt) }
-        };
+        m.cell_groups[lid].add_sampler(m.make_simple_sampler(probe_soma, "vsoma", gid, sample_dt));
+        m.cell_groups[lid].add_sampler(m.make_simple_sampler(probe_dend, "vdend", gid, sample_dt));
     }
 
     // lid is local cell/group id
@@ -381,11 +428,12 @@ mc::cell make_cell(int compartments_per_segment, int num_synapses) {
     }
 
     // add probes: 
-    auto probe_soma = cell.add_probe(nest::mc::cell::membrane_potential, {0,0});
-    auto probe_dendrite = cell.add_probe(nest::mc::cell::membrane_potential, {1,0.5});
+    auto probe_soma = cell.add_probe({0, 0}, mc::cell::membrane_voltage);
+    auto probe_dendrite = cell.add_probe({1, 0.5}, mc::cell::membrane_voltage);
 
-    EXPECT(probe_soma==0);
-    EXPECT(probe_dendrite==1);
+    EXPECTS(probe_soma==0);
+    EXPECTS(probe_dendrite==1);
+    (void)probe_soma, (void)probe_dendrite;
 
     return cell;
 }
diff --git a/src/cell.hpp b/src/cell.hpp
index 0e13e588d42f7d213a7aa6f4928f1d043f95e07d..f53fb0592c94d76c3dfcd7552814345fde28f874 100644
--- a/src/cell.hpp
+++ b/src/cell.hpp
@@ -40,13 +40,15 @@ int find_compartment_index(
 
 /// high-level abstract representation of a cell and its segments
 class cell {
-    public:
+public:
 
     // types
     using index_type = int;
     using value_type = double;
     using point_type = point<value_type>;
 
+    enum probe_sort { membrane_voltage, membrane_current };
+
     // constructor
     cell();
 
@@ -139,7 +141,18 @@ class cell {
         return spike_detectors_;
     }
 
-    private:
+    //////////////////
+    // probes
+    //////////////////
+    index_type add_probe(segment_location loc, enum probe_sort sort) {
+        probes_.push_back({loc, sort});
+        return probes_.size()-1;
+    }
+
+    const std::vector<std::pair<segment_location, enum probe_sort>>&
+    probes() const { return probes_; }
+
+private:
 
     // storage for connections
     std::vector<index_type> parents_;
@@ -155,6 +168,9 @@ class cell {
 
     // the sensors
     std::vector<std::pair<segment_location, double>> spike_detectors_;
+
+    // the probes
+    std::vector<std::pair<segment_location, enum probe_sort>> probes_;
 };
 
 // Checks that two cells have the same
diff --git a/src/cell_group.hpp b/src/cell_group.hpp
index f1360ffe03c6fd344191955ed7b9eec19b2d9b3f..7b6f5bedd1b80705631a5a82cb965dc227e9768b 100644
--- a/src/cell_group.hpp
+++ b/src/cell_group.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <cstdint>
 #include <vector>
 
 #include <cell.hpp>
@@ -14,6 +15,7 @@ namespace mc {
 // for the next desired sample.
 
 struct sampler {
+    using index_type = int;
     using time_type = float;
     using value_type = double;
 
@@ -86,16 +88,16 @@ public:
     void advance(double tfinal, double dt) {
         while (cell_.time()<tfinal) {
             // take any pending samples
-            while (sample_event m = sample_events_.pop_if_before(cell_.time())) {
-                auto &sampler = samplers_[m.sampler_index];
-                EXPECT((bool)sampler.sample);
+            float cell_time = cell_.time();
+            while (auto m = sample_events_.pop_if_before(cell_time)) {
+                auto &sampler = samplers_[m->sampler_index];
+                EXPECTS((bool)sampler.sample);
 
                 index_type probe_index = sampler.probe_gid-first_probe_gid_;
                 auto next = sampler.sample(cell_.time(), cell_.probe(probe_index));
                 if (next) {
-                    EXPECT(*next>m.time);
-                    m.time = *next;
-                    sample_events_.push(m);
+                    m->time = std::max(*next, cell_time);
+                    sample_events_.push(*m);
                 }
             }
 
@@ -158,8 +160,12 @@ public:
         spikes_.clear();
     }
 
-    std::vector<sampler> samplers;
-
+    void add_sampler(const sampler &s, float start_time = 0) {
+        unsigned sampler_index = samplers_.size();
+        samplers_.push_back(s);
+        sample_events_.push({sampler_index, start_time});
+    }
+        
 private:
 
 #ifdef SPLAT
@@ -189,6 +195,9 @@ private:
  
     /// the global id of the first probe in this group
     index_type first_probe_gid_;
+
+    /// collection of samplers to be run against probes in this group
+    std::vector<sampler> samplers_;
 };
 
 } // namespace mc
diff --git a/src/communication/communicator.hpp b/src/communication/communicator.hpp
index eee922541e41d3d3d8b317660ab5b390b42d99fa..34185ac0fbd795294211ced7190d8b84a3e70874 100644
--- a/src/communication/communicator.hpp
+++ b/src/communication/communicator.hpp
@@ -194,7 +194,7 @@ public:
         return communication_policy_.size();
     }
 
-    const std::vector<local_event>& queue(int i) const {
+    const std::vector<postsynaptic_spike_event>& queue(int i) const {
         return events_[i];
     }
 
@@ -240,7 +240,7 @@ private:
     local_spike_store_type thread_spikes_;
 
     std::vector<connection> connections_;
-    std::vector<std::vector<nest::mc::local_event>> events_;
+    std::vector<std::vector<postsynaptic_spike_event>> events_;
 
     // local target group i has targets in the half open range
     //      [target_map_[i], target_map_[i+1])
diff --git a/src/communication/connection.hpp b/src/communication/connection.hpp
index 528c57b59fddbfc4b7f3c96772c5c0c04685758b..ee1acfd72dc21cffe01f28239bca29526366df31 100644
--- a/src/communication/connection.hpp
+++ b/src/communication/connection.hpp
@@ -25,7 +25,7 @@ public:
     id_type source() const { return source_; }
     id_type destination() const { return destination_; }
 
-    local_event make_event(spike<id_type> s) {
+    postsynaptic_spike_event make_event(spike<id_type> s) {
         return {destination_, s.time + delay_, weight_};
     }
 
diff --git a/src/communication/mpi.hpp b/src/communication/mpi.hpp
index 063e36ef3fc3ae44f952e2caeddf18663832cefd..5be71b6381bf8311bcab85d5396d64ba2c3eb26b 100644
--- a/src/communication/mpi.hpp
+++ b/src/communication/mpi.hpp
@@ -43,7 +43,7 @@ namespace mpi {
     template <>                 \
     struct mpi_traits<T> {  \
         constexpr static size_t count()            { return 1; } \
-        constexpr static MPI_Datatype mpi_type()   { return M; } \
+        /* constexpr */ static MPI_Datatype mpi_type()   { return M; } \
         constexpr static bool is_mpi_native_type() { return true; } \
     };
 
diff --git a/src/event_queue.hpp b/src/event_queue.hpp
index 9250eb862e5a167fdf1ae0b962e0e30e3e772126..7f3034f3f08bbb2235bf457c95e8eedfe8d0895d 100644
--- a/src/event_queue.hpp
+++ b/src/event_queue.hpp
@@ -13,26 +13,26 @@ struct postsynaptic_spike_event {
     uint32_t target;
     float time;
     float weight;
-
-    float event_time() const { return time; }
 };
 
+inline float event_time(const postsynaptic_spike_event &ev) { return ev.time; }
+
 struct sample_event {
     uint32_t sampler_index;
     float time;
-
-    float event_time() const { return time; }
 };
 
+inline float event_time(const sample_event &ev) { return ev.time; }
+
 /* Event objects must have a method event_time() which returns a value
  * from a type with a total ordering with respect to <, >, etc.
  */     
 
-template <type Event>
+template <typename Event>
 class event_queue {
 public :
     using value_type = Event;
-    using time_type = template std::result_of<decltype(&Event::event_time)(Event)>::type;
+    using time_type = decltype(event_time(std::declval<Event>()));
 
     // create
     event_queue() {}
@@ -46,7 +46,7 @@ public :
     }
 
     // push thing
-    void push(local_event e) {
+    void push(const value_type &e) {
          queue_.push(e);
     }
 
@@ -68,8 +68,8 @@ public :
 
 private:
     struct event_greater {
-        bool operator(const Event &a, const Event &b) {
-            return a.event_time() > b.event_time();
+        bool operator()(const Event &a, const Event &b) {
+            return event_time(a) > event_time(b);
         }
     };
 
@@ -84,7 +84,7 @@ private:
 } // namespace mc
 
 inline
-std::ostream& operator<< (std::ostream& o, nest::mc::local_event e)
+std::ostream& operator<< (std::ostream& o, const nest::mc::postsynaptic_spike_event& e)
 {
     return o << "event[" << e.target << "," << e.time << "," << e.weight << "]";
 }
diff --git a/src/fvm_cell.hpp b/src/fvm_cell.hpp
index 4bd215716b2b77471e8c488925f3c54f6105e165..5cadd2912d79f793fa01812fc4dcb2a24aefee3d 100644
--- a/src/fvm_cell.hpp
+++ b/src/fvm_cell.hpp
@@ -27,7 +27,7 @@ namespace fvm {
 
 template <typename T, typename I>
 class fvm_cell {
-    public :
+public:
 
     fvm_cell() = default;
 
@@ -117,7 +117,7 @@ class fvm_cell {
     void advance_to(value_type tfinal, value_type dt);
 
     /// pass an event to the appropriate synapse and call net_receive
-    void apply_event(local_event e) {
+    void apply_event(postsynaptic_spike_event e) {
         mechanisms_[synapse_index_]->net_receive(e.target, e.weight);
     }
 
@@ -136,7 +136,14 @@ class fvm_cell {
 
     value_type time() const { return t_; }
 
-    private:
+    value_type probe(uint32_t i) const {
+        auto p = probes_[i];
+        return (*p.first)[p.second];
+    }
+
+    std::size_t num_probes() const { return probes_.size(); }
+
+private:
 
     /// current time
     value_type t_ = value_type{0};
@@ -180,8 +187,12 @@ class fvm_cell {
 
     std::vector<std::pair<uint32_t, i_clamp>> stimulii_;
 
-    /// event queue
-    event_queue events_;
+    std::vector<std::pair<const vector_type*, uint32_t>> probes_;
+
+    /*
+    /// spike event queue
+    event_queue<postsynaptic_spike_event> events_;
+    */
 };
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -398,6 +409,21 @@ fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell)
     synapse_index_ = mechanisms_.size()-1;
     // don't forget to give point processes access to cv_areas_
     mechanisms_[synapse_index_]->set_areas(cv_areas_);
+
+    // record probe locations by index into corresponding state vector
+    for (auto probe : cell.probes()) {
+        uint32_t comp = find_compartment_index(probe.first, graph);
+        switch (probe.second) {
+        case nest::mc::cell::membrane_voltage:
+            probes_.push_back({&voltage_, comp});
+            break;
+        case nest::mc::cell::membrane_current:
+            probes_.push_back({&current_, comp});
+            break;
+        default:
+            throw std::logic_error("unrecognized probe sort");
+        }
+    }
 }
 
 template <typename T, typename I>
diff --git a/tests/test_event_queue.cpp b/tests/test_event_queue.cpp
index e67ade0bea22a417ca417a78c550b67bb0c3e45b..5ac4c75747d761638b0bbd4b90a9b6fed6fa3c18 100644
--- a/tests/test_event_queue.cpp
+++ b/tests/test_event_queue.cpp
@@ -7,8 +7,9 @@
 TEST(event_queue, push)
 {
     using namespace nest::mc;
+    using ps_event_queue = event_queue<postsynaptic_spike_event>;
 
-    event_queue q;
+    ps_event_queue q;
 
     q.push({1u, 2.f, 2.f});
     q.push({4u, 1.f, 2.f});
@@ -30,15 +31,16 @@ TEST(event_queue, push)
 TEST(event_queue, push_range)
 {
     using namespace nest::mc;
+    using ps_event_queue = event_queue<postsynaptic_spike_event>;
 
-    local_event events[] = {
+    postsynaptic_spike_event events[] = {
         {1u, 2.f, 2.f},
         {4u, 1.f, 2.f},
         {8u, 20.f, 2.f},
         {2u, 8.f, 2.f}
     };
 
-    event_queue q;
+    ps_event_queue q;
     q.push(std::begin(events), std::end(events));
 
     std::vector<float> times;
@@ -54,15 +56,16 @@ TEST(event_queue, push_range)
 TEST(event_queue, pop_if_before)
 {
     using namespace nest::mc;
+    using ps_event_queue = event_queue<postsynaptic_spike_event>;
 
-    local_event events[] = {
+    postsynaptic_spike_event events[] = {
         {1u, 1.f, 2.f},
         {2u, 2.f, 2.f},
         {3u, 3.f, 2.f},
         {4u, 4.f, 2.f}
     };
 
-    event_queue q;
+    ps_event_queue q;
     q.push(std::begin(events), std::end(events));
 
     EXPECT_EQ(q.size(), 4u);