diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index 21d6134d74c4170143ca94354beb1761117a1098..9de6731fcd03d154c9fe2b33b246accd127cb65e 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -83,8 +83,6 @@ set(SRCS
   evselector/DigiEventSelector.cxx
   evselector/DigiEventSelectorConfig.cxx
   unpack/CommonUnpacker.cxx
-  unpack/Unpack.cxx
-  unpack/UnpackChain.cxx
   detectors/sts/ChannelMaskSet_mCBM2022.cxx
   detectors/sts/ReadoutConfig.cxx
   detectors/sts/ReadoutConfig_mCBM2022.cxx
@@ -94,22 +92,28 @@ set(SRCS
   detectors/sts/WalkMap.cxx
   detectors/sts/WalkMap_mCBM2022.cxx
   detectors/much/ReadoutConfig.cxx
+  detectors/much/Unpack.cxx
   detectors/much/UnpackMS.cxx
   detectors/tof/HitFinder.cxx
   detectors/tof/Calibrate.cxx
   detectors/tof/Clusterizer.cxx
   detectors/tof/ReadoutConfig.cxx
+  detectors/tof/Unpack.cxx
   detectors/tof/UnpackMS.cxx
   detectors/tof/Hitfind.cxx
   detectors/tof/HitfinderChain.cxx
   detectors/tof/CalibratorChain.cxx
   detectors/bmon/ReadoutConfig.cxx
+  detectors/bmon/Unpack.cxx
   detectors/bmon/UnpackMS.cxx
   detectors/trd/ReadoutConfig.cxx
+  detectors/trd/Unpack.cxx
   detectors/trd/UnpackMS.cxx
   detectors/trd2d/ReadoutConfig.cxx
+  detectors/trd2d/Unpack.cxx
   detectors/trd2d/UnpackMS.cxx
   detectors/rich/ReadoutConfig.cxx
+  detectors/rich/Unpack.cxx
   detectors/rich/UnpackMS.cxx
   global/Reco.cxx
   qa/DigiEventQa.cxx
diff --git a/algo/base/AlgoTraits.h b/algo/base/AlgoTraits.h
new file mode 100644
index 0000000000000000000000000000000000000000..21954e3030ff6ec514ab9cad56530ecaddffee78
--- /dev/null
+++ b/algo/base/AlgoTraits.h
@@ -0,0 +1,60 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+
+#pragma once
+
+#include <type_traits>
+
+/**
+ * @file AlgoTraits.h
+ * @brief Type traits for online algorithms
+ */
+
+namespace cbm::algo::algo_traits
+{
+
+  namespace detail
+  {
+    template<typename...>
+    struct ResultOf {
+    };
+
+    template<typename R, typename Algo, typename... Args>
+    struct ResultOf<R (Algo::*)(Args...) const> {
+      using type = R;
+    };
+
+    template<typename R, typename Algo, typename... Args>
+    struct ResultOf<R (Algo::*)(Args...)> {
+      using type = R;
+    };
+
+    template<typename Algo>
+    struct ResultOf<Algo> : ResultOf<decltype(&Algo::operator())> {
+    };
+
+  }  // namespace detail
+
+  /**
+   * @brief Type alias for the return type produced by an algorithm when invoked via callable-operator
+   */
+  template<typename Algo>
+  using ResultOf_t = typename detail::ResultOf<Algo>::type;
+
+  // Currently assume algorithms return std::pair<R, M>
+  // where R is the output and M is the monitoring data
+
+  /**
+   * @brief Type alias for the output type produced by an algorithm
+   */
+  template<typename Algo>
+  using Output_t = typename ResultOf_t<Algo>::first_type;
+
+  /**
+   * @brief Type alias for the monitoring type produced by an algorithm
+   */
+  template<typename Algo>
+  using Monitor_t = typename ResultOf_t<Algo>::second_type;
+
+}  // namespace cbm::algo::algo_traits
diff --git a/algo/base/compat/Algorithm.h b/algo/base/compat/Algorithm.h
index 2fee8a387dcd63c4a760d2a93c130e5f384f6533..94c03a7de8c96c0c397dcf896d1fe95d2ae4a1b5 100644
--- a/algo/base/compat/Algorithm.h
+++ b/algo/base/compat/Algorithm.h
@@ -19,7 +19,7 @@
 
 #include <algorithm>
 
-#ifdef __cpp_lib_execution
+#if defined(__cpp_lib_execution) && defined(HAVE_PARALLEL_ALGORITHM)
 #define WITH_EXECUTION
 #include <execution>
 #endif
@@ -27,18 +27,6 @@
 namespace cbm::algo
 {
 
-  namespace detail
-  {
-#ifdef WITH_EXECUTION
-    inline constexpr auto ExecPolicy =
-#ifdef HAVE_PARALLEL_ALGORITHM
-      std::execution::par_unseq;
-#else
-      std::execution::seq;
-#endif  // HAVE_PARALLEL_ALGORITHM
-#endif  // WITH_EXECUTION
-  }  // namespace detail
-
   /**
    * @brief Wrapper for std::sort
    *
@@ -49,7 +37,7 @@ namespace cbm::algo
   void Sort(It first, It last, Compare comp)
   {
 #ifdef WITH_EXECUTION
-    std::sort(detail::ExecPolicy, first, last, comp);
+    std::sort(std::execution::par_unseq, first, last, comp);
 #else
     std::sort(first, last, comp);
 #endif
diff --git a/algo/detectors/bmon/Unpack.cxx b/algo/detectors/bmon/Unpack.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..ed0313d552860a6148666aa07be53c8801587420
--- /dev/null
+++ b/algo/detectors/bmon/Unpack.cxx
@@ -0,0 +1,37 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer], Dominik Smith */
+
+#include "Unpack.h"
+
+#include "log.hpp"
+
+using namespace cbm::algo::bmon;
+using fles::Subsystem;
+
+Unpack::Unpack(const ReadoutConfig& readout) : fReadout(readout)
+{
+  constexpr i64 SystemTimeOffset = 0;
+
+  // Create one algorithm per component for Bmon and configure it with parameters
+  auto equipIdsBmon = fReadout.GetEquipmentIds();
+  for (auto& equip : equipIdsBmon) {
+    std::unique_ptr<bmon::UnpackPar> par(new bmon::UnpackPar());
+    const size_t numElinks = fReadout.GetNumElinks(equip);
+    for (size_t elink = 0; elink < numElinks; elink++) {
+      bmon::UnpackElinkPar elinkPar;
+      elinkPar.fChannelUId = fReadout.Map(equip, elink);  // Vector of Bmon addresses for this elink
+      elinkPar.fTimeOffset = SystemTimeOffset;
+      par->fElinkParams.push_back(elinkPar);
+    }
+    fAlgos[equip].SetParams(std::move(par));
+    L_(debug) << "--- Configured equipment " << equip << " with " << numElinks << " elinks";
+  }
+  L_(info) << "--- Configured " << fAlgos.size() << " unpacker algorithms for Bmon.";
+}
+
+Unpack::Result_t Unpack::operator()(const fles::Timeslice& ts) const
+{
+  constexpr int SystemVersion = 0x00;
+  return DoUnpack(Subsystem::BMON, ts, SystemVersion);
+}
diff --git a/algo/detectors/bmon/Unpack.h b/algo/detectors/bmon/Unpack.h
new file mode 100644
index 0000000000000000000000000000000000000000..aca19761f62113c3cc39ef3ac20c074b2581e696
--- /dev/null
+++ b/algo/detectors/bmon/Unpack.h
@@ -0,0 +1,32 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer], Dominik Smith */
+
+#pragma once
+
+#include "CommonUnpacker.h"
+#include "ReadoutConfig.h"
+#include "UnpackMS.h"
+
+namespace cbm::algo::bmon
+{
+
+  namespace detail
+  {
+    using UnpackBase = CommonUnpacker<CbmBmonDigi, UnpackMS, UnpackMonitorData>;
+  }
+
+  class Unpack : public detail::UnpackBase {
+
+   public:
+    using Result_t = detail::UnpackBase::Result_t;
+
+    Unpack(const ReadoutConfig& readout);
+
+    Result_t operator()(const fles::Timeslice&) const;
+
+   private:
+    ReadoutConfig fReadout;
+  };
+
+}  // namespace cbm::algo::bmon
diff --git a/algo/detectors/much/Unpack.cxx b/algo/detectors/much/Unpack.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..0e8797e1d6de6c6b8931d185f3d7d22ab8abfd60
--- /dev/null
+++ b/algo/detectors/much/Unpack.cxx
@@ -0,0 +1,38 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer], Dominik Smith */
+
+#include "Unpack.h"
+
+#include "log.hpp"
+
+using namespace cbm::algo::much;
+using fles::Subsystem;
+
+Unpack::Unpack(const ReadoutConfig& readout) : fReadout(readout)
+{
+  constexpr i64 SystemTimeOffset = -980;
+
+  // Create one algorithm per component for Bmon and configure it with parameters
+  auto equipIdsMuch = fReadout.GetEquipmentIds();
+  for (auto& equip : equipIdsMuch) {
+    std::unique_ptr<much::UnpackPar> par(new much::UnpackPar());
+    const size_t numElinks = fReadout.GetNumElinks(equip);
+    for (size_t elink = 0; elink < numElinks; elink++) {
+      much::UnpackElinkPar elinkPar;
+      elinkPar.fAddress    = fReadout.Map(equip, elink);  // Vector of MUCH addresses for this elink
+      elinkPar.fTimeOffset = SystemTimeOffset;
+      elinkPar.fChanMask   = fReadout.MaskMap(equip, elink);
+      par->fElinkParams.push_back(elinkPar);
+    }
+    fAlgos[equip].SetParams(std::move(par));
+    L_(debug) << "--- Configured equipment " << equip << " with " << numElinks << " elinks";
+  }
+  L_(info) << "--- Configured " << fAlgos.size() << " unpacker algorithms for MUCH.";
+}
+
+Unpack::Result_t Unpack::operator()(const fles::Timeslice& ts) const
+{
+  constexpr int SystemVersion = 0x20;
+  return DoUnpack(Subsystem::MUCH, ts, SystemVersion);
+}
diff --git a/algo/detectors/much/Unpack.h b/algo/detectors/much/Unpack.h
new file mode 100644
index 0000000000000000000000000000000000000000..8721ea9b41d8d6f59f81d78769f95bf304ad4026
--- /dev/null
+++ b/algo/detectors/much/Unpack.h
@@ -0,0 +1,32 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer], Dominik Smith */
+
+#pragma once
+
+#include "CommonUnpacker.h"
+#include "ReadoutConfig.h"
+#include "UnpackMS.h"
+
+namespace cbm::algo::much
+{
+
+  namespace detail
+  {
+    using UnpackBase = CommonUnpacker<CbmMuchDigi, UnpackMS, UnpackMonitorData>;
+  }
+
+  class Unpack : public detail::UnpackBase {
+
+   public:
+    using Result_t = detail::UnpackBase::Result_t;
+
+    Unpack(const ReadoutConfig& readout);
+
+    Result_t operator()(const fles::Timeslice&) const;
+
+   private:
+    ReadoutConfig fReadout;
+  };
+
+}  // namespace cbm::algo::much
diff --git a/algo/detectors/rich/Unpack.cxx b/algo/detectors/rich/Unpack.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..04fc255666c7c336e8dfdd64f72defb7c3a24dde
--- /dev/null
+++ b/algo/detectors/rich/Unpack.cxx
@@ -0,0 +1,36 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer], Dominik Smith */
+
+#include "Unpack.h"
+
+#include "log.hpp"
+
+using namespace cbm::algo::rich;
+using fles::Subsystem;
+
+Unpack::Unpack(const ReadoutConfig& readout) : fReadout(readout)
+{
+  constexpr i64 SystemTimeOffset = 100;
+
+  // Create one algorithm per component for Bmon and configure it with parameters
+  auto equipIdsRich = fReadout.GetEquipmentIds();
+  for (auto& equip : equipIdsRich) {
+    std::unique_ptr<rich::UnpackPar> par(new rich::UnpackPar());
+    std::map<uint32_t, std::vector<double>> compMap = fReadout.Map(equip);
+    for (auto const& val : compMap) {
+      uint32_t address                       = val.first;
+      par->fElinkParams[address].fToTshift   = val.second;
+      par->fElinkParams[address].fTimeOffset = SystemTimeOffset;
+    }
+    fAlgos[equip].SetParams(std::move(par));
+    L_(info) << "--- Configured equipment " << equip << " with " << fReadout.GetNumElinks(equip) << " elinks";
+  }
+  L_(info) << "--- Configured " << fAlgos.size() << " unpacker algorithms for RICH.";
+}
+
+Unpack::Result_t Unpack::operator()(const fles::Timeslice& ts) const
+{
+  constexpr int SystemVersion = 0x03;
+  return DoUnpack(Subsystem::RICH, ts, SystemVersion);
+}
diff --git a/algo/detectors/rich/Unpack.h b/algo/detectors/rich/Unpack.h
new file mode 100644
index 0000000000000000000000000000000000000000..dbb7bd6b904c0fa4a0417b50e2efac70d70aa468
--- /dev/null
+++ b/algo/detectors/rich/Unpack.h
@@ -0,0 +1,32 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer], Dominik Smith */
+
+#pragma once
+
+#include "CommonUnpacker.h"
+#include "ReadoutConfig.h"
+#include "UnpackMS.h"
+
+namespace cbm::algo::rich
+{
+
+  namespace detail
+  {
+    using UnpackBase = CommonUnpacker<CbmRichDigi, UnpackMS, UnpackMonitorData>;
+  }
+
+  class Unpack : public detail::UnpackBase {
+
+   public:
+    using Result_t = detail::UnpackBase::Result_t;
+
+    Unpack(const ReadoutConfig& readout);
+
+    Result_t operator()(const fles::Timeslice&) const;
+
+   private:
+    ReadoutConfig fReadout;
+  };
+
+}  // namespace cbm::algo::rich
diff --git a/algo/detectors/sts/Unpack.cxx b/algo/detectors/sts/Unpack.cxx
index 4097fb33f95465a65b85744a99eb59295b63cff4..c40e4e75c9b4dc990efb68efcd98ebb7c4da60f5 100644
--- a/algo/detectors/sts/Unpack.cxx
+++ b/algo/detectors/sts/Unpack.cxx
@@ -39,6 +39,8 @@ Unpack::Unpack(const Config& config) : fConfig(config)
     fAlgos[equip].SetParams(std::move(par));
     L_(debug) << "--- Configured equipment " << equip << " with " << numElinks << " elinks";
   }  //# equipments
+
+  L_(info) << "--- Configured " << fAlgos.size() << " unpacker algorithms for STS.";
 }
 
 Unpack::Result_t Unpack::operator()(const fles::Timeslice& ts) const
diff --git a/algo/detectors/tof/Unpack.cxx b/algo/detectors/tof/Unpack.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..7764470e22284eb48d96e6b029fd6ee89581e637
--- /dev/null
+++ b/algo/detectors/tof/Unpack.cxx
@@ -0,0 +1,37 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer], Dominik Smith */
+
+#include "Unpack.h"
+
+#include "log.hpp"
+
+using namespace cbm::algo::tof;
+using fles::Subsystem;
+
+Unpack::Unpack(const ReadoutConfig& readout) : fReadout(readout)
+{
+  constexpr i64 SystemTimeOffset = 40;
+
+  auto equipIdsTof = fReadout.GetEquipmentIds();
+  for (auto& equip : equipIdsTof) {
+    std::unique_ptr<tof::UnpackPar> par(new tof::UnpackPar());
+    const size_t numElinks = fReadout.GetNumElinks(equip);
+    for (size_t elink = 0; elink < numElinks; elink++) {
+      tof::UnpackElinkPar elinkPar;
+      elinkPar.fChannelUId = fReadout.Map(equip, elink);  // Vector of TOF addresses for this elink
+      elinkPar.fTimeOffset = SystemTimeOffset;
+      par->fElinkParams.push_back(elinkPar);
+    }
+    fAlgos[equip].SetParams(std::move(par));
+    L_(debug) << "--- Configured equipment " << equip << " with " << numElinks << " elinks";
+  }
+
+  L_(info) << "--- Configured " << fAlgos.size() << " unpacker algorithms for TOF.";
+}
+
+Unpack::Result_t Unpack::operator()(const fles::Timeslice& ts) const
+{
+  constexpr int SystemVersion = 0x00;
+  return DoUnpack(Subsystem::TOF, ts, SystemVersion);
+}
diff --git a/algo/detectors/tof/Unpack.h b/algo/detectors/tof/Unpack.h
new file mode 100644
index 0000000000000000000000000000000000000000..d11e669f03d92c8c1be713efe223847954a00263
--- /dev/null
+++ b/algo/detectors/tof/Unpack.h
@@ -0,0 +1,31 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer], Dominik Smith */
+#pragma once
+
+#include "CommonUnpacker.h"
+#include "tof/ReadoutConfig.h"
+#include "tof/UnpackMS.h"
+
+namespace cbm::algo::tof
+{
+
+  namespace detail
+  {
+    using UnpackBase = CommonUnpacker<CbmTofDigi, UnpackMS, UnpackMonitorData>;
+  }
+
+  class Unpack : public detail::UnpackBase {
+
+   public:
+    using Result_t = detail::UnpackBase::Result_t;
+
+    Unpack(const ReadoutConfig& readout);
+
+    Result_t operator()(const fles::Timeslice&) const;
+
+   private:
+    ReadoutConfig fReadout;
+  };
+
+}  // namespace cbm::algo::tof
diff --git a/algo/detectors/trd/Unpack.cxx b/algo/detectors/trd/Unpack.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..9beddb8e7cc6db978a61f457d11fbc67d3521800
--- /dev/null
+++ b/algo/detectors/trd/Unpack.cxx
@@ -0,0 +1,47 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer], Dominik Smith */
+
+#include "Unpack.h"
+
+#include "log.hpp"
+
+using namespace cbm::algo::trd;
+using fles::Subsystem;
+
+Unpack::Unpack(const ReadoutConfig& readout) : fReadout(readout)
+{
+  constexpr i64 SystemTimeOffset = 1300;
+
+  // Create one algorithm per component for TRD and configure it with parameters
+  auto equipIdsTrd = fReadout.GetEquipmentIds();
+  for (auto& equip : equipIdsTrd) {
+
+    std::unique_ptr<trd::UnpackPar> par(new trd::UnpackPar());
+    const size_t numCrobs = fReadout.GetNumCrobs(equip);
+
+    for (size_t crob = 0; crob < numCrobs; crob++) {
+      trd::UnpackCrobPar crobPar;
+      const size_t numElinks = fReadout.GetNumElinks(equip, crob);
+
+      for (size_t elink = 0; elink < numElinks; elink++) {
+        trd::UnpackElinkPar elinkPar;
+        auto addresses        = fReadout.Map(equip, crob, elink);
+        elinkPar.fAddress     = addresses.first;   // Asic address for this elink
+        elinkPar.fChanAddress = addresses.second;  // Channel addresses for this elink
+        elinkPar.fTimeOffset  = SystemTimeOffset;
+        crobPar.fElinkParams.push_back(elinkPar);
+      }
+      par->fCrobParams.push_back(crobPar);
+    }
+    fAlgos[equip].SetParams(std::move(par));
+    L_(debug) << "--- Configured equipment " << equip << " with " << numCrobs << " crobs";
+  }
+  L_(info) << "--- Configured " << fAlgos.size() << " unpacker algorithms for TRD.";
+}
+
+Unpack::Result_t Unpack::operator()(const fles::Timeslice& ts) const
+{
+  constexpr int SystemVersion = 0x01;
+  return DoUnpack(Subsystem::TRD, ts, SystemVersion);
+}
diff --git a/algo/detectors/trd/Unpack.h b/algo/detectors/trd/Unpack.h
new file mode 100644
index 0000000000000000000000000000000000000000..b35c26b2304bfb7bde5ac65cc6a1f2303cce5fff
--- /dev/null
+++ b/algo/detectors/trd/Unpack.h
@@ -0,0 +1,34 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer], Dominik Smith */
+
+#pragma once
+
+#include "CommonUnpacker.h"
+#include "ReadoutConfig.h"
+#include "UnpackMS.h"
+
+namespace cbm::algo::trd
+{
+
+  namespace detail
+  {
+    using UnpackBase = CommonUnpacker<CbmTrdDigi, UnpackMS, UnpackMonitorData>;
+  }
+
+  class Unpack : public detail::UnpackBase {
+
+   public:
+    using Result_t = detail::UnpackBase::Result_t;
+
+    Unpack(const ReadoutConfig& readout);
+
+    Result_t operator()(const fles::Timeslice&) const;
+
+    const ReadoutConfig& Readout() const { return fReadout; }
+
+   private:
+    ReadoutConfig fReadout;
+  };
+
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd2d/Unpack.cxx b/algo/detectors/trd2d/Unpack.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..16f82926fac58d225d5fefdcee4cc74370a45c85
--- /dev/null
+++ b/algo/detectors/trd2d/Unpack.cxx
@@ -0,0 +1,51 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer], Dominik Smith */
+
+#include "Unpack.h"
+
+#include "log.hpp"
+
+using namespace cbm::algo::trd2d;
+using fles::Subsystem;
+
+Unpack::Unpack(const ReadoutConfig& readout) : fReadout(readout)
+{
+  constexpr i64 SystemTimeOffset = -510;
+
+  // Create one algorithm per component for TRD and configure it with parameters
+  auto equipIdsTrd2d = fReadout.GetEquipmentIds();
+  for (auto& equip : equipIdsTrd2d) {
+
+    std::unique_ptr<trd2d::UnpackPar> par(new trd2d::UnpackPar());
+    const size_t numAsics = fReadout.GetNumAsics(equip);
+
+    for (size_t asic = 0; asic < numAsics; asic++) {
+      trd2d::UnpackAsicPar asicPar;
+      const size_t numChans = fReadout.GetNumChans(equip, asic);
+
+      for (size_t chan = 0; chan < numChans; chan++) {
+        trd2d::UnpackChannelPar chanPar;
+        auto pars           = fReadout.ChanMap(equip, asic, chan);
+        chanPar.fPadAddress = pars.padAddress;    // Pad address for channel
+        chanPar.fMask       = pars.rPairingFlag;  // Flag channel mask
+        chanPar.fDaqOffset  = pars.daqOffset;     // Time calibration parameter
+        asicPar.fChanParams.push_back(chanPar);
+      }
+      auto comppars          = fReadout.CompMap(equip);
+      par->fSystemTimeOffset = SystemTimeOffset;
+      par->fModId            = comppars.moduleId;
+      par->fCrobId           = comppars.crobId;
+      par->fAsicParams.push_back(asicPar);
+    }
+    fAlgos[equip].SetParams(std::move(par));
+    L_(debug) << "--- Configured equipment " << equip << " with " << numAsics << " asics";
+  }
+  L_(info) << "--- Configured " << fAlgos.size() << " unpacker algorithms for TRD2D.";
+}
+
+Unpack::Result_t Unpack::operator()(const fles::Timeslice& ts) const
+{
+  constexpr int SystemVersion = 0x02;
+  return DoUnpack(Subsystem::TRD2D, ts, SystemVersion);
+}
diff --git a/algo/detectors/trd2d/Unpack.h b/algo/detectors/trd2d/Unpack.h
new file mode 100644
index 0000000000000000000000000000000000000000..eac2415c06bef15862ac32599119d30e2798f5d8
--- /dev/null
+++ b/algo/detectors/trd2d/Unpack.h
@@ -0,0 +1,34 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer], Dominik Smith */
+
+#pragma once
+
+#include "CommonUnpacker.h"
+#include "ReadoutConfig.h"
+#include "UnpackMS.h"
+
+namespace cbm::algo::trd2d
+{
+
+  namespace detail
+  {
+    using UnpackBase = CommonUnpacker<CbmTrdDigi, UnpackMS, UnpackMonitorData>;
+  }
+
+  class Unpack : public detail::UnpackBase {
+
+   public:
+    using Result_t = detail::UnpackBase::Result_t;
+
+    Unpack(const ReadoutConfig& readout);
+
+    Result_t operator()(const fles::Timeslice&) const;
+
+    const ReadoutConfig& Readout() const { return fReadout; }
+
+   private:
+    ReadoutConfig fReadout;
+  };
+
+}  // namespace cbm::algo::trd2d
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index bd3cb4c656fa8f0e9a976ad3a85b41e6dcae4aec..785f6b936fef9da19ac79448fbb9cd637ac40fa1 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -20,6 +20,7 @@
 #include <xpu/host.h>
 
 using namespace cbm::algo;
+using fles::Subsystem;
 
 Reco::Reco() {}
 Reco::~Reco() {}
@@ -60,7 +61,6 @@ void Reco::Init(const Options& opts)
 
   fContext.opts = opts;
   SetContext(&fContext);
-  fUnpack.SetContext(&fContext);
   fStsHitFinder.SetContext(&fContext);
   fTofCalibrator.SetContext(&fContext);
   fTofHitFinder.SetContext(&fContext);
@@ -85,9 +85,22 @@ void Reco::Init(const Options& opts)
   fContext.recoParams     = config::Read<RecoParams>(yaml);
 
   // Unpackers
-  fUnpack.Init();
+  if (Opts().Has(Subsystem::BMON) && Opts().Has(Step::Unpack)) {
+    bmon::ReadoutConfig cfg{};
+    fBmonUnpack = std::make_unique<bmon::Unpack>(cfg);
+  }
+
+  if (Opts().Has(Subsystem::MUCH) && Opts().Has(Step::Unpack)) {
+    much::ReadoutConfig cfg{};
+    fMuchUnpack = std::make_unique<much::Unpack>(cfg);
+  }
+
+  if (Opts().Has(Subsystem::RICH) && Opts().Has(Step::Unpack)) {
+    rich::ReadoutConfig cfg{};
+    fRichUnpack = std::make_unique<rich::Unpack>(cfg);
+  }
 
-  if (Opts().Has(fles::Subsystem::STS) && Opts().Has(Step::Unpack)) {
+  if (Opts().Has(Subsystem::STS) && Opts().Has(Step::Unpack)) {
     sts::ReadoutSetup readoutSetup = config::ReadFromFile<sts::ReadoutSetup>(Opts().ParamsDir() / "StsReadout.yaml");
     auto chanMask = config::ReadFromFile<sts::ChannelMaskSet>(Opts().ParamsDir() / "StsChannelMaskSet.yaml");
     sts::ReadoutConfig readout{readoutSetup, chanMask};
@@ -96,6 +109,23 @@ void Reco::Init(const Options& opts)
     fStsUnpack = std::make_unique<sts::Unpack>(cfg);
   }
 
+  if (Opts().Has(Subsystem::TOF) && Opts().Has(Step::Unpack)) {
+    tof::ReadoutConfig cfg{};
+    fTofUnpack = std::make_unique<tof::Unpack>(cfg);
+  }
+
+  if (Opts().Has(Subsystem::TRD) && Opts().Has(Step::Unpack)) {
+    yaml       = YAML::LoadFile((Opts().ParamsDir() / "TrdReadoutSetup.yaml").string());
+    auto cfg   = config::Read<trd::ReadoutConfig>(yaml);
+    fTrdUnpack = std::make_unique<trd::Unpack>(cfg);
+  }
+
+  if (Opts().Has(Subsystem::TRD2D) && Opts().Has(Step::Unpack)) {
+    yaml         = YAML::LoadFile((Opts().ParamsDir() / "Trd2dReadoutSetup.yaml").string());
+    auto cfg     = config::Read<trd2d::ReadoutConfig>(yaml);
+    fTrd2dUnpack = std::make_unique<trd2d::Unpack>(cfg);
+  }
+
   // --- Event building
   fs::path configFile = opts.ParamsDir() / "EventbuildConfig.yaml";
   evbuild::Config config(YAML::LoadFile(configFile.string()));
@@ -152,29 +182,22 @@ RecoResults Reco::Run(const fles::Timeslice& ts)
       xpu::scoped_timer timerU("Unpack", &procMon.timeUnpack);
       xpu::t_add_bytes(ts_utils::SizeBytes(ts));
 
-      if (fStsUnpack) {
-        auto [stsDigis, stsMonitor] = (*fStsUnpack)(ts);
-
-        digis.fSts = std::move(stsDigis);
-        QueueUnpackerMetricsDet(stsMonitor);
-      }
-
-      auto [digisU, unpackMonitor] = fUnpack.Run(ts);
+      digis.fBmon  = RunUnpacker(fBmonUnpack, ts);
+      digis.fMuch  = RunUnpacker(fMuchUnpack, ts);
+      digis.fRich  = RunUnpacker(fRichUnpack, ts);
+      digis.fSts   = RunUnpacker(fStsUnpack, ts);
+      digis.fTof   = RunUnpacker(fTofUnpack, ts);
+      digis.fTrd   = RunUnpacker(fTrdUnpack, ts);
+      digis.fTrd2d = RunUnpacker(fTrd2dUnpack, ts);
 
-      digis.fMuch  = std::move(digisU.fMuch);
-      digis.fTof   = std::move(digisU.fTof);
-      digis.fBmon  = std::move(digisU.fBmon);
-      digis.fTrd   = std::move(digisU.fTrd);
-      digis.fTrd2d = std::move(digisU.fTrd2d);
-      digis.fRich  = std::move(digisU.fRich);
-      digis.fPsd   = std::move(digisU.fPsd);
-      digis.fFsd   = std::move(digisU.fFsd);
+      // No unpackers for these yet
+      // digis.fPsd   = RunUnpacker(fPsdUnpack, ts);
+      // digis.fFsd   = RunUnpacker(fFsdUnpack, ts);
 
       L_(info) << "TS contains Digis: STS=" << digis.fSts.size() << " MUCH=" << digis.fMuch.size()
                << " TOF=" << digis.fTof.size() << " BMON=" << digis.fBmon.size() << " TRD=" << digis.fTrd.size()
                << " TRD2D=" << digis.fTrd2d.size() << " RICH=" << digis.fRich.size() << " PSD=" << digis.fPsd.size()
                << " FSD=" << digis.fFsd.size();
-      QueueUnpackerMetrics(ts, unpackMonitor, digis);
     }
 
     // --- Digi event building
@@ -232,6 +255,10 @@ RecoResults Reco::Run(const fles::Timeslice& ts)
     }
   }
   PrintTimings(procMon.time);
+  if (prevTsId) {
+    procMon.tsDelta = ts.index() - *prevTsId;
+  }
+  prevTsId = ts.index();
   QueueProcessingMetrics(procMon);
 
   return results;
@@ -263,59 +290,16 @@ void Reco::PrintTimings(xpu::timings& timings)
   }
 }
 
-void Reco::QueueUnpackerMetrics(const fles::Timeslice& ts, const UnpackMonitorData& monitor, const DigiData& digis)
+template<class Unpacker>
+auto Reco::RunUnpacker(const std::unique_ptr<Unpacker>& unpacker, const fles::Timeslice& ts)
+  -> algo_traits::Output_t<Unpacker>
 {
-  if (!HasMonitor()) return;
-
-  auto sizeBytes       = [](const auto& d) { return d.size() * sizeof(d[0]); };
-  auto expansionFactor = [&](size_t x, const auto& d) {
-    return d.size() > 0 ? static_cast<double>(sizeBytes(d)) / x : 0.0;
-  };
-
-  size_t tsDelta = ts.index() - prevTsId;
-  prevTsId       = ts.index();
-
-  auto& stsDigis   = digis.fSts;
-  auto& muchDigis  = digis.fMuch;
-  auto& tofDigis   = digis.fTof;
-  auto& bmonDigis  = digis.fBmon;
-  auto& trdDigis   = digis.fTrd;
-  auto& trd2dDigis = digis.fTrd2d;
-  auto& richDigis  = digis.fRich;
-
-  size_t nDigisTotal = sizeBytes(stsDigis) + sizeBytes(muchDigis) + sizeBytes(tofDigis) + sizeBytes(bmonDigis)
-                       + sizeBytes(trdDigis) + sizeBytes(trd2dDigis) + sizeBytes(richDigis);
-
-  double totalExpansionFactor = static_cast<double>(nDigisTotal) / monitor.fNumBytes;
-
-  GetMonitor().QueueMetric("cbmreco", {{"hostname", fles::system::current_hostname()}, {"child", Opts().ChildId()}},
-                           {
-                             {"tsIdDelta", tsDelta},
-                             {"unpackTimeTotal", monitor.fTime.wall()},
-                             {"unpackThroughput", monitor.fTime.throughput()},
-                             {"unpackBytesInMuch", monitor.fNumBytesInMuch},
-                             {"unpackBytesInTof", monitor.fNumBytesInTof},
-                             {"unpackBytesInBmon", monitor.fNumBytesInBmon},
-                             {"unpackBytesInTrd", monitor.fNumBytesInTrd},
-                             {"unpackBytesInTrd2d", monitor.fNumBytesInTrd2d},
-                             {"unpackBytesInRich", monitor.fNumBytesInRich},
-                             {"unpackBytesOutMuch", sizeBytes(muchDigis)},
-                             {"unpackBytesOutTof", sizeBytes(tofDigis)},
-                             {"unpackBytesOutBmon", sizeBytes(bmonDigis)},
-                             {"unpackBytesOutTrd", sizeBytes(trdDigis)},
-                             {"unpackBytesOutTrd2d", sizeBytes(trd2dDigis)},
-                             {"unpackBytesOutRich", sizeBytes(richDigis)},
-                             {"unpackExpansionFactorMuch", expansionFactor(monitor.fNumBytesInMuch, muchDigis)},
-                             {"unpackExpansionFactorTof", expansionFactor(monitor.fNumBytesInTof, tofDigis)},
-                             {"unpackExpansionFactorBmon", expansionFactor(monitor.fNumBytesInBmon, bmonDigis)},
-                             {"unpackExpansionFactorTrd", expansionFactor(monitor.fNumBytesInTrd, trdDigis)},
-                             {"unpackExpansionFactorTrd2d", expansionFactor(monitor.fNumBytesInTrd2d, trd2dDigis)},
-                             {"unpackExpansionFactorRich", expansionFactor(monitor.fNumBytesInRich, richDigis)},
-                             {"unpackExpansionFactorTotal", totalExpansionFactor},
-                             {"unpackNumCompUsed", monitor.fNumCompUsed},
-                             {"unpackNumErrInvalidEqId", monitor.fNumErrInvalidEqId},
-                             {"unpackNumErrInvalidSysVer", monitor.fNumErrInvalidSysVer},
-                           });
+  if (!unpacker) {
+    return {};
+  }
+  auto [digis, monitor] = (*unpacker)(ts);
+  QueueUnpackerMetricsDet(monitor);
+  return digis;
 }
 
 template<class MSMonitor>
@@ -446,9 +430,15 @@ void Reco::QueueProcessingMetrics(const ProcessingMonitor& mon)
     return;
   }
 
+  MetricFieldSet fields = {
+    {"processingTimeTotal", mon.time.wall()},
+    {"processingThroughput", mon.time.throughput()},
+  };
+
+  if (mon.tsDelta) {
+    fields.emplace_back("tsDelta", *mon.tsDelta);
+  }
+
   GetMonitor().QueueMetric("cbmreco", {{"hostname", fles::system::current_hostname()}, {"child", Opts().ChildId()}},
-                           {
-                             {"processingTimeTotal", mon.time.wall()},
-                             {"processingThroughput", mon.time.throughput()},
-                           });
+                           std::move(fields));
 }
diff --git a/algo/global/Reco.h b/algo/global/Reco.h
index 8649f997df76961fce165e40f62f118cce1c47bb..569c7bbe8e17d511a2c3ffcdfcf6b2a6727f3cc4 100644
--- a/algo/global/Reco.h
+++ b/algo/global/Reco.h
@@ -4,16 +4,22 @@
 #ifndef CBM_ALGO_GLOBAL_RECO_H
 #define CBM_ALGO_GLOBAL_RECO_H
 
+#include "AlgoTraits.h"
 #include "EventbuildChain.h"
 #include "HistogramSender.h"
 #include "SubChain.h"
-#include "UnpackChain.h"
+#include "bmon/Unpack.h"
 #include "ca/TrackingChain.h"
 #include "global/RecoResults.h"
+#include "much/Unpack.h"
+#include "rich/Unpack.h"
 #include "sts/HitfinderChain.h"
 #include "sts/Unpack.h"
 #include "tof/CalibratorChain.h"
 #include "tof/HitfinderChain.h"
+#include "tof/Unpack.h"
+#include "trd/Unpack.h"
+#include "trd2d/Unpack.h"
 
 #include <xpu/host.h>
 
@@ -27,8 +33,9 @@ namespace cbm::algo
   class Options;
 
   struct ProcessingMonitor {
-    xpu::timings time;        //< total processing time
-    xpu::timings timeUnpack;  //< time spent in unpacking
+    xpu::timings time;           //< total processing time
+    xpu::timings timeUnpack;     //< time spent in unpacking
+    std::optional<i64> tsDelta;  //< id difference between current and previous timeslice
   };
 
   class Reco : SubChain {
@@ -52,17 +59,32 @@ namespace cbm::algo
     xpu::timings fTimesliceTimesAcc;
     std::shared_ptr<HistogramSender> fSender;
 
-    size_t prevTsId = 0;
+    std::optional<u64> prevTsId;
+
+    // BMON
+    std::unique_ptr<bmon::Unpack> fBmonUnpack;
+
+    // MUCH
+    std::unique_ptr<much::Unpack> fMuchUnpack;
+
+    // RICH
+    std::unique_ptr<rich::Unpack> fRichUnpack;
 
     // STS
-    UnpackChain fUnpack;
     std::unique_ptr<sts::Unpack> fStsUnpack;
     sts::HitfinderChain fStsHitFinder;
 
     // TOF
+    std::unique_ptr<tof::Unpack> fTofUnpack;
     tof::HitfinderChain fTofHitFinder;
     tof::CalibratorChain fTofCalibrator;
 
+    // TRD
+    std::unique_ptr<trd::Unpack> fTrdUnpack;
+
+    // TRD2D
+    std::unique_ptr<trd2d::Unpack> fTrd2dUnpack;
+
     // Eventbuilding
     std::unique_ptr<evbuild::EventbuildChain> fEventBuild;
 
@@ -71,7 +93,9 @@ namespace cbm::algo
 
     void Validate(const Options& opts);
 
-    void QueueUnpackerMetrics(const fles::Timeslice&, const UnpackMonitorData&, const DigiData&);
+    template<class Unpacker>
+    auto RunUnpacker(const std::unique_ptr<Unpacker>&, const fles::Timeslice&) -> algo_traits::Output_t<Unpacker>;
+
     template<class MSMonitor>
     void QueueUnpackerMetricsDet(const UnpackMonitor<MSMonitor>&);
     void QueueStsRecoMetrics(const sts::HitfinderMonitor&);
diff --git a/algo/unpack/CommonUnpacker.cxx b/algo/unpack/CommonUnpacker.cxx
index ab4d188b29bd5563e39fec686c318d1c7940f583..b1c657bc7dceb78da23d1afe046e2f155aafc736 100644
--- a/algo/unpack/CommonUnpacker.cxx
+++ b/algo/unpack/CommonUnpacker.cxx
@@ -29,6 +29,7 @@ detail::MSData::MSData(const fles::Timeslice& ts, fles::Subsystem subsystem, gsl
       continue;
     }
 
+    monitor.numComponents++;
     monitor.sizeBytesIn += ts.size_component(comp);
     monitor.numMs += numMsInComp;
     for (u64 mslice = 0; mslice < numMsInComp; mslice++) {
diff --git a/algo/unpack/CommonUnpacker.h b/algo/unpack/CommonUnpacker.h
index 2db2791b2d7d100b45dd0f87d80ff70281e72c45..35bf7fe552feac2bd3208116a774ae8161ec6ee3 100644
--- a/algo/unpack/CommonUnpacker.h
+++ b/algo/unpack/CommonUnpacker.h
@@ -24,6 +24,7 @@ namespace cbm::algo
 
     struct UnpackMonitorBase {
       fles::Subsystem system;       // subsystem
+      size_t numComponents    = 0;  // number of components used
       size_t numMs            = 0;  // number of microslices
       size_t sizeBytesIn      = 0;  // total size of microslice contents
       size_t sizeBytesOut     = 0;  // total size of unpacked digis
diff --git a/algo/unpack/Unpack.cxx b/algo/unpack/Unpack.cxx
deleted file mode 100644
index 76912437224e489d1a28e422827494c3500afdb4..0000000000000000000000000000000000000000
--- a/algo/unpack/Unpack.cxx
+++ /dev/null
@@ -1,440 +0,0 @@
-/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
-   SPDX-License-Identifier: GPL-3.0-only
-   Authors: Volker Friese [committer], Sebastian Heinemann, Felix Weiglhofer */
-
-
-#include "Unpack.h"
-
-#include "compat/Algorithm.h"
-#include "compat/OpenMP.h"
-#include "log.hpp"
-#include "util/TsUtils.h"
-
-#include <chrono>
-
-#include <xpu/host.h>
-
-using namespace std;
-using fles::Subsystem;
-
-namespace cbm::algo
-{
-  // -----   Execution   -------------------------------------------------------
-  Unpack::resultType Unpack::operator()(const fles::Timeslice* timeslice)
-  {
-    xpu::push_timer("Unpack");
-    xpu::t_add_bytes(ts_utils::SizeBytes(*timeslice));
-
-    // --- Output data
-    resultType result          = {};
-    DigiData& digiTs           = result.first;
-    UnpackMonitorData& monitor = result.second;
-
-    if (DetectorEnabled(Subsystem::TOF)) {
-      monitor.fNumBytesInTof +=
-        ParallelMsLoop(Subsystem::TOF, monitor, digiTs.fTof, monitor.fTof, *timeslice, fAlgoTof, 0x00);
-    }
-
-    if (DetectorEnabled(Subsystem::BMON)) {
-      monitor.fNumBytesInBmon +=
-        ParallelMsLoop(Subsystem::BMON, monitor, digiTs.fBmon, monitor.fBmon, *timeslice, fAlgoBmon, 0x00);
-    }
-
-    if (DetectorEnabled(Subsystem::MUCH)) {
-      monitor.fNumBytesInMuch +=
-        ParallelMsLoop(Subsystem::MUCH, monitor, digiTs.fMuch, monitor.fMuch, *timeslice, fAlgoMuch, 0x20);
-    }
-
-    if (DetectorEnabled(Subsystem::RICH)) {
-      monitor.fNumBytesInRich +=
-        ParallelMsLoop(Subsystem::RICH, monitor, digiTs.fRich, monitor.fRich, *timeslice, fAlgoRich, 0x03);
-    }
-
-    if (DetectorEnabled(Subsystem::TRD)) {
-      monitor.fNumBytesInTrd +=
-        ParallelMsLoop(Subsystem::TRD, monitor, digiTs.fTrd, monitor.fTrd, *timeslice, fAlgoTrd, 0x01);
-    }
-
-    if (DetectorEnabled(Subsystem::TRD2D)) {
-      monitor.fNumBytesInTrd2d +=
-        ParallelMsLoop(Subsystem::TRD2D, monitor, digiTs.fTrd2d, monitor.fTrd2d, *timeslice, fAlgoTrd2d, 0x02);
-    }
-
-
-    // ---  Component loop
-    for (uint64_t comp = 0; comp < timeslice->num_components(); comp++) {
-
-      // System ID of current component
-      const auto subsystem = static_cast<Subsystem>(timeslice->descriptor(comp, 0).sys_id);
-
-      if (!DetectorEnabled(subsystem)) continue;
-
-      xpu::scoped_timer t1(fles::to_string(subsystem));
-      xpu::t_add_bytes(timeslice->size_component(comp));
-
-      // Equipment ID of current component
-      // const uint16_t equipmentId = timeslice->descriptor(comp, 0).eq_id;
-
-      // The current algorithms work for the format versions hard-coded as parameters to MsLoop() below.
-      // Other versions are not yet supported.
-      // In the future, different data formats will be supported by instantiating different
-      // algorithms depending on the version.
-
-      // if (subsystem == Subsystem::STS) {
-      //   MsLoop(timeslice, fAlgoSts, comp, equipmentId, &digiTs.fSts, monitor, &monitor.fSts, 0x20);
-      // }
-      // if (subsystem == Subsystem::MUCH) {
-      //   MsLoop(timeslice, fAlgoMuch, comp, equipmentId, &digiTs.fMuch, monitor, &monitor.fMuch, 0x20);
-      // }
-      // if (subsystem == Subsystem::TOF) {
-      //   MsLoop(timeslice, fAlgoTof, comp, equipmentId, &digiTs.fTof, monitor, &monitor.fTof, 0x00);
-      // }
-      // if (subsystem == Subsystem::BMON) {
-      //   MsLoop(timeslice, fAlgoBmon, comp, equipmentId, &digiTs.fBmon, monitor, &monitor.fBmon, 0x00);
-      // }
-      // if (subsystem == Subsystem::TRD) {
-      //   monitor.fNumBytesInTrd +=
-      //     MsLoop(timeslice, fAlgoTrd, comp, equipmentId, &digiTs.fTrd, monitor, &monitor.fTrd, 0x01);
-      // }
-      // if (subsystem == Subsystem::TRD2D) {
-      //   monitor.fNumBytesInTrd2d +=
-      //     MsLoop(timeslice, fAlgoTrd2d, comp, equipmentId, &digiTs.fTrd2d, monitor, &monitor.fTrd2d, 0x02);
-      // }
-      // if (subsystem == Subsystem::RICH) {
-      //   monitor.fNumBytesInRich +=
-      //     MsLoop(timeslice, fAlgoRich, comp, equipmentId, &digiTs.fRich, monitor, &monitor.fRich, 0x03);
-      // }
-    }  //# component
-
-    // --- Sorting of output digis. Is required by both digi trigger and event builder.
-
-    xpu::push_timer("Sort");
-    DoSort(digiTs.fSts);
-    DoSort(digiTs.fMuch);
-    DoSort(digiTs.fTof);
-    DoSort(digiTs.fBmon);
-    DoSort(digiTs.fTrd);
-    DoSort(digiTs.fTrd2d);
-    DoSort(digiTs.fRich);
-    xpu::pop_timer();
-
-    monitor.fTime = xpu::pop_timer();
-
-    return result;
-  }
-  // ----------------------------------------------------------------------------
-
-
-  // ----------------------------------------------------------------------------
-  std::pair<size_t, size_t> Unpack::ParallelInit(const fles::Timeslice& ts, Subsystem subsystem,
-                                                 gsl::span<const uint16_t> legalEqIds, uint8_t sys_ver,
-                                                 UnpackMonitorData& monitor, std::vector<u16>& msEqIds,
-                                                 std::vector<fles::MicrosliceDescriptor>& msDesc,
-                                                 std::vector<const u8*>& msContent)
-  {
-    size_t numMs     = 0;
-    size_t sizeBytes = 0;
-
-    for (uint64_t comp = 0; comp < ts.num_components(); comp++) {
-      auto this_subsystem = static_cast<Subsystem>(ts.descriptor(comp, 0).sys_id);
-
-      if (this_subsystem == subsystem) {
-        const u64 numMsInComp = ts.num_microslices(comp);
-        const u16 componentId = ts.descriptor(comp, 0).eq_id;
-
-        if (ts.descriptor(comp, 0).sys_ver != sys_ver) {
-          monitor.fNumErrInvalidSysVer++;
-          continue;
-        }
-
-        if (std::find(legalEqIds.begin(), legalEqIds.end(), componentId) == legalEqIds.end()) {
-          monitor.fNumErrInvalidEqId++;
-          continue;
-        }
-
-        sizeBytes += ts.size_component(comp);
-        numMs += numMsInComp;
-        for (u64 mslice = 0; mslice < numMsInComp; mslice++) {
-          msEqIds.push_back(componentId);
-          msDesc.push_back(ts.descriptor(comp, mslice));
-          msContent.push_back(ts.content(comp, mslice));
-        }
-      }
-    }
-
-    return {numMs, sizeBytes};
-  }
-  // ----------------------------------------------------------------------------
-
-
-  // ----------------------------------------------------------------------------
-  template<class Digi, class UnpackAlgo, class Monitor>
-  size_t Unpack::ParallelMsLoop(const Subsystem subsystem, UnpackMonitorData& genericMonitor, PODVector<Digi>& digisOut,
-                                std::vector<Monitor>& monitorOut, const fles::Timeslice& ts,
-                                const std::map<u16, UnpackAlgo>& algos, u8 sys_ver)
-  {
-    xpu::scoped_timer t_(fles::to_string(subsystem));
-
-    std::vector<u16> msEqIds;                        // equipment ids of microslices
-    std::vector<fles::MicrosliceDescriptor> msDesc;  // microslice descriptors
-    std::vector<const u8*> msContent;                // pointer to microslice content
-    auto legalEqIds = GetEqIds(algos);
-
-    //    Workaround a problem for some clang versions
-    //    Capturing structured bindings either is avaialable with C++20
-    //    Obviously GCC supports it already and has no problems but clang or at
-    //    least some clang versions fail during compilation
-    //    auto [numMs, sizeBytes] =
-    //      ParallelInit(ts, subsystem, gsl::make_span(legalEqIds), sys_ver, genericMonitor, msEqIds, msDesc, msContent);
-    std::pair<size_t, size_t> tmp =
-      ParallelInit(ts, subsystem, gsl::make_span(legalEqIds), sys_ver, genericMonitor, msEqIds, msDesc, msContent);
-    auto numMs     = tmp.first;
-    auto sizeBytes = tmp.second;
-    std::vector<std::vector<Digi>> msDigis(numMs);  // unpacked digis per microslice
-    std::vector<Monitor> monitor(numMs);            // unpacking monitoring data per microslice
-
-    xpu::t_add_bytes(sizeBytes);
-    genericMonitor.fNumBytes += sizeBytes;
-
-    xpu::push_timer("Unpack");
-    xpu::t_add_bytes(sizeBytes);
-    CBM_PARALLEL_FOR(schedule(dynamic))
-    for (size_t i = 0; i < numMs; i++) {
-      auto result = algos.at(msEqIds[i])(msContent[i], msDesc[i], ts.start_time());
-      msDigis[i]  = std::move(result.first);
-      monitor[i]  = std::move(result.second);
-    }
-    xpu::pop_timer();
-
-    xpu::push_timer("Resize");
-    size_t nDigisTotal = 0;
-    for (const auto& digis : msDigis) {
-      nDigisTotal += digis.size();
-    }
-    digisOut.resize(nDigisTotal);
-    xpu::pop_timer();
-
-    xpu::push_timer("Merge");
-    xpu::t_add_bytes(nDigisTotal * sizeof(Digi));
-    CBM_PARALLEL_FOR(schedule(dynamic))
-    for (size_t i = 0; i < numMs; i++) {
-      size_t offset = 0;
-      for (size_t x = 0; x < i; x++)
-        offset += msDigis[x].size();
-      std::copy(msDigis[i].begin(), msDigis[i].end(), digisOut.begin() + offset);
-    }
-    xpu::pop_timer();
-
-    monitorOut = std::move(monitor);
-
-    return sizeBytes;
-  }
-  // ----------------------------------------------------------------------------
-
-
-  // ----------------- Microslice loop ------------------------------------------
-  template<class Digi, class UnpackAlgo, class MonitorData>
-  size_t Unpack::MsLoop(const fles::Timeslice* timeslice, std::map<uint16_t, UnpackAlgo>& algoMap, const uint64_t comp,
-                        const uint16_t eqId, PODVector<Digi>* digis, UnpackMonitorData& monitor,
-                        std::vector<MonitorData>* monitorMs, uint8_t sys_ver)
-  {
-    // --- Component log
-    size_t numBytesInComp = 0;
-    size_t numDigisInComp = 0;
-
-    // For profiling
-    const auto starttime = std::chrono::high_resolution_clock::now();
-
-    // Get Unpacker
-    const auto algoIt = algoMap.find(eqId);
-    if (algoIt == algoMap.end()) {
-      monitor.fNumErrInvalidEqId++;
-      return 0;
-    }
-    UnpackAlgo& algo = algoIt->second;
-
-    if (timeslice->descriptor(comp, 0).sys_ver != sys_ver) {
-      monitor.fNumErrInvalidSysVer++;
-      return 0;
-    }
-
-    const uint64_t numMsInComp = timeslice->num_microslices(comp);
-
-    for (uint64_t mslice = 0; mslice < numMsInComp; mslice++) {
-      const auto msDescriptor = timeslice->descriptor(comp, mslice);
-      const auto msContent    = timeslice->content(comp, mslice);
-      auto result             = algo(msContent, msDescriptor, timeslice->start_time());
-      L_(debug) << "Unpack::MsLoop(): Component " << comp << ", microslice " << mslice << ", digis "
-                << result.first.size() << ", " << result.second.print();
-      numBytesInComp += msDescriptor.size;
-      numDigisInComp += result.first.size();
-      digis->insert(digis->end(), result.first.begin(), result.first.end());
-      monitorMs->push_back(result.second);
-    }
-    // Get elapsed time
-    const auto endtime  = std::chrono::high_resolution_clock::now();
-    const auto duration = std::chrono::duration_cast<std::chrono::microseconds>(endtime - starttime);
-
-    L_(debug) << "Unpack(): Component " << comp << ", subsystem "
-              << fles::to_string(static_cast<Subsystem>(timeslice->descriptor(comp, 0).sys_id)) << ", microslices "
-              << numMsInComp << " input size " << numBytesInComp << " bytes,"
-              << " digis " << numDigisInComp << ", CPU time " << duration.count() / 1000. << " ms";
-
-    monitor.fNumMs += numMsInComp;
-    monitor.fNumBytes += numBytesInComp;
-    monitor.fNumDigis += numDigisInComp;
-    monitor.fNumCompUsed++;
-
-    return numBytesInComp;
-  }
-  // ----------------------------------------------------------------------------
-
-
-  // -----   Initialisation   ---------------------------------------------------
-  void Unpack::Init(std::vector<Subsystem> subIds)
-  {
-    fSubsystems = subIds;
-
-    // --- Common parameters for all components for STS
-
-    // Create one algorithm per component for STS and configure it with parameters
-
-
-    // Create one algorithm per component for MUCH and configure it with parameters
-    auto equipIdsMuch = fMuchConfig.GetEquipmentIds();
-    for (auto& equip : equipIdsMuch) {
-      std::unique_ptr<much::UnpackPar> par(new much::UnpackPar());
-      const size_t numElinks = fMuchConfig.GetNumElinks(equip);
-      for (size_t elink = 0; elink < numElinks; elink++) {
-        much::UnpackElinkPar elinkPar;
-        elinkPar.fAddress    = fMuchConfig.Map(equip, elink);  // Vector of MUCH addresses for this elink
-        elinkPar.fTimeOffset = fSystemTimeOffset[Subsystem::MUCH];
-        elinkPar.fChanMask   = fMuchConfig.MaskMap(equip, elink);
-        par->fElinkParams.push_back(elinkPar);
-      }
-      fAlgoMuch[equip].SetParams(std::move(par));
-      L_(debug) << "--- Configured equipment " << equip << " with " << numElinks << " elinks";
-    }
-
-    // Create one algorithm per component for TOF and configure it with parameters
-    auto equipIdsTof = fTofConfig.GetEquipmentIds();
-    for (auto& equip : equipIdsTof) {
-      std::unique_ptr<tof::UnpackPar> par(new tof::UnpackPar());
-      const size_t numElinks = fTofConfig.GetNumElinks(equip);
-      for (size_t elink = 0; elink < numElinks; elink++) {
-        tof::UnpackElinkPar elinkPar;
-        elinkPar.fChannelUId = fTofConfig.Map(equip, elink);  // Vector of TOF addresses for this elink
-        elinkPar.fTimeOffset = fSystemTimeOffset[Subsystem::TOF];
-        par->fElinkParams.push_back(elinkPar);
-      }
-      fAlgoTof[equip].SetParams(std::move(par));
-      L_(debug) << "--- Configured equipment " << equip << " with " << numElinks << " elinks";
-    }
-
-    // Create one algorithm per component for Bmon and configure it with parameters
-    auto equipIdsBmon = fBmonConfig.GetEquipmentIds();
-    for (auto& equip : equipIdsBmon) {
-      std::unique_ptr<bmon::UnpackPar> par(new bmon::UnpackPar());
-      const size_t numElinks = fBmonConfig.GetNumElinks(equip);
-      for (size_t elink = 0; elink < numElinks; elink++) {
-        bmon::UnpackElinkPar elinkPar;
-        elinkPar.fChannelUId = fBmonConfig.Map(equip, elink);  // Vector of Bmon addresses for this elink
-        elinkPar.fTimeOffset = fSystemTimeOffset[Subsystem::BMON];
-        par->fElinkParams.push_back(elinkPar);
-      }
-      fAlgoBmon[equip].SetParams(std::move(par));
-      L_(debug) << "--- Configured equipment " << equip << " with " << numElinks << " elinks";
-    }
-
-    // Create one algorithm per component and configure it with parameters
-    auto equipIdsRich = fRichConfig.GetEquipmentIds();
-    for (auto& equip : equipIdsRich) {
-      std::unique_ptr<rich::UnpackPar> par(new rich::UnpackPar());
-      std::map<uint32_t, std::vector<double>> compMap = fRichConfig.Map(equip);
-      for (auto const& val : compMap) {
-        uint32_t address                       = val.first;
-        par->fElinkParams[address].fToTshift   = val.second;
-        par->fElinkParams[address].fTimeOffset = fSystemTimeOffset[Subsystem::RICH];
-      }
-      fAlgoRich[equip].SetParams(std::move(par));
-      L_(info) << "--- Configured equipment " << equip << " with " << fRichConfig.GetNumElinks(equip) << " elinks";
-    }
-
-    // Create one algorithm per component for TRD and configure it with parameters
-    auto equipIdsTrd = fTrdConfig.GetEquipmentIds();
-    for (auto& equip : equipIdsTrd) {
-
-      std::unique_ptr<trd::UnpackPar> par(new trd::UnpackPar());
-      const size_t numCrobs = fTrdConfig.GetNumCrobs(equip);
-
-      for (size_t crob = 0; crob < numCrobs; crob++) {
-        trd::UnpackCrobPar crobPar;
-        const size_t numElinks = fTrdConfig.GetNumElinks(equip, crob);
-
-        for (size_t elink = 0; elink < numElinks; elink++) {
-          trd::UnpackElinkPar elinkPar;
-          auto addresses        = fTrdConfig.Map(equip, crob, elink);
-          elinkPar.fAddress     = addresses.first;   // Asic address for this elink
-          elinkPar.fChanAddress = addresses.second;  // Channel addresses for this elink
-          elinkPar.fTimeOffset  = fSystemTimeOffset[Subsystem::TRD];
-          crobPar.fElinkParams.push_back(elinkPar);
-        }
-        par->fCrobParams.push_back(crobPar);
-      }
-      fAlgoTrd[equip].SetParams(std::move(par));
-      L_(debug) << "--- Configured equipment " << equip << " with " << numCrobs << " crobs";
-    }
-
-    // Create one algorithm per component for TRD2D and configure it with parameters
-    auto equipIdsTrd2d = fTrd2dConfig.GetEquipmentIds();
-    for (auto& equip : equipIdsTrd2d) {
-
-      std::unique_ptr<trd2d::UnpackPar> par(new trd2d::UnpackPar());
-      const size_t numAsics = fTrd2dConfig.GetNumAsics(equip);
-
-      for (size_t asic = 0; asic < numAsics; asic++) {
-        trd2d::UnpackAsicPar asicPar;
-        const size_t numChans = fTrd2dConfig.GetNumChans(equip, asic);
-
-        for (size_t chan = 0; chan < numChans; chan++) {
-          trd2d::UnpackChannelPar chanPar;
-          auto pars           = fTrd2dConfig.ChanMap(equip, asic, chan);
-          chanPar.fPadAddress = pars.padAddress;    // Pad address for channel
-          chanPar.fMask       = pars.rPairingFlag;  // Flag channel mask
-          chanPar.fDaqOffset  = pars.daqOffset;     // Time calibration parameter
-          asicPar.fChanParams.push_back(chanPar);
-        }
-        auto comppars          = fTrd2dConfig.CompMap(equip);
-        par->fSystemTimeOffset = fSystemTimeOffset[Subsystem::TRD2D];
-        par->fModId            = comppars.moduleId;
-        par->fCrobId           = comppars.crobId;
-        par->fAsicParams.push_back(asicPar);
-      }
-      fAlgoTrd2d[equip].SetParams(std::move(par));
-      L_(debug) << "--- Configured equipment " << equip << " with " << numAsics << " asics";
-    }
-
-    //  L_(debug) << "Readout map:" << fStsConfig.PrintReadoutMap();
-    L_(info) << "--- Configured " << fAlgoMuch.size() << " unpacker algorithms for MUCH.";
-    L_(info) << "--- Configured " << fAlgoRich.size() << " unpacker algorithms for RICH.";
-    //  L_(debug) << "Readout map:" << fRichConfig.PrintReadoutMap();
-    L_(info) << "--- Configured " << fAlgoTof.size() << " unpacker algorithms for TOF.";
-    L_(info) << "--- Configured " << fAlgoTrd.size() << " unpacker algorithms for TRD.";
-    L_(info) << "--- Configured " << fAlgoTrd2d.size() << " unpacker algorithms for TRD2D.";
-    L_(info) << "--- Configured " << fAlgoBmon.size() << " unpacker algorithms for Bmon.";
-    L_(info) << "==================================================";
-  }
-  // ----------------------------------------------------------------------------
-
-
-  // ----------------------------------------------------------------------------
-  template<class Digi>
-  void Unpack::DoSort(PODVector<Digi>& digis)
-  {
-    xpu::t_add_bytes(digis.size() * sizeof(Digi));  // Add bytes to timer, assumes xpu::timers are started in operator()
-    Sort(digis.begin(), digis.end(),
-         [](const Digi& digi1, const Digi& digi2) { return digi1.GetTime() < digi2.GetTime(); });
-  }
-
-
-} /* namespace cbm::algo */
diff --git a/algo/unpack/Unpack.h b/algo/unpack/Unpack.h
deleted file mode 100644
index dc569130349502211ee9bd4bd179713bd320507f..0000000000000000000000000000000000000000
--- a/algo/unpack/Unpack.h
+++ /dev/null
@@ -1,195 +0,0 @@
-/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
-   SPDX-License-Identifier: GPL-3.0-only
-   Authors: Volker Friese, Dominik Smith [committer], Sebastian Heinemann, Felix Weiglhofer */
-
-
-#ifndef UNPACK_H
-#define UNPACK_H 1
-
-#include "DigiData.h"
-#include "PODVector.h"
-#include "bmon/ReadoutConfig.h"
-#include "bmon/UnpackMS.h"
-#include "much/ReadoutConfig.h"
-#include "much/UnpackMS.h"
-#include "rich/ReadoutConfig.h"
-#include "rich/UnpackMS.h"
-#include "sts/Digi.h"
-#include "tof/ReadoutConfig.h"
-#include "tof/UnpackMS.h"
-#include "trd/ReadoutConfig.h"
-#include "trd/UnpackMS.h"
-#include "trd2d/ReadoutConfig.h"
-#include "trd2d/UnpackMS.h"
-
-#include <gsl/span>
-#include <optional>
-#include <sstream>
-#include <vector>
-
-#include <xpu/host.h>
-
-namespace cbm::algo
-{
-
-  /** @struct UnpackMonitorData
-   ** @author Dominik Smith <d.smith@gsi.de>
-   ** @since 2 Jun 2023
-   ** @brief Monitoring data for unpacking
-   **/
-  struct UnpackMonitorData {
-    std::vector<much::UnpackMonitorData> fMuch;    ///< Monitoring data for MUCH
-    std::vector<tof::UnpackMonitorData> fTof;      ///< Monitoring data for TOF
-    std::vector<bmon::UnpackMonitorData> fBmon;    ///< Monitoring data for Bmon
-    std::vector<trd::UnpackMonitorData> fTrd;      ///< Monitoring data for TRD
-    std::vector<trd2d::UnpackMonitorData> fTrd2d;  ///< Monitoring data for TRD2D
-    std::vector<rich::UnpackMonitorData> fRich;    ///< Monitoring data for RICH
-    xpu::timings fTime;
-    size_t fNumMs               = 0;
-    size_t fNumBytes            = 0;
-    size_t fNumBytesInMuch      = 0;
-    size_t fNumBytesInTof       = 0;
-    size_t fNumBytesInBmon      = 0;
-    size_t fNumBytesInTrd       = 0;
-    size_t fNumBytesInTrd2d     = 0;
-    size_t fNumBytesInRich      = 0;
-    size_t fNumDigis            = 0;
-    size_t fNumCompUsed         = 0;
-    size_t fNumErrInvalidEqId   = 0;
-    size_t fNumErrInvalidSysVer = 0;
-    std::string print() const
-    {
-      std::stringstream ss;
-      ss << "TS stats: num MS " << fNumMs << ", bytes " << fNumBytes << ", digis " << fNumDigis << ", components "
-         << fNumCompUsed << ", invalidEqIds " << fNumErrInvalidEqId << ", invalidSysVersions " << fNumErrInvalidSysVer
-         << std::endl;
-      return ss.str();
-    }
-  };
-
-  /** @class Unpack
- ** @brief Algo class for unpacking digi timeslicess
- ** @author Dominik Smith <d.smith@gsi.de>
- ** @since 02.06.2023
- **
- **/
-  class Unpack {
-
-   public:
-    typedef std::pair<DigiData, UnpackMonitorData> resultType;
-    using Subsystem = fles::Subsystem;
-
-    /** @brief Algorithm execution
-     ** @param fles timeslice to unpack
-     ** @return pair: digi timeslice, monitoring data
-     **/
-    resultType operator()(const fles::Timeslice* timeslice);
-
-    /** @brief Default constructor **/
-    Unpack(){};
-
-    /** @brief Destructor **/
-    ~Unpack(){};
-
-    /** @brief Parameters for MUCH unpackers **/
-    much::ReadoutConfig fMuchConfig{};
-
-    /** @brief Parameters for TOF unpackers **/
-    tof::ReadoutConfig fTofConfig{};
-
-    /** @brief Parameters for Bmon unpackers **/
-    bmon::ReadoutConfig fBmonConfig{};
-
-    /** @brief Parameters for TRD unpackers **/
-    trd::ReadoutConfig fTrdConfig{};
-
-    /** @brief Parameters for TRD2D unpackers **/
-    trd2d::ReadoutConfig fTrd2dConfig{};
-
-    /** @brief Parameters for RICH unpackers **/
-    rich::ReadoutConfig fRichConfig{};
-
-    /** @brief Initialize unpackers and fill parameters from config objects
-     * @param subIds: vector of subsystem identifiers to unpack, default: all
-     * @see Init()
-     **/
-    void Init(std::vector<fles::Subsystem> subIds = {
-                Subsystem::STS,
-                Subsystem::MUCH,
-                Subsystem::TOF,
-                Subsystem::BMON,
-                Subsystem::TRD,
-                Subsystem::TRD2D,
-                Subsystem::RICH,
-              });
-
-    bool DetectorEnabled(Subsystem subsystem)
-    {
-      return std::find(fSubsystems.begin(), fSubsystems.end(), subsystem) != fSubsystems.end();
-    }
-
-   private:  // methods
-    /** @brief Microslice loop **/
-    template<class Digi, class UnpackAlgo, class MonitorData>
-    size_t MsLoop(const fles::Timeslice* timeslice, std::map<uint16_t, UnpackAlgo>& algoMap, const uint64_t comp,
-                  const uint16_t eqId, PODVector<Digi>* digis, UnpackMonitorData& monitor,
-                  std::vector<MonitorData>* monitorMs, uint8_t sys_ver);
-
-    /** @brief Parallel microslice loop **/
-    template<class Digi, class UnpackAlgo, class Monitor>
-    size_t ParallelMsLoop(const Subsystem subsystem, UnpackMonitorData& monitor, PODVector<Digi>& digisOut,
-                          std::vector<Monitor>& monitorOut, const fles::Timeslice& ts,
-                          const std::map<u16, UnpackAlgo>& algos, u8 sys_ver);
-
-    std::pair<size_t, size_t> ParallelInit(const fles::Timeslice& ts, Subsystem subsystem,
-                                           gsl::span<const uint16_t> legalEqIds, uint8_t sys_ver,
-                                           UnpackMonitorData& monitor, std::vector<u16>& eqIds,
-                                           std::vector<fles::MicrosliceDescriptor>& msDesc,
-                                           std::vector<const u8*>& msContent);
-
-    /** @brief Sort Digis and add bytes to timer for throughput */
-    template<class Digi>
-    void DoSort(PODVector<Digi>& digis);
-
-
-   private:                                     // members
-    std::vector<Subsystem> fSubsystems = {};    ///< Detector identifiers to unpack
-
-    /** @brief MUCH unpackers **/
-    std::map<uint16_t, much::UnpackMS> fAlgoMuch = {};
-
-    /** @brief TOF unpackers **/
-    std::map<uint16_t, tof::UnpackMS> fAlgoTof = {};
-
-    /** @brief Bmon unpackers **/
-    std::map<uint16_t, bmon::UnpackMS> fAlgoBmon = {};
-
-    /** @brief TRD unpackers **/
-    std::map<uint16_t, trd::UnpackMS> fAlgoTrd = {};
-
-    /** @brief TRD2D unpackers **/
-    std::map<uint16_t, trd2d::UnpackMS> fAlgoTrd2d = {};
-
-    /** @brief RICH unpackers **/
-    std::map<uint16_t, rich::UnpackMS> fAlgoRich = {};
-
-    /** @brief System time offsets **/
-    std::map<Subsystem, int32_t> fSystemTimeOffset = {
-      {Subsystem::STS, -970}, {Subsystem::MUCH, -980}, {Subsystem::RICH, 100},  {Subsystem::TOF, 40},
-      {Subsystem::BMON, 0},   {Subsystem::TRD, 1300},  {Subsystem::TRD2D, -510}};
-
-   private:  // methods
-    template<typename UnpackAlgo>
-    std::vector<uint16_t> GetEqIds(const std::map<uint16_t, UnpackAlgo>& algoMap) const
-    {
-      std::vector<uint16_t> eqIds;
-      eqIds.reserve(algoMap.size());
-      for (const auto& [eqId, algo] : algoMap) {
-        eqIds.push_back(eqId);
-      }
-      return eqIds;
-    }
-  };
-}  // namespace cbm::algo
-
-#endif /* UNPACK_H */
diff --git a/algo/unpack/UnpackChain.cxx b/algo/unpack/UnpackChain.cxx
deleted file mode 100644
index 8dd57874cbb3b95fe49440fcfb34c8fab4c63f3c..0000000000000000000000000000000000000000
--- a/algo/unpack/UnpackChain.cxx
+++ /dev/null
@@ -1,29 +0,0 @@
-/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
-   SPDX-License-Identifier: GPL-3.0-only
-   Authors: Felix Weiglhofer [committer] */
-#include "UnpackChain.h"
-
-#include "config/Yaml.h"
-
-using namespace cbm::algo;
-using fles::Subsystem;
-
-void UnpackChain::Init()
-{
-  if (Opts().Has(Subsystem::TRD)) {
-    auto yaml          = YAML::LoadFile((Opts().ParamsDir() / "TrdReadoutSetup.yaml").string());
-    fUnpack.fTrdConfig = config::Read<trd::ReadoutConfig>(yaml);
-  }
-  if (Opts().Has(Subsystem::TRD2D)) {
-    auto yaml            = YAML::LoadFile((Opts().ParamsDir() / "Trd2dReadoutSetup.yaml").string());
-    fUnpack.fTrd2dConfig = config::Read<trd2d::ReadoutConfig>(yaml);
-  }
-  fUnpack.Init(Opts().Detectors());
-}
-
-Unpack::resultType UnpackChain::Run(const fles::Timeslice& timeslice)
-{
-  auto result = fUnpack(&timeslice);
-
-  return result;
-}
diff --git a/algo/unpack/UnpackChain.h b/algo/unpack/UnpackChain.h
deleted file mode 100644
index fbab36a67fbfde10c74b28baaed1a67b005a0d85..0000000000000000000000000000000000000000
--- a/algo/unpack/UnpackChain.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
-   SPDX-License-Identifier: GPL-3.0-only
-   Authors: Felix Weiglhofer [committer] */
-#ifndef CBM_ALGO_UNPACK_UNPACKCHAIN_H
-#define CBM_ALGO_UNPACK_UNPACKCHAIN_H
-
-#include "SubChain.h"
-#include "Unpack.h"
-
-namespace cbm::algo
-{
-
-  class UnpackChain : public SubChain {
-
-   public:
-    void Init();
-
-    Unpack::resultType Run(const fles::Timeslice&);
-
-   private:
-    Unpack fUnpack;
-  };
-
-}  // namespace cbm::algo
-
-
-#endif  //CBM_ALGO_UNPACK_UNPACKCHAIN_H
diff --git a/reco/tasks/CbmTaskUnpack.cxx b/reco/tasks/CbmTaskUnpack.cxx
index aea57d1d8e71819c61d8f2459923ddbb37f08b7e..378a3935776860f161d6538e0a4bc60e9370fcf4 100644
--- a/reco/tasks/CbmTaskUnpack.cxx
+++ b/reco/tasks/CbmTaskUnpack.cxx
@@ -17,9 +17,9 @@
 #include "CbmTrdParSetAsic.h"
 #include "CbmTrdParSetDigi.h"
 #include "CbmTrdParSpadic.h"
-
 #include "MicrosliceDescriptor.hpp"
 #include "System.hpp"
+#include "config/Yaml.h"
 
 #include <FairParAsciiFileIo.h>
 #include <FairParamList.h>
@@ -36,8 +36,6 @@
 #include <sstream>
 #include <vector>
 
-#include "config/Yaml.h"
-
 using namespace std;
 using namespace cbm::algo;
 
@@ -71,8 +69,18 @@ void CbmTaskUnpack::Exec(Option_t*)
   timer.Start();
 
   // --- Unpack the timeslice
-  auto result = fUnpack(timeslice);
-  fTimeslice->fData = result.first.ToStorable();
+  DigiData digis;
+  Monitor monitor;
+
+  digis.fBmon  = RunUnpacker(fBmonUnpack, *timeslice, monitor);
+  digis.fMuch  = RunUnpacker(fMuchUnpack, *timeslice, monitor);
+  digis.fRich  = RunUnpacker(fRichUnpack, *timeslice, monitor);
+  digis.fSts   = RunUnpacker(fStsUnpack, *timeslice, monitor);
+  digis.fTof   = RunUnpacker(fTofUnpack, *timeslice, monitor);
+  digis.fTrd   = RunUnpacker(fTrdUnpack, *timeslice, monitor);
+  digis.fTrd2d = RunUnpacker(fTrd2dUnpack, *timeslice, monitor);
+
+  fTimeslice->fData = digis.ToStorable();
 
   // --- Timeslice log
   timer.Stop();
@@ -80,10 +88,10 @@ void CbmTaskUnpack::Exec(Option_t*)
   logOut << setw(15) << left << GetName() << " [";
   logOut << fixed << setw(8) << setprecision(1) << right << timer.RealTime() * 1000. << " ms] ";
   logOut << "TS " << fNumTs << " (index " << timeslice->index() << ")";
-  logOut << ", components " << result.second.fNumCompUsed << " / " << timeslice->num_components();
-  logOut << ", microslices " << result.second.fNumMs;
-  logOut << ", input rate " << double(result.second.fNumBytes) / timer.RealTime() / 1.e6 << " MB/s";
-  logOut << ", digis " << result.second.fNumDigis;
+  logOut << ", components " << monitor.numCompUsed << " / " << timeslice->num_components();
+  logOut << ", microslices " << monitor.numMs;
+  logOut << ", input rate " << double(monitor.numBytes) / timer.RealTime() / 1.e6 << " MB/s";
+  logOut << ", digis " << monitor.numDigis;
   LOG(info) << logOut.str();
 
 #if !defined(__CLING__) && !defined(__ROOTCLING__)
@@ -91,17 +99,17 @@ void CbmTaskUnpack::Exec(Option_t*)
     fMonitor->QueueMetric(GetName(), {{"host", fHostname}},
                           {{"realtime", timer.RealTime()},
                            {"cputime", timer.CpuTime()},
-                           {"input_size", result.second.fNumBytes},
-                           {"input_rate", double(result.second.fNumBytes) / timer.RealTime()},
-                           {"digis", result.second.fNumDigis}});
+                           {"input_size", monitor.numBytes},
+                           {"input_rate", double(monitor.numBytes) / timer.RealTime()},
+                           {"digis", monitor.numDigis}});
   }
 #endif
 
   // --- Run statistics
   fNumTs++;
-  fNumMs += result.second.fNumMs;
-  fNumBytes += result.second.fNumBytes;
-  fNumDigis += result.second.fNumDigis;
+  fNumMs += monitor.numMs;
+  fNumBytes += monitor.numBytes;
+  fNumDigis += monitor.numDigis;
   fTime += timer.RealTime();
 }
 // ----------------------------------------------------------------------------
@@ -155,11 +163,21 @@ InitStatus CbmTaskUnpack::Init()
   LOG(info) << "--- Registered branch DigiTimeslice.";
 
   // ---- Initialize unpacker
-  fUnpack.fTrdConfig   = InitTrdReadoutConfig();
-  fUnpack.fTrd2dConfig = InitTrd2dReadoutConfig();
-  if (fConfig.dumpSetup) { DumpUnpackSetup(); }
 
-  fUnpack.Init();
+  fBmonUnpack = std::make_unique<bmon::Unpack>(bmon::ReadoutConfig{});
+  fMuchUnpack = std::make_unique<much::Unpack>(much::ReadoutConfig{});
+  fRichUnpack = std::make_unique<rich::Unpack>(rich::ReadoutConfig{});
+
+  auto stsReadout = sts::ReadoutConfig::MakeMCBM2022();
+  auto stsWalkMap = sts::WalkMap::MakeMCBM2022();
+  fStsUnpack      = std::make_unique<sts::Unpack>(sts::Unpack::Config{stsReadout, stsWalkMap});
+  fTofUnpack      = std::make_unique<tof::Unpack>(tof::ReadoutConfig{});
+  fTrdUnpack      = std::make_unique<trd::Unpack>(InitTrdReadoutConfig());
+  fTrd2dUnpack    = std::make_unique<trd2d::Unpack>(InitTrd2dReadoutConfig());
+
+  if (fConfig.dumpSetup) {
+    DumpUnpackSetup();
+  }
 
   return kSUCCESS;
 }
@@ -178,12 +196,16 @@ cbm::algo::trd2d::ReadoutConfig CbmTaskUnpack::InitTrd2dReadoutConfig()
 
   // Read the .digi file and store result
   CbmTrdParSetDigi digiparset;
-  if (asciiInput.open(digiparfile.data())) { digiparset.init(&asciiInput); }
+  if (asciiInput.open(digiparfile.data())) {
+    digiparset.init(&asciiInput);
+  }
   asciiInput.close();
 
   // Read the .asic file and store result
   CbmTrdParSetAsic asicparset;
-  if (asciiInput.open(asicparfile.data())) { asicparset.init(&asciiInput); }
+  if (asciiInput.open(asicparfile.data())) {
+    asicparset.init(&asciiInput);
+  }
   asciiInput.close();
 
   // Map (moduleId) -> (array of crobId)
@@ -247,7 +269,9 @@ cbm::algo::trd::ReadoutConfig CbmTaskUnpack::InitTrdReadoutConfig()
   CbmTrdParSetAsic trdpar;
 
   FairParAsciiFileIo asciiInput;
-  if (asciiInput.open(trdparfile.data())) { trdpar.init(&asciiInput); }
+  if (asciiInput.open(trdparfile.data())) {
+    trdpar.init(&asciiInput);
+  }
 
   FairParamList parlist;
   trdpar.putParams(&parlist);
@@ -273,7 +297,9 @@ cbm::algo::trd::ReadoutConfig CbmTaskUnpack::InitTrdReadoutConfig()
       const uint16_t criId     = asicPar->GetCriId();
       const uint8_t crobId     = asicPar->GetCrobId();
       const uint8_t elinkId    = asicPar->GetElinkId(0);
-      if (elinkId >= 98) { continue; }  // Don't add not connected asics to the map
+      if (elinkId >= 98) {
+        continue;
+      }  // Don't add not connected asics to the map
       addressMap[criId][crobId][elinkId]     = address;
       addressMap[criId][crobId][elinkId + 1] = address;
 
@@ -301,11 +327,23 @@ void CbmTaskUnpack::DumpUnpackSetup()
 {
   LOG(info) << "--- Dumping readout setup to yaml file ---";
 
-  auto yaml = config::Dump {}(fUnpack.fTrdConfig);
+  auto yaml = config::Dump{}(fTrdUnpack->Readout());
   std::ofstream("TrdReadoutSetup.yaml") << yaml;
 
-  yaml = config::Dump {}(fUnpack.fTrd2dConfig);
+  yaml = config::Dump{}(fTrd2dUnpack->Readout());
   std::ofstream("Trd2dReadoutSetup.yaml") << yaml;
 }
 
+template<class Unpacker>
+auto CbmTaskUnpack::RunUnpacker(const std::unique_ptr<Unpacker>& unpacker, const fles::Timeslice& timeslice,
+                                Monitor& monitor) -> cbm::algo::algo_traits::Output_t<Unpacker>
+{
+  auto [digis, detmon] = (*unpacker)(timeslice);
+  monitor.numCompUsed += detmon.numComponents;
+  monitor.numMs += detmon.numMs;
+  monitor.numBytes += detmon.sizeBytesIn;
+  monitor.numDigis += digis.size();
+  return digis;
+}
+
 ClassImp(CbmTaskUnpack)
diff --git a/reco/tasks/CbmTaskUnpack.h b/reco/tasks/CbmTaskUnpack.h
index 2c5c3c78b714d623162db0531ab26e9590a3ea07..ccb50f7a87182a106201b2739937b97a628eae1b 100644
--- a/reco/tasks/CbmTaskUnpack.h
+++ b/reco/tasks/CbmTaskUnpack.h
@@ -19,14 +19,21 @@ namespace cbm
 }
 #endif
 
+#include "AlgoTraits.h"
+#include "EventBuilder.h"
+#include "bmon/Unpack.h"
+#include "much/Unpack.h"
+#include "rich/Unpack.h"
+#include "sts/Unpack.h"
+#include "tof/Unpack.h"
+#include "trd/Unpack.h"
+#include "trd2d/Unpack.h"
+
 #include <FairTask.h>
 
 #include <sstream>
 #include <vector>
 
-#include "EventBuilder.h"
-#include "Unpack.h"
-
 class CbmDigiManager;
 class CbmSourceTs;
 
@@ -50,6 +57,13 @@ public:
     bool dumpSetup = false;
   };
 
+  struct Monitor {
+    size_t numCompUsed = 0;
+    size_t numMs       = 0;
+    size_t numBytes    = 0;
+    size_t numDigis    = 0;
+  };
+
   /** @brief Constructor **/
   CbmTaskUnpack(Config config);
 
@@ -98,8 +112,14 @@ private:  // members
   cbm::Monitor* fMonitor = nullptr;
   std::string fHostname;
 
-  /** @brief Unpacker algorithm **/
-  cbm::algo::Unpack fUnpack;
+  /* Unpacker algorithms */
+  std::unique_ptr<cbm::algo::bmon::Unpack> fBmonUnpack;
+  std::unique_ptr<cbm::algo::much::Unpack> fMuchUnpack;
+  std::unique_ptr<cbm::algo::rich::Unpack> fRichUnpack;
+  std::unique_ptr<cbm::algo::sts::Unpack> fStsUnpack;
+  std::unique_ptr<cbm::algo::tof::Unpack> fTofUnpack;
+  std::unique_ptr<cbm::algo::trd::Unpack> fTrdUnpack;
+  std::unique_ptr<cbm::algo::trd2d::Unpack> fTrd2dUnpack;
 
   size_t fNumTs                = 0;
   size_t fNumMs                = 0;
@@ -108,6 +128,10 @@ private:  // members
   double fTime                 = 0.;
   CbmDigiTimeslice* fTimeslice = nullptr;  ///< Output data
 
+  template<class Unpacker>
+  auto RunUnpacker(const std::unique_ptr<Unpacker>& unpacker, const fles::Timeslice& ts, Monitor& monitor)
+    -> cbm::algo::algo_traits::Output_t<Unpacker>;
+
   ClassDef(CbmTaskUnpack, 3);
 };