diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index 489eba06c8480655ea7876aea66b9013f3e42fe7..69f1657da64d48125dda6e99f2a9316de6ce2cf5 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -1 +1,26 @@
 add_subdirectory (data)
+
+
+
+# Create a library libCbmAlgo
+
+set(SRCS
+  evbuild/EventBuilder.cxx
+ )
+
+add_library(Algo SHARED ${SRCS})
+
+target_include_directories(Algo
+  PUBLIC ${CMAKE_SOURCE_DIR}/core/data
+  PUBLIC ${CMAKE_SOURCE_DIR}/core/data/base
+  PUBLIC ${CMAKE_SOURCE_DIR}/core/data/global
+  PUBLIC ${CMAKE_SOURCE_DIR}/core/data/sts
+  PUBLIC ${CMAKE_SOURCE_DIR}/external/ipc/ipc/lib/fles_ipc 
+)
+
+target_include_directories(Algo SYSTEM
+  PUBLIC ${Boost_INCLUDE_DIR}
+)
+
+target_compile_definitions(Algo PUBLIC NO_ROOT)
+
diff --git a/algo/evbuild/EventBuilder.cxx b/algo/evbuild/EventBuilder.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..baac61a175db8984043321015173730b8cdfa706
--- /dev/null
+++ b/algo/evbuild/EventBuilder.cxx
@@ -0,0 +1,44 @@
+/* Copyright (C) 2021 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese [committer] */
+
+#include "EventBuilder.h"
+
+#include <algorithm>
+#include <cassert>
+#include <vector>
+
+using std::is_sorted;
+using std::vector;
+
+namespace cbm
+{
+  namespace algo
+  {
+
+    // --- Execution
+    std::vector<CbmDigiEvent> EventBuilder::operator()(const CbmDigiTimeslice& ts, const vector<double> triggers) const
+    {
+      assert(is_sorted(triggers.begin(), triggers.end()));
+      vector<CbmDigiEvent> eventVec(triggers.size());
+      std::transform(triggers.begin(), triggers.end(), eventVec.begin(),
+                     [&ts, this](const double& trigger) { return BuildEvent(ts, trigger); });
+      return eventVec;
+    }
+
+
+    // --- Build a single event
+    CbmDigiEvent EventBuilder::BuildEvent(const CbmDigiTimeslice& ts, double trigger) const
+    {
+      CbmDigiEvent event;
+      event.fTime = trigger;
+      double tMin = trigger + fTriggerWindows.find(ECbmModuleId::kSts)->second.first;
+      double tMax = trigger + fTriggerWindows.find(ECbmModuleId::kSts)->second.second;
+      assert(is_sorted(ts.fData.fSts.fDigis.begin(), ts.fData.fSts.fDigis.end(), IsBefore<CbmStsDigi, CbmStsDigi>));
+      event.fData.fSts.fDigis = CopyRange(ts.fData.fSts.fDigis, tMin, tMax);
+      return event;
+    }
+
+
+  }  // namespace algo
+}  // namespace cbm
diff --git a/algo/evbuild/EventBuilder.h b/algo/evbuild/EventBuilder.h
new file mode 100644
index 0000000000000000000000000000000000000000..948956f3aaa3105c754eb7353dcd527b24575138
--- /dev/null
+++ b/algo/evbuild/EventBuilder.h
@@ -0,0 +1,119 @@
+/* Copyright (C) 2021 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese [committer] */
+
+#ifndef CBM_ALGO_EVENTBUILDER_H
+#define CBM_ALGO_EVENTBUILDER_H 1
+
+
+#include "CbmDefs.h"
+#include "CbmDigiEvent.h"
+#include "CbmDigiTimeslice.h"
+
+#include <algorithm>
+#include <map>
+#include <vector>
+
+namespace cbm
+{
+  namespace algo
+  {
+
+    /** @brief Time comparison of two objects with time stamps (class implementing GetTime()) **/
+    template<typename Data1, typename Data2>
+    bool IsBefore(const Data1& obj1, const Data2& obj2)
+    {
+      return obj1.GetTime() < obj2.GetTime();
+    }
+
+
+    /** @class EventBuilder
+     ** @author Volker Friese <v.friese@gsi.de>
+     ** @since 2021
+     ** @brief Constructs CbmDigiEvents out of CbmDigiTimeslices
+     **
+     ** Events are constructed by copying digi data from the source (CbmDigiTimeslice).
+     ** Digis are selected in trigger windows, the sizes of which relative to a trigger time are configurable.
+     ** For each trigger time, an event is generated. The time intervals may overlap, resulting in digis
+     ** being attributed to multiple events.
+     **
+     ** The source digi vectors (in CbmDigiTimeslice) must be sorted w.r.t. time, otherwise the behaviour is
+     ** undefined.
+     **
+     ** The trigger vector must be sorted.
+     **/
+    class EventBuilder {
+
+    public:
+      /** @brief Constructor **/
+      EventBuilder() {};
+
+
+      /** @brief Destructor **/
+      virtual ~EventBuilder() {};
+
+
+      /** @brief Execution
+         ** @param  ts       Digi source (timeslice)
+         ** @param  triggers List of trigger times
+         ** @return Vector of constructed events
+         **/
+      std::vector<CbmDigiEvent> operator()(const CbmDigiTimeslice& ts, const std::vector<double> triggers) const;
+
+
+      /** @brief Build a single event from a trigger time
+         ** @param  ts      Digi source (timeslice)
+         ** @param  trigger Trigger time
+         ** @return Digi event
+         **/
+      CbmDigiEvent BuildEvent(const CbmDigiTimeslice& ts, double trigger) const;
+
+
+      /** @brief Configure the trigger windows
+         ** @param system  Detector system identifier
+         ** @param tMin    Trigger window start time w.r.t. trigger time
+         ** @param tMax    Trigger window end time w.r.t. trigger time
+         **/
+      void SetTriggerWindow(ECbmModuleId system, double tMin, double tMax)
+      {
+        fTriggerWindows[system] = std::make_pair(tMin, tMax);
+      }
+
+
+    private:  // methods
+      /** @brief Copy data objects in a given time interval from the source to the target vector
+         ** @param source Source data vector
+         ** @param tMin   Minimal time
+         ** @param tMax   Maximal time
+         ** @return Target data vector
+         **
+         ** The Data class specialisation must implement the method double GetTime(), which is used to
+         ** check whether the Data object falls into the specified time interval.
+         **
+         ** The source vector must be ordered w.r.t. GetTime(), otherwise the behaviour is undefined.
+         **
+         ** TODO: The current implementation searches, for each trigger, the entire source vector. This
+         ** can surely be optimised when the contract that the trigger vector be sorted is properly exploited,
+         ** e.g., by starting the search for the first digi in the trigger window from the start of the
+         ** previous trigger window. This, however, requires bookkeeping hardly to be realised without
+         ** changing the state of the class. I leave this for the future and for bright specialists.
+         **/
+      template<typename Data>
+      static typename std::vector<Data> CopyRange(const std::vector<Data>& source, double tMin, double tMax)
+      {
+        auto comp  = [](const Data& obj, double value) { return obj.GetTime() < value; };
+        auto lower = std::lower_bound(source.begin(), source.end(), tMin, comp);
+        auto upper = std::lower_bound(lower, source.end(), tMax, comp);
+        return std::vector<Data>(lower, upper);
+      }
+
+
+    private:  // data members
+      std::map<ECbmModuleId, std::pair<double, double>> fTriggerWindows;
+    };
+
+
+  }  // namespace algo
+}  // namespace cbm
+
+#endif /* CBM_ALGO_EVENTBUILDER_H */
diff --git a/reco/CMakeLists.txt b/reco/CMakeLists.txt
index c31e7d8f51a63f8f8254872194ba42ed57bfb2af..68dcb85e80b53185df1137bf70a409656ca720b6 100644
--- a/reco/CMakeLists.txt
+++ b/reco/CMakeLists.txt
@@ -12,4 +12,3 @@ add_subdirectory(littrack)
 add_subdirectory(steer)
 add_subdirectory(tracking)
 add_subdirectory(qa)
-