diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index a2a052fa4026f62246d4be605c6a50c2217bf65a..a70b0872c98516d229715c67c7f86b14d8102a83 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -52,10 +52,12 @@ if (CBM_ONLINE_STANDALONE)
   add_subdirectory(../external external)
 endif()
 
+add_subdirectory(kfp)
 add_subdirectory(log)
 add_subdirectory(data)
 add_subdirectory(kf)
 add_subdirectory(ca)
+#add_subdirectory(kfp) # For KFParticleOnline
 
 # exclude unittests from being build inside the container
 if (NOT CBM_ONLINE_STANDALONE)
@@ -160,7 +162,10 @@ set(SRCS
   ca/TrackingSetup.cxx
   ca/TrackingChain.cxx
   ca/qa/CaQa.cxx
+  kfp/KfpV0Finder.cxx
+  kfp/KfpV0FinderChain.cxx
   kfp/KfpV0FinderConfig.cxx
+  kfp/KfpV0FinderQa.cxx
 )
 
 set(BUILD_INFO_CXX ${CMAKE_CURRENT_BINARY_DIR}/base/BuildInfo.cxx)
@@ -223,6 +228,7 @@ target_link_libraries(Algo
             external::fles_monitoring
             cppzmq
             poolstl
+  PRIVATE   CbmKFParticleOnlineInterface 
 )
 target_compile_definitions(Algo PUBLIC NO_ROOT)
 xpu_attach(Algo ${DEVICE_SRCS})
@@ -296,6 +302,7 @@ if (NOT CBM_ONLINE_STANDALONE)
               external::fles_monitoring
               cppzmq
               poolstl
+    PRIVATE   CbmKFParticleOnlineInterface 
   )
   xpu_attach(AlgoOffline ${DEVICE_SRCS})
 
diff --git a/algo/base/Definitions.h b/algo/base/Definitions.h
index 6c6d77ba8715786fada47c3483207254bb269b49..b1ff4c9ccc8e804a33a67fe0ab40d81d74dd616e 100644
--- a/algo/base/Definitions.h
+++ b/algo/base/Definitions.h
@@ -78,6 +78,7 @@ namespace cbm::algo
     RecoTof,
     RecoFsd,
     Tracking,
+    V0Finder,
   };
 
 }  // namespace cbm::algo
@@ -148,7 +149,8 @@ CBM_ENUM_DICT(cbm::algo::QaStep,
   {"RecoMuch", cbm::algo::QaStep::RecoMuch},
   {"RecoTof", cbm::algo::QaStep::RecoTof},
   {"RecoFsd", cbm::algo::QaStep::RecoFsd},
-  {"Tracking", cbm::algo::QaStep::Tracking}
+  {"Tracking", cbm::algo::QaStep::Tracking},
+  {"V0Finder", cbm::algo::QaStep::V0Finder}
 );
 
 #endif
diff --git a/algo/base/DigiData.cxx b/algo/base/DigiData.cxx
index 179c8ca31508e322b1bd1222b70bc417a9aafd7b..79fd5a4836022d2caae819e0715f3fcf0a1ecfe7 100644
--- a/algo/base/DigiData.cxx
+++ b/algo/base/DigiData.cxx
@@ -119,14 +119,16 @@ DigiEvent::DigiEvent(const CbmDigiEvent& storable)
   : DigiData(storable.fData)
   , fNumber(storable.fNumber)
   , fTime(storable.fTime)
+  , fSelectionTriggers(storable.fSelectionTriggers)
 {
 }
 
 CbmDigiEvent DigiEvent::ToStorable() const
 {
   return CbmDigiEvent{
-    .fData   = DigiData::ToStorable(),
-    .fNumber = fNumber,
-    .fTime   = fTime,
+    .fData              = DigiData::ToStorable(),
+    .fNumber            = fNumber,
+    .fTime              = fTime,
+    .fSelectionTriggers = fSelectionTriggers,
   };
 }
diff --git a/algo/base/DigiData.h b/algo/base/DigiData.h
index c19b685d996ea4fadc68b7c50e170f356e3c3a44..09b82d933478c57b65087b93e1d0a799120d287b 100644
--- a/algo/base/DigiData.h
+++ b/algo/base/DigiData.h
@@ -7,6 +7,7 @@
 #include "CbmBmonDigi.h"
 #include "CbmDigiData.h"
 #include "CbmDigiEvent.h"
+#include "CbmEventTriggers.h"
 #include "CbmFsdDigi.h"
 #include "CbmMuchDigi.h"
 #include "CbmPsdDigi.h"
@@ -80,6 +81,7 @@ namespace cbm::algo
     // FIXME: Event number not set yet!
     uint64_t fNumber = -1;  ///< Event identifier
     double fTime     = 0;   ///< Event trigger time [ns]
+    CbmEventTriggers fSelectionTriggers;
 
     static std::vector<DigiEvent> FromCbmDigiEvents(const std::vector<CbmDigiEvent>& events);
     static std::vector<CbmDigiEvent> ToCbmDigiEvents(const std::vector<DigiEvent>& events);
diff --git a/algo/base/Options.cxx b/algo/base/Options.cxx
index ea9ecace39cfe29f8fff9cec2161871e9f08c569..abce4f27d969904068407a2022446d9cc36946c4 100644
--- a/algo/base/Options.cxx
+++ b/algo/base/Options.cxx
@@ -93,6 +93,7 @@ Options::Options(int argc, char** argv)
     ("compress-archive", po::bool_switch(&fCompressArchive)->default_value(false), "Enable compression for output archives")
     ("steps", po::value(&fRecoSteps)->multitoken()->default_value({Step::Unpack, Step::DigiTrigger, Step::LocalReco, Step::Tracking})->value_name("<steps>"),
       "space separated list of reconstruction steps (unpack, digitrigger, localreco, ...)")
+    ("event-reco", po::bool_switch(&fReconstructDigiEvents)->default_value(false), "runs digi event reconstruction (local reco, tracking, trigger)")
     ("systems,s", po::value(&fDetectors)->multitoken()->default_value({Subsystem::STS, Subsystem::TOF, Subsystem::BMON, Subsystem::MUCH, Subsystem::RICH, Subsystem::TRD, Subsystem::TRD2D})->value_name("<detectors>"),
       "space separated list of detectors to process (sts, mvd, ...)")
     ("child-id,c", po::value(&fChildId)->default_value("00")->value_name("<id>"), "online process id on node")
diff --git a/algo/base/Options.h b/algo/base/Options.h
index 5a9d232fcac8b006b54cfc0ab3f4cdbe5b62a535..f1b508a0699f5839a0b1f5b0e764f0d6a29f8e34 100644
--- a/algo/base/Options.h
+++ b/algo/base/Options.h
@@ -68,6 +68,8 @@ namespace cbm::algo
 
     bool Has(QaStep qastep) const;
 
+    bool ReconstructDigiEvents() const { return fReconstructDigiEvents; }
+
    private:                  // members
     std::string fParamsDir;  // TODO: can we make this a std::path?
     std::string fInputLocator;
@@ -97,6 +99,7 @@ namespace cbm::algo
     uint64_t fRunId        = 2391;
     uint64_t fRunStartTime = 0;
     bool fCollectAuxData   = false;
+    bool fReconstructDigiEvents = false;
   };
 
 }  // namespace cbm::algo
diff --git a/algo/ca/TrackingChain.cxx b/algo/ca/TrackingChain.cxx
index 1d481261a1af5143deb8128a969175122fcf94eb..374c23bb1e053d132dda601a4049746b910c90e7 100644
--- a/algo/ca/TrackingChain.cxx
+++ b/algo/ca/TrackingChain.cxx
@@ -40,8 +40,10 @@ using cbm::algo::ca::constants::clrs::GNb;  // grin bald text
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-TrackingChain::TrackingChain(const std::unique_ptr<cbm::algo::qa::Manager>& qaManager, std::string_view name)
+TrackingChain::TrackingChain(ECbmRecoMode recoMode, const std::unique_ptr<cbm::algo::qa::Manager>& qaManager,
+                             std::string_view name)
   : fQa(Qa(qaManager, name))
+  , fRecoMode(recoMode)
 {
 }
 
@@ -116,8 +118,13 @@ void TrackingChain::Init()
 
   // ------ Initialize CA framework
   fCaMonitor.Reset();
-  fCaFramework.SetNofThreads(Opts().NumOMPThreads() == std::nullopt ? openmp::GetMaxThreads()
-                                                                    : *(Opts().NumOMPThreads()));
+  if (ECbmRecoMode::Timeslice == fRecoMode) {
+    fCaFramework.SetNofThreads(Opts().NumOMPThreads() == std::nullopt ? openmp::GetMaxThreads()
+                                                                      : *(Opts().NumOMPThreads()));
+  }
+  else {
+    fCaFramework.SetNofThreads(1);
+  }
   fCaFramework.ReceiveParameters(std::move(parameters));
   fCaFramework.Init(ca::TrackingMode::kMcbm);
 
@@ -126,6 +133,7 @@ void TrackingChain::Init()
     fQa.RegisterParameters(&fCaFramework.GetParameters());
     fQa.Init();
   }
+  L_(info) << "TRACKING QA: " << fQa.IsActive();
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -174,18 +182,26 @@ void TrackingChain::PrepareInput(Input_t recoResults)
   faHitExternalIndices.clear();
   faHitExternalIndices.reserve(nHitsTot);
   if (fbDetUsed[EDetectorID::kSts]) {
+    fCaMonitorData.StartTimer(ca::ETimer::PrepareStsHits);
     ReadHits<EDetectorID::kSts>(recoResults.stsHits);
+    fCaMonitorData.StopTimer(ca::ETimer::PrepareStsHits);
   }
   if (fbDetUsed[EDetectorID::kTrd]) {
+    fCaMonitorData.StartTimer(ca::ETimer::PrepareTrdHits);
     ReadHits<EDetectorID::kTrd>(recoResults.trdHits);
+    fCaMonitorData.StopTimer(ca::ETimer::PrepareTrdHits);
   }
   if (fbDetUsed[EDetectorID::kTof]) {
+    fCaMonitorData.StartTimer(ca::ETimer::PrepareTofHits);
     ReadHits<EDetectorID::kTof>(recoResults.tofHits);
+    fCaMonitorData.StopTimer(ca::ETimer::PrepareTofHits);
   }
   faHitExternalIndices.shrink_to_fit();
   fCaDataManager.SetNhitKeys(fNofHitKeys);
   L_(debug) << "Tracking chain: " << fCaDataManager.GetNofHits() << " hits will be passed to the ca::Framework";
+  fCaMonitorData.StartTimer(ca::ETimer::InputDataTransmission);
   fCaFramework.ReceiveInputData(fCaDataManager.TakeInputData());
+  fCaMonitorData.StopTimer(ca::ETimer::InputDataTransmission);
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -210,6 +226,7 @@ TrackingChain::Output_t TrackingChain::PrepareOutput()
       int iHitInternal = fCaFramework.GetInputData().GetHit(fCaFramework.fRecoHits[trackFirstHit + iHit]).Id();
       const auto [detID, iPartition, iPartHit] = faHitExternalIndices[iHitInternal];
       switch (detID) {
+        // FIXME: store a global hit index instead of (partition, hit)
         case ca::EDetectorID::kSts: output.stsHitIndices[iTrk].push_back(std::make_pair(iPartition, iPartHit)); break;
         case ca::EDetectorID::kTof: output.tofHitIndices[iTrk].push_back(std::make_pair(iPartition, iPartHit)); break;
         case ca::EDetectorID::kTrd: output.trdHitIndices[iTrk].push_back(std::make_pair(iPartition, iPartHit)); break;
@@ -223,12 +240,13 @@ TrackingChain::Output_t TrackingChain::PrepareOutput()
     trackFirstHit += nHits;
   }
 
-  L_(info) << "TrackingChain: Timeslice contains " << fCaMonitorData.GetCounterValue(ca::ECounter::RecoTrack)
-           << " tracks, with " << fCaMonitorData.GetCounterValue(ca::ECounter::RecoStsHit) << " sts hits, "
-           << fCaMonitorData.GetCounterValue(ca::ECounter::RecoTofHit) << " tof hits, "
-           << fCaMonitorData.GetCounterValue(ca::ECounter::RecoTrdHit) << " trd hits"
-           << "; the FindTracks routine ran " << fCaMonitorData.GetTimer(ca::ETimer::FindTracks).GetTotal() << " s";
-
+  if (ECbmRecoMode::Timeslice == fRecoMode) {
+    L_(info) << "TrackingChain: Timeslice contains " << fCaMonitorData.GetCounterValue(ca::ECounter::RecoTrack)
+             << " tracks, with " << fCaMonitorData.GetCounterValue(ca::ECounter::RecoStsHit) << " sts hits, "
+             << fCaMonitorData.GetCounterValue(ca::ECounter::RecoTofHit) << " tof hits, "
+             << fCaMonitorData.GetCounterValue(ca::ECounter::RecoTrdHit) << " trd hits"
+             << "; the FindTracks routine ran " << fCaMonitorData.GetTimer(ca::ETimer::FindTracks).GetTotal() << " s";
+  }
 
   // QA
   if (fQa.IsActive()) {
@@ -327,6 +345,7 @@ void TrackingChain::ReadHits(PartitionedSpan<const ca::HitTypes_t::at<DetID>> hi
 
       //int iHitExt     = iOffset + iPartHit;
       // ---- Fill ca::Hit
+      fCaMonitorData.StartTimer(ca::ETimer::CaHitCreation);
       ca::Hit caHit;
       if constexpr (IsSts) {
         caHit.SetFrontKey(firstHitKey + hit.fFrontClusterId);
@@ -337,6 +356,15 @@ void TrackingChain::ReadHits(PartitionedSpan<const ca::HitTypes_t::at<DetID>> hi
         caHit.SetFrontKey(firstHitKey + iPartHit);
         caHit.SetBackKey(caHit.FrontKey());
       }
+
+      if constexpr (IsTof) {
+        // Cut the BMon hits (if any)
+        if (hit.Z() < 0.1) {
+          // FIXME: Provide BMon addresses explicitly in all the parameter files
+          continue;
+        }
+      }
+
       caHit.SetX(hit.X());
       caHit.SetY(hit.Y());
       caHit.SetZ(hit.Z());
@@ -366,11 +394,16 @@ void TrackingChain::ReadHits(PartitionedSpan<const ca::HitTypes_t::at<DetID>> hi
       caHit.SetStation(iStActive);
       caHit.SetId(fCaDataManager.GetNofHits());
       if (caHit.Check()) {
-        if ((caHit.T() < lastTime - 1000.) && (dataStream < 100000)) {
-          dataStream++;
+        if (ECbmRecoMode::Timeslice == fRecoMode) {
+          if ((caHit.T() < lastTime - 1000.) && (dataStream < 100000)) {
+            dataStream++;
+          }
+          lastTime = caHit.T();
+          fCaDataManager.PushBackHit(caHit, dataStreamDet | dataStream);
+        }
+        else {
+          fCaDataManager.PushBackHit(caHit);  // A single data stream in event-by-event mode
         }
-        lastTime = caHit.T();
-        fCaDataManager.PushBackHit(caHit, dataStreamDet | dataStream);
         faHitExternalIndices.push_back(std::make_tuple(DetID, iPartition, iPartHit));
         if (fNofHitKeys <= caHit.FrontKey()) {
           fNofHitKeys = caHit.FrontKey() + 1;
@@ -396,6 +429,7 @@ void TrackingChain::ReadHits(PartitionedSpan<const ca::HitTypes_t::at<DetID>> hi
           fCaMonitorData.IncrementCounter(ca::ECounter::UndefinedTofHit);
         }
       }
+      fCaMonitorData.StopTimer(ca::ETimer::CaHitCreation);
       //prevTime = caHit.T();
       // ---- Update number of hit keys
     }  // iPartHit
diff --git a/algo/ca/TrackingChain.h b/algo/ca/TrackingChain.h
index 3f949d3605e87e1f58b5404886a63ffd44c636d1..39ba48e26fca9cea0cfa295408eb14345a856918 100644
--- a/algo/ca/TrackingChain.h
+++ b/algo/ca/TrackingChain.h
@@ -41,9 +41,11 @@ namespace cbm::algo
   class TrackingChain : public SubChain {
    public:
     /// \brief Constructor from parameters
+    /// \param recoMode     Reconstruction mode
     /// \param pManager a QA-manager
     /// \param name A name of the task (histograms directory)
-    TrackingChain(const std::unique_ptr<cbm::algo::qa::Manager>& qaManager = nullptr, std::string_view name = "");
+    TrackingChain(ECbmRecoMode recoMode, const std::unique_ptr<cbm::algo::qa::Manager>& qaManager = nullptr,
+                  std::string_view name = "");
 
     /// \struct Input_t
     /// \brief  Input to the TrackingChain
@@ -92,6 +94,7 @@ namespace cbm::algo
     /// \brief  Provides action in the end of the run
     void Finalize();
 
+
    private:
     // *********************
     // **  Utility functions
@@ -132,6 +135,8 @@ namespace cbm::algo
     /// \note  Indexing: [globalHitID], value: (DetID, partitionID, hitID)
     ca::Vector<std::tuple<ca::EDetectorID, uint32_t, uint32_t>> faHitExternalIndices{"faHitExternalIndices"};
 
+    ECbmRecoMode fRecoMode{ECbmRecoMode::Undefined};  ///< Reconstruction mode
+
     static constexpr bool kDEBUG = false;  ///< Debug mode
   };
 
diff --git a/algo/ca/core/data/CaDataManager.cxx b/algo/ca/core/data/CaDataManager.cxx
index 7d237151d6b690ccdaa9cdc4439aba2ee0fb913b..4e74a2524c9aff2641deb285cce8262be38d310f 100644
--- a/algo/ca/core/data/CaDataManager.cxx
+++ b/algo/ca/core/data/CaDataManager.cxx
@@ -17,23 +17,6 @@
 using cbm::algo::ca::DataManager;
 using cbm::algo::ca::InputData;
 
-// ---------------------------------------------------------------------------------------------------------------------
-//
-bool DataManager::SendInputData(InputData& destination)
-{
-  // Set boundary hit indexes
-  InitData();
-
-  // Check data before input
-  if (CheckInputData<constants::control::InputDataQaLevel>()) {
-    destination = std::move(fInputData);
-    assert(this->GetNofHits() == 0);
-    return true;
-  }
-  LOG(error) << "L1: Attempt to set up inconsistent input data";
-  return false;
-}
-
 // ---------------------------------------------------------------------------------------------------------------------
 //
 InputData&& DataManager::TakeInputData()
@@ -93,16 +76,21 @@ void DataManager::InitData()
 {
   // set the end pointers to data streams
   // TODO: SZh 14.08.2023: Move the max allowed number of streams to the constants.h
-  if (fInputData.fvStreamStartIndices.size() > 3000) {
-    LOG(warning) << "ca::DataManager: unexpected order of input data: too many data streams!!! ";
-    fInputData.fvStreamStartIndices.shrink(3000);
-  }
+
   int nStreams = fInputData.fvStreamStartIndices.size();
-  fInputData.fvStreamStopIndices.reset(nStreams);
-  for (int i = 0; i < nStreams - 1; i++) {
-    fInputData.fvStreamStopIndices[i] = fInputData.fvStreamStartIndices[i + 1];
+  if (!nStreams) {  // No data streams provided
+    fInputData.fvStreamStartIndices.push_back(0);
+    fInputData.fvStreamStopIndices.push_back(fInputData.fvHits.size());
   }
-  if (nStreams > 0) {
+  else {
+    if (nStreams > 3000) {
+      LOG(warning) << "ca::DataManager: unexpected order of input data: too many data streams!!! ";
+      fInputData.fvStreamStartIndices.shrink(3000);
+    }
+    fInputData.fvStreamStopIndices.reset(nStreams);
+    for (int i = 0; i < nStreams - 1; i++) {
+      fInputData.fvStreamStopIndices[i] = fInputData.fvStreamStartIndices[i + 1];
+    }
     fInputData.fvStreamStopIndices[nStreams - 1] = fInputData.fvHits.size();
   }
 }
@@ -112,10 +100,12 @@ void DataManager::InitData()
 void DataManager::WriteInputData(const std::string& fileName) const
 {
   // Check current data object for consistency
-  if (!CheckInputData<constants::control::InputDataQaLevel>()) {
-    LOG(error) << "ca::DataManager: input data writer: attempt to write invalid input data object to file \""
-               << fileName << "\"";
-    return;
+  if constexpr (constants::control::InputDataQaLevel > 0) {
+    if (!CheckInputData<constants::control::InputDataQaLevel>()) {
+      LOG(error) << "ca::DataManager: input data writer: attempt to write invalid input data object to file \""
+                 << fileName << "\"";
+      return;
+    }
   }
 
   // Open output binary file
diff --git a/algo/ca/core/data/CaDataManager.h b/algo/ca/core/data/CaDataManager.h
index d210e52b93035516022b1b8411d1a1761b44dc5e..b8dac8630607d978e9152ce3ac392c7f765bf5c1 100644
--- a/algo/ca/core/data/CaDataManager.h
+++ b/algo/ca/core/data/CaDataManager.h
@@ -54,8 +54,9 @@ namespace cbm::algo::ca
     /// \param  nHits  Number of hits to reserve
     void ResetInputData(HitIndex_t nHits = 0) noexcept;
 
-    /// \brief  Pushes back a hit
-    /// \param  hit  An ca::Hit object
+    /// \brief  Pushes back a hit (with a data stream info)
+    /// \param  hit       An ca::Hit object
+    /// \param  streamId  Index of a data stream
     void PushBackHit(const Hit& hit, int64_t streamId)
     {
       if (fInputData.fvStreamStartIndices.size() == 0 || fLastStreamId != streamId) {  // new data stream
@@ -67,14 +68,14 @@ namespace cbm::algo::ca
       fInputData.fvHits.push_back(hit);
     }
 
+    /// \brief  Pushes back a hit
+    /// \param  hit       An ca::Hit object
+    void PushBackHit(const Hit& hit) { fInputData.fvHits.push_back(hit); }
+
     /// \brief  Sets the number of hit keys
     /// \param  nKeys  Number of hit keys
     void SetNhitKeys(int nKeys) { fInputData.fNhitKeys = nKeys; }
 
-    /// \brief Sends (moves) input data to an object (alternative method of data sending)
-    /// \param destination  Destination object of input data
-    bool SendInputData(InputData& destination);
-
     /// \brief Takes (moves) the instance of the input data object
     InputData&& TakeInputData();
 
diff --git a/algo/ca/core/data/CaTrack.h b/algo/ca/core/data/CaTrack.h
index 29b7f3859db3513eaa525e474e5d2b3aa6f61d06..cf466361076dd8fcadcd22d80e53bf69832a8be8 100644
--- a/algo/ca/core/data/CaTrack.h
+++ b/algo/ca/core/data/CaTrack.h
@@ -27,6 +27,8 @@ namespace cbm::algo::ca
   ///
   class Track {
    public:
+    using TrackParam_t = cbm::algo::kf::TrackParamS;
+
     friend class boost::serialization::access;
 
     Track() = default;
@@ -43,9 +45,9 @@ namespace cbm::algo::ca
    public:
     int fNofHits{kfdefs::Undef<int>};  ///< Number of hits in track
 
-    cbm::algo::kf::TrackParamS fParFirst;  ///< Track parameters on the first station
-    cbm::algo::kf::TrackParamS fParLast;   ///< Track parameters on the last station
-    cbm::algo::kf::TrackParamS fParPV;     ///< Track parameters in the primary vertex
+    TrackParam_t fParFirst;  ///< Track parameters on the first station
+    TrackParam_t fParLast;   ///< Track parameters on the last station
+    TrackParam_t fParPV;     ///< Track parameters in the primary vertex
   };
 
 }  // namespace cbm::algo::ca
diff --git a/algo/ca/core/utils/CaEnumArray.h b/algo/ca/core/utils/CaEnumArray.h
index 5acd385893cffa7941e92182c268f44adbe0aed8..84e90a0eda1557717935c876b51dd6514b69235a 100644
--- a/algo/ca/core/utils/CaEnumArray.h
+++ b/algo/ca/core/utils/CaEnumArray.h
@@ -38,7 +38,7 @@ namespace cbm::algo::ca
 
    public:
     /// \brief Mutable access operator, indexed by enum entry
-    T& operator[](const E& entry)
+    T& operator[](const E entry)
     {
       return std::array<T, static_cast<std::size_t>(E::END)>::operator[](static_cast<U>(entry));
     }
diff --git a/algo/ca/core/utils/CaMonitor.h b/algo/ca/core/utils/CaMonitor.h
index 389b196fef4b51be0b5c8b054c0967cb6216fe7b..6d26469cfa6ceec81d71a9742786360776629f24 100644
--- a/algo/ca/core/utils/CaMonitor.h
+++ b/algo/ca/core/utils/CaMonitor.h
@@ -171,12 +171,13 @@ namespace cbm::algo::ca
     using std::setfill;
     using std::setw;
     std::stringstream msg;
-    constexpr size_t widthKey = 30;
     constexpr size_t width    = 24;
+    auto Cmp                  = [](const std::string& l, const std::string& r) { return l.size() < r.size(); };
     msg << "\n===== Monitor: " << fsName << "\n";
     if constexpr (!std::is_same_v<ETimerKey, EDummy>) {
+      size_t widthKeyTimer{std::max_element(faTimerNames.begin(), faTimerNames.end(), Cmp)->size() + 1};
       msg << "\n----- Timers:\n";
-      msg << setw(widthKey) << left << "Key" << ' ';
+      msg << setw(widthKeyTimer) << left << "Key" << ' ';
       msg << setw(width) << left << "N Calls" << ' ';
       msg << setw(width) << left << "Average [s]" << ' ';
       msg << setw(width) << left << "Min [s]" << ' ';
@@ -185,7 +186,7 @@ namespace cbm::algo::ca
       msg << right;
       for (int iKey = 0; iKey < fMonitorData.GetNofTimers(); ++iKey) {
         const Timer& timer = fMonitorData.GetTimer(static_cast<ETimerKey>(iKey));
-        msg << setw(widthKey) << faTimerNames[iKey] << ' ';
+        msg << setw(widthKeyTimer) << faTimerNames[iKey] << ' ';
         msg << setw(width) << timer.GetNofCalls() << ' ';
         msg << setw(width) << timer.GetAverage() << ' ';
         msg << setw(width) << timer.GetMin() << ' ';
@@ -195,7 +196,8 @@ namespace cbm::algo::ca
     }
 
     msg << "\n----- Counters:\n";
-    msg << setw(widthKey) << left << "Key" << ' ';
+    size_t widthKeyCounter{std::max_element(faCounterNames.begin(), faCounterNames.end(), Cmp)->size() + 1};
+    msg << setw(widthKeyCounter) << left << "Key" << ' ';
     msg << setw(width) << left << "Total" << ' ';
     for (auto key : fvCounterRatioKeys) {
       msg << setw(width) << left << std::string("per ") + faCounterNames[key] << ' ';
@@ -203,7 +205,7 @@ namespace cbm::algo::ca
     msg << '\n';
     for (int iKey = 0; iKey < fMonitorData.GetNofCounters(); ++iKey) {
       auto counterValue = fMonitorData.GetCounterValue(static_cast<ECounterKey>(iKey));
-      msg << setw(widthKey) << left << faCounterNames[iKey] << ' ';
+      msg << setw(widthKeyCounter) << left << faCounterNames[iKey] << ' ';
       msg << setw(width) << right << counterValue << ' ';
       for (auto keyDen : fvCounterRatioKeys) {
         auto ratio = static_cast<double>(counterValue) / fMonitorData.GetCounterValue(keyDen);
diff --git a/algo/ca/core/utils/CaMonitorData.h b/algo/ca/core/utils/CaMonitorData.h
index 09b3d0e1174cc385357d17eaff108538cc7d80ec..5d93816c1fa0d8998957e98ff6f1b7d0651a9ff8 100644
--- a/algo/ca/core/utils/CaMonitorData.h
+++ b/algo/ca/core/utils/CaMonitorData.h
@@ -82,6 +82,10 @@ namespace cbm::algo::ca
     /// \brief Resets all the counters and timers
     void Reset();
 
+    /// \brief Resets a particular counter
+    /// \param key  Counter key
+    void ResetCounter(ECounterKey key) { faCounters[key] = 0; }
+
     /// \brief Starts timer
     /// \param key  Timer key
     void StartTimer(ETimerKey key) { faTimers[key].Start(); }
diff --git a/algo/ca/core/utils/CaTrackingMonitor.h b/algo/ca/core/utils/CaTrackingMonitor.h
index f91de1d75ac356d14bbe01c330800d188b33788e..ffcee0c35371b8b30b40d84b82c347b497c58cd9 100644
--- a/algo/ca/core/utils/CaTrackingMonitor.h
+++ b/algo/ca/core/utils/CaTrackingMonitor.h
@@ -52,6 +52,11 @@ namespace cbm::algo::ca
   {
     TrackingChain,
       PrepareInputData,
+        PrepareStsHits,
+        PrepareTrdHits,
+        PrepareTofHits,
+        InputDataTransmission,
+        CaHitCreation,
       Tracking,
         PrepareTimeslice,
         TrackingThread,
@@ -103,6 +108,11 @@ namespace cbm::algo::ca
 
       SetTimerName(ETimer::TrackingChain, "tracking chain");
       SetTimerName(ETimer::PrepareInputData, "input data preparation");
+      SetTimerName(ETimer::PrepareStsHits, "STS hits preparation");
+      SetTimerName(ETimer::PrepareTrdHits, "TRD hits preparation");
+      SetTimerName(ETimer::PrepareTofHits, "TOF hits preparation");
+      SetTimerName(ETimer::InputDataTransmission, "input data transmission");
+      SetTimerName(ETimer::CaHitCreation, "CA hit creation");
       SetTimerName(ETimer::Tracking, "algorithm execution");
       SetTimerName(ETimer::PrepareTimeslice, "timeslice preparation");
       SetTimerName(ETimer::TrackingThread, "tracking on one thread");
diff --git a/algo/ca/qa/CaQa.cxx b/algo/ca/qa/CaQa.cxx
index 05815198dd6e6a228f9db995496470a830d18b2c..8d081862c78d399aca6ec01d0dda38e432517185 100644
--- a/algo/ca/qa/CaQa.cxx
+++ b/algo/ca/qa/CaQa.cxx
@@ -187,22 +187,22 @@ void Qa::Init()
       continue;
     }
     {
-      auto sName      = format("track_{}_theta", vsPointName[i]);
+      auto sName      = format("{}_track_{}_theta", GetTaskName(), vsPointName[i]);
       auto sTitl      = format("#theta at {} hit; #theta", vsPointName[i]);
       fvphTrkTheta[i] = MakeObj<H1D>(sName, sTitl, 62, 0., 90.);
     }
     {
-      auto sName    = format("track_{}_phi", vsPointName[i]);
+      auto sName    = format("{}_track_{}_phi", GetTaskName(), vsPointName[i]);
       auto sTitl    = format("#phi at {} hit; #phi", vsPointName[i]);
       fvphTrkPhi[i] = MakeObj<H1D>(sName, sTitl, 62, -180., 180.);
     }
     {
-      auto sName         = format("track_{}_thata_phi", vsPointName[i]);
+      auto sName         = format("{}_track_{}_thata_phi", GetTaskName(), vsPointName[i]);
       auto sTitl         = format("#theta vs #phi at {} hit; #phi; #theta", vsPointName[i]);
       fvphTrkPhiTheta[i] = MakeObj<H2D>(sName, sTitl, 62, -180., 180., 62, 0., 90.);
     }
     {
-      auto sName        = format("track_{}_chi2_ndf", vsPointName[i]);
+      auto sName        = format("{}_track_{}_chi2_ndf", GetTaskName(), vsPointName[i]);
       auto sTitl        = format("#chi^{{2}}/NDF at {} hit; #chi^{{2}}/NDF", vsPointName[i]);
       fvphTrkChi2Ndf[i] = MakeObj<H1D>(sName, sTitl, 100, 0., 20.);
     }
@@ -212,7 +212,7 @@ void Qa::Init()
     double xMin = -0.5;
     double xMax = double(knStaMax) - 0.5;
     {
-      auto sName      = "track_fst_lst_sta";
+      auto sName      = format("{}_track_fst_lst_sta", GetTaskName());
       auto sTitl      = "First vs. last station index;ID^{last}_{station};ID^{first}_{station}";
       fphTrkFstLstSta = MakeObj<H2D>(sName, sTitl, nBins, xMin, xMax, nBins, xMin, xMax);
     }
@@ -227,7 +227,7 @@ void Qa::Init()
         auto setNm = EHitSet::Input == hitSet ? "input" : "used";
         auto setTl = EHitSet::Input == hitSet ? "Input" : "Used";
         {  // XY
-          auto name = format("ca_hit_{}_occupancy_xy", setNm);
+          auto name = format("{}_ca_hit_{}_occupancy_xy", GetTaskName(), setNm);
           auto titl = format("{} hit occupancy in different stations in XY plane", setNm);
           auto canv = CanvasConfig(name, titl);
           for (int iSt = 0; iSt < nSt; ++iSt) {
@@ -238,7 +238,7 @@ void Qa::Init()
           AddCanvasConfig(canv);
         }
         {  // ZX and ZY
-          auto name = format("ca_hit_{}_occupancy_zx_zy", setNm);
+          auto name = format("{}_ca_hit_{}_occupancy_zx_zy", GetTaskName(), setNm);
           auto titl = format("{} hit occupancy in different stations in ZX and ZY planes", setTl);
           auto canv = CanvasConfig(name, titl);
           {  // ZX
@@ -259,7 +259,7 @@ void Qa::Init()
         }
       }
       if (kDebug) {
-        auto name = format("ca_hit_usage_xy");
+        auto name = format("{}_ca_hit_usage_xy", GetTaskName());
         auto titl = format("Hit usage in different stations in XY plane");
         auto canv = CanvasConfig(name, titl);
         for (int iSt = 0; iSt < nSt; ++iSt) {
@@ -273,7 +273,7 @@ void Qa::Init()
 
     // Tracks canvas
     {
-      auto canv = CanvasConfig("ca_tracks", "Tracking output QA");
+      auto canv = CanvasConfig(format("{}_ca_tracks", GetTaskName()), "Tracking output QA");
       {
         auto pad = PadConfig(true, true, false, false, true);
         pad.RegisterHistogram(fvphTrkPhiTheta[0], "colz");
diff --git a/algo/data/sts/Hit.h b/algo/data/sts/Hit.h
index ab1a733f2ca8372e1381433e61d9e94cdc8abb24..2df0d8370f9f0eb1edead87114bbaae959a8021e 100644
--- a/algo/data/sts/Hit.h
+++ b/algo/data/sts/Hit.h
@@ -26,14 +26,13 @@ namespace cbm::algo::sts
     u32 fBackClusterId;   ///< Index of back-side cluster, used by tracking to reduce combinatorics
 
     // Interface for tracking
-    real X() const { return fX; }
-    real Y() const { return fY; }
-    real Z() const { return fZ; }
-    u32 Time() const { return fTime; }
-
-    real Dx() const { return fDx; }
-    real Dy() const { return fDy; }
-    real TimeError() const { return fTimeError; }
+    double X() const { return fX; }
+    double Y() const { return fY; }
+    double Z() const { return fZ; }
+    double Time() const { return fTime; }
+    double Dx() const { return fDx; }
+    double Dy() const { return fDy; }
+    double TimeError() const { return fTimeError; }
 
    private:  // serialization
     friend class boost::serialization::access;
diff --git a/algo/detectors/bmon/Calibrate.cxx b/algo/detectors/bmon/Calibrate.cxx
index d5519854902605e3c84e77f584324d3811c88d04..fc2a17f29cfcf6ca0cef2b159f0bf6da3eb795b8 100644
--- a/algo/detectors/bmon/Calibrate.cxx
+++ b/algo/detectors/bmon/Calibrate.cxx
@@ -57,13 +57,6 @@ Calibrate::Calibrate(CalibrateSetup setup) : fSetup(setup), fSelectionBitsOffset
   }
   fChannelDeadTime = std::vector<double>(fChannelOffset.back(), std::numeric_limits<double>::quiet_NaN());
 
-  // **** DEBUG: BEGIN
-  for (size_t iO = 0; iO < fChannelOffset.size(); ++iO) {
-    L_(info) << "Channel offset: diamond=" << iO << ", offset=" << fChannelOffset[iO];
-  }
-  L_(info) << "Selection mask: 0b" << std::bitset<32>(fSetup.selectionMask);
-  L_(info) << "Bits offset: " << static_cast<uint32_t>(fSelectionBitsOffset);
-  L_(info) << "Size of the channel dead time vector: " << fChannelDeadTime.size();
   // **** DEBUG: END
 }
 
diff --git a/algo/detectors/bmon/Hitfind.h b/algo/detectors/bmon/Hitfind.h
index 68d7d3a9b57e80099a6a4ce3854445622cca5384..0257a9b7483a588e0f06b89f54de477d08a1445a 100644
--- a/algo/detectors/bmon/Hitfind.h
+++ b/algo/detectors/bmon/Hitfind.h
@@ -45,11 +45,14 @@ namespace cbm::algo::bmon
     /// \param iThread  Index of thread
     Output_t operator()(gsl::span<CbmBmonDigi> digisIn, uint32_t iThread = 0);
 
-   private:  // members
     /// \brief Returns an index of the diamond by the address
     /// \param address  A hardware address of the digi
     size_t GetDiamondIndex(uint32_t address) const { return ((fSelectionBitmask & address) >> fSelectionBitsOffset); }
 
+    /// \brief  Gets diamond addresses vector
+    const PODVector<uint32_t>& GetDiamondAddresses() const { return fDiamondAddress; }
+
+   private:                         // members
     uint32_t fNofThreads;           ///< Number of threads
     uint32_t fSelectionBitsOffset;  ///< Number of bits to ther right from the first bit in the selection mask
     uint32_t fSelectionBitmask;     ///< Selection bitmask
diff --git a/algo/detectors/tof/Clusterizer.cxx b/algo/detectors/tof/Clusterizer.cxx
index caf6d34f758dcc976f9c70358f1c133605f1bbf6..fb36f7d47f3e924ac238574166128c4cf338b4dd 100644
--- a/algo/detectors/tof/Clusterizer.cxx
+++ b/algo/detectors/tof/Clusterizer.cxx
@@ -87,7 +87,6 @@ namespace cbm::algo::tof
       auto digiIt         = storDigi.begin();
 
       while (1 < std::distance(digiIt, storDigi.end())) {
-
         while (digiIt->first->GetSide() == std::next(digiIt)->first->GetSide()) {  // Not one Digi of each end!
           digiIt++;
           if (2 > std::distance(digiIt, storDigi.end())) {
diff --git a/algo/detectors/tof/Hit.h b/algo/detectors/tof/Hit.h
index a7608fc72e94383cfc286e9fa7f005de276e2cb5..8e3a1a068531b531625f5118b2300643f0f3fc6d 100644
--- a/algo/detectors/tof/Hit.h
+++ b/algo/detectors/tof/Hit.h
@@ -53,14 +53,13 @@ namespace cbm::algo::tof
 
     // Interface for tracker
 
-    real X() const { return hitPos.X(); }
-    real Y() const { return hitPos.Y(); }
-    real Z() const { return hitPos.Z(); }
-    u32 Time() const { return hitTime; }
-
-    real Dx() const { return hitPosErr.X(); }
-    real Dy() const { return hitPosErr.Y(); }
-    real TimeError() const { return hitTimeErr; }
+    double X() const { return hitPos.X(); }
+    double Y() const { return hitPos.Y(); }
+    double Z() const { return hitPos.Z(); }
+    double Time() const { return hitTime; }
+    double Dx() const { return hitPosErr.X(); }
+    double Dy() const { return hitPosErr.Y(); }
+    double TimeError() const { return hitTimeErr; }
 
     // Interface end
 
diff --git a/algo/detectors/tof/Hitfind.cxx b/algo/detectors/tof/Hitfind.cxx
index 119db83b1a43a26b37db80abe1e919ec39953f00..5abf282f3aecabec76ed6b541cd8f2877962360a 100644
--- a/algo/detectors/tof/Hitfind.cxx
+++ b/algo/detectors/tof/Hitfind.cxx
@@ -28,7 +28,6 @@ namespace cbm::algo::tof
 
       for (int32_t Sm = 0; Sm < NbSm; Sm++) {
         for (int32_t Rpc = 0; Rpc < NbRpc; Rpc++) {
-
           auto par = std::make_unique<cbm::algo::tof::ClusterizerRpcPar>();
 
           HitfindSetup::Rpc rpcPar = setup.rpcs[SmType][Sm * NbRpc + Rpc];
diff --git a/algo/evselector/RecoEventSelectorMonitor.h b/algo/evselector/RecoEventSelectorMonitor.h
new file mode 100644
index 0000000000000000000000000000000000000000..ae6524f804edd794bb3ec7bc0d3a377c64ba2946
--- /dev/null
+++ b/algo/evselector/RecoEventSelectorMonitor.h
@@ -0,0 +1,87 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   RecoEventSelectorMonitor.h
+/// \date   28.01.2025
+/// \brief  A monitor for reco event selector
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "CaMonitor.h"
+
+#include <boost/serialization/base_object.hpp>
+
+namespace cbm::algo::evselect
+{
+  /// \enum  ECounter
+  /// \brief Counter keys for the event selector monitor
+  enum class ECounter
+  {
+    Timeslices,        ///< number of processed timeslices
+    EventsTotal,       ///< Total number of events processed
+    EventsNeStsHits,   ///< Events with not enough STS hits
+    EventsNeTofHits,   ///< Events with enough STS hits, but not enough TOF hits
+    EventsNeBmonHits,  ///< Events with not enough BMon hits
+    EventsNeTracks,    ///< Events with enough hits, but not enough tracks
+    EventsSelected,    ///< Number of selected events
+    LambdaCandidates,  ///< Number of lambda-candidates, returned by KFParticleFinder
+    END
+  };
+
+  /// \enum  ETimer
+  /// \brief Timer keys for the event selector monitor
+  /* clang-format off */
+  enum class ETimer {
+    EventReconstruction,
+      BmonHitFinder,
+      StsHitFinder,
+      TofHitFinder,
+      TrdHitFinder,
+      TrackFinder,
+      V0Finder,
+    END
+  };
+  /* clang-format on */
+
+  /// \brief Specification of ca::MonitorData for the event selector
+  using MonitorData_t = ca::MonitorData<ECounter, ETimer>;
+
+  /// \class Monitor
+  /// \brief A monitor for the event selector
+  class Monitor : public ca::Monitor<ECounter, ETimer> {
+   public:
+    /// \brief Default constructor
+    Monitor() : ca::Monitor<ECounter, ETimer>("Event-selector Monitor")
+    {
+      SetCounterName(ECounter::Timeslices, "processed timeslices");
+      SetCounterName(ECounter::EventsTotal, "total events");
+      SetCounterName(ECounter::EventsNeStsHits, "events discarded by N STS hits");
+      SetCounterName(ECounter::EventsNeTofHits, "events discarded by N TOF hits");
+      SetCounterName(ECounter::EventsNeBmonHits, "events discarded by N BMon hits");
+      SetCounterName(ECounter::EventsNeTracks, "events discarded by N tracks");
+      SetCounterName(ECounter::LambdaCandidates, "potential lambda candidates");
+      SetCounterName(ECounter::EventsSelected, "selected events");
+
+      SetTimerName(ETimer::EventReconstruction, "event reconstruction");
+      SetTimerName(ETimer::BmonHitFinder, "hit finding in Bmon");
+      SetTimerName(ETimer::StsHitFinder, "hit finding in STS");
+      SetTimerName(ETimer::TofHitFinder, "hit finding in TOF");
+      SetTimerName(ETimer::TrdHitFinder, "hit finding in TRD");
+      SetTimerName(ETimer::TrackFinder, "track finding");
+      SetTimerName(ETimer::V0Finder, "V0 finding");
+
+      SetRatioKeys({ECounter::Timeslices});
+    }
+
+   private:
+    friend class boost::serialization::access;
+    template<typename Archive>
+    void serialize(Archive& ar, const unsigned int /*version*/)
+    {
+      ar& boost::serialization::base_object<ca::Monitor<ECounter, ETimer>>(*this);
+    }
+  };
+
+}  // namespace cbm::algo::evselect
diff --git a/algo/global/ParFiles.cxx b/algo/global/ParFiles.cxx
index ac2c3b410accd1415bc4de89811785442eb9aca1..7b73fe77991c059b37d962a3694a552e36e278fd 100644
--- a/algo/global/ParFiles.cxx
+++ b/algo/global/ParFiles.cxx
@@ -45,6 +45,8 @@ ParFiles::ParFiles(uint32_t runId)
       trd.hitfinder2d = "TrdHitfinder2DPar_mcbm2022.yaml";
 
       ca.mainConfig = "TrackingChainConfig_mcbm2022.yaml";
+
+      kfp.V0FinderConfig = "kfp_lambda_v22a.yaml";
       break;
 
     case Setup::mCBM2024_03:
@@ -68,6 +70,8 @@ ParFiles::ParFiles(uint32_t runId)
       trd.hitfinder2d = "TrdHitfinder2DPar_mcbm2024.yaml";
 
       ca.mainConfig = "TrackingChainConfig_mcbm2024.yaml";
+
+      kfp.V0FinderConfig = "kfp_lambda_v24a.yaml";
       break;
 
     case Setup::mCBM2024_05:
@@ -91,6 +95,8 @@ ParFiles::ParFiles(uint32_t runId)
       trd.hitfinder2d = "mcbm2024_05/TrdHitfinder2DPar.yaml";
 
       ca.mainConfig = "mcbm2024_05/TrackingChainConfig.yaml";
+
+      kfp.V0FinderConfig = "kfp_lambda_v24b.yaml";
       break;
 
     case Setup::mCBM2025_02:
diff --git a/algo/global/ParFiles.h b/algo/global/ParFiles.h
index 691e27b39bf8af9b64b89cc209422f786048e1e7..0fe7b3094b1e25eaa0d7f3ccb207a6977f5d2836 100644
--- a/algo/global/ParFiles.h
+++ b/algo/global/ParFiles.h
@@ -54,6 +54,10 @@ namespace cbm::algo
     struct {
       fs::path mainConfig;
     } ca;
+
+    struct {
+      fs::path V0FinderConfig;
+    } kfp;
   };
 
 }  // namespace cbm::algo
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index 8460ea28bf34fa71071601370839602433199058..0f192df1b6c68934269a1eea7e5561b73dd6aa5d 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -22,6 +22,7 @@
 #include "ca/core/data/CaTrack.h"
 #include "compat/OpenMP.h"
 #include "evbuild/Config.h"
+#include "kfp/KfpV0FinderChain.h"
 #include "much/Unpack.h"
 #include "qa/QaManager.h"
 #include "qa/hitfind/BmonHitfindQa.h"
@@ -44,10 +45,6 @@
 
 #include <xpu/host.h>
 
-// DEBUG: BEGIN
-#include <set>
-// DEBUG: END
-
 using namespace cbm::algo;
 using fles::Subsystem;
 
@@ -211,21 +208,6 @@ void Reco::Init(const Options& opts)
     fStsHitFinder->SetParameters(hitFinderPars);
   }
 
-  // BMON Hitfinder
-  if (Opts().Has(fles::Subsystem::BMON) && Opts().Has(Step::LocalReco)) {
-    auto calibSetup = yaml::ReadFromFile<bmon::CalibrateSetup>(opts.ParamsDir() / parFiles.bmon.calibrate);
-    fBmonCalibrator = std::make_unique<bmon::Calibrate>(calibSetup);
-
-    auto hitfindSetup = yaml::ReadFromFile<bmon::HitfindSetup>(opts.ParamsDir() / parFiles.bmon.hitfinder);
-    fBmonHitFinder    = std::make_unique<bmon::Hitfind>(hitfindSetup);
-
-    if (fQaManager != nullptr && Opts().Has(QaStep::RecoBmon)) {
-      fBmonHitFinderQa = std::make_unique<bmon::HitfindQa>(fQaManager, "BmonHitfindEvent");
-      fBmonHitFinderQa->InitParameters(calibSetup, hitfindSetup);
-      fBmonHitFinderQa->Init();
-    }
-  }
-
   // TOF Hitfinder
   if (Opts().Has(fles::Subsystem::TOF) && Opts().Has(Step::LocalReco)) {
     auto calibSetup = yaml::ReadFromFile<tof::CalibrateSetup>(opts.ParamsDir() / parFiles.tof.calibrate);
@@ -241,19 +223,65 @@ void Reco::Init(const Options& opts)
     fTrdHitfind  = std::make_unique<trd::Hitfind>(setup, setup2d);
   }
 
+  // Digi event reconstruction:
+  {
+    fbReconstructDigiEvents = Opts().ReconstructDigiEvents();
+    // It makes no sence to reconstruct an event, if there is no STS, TRD or TOF
+    fbReconstructDigiEvents &= Opts().Has(fles::Subsystem::STS);
+    fbReconstructDigiEvents &= Opts().Has(fles::Subsystem::TRD);
+    fbReconstructDigiEvents &= Opts().Has(fles::Subsystem::TOF);
+    fbReconstructDigiEvents &= Opts().Has(fles::Subsystem::BMON);
+  }
+
   // Tracking
   if (Opts().Has(Step::Tracking)) {
     if (fQaManager != nullptr && Opts().Has(QaStep::Tracking)) {
-      fTracking = std::make_unique<TrackingChain>(fQaManager, "CaTimeslice");
+      fTracking = std::make_unique<TrackingChain>(ECbmRecoMode::Timeslice, fQaManager, "CaTimeslice");
     }
     else {
-      fTracking = std::make_unique<TrackingChain>();
+      fTracking = std::make_unique<TrackingChain>(ECbmRecoMode::Timeslice);
     }
     fTracking->RegisterSetup(pTrackingSetup);
     fTracking->SetContext(&fContext);
     fTracking->Init();
   }
 
+  if (fbReconstructDigiEvents) {
+    fEvSelectingMonitor.Reset();
+
+    // BMON hit finding in event reconstruction
+    auto bmonCalSetup = yaml::ReadFromFile<bmon::CalibrateSetup>(opts.ParamsDir() / parFiles.bmon.calibrate);
+    auto bmonHitSetup = yaml::ReadFromFile<bmon::HitfindSetup>(opts.ParamsDir() / parFiles.bmon.hitfinder);
+    fBmonCalibrator   = std::make_unique<bmon::Calibrate>(bmonCalSetup);
+    fBmonHitFinder    = std::make_unique<bmon::Hitfind>(bmonHitSetup);
+    if (fQaManager != nullptr && Opts().Has(QaStep::RecoBmon)) {
+      fBmonHitFinderQa = std::make_unique<bmon::HitfindQa>(fQaManager, "BmonHitfindEvent");
+      fBmonHitFinderQa->InitParameters(bmonCalSetup, bmonHitSetup);
+      fBmonHitFinderQa->Init();
+    }
+
+    // Tracking in event reconstruction
+    if (fQaManager != nullptr && Opts().Has(QaStep::Tracking)) {
+      fTrackingEvent = std::make_unique<TrackingChain>(ECbmRecoMode::EventByEvent, fQaManager, "CaEvent");
+    }
+    else {
+      fTrackingEvent = std::make_unique<TrackingChain>(ECbmRecoMode::EventByEvent);
+    }
+    fTrackingEvent->RegisterSetup(pTrackingSetup);
+    fTrackingEvent->SetContext(&fContext);
+    fTrackingEvent->Init();
+
+    if (fQaManager != nullptr && Opts().Has(QaStep::V0Finder)) {
+      fV0Finder = std::make_unique<V0FinderChain>(fQaManager);
+    }
+    else {
+      fV0Finder = std::make_unique<V0FinderChain>();
+    }
+    fV0Finder->SetContext(&fContext);
+    fV0Finder->SetBmonDefinedAddresses(fBmonHitFinder->GetDiamondAddresses());
+    fV0Finder->Init();
+  }
+
   // Initialize the QA manager
   if (fQaManager != nullptr) {
     fQaManager->Init();
@@ -393,23 +421,19 @@ RecoResults Reco::Run(const fles::Timeslice& ts)
       QueueEvbuildMetrics(evbuildMonitor);
     }
 
-    // ***** DEBUG: BEGIN
-    if constexpr (0) {
-      int nEvents = events.size();
-      for (int iE = 0; iE < nEvents; ++iE) {
-        const auto& event = events[iE];
-        // Calibrate TOF digis:
-        auto [bmonDigis, bmonCalMonitor]         = (*fBmonCalibrator)(event.fBmon);
-        auto [bmonHits, hitmonitor, digiIndices] = (*fBmonHitFinder)(bmonDigis);
-        if (fBmonHitFinderQa != nullptr) {
-          fBmonHitFinderQa->RegisterDigis(&bmonDigis);
-          fBmonHitFinderQa->RegisterHits(&bmonHits);
-          fBmonHitFinderQa->RegisterDigiIndices(&digiIndices);
-          fBmonHitFinderQa->Exec();
-        }
+    // --- Reconstruct and select digi events
+    if (fbReconstructDigiEvents) {
+      fEvSelectingMonitor.IncrementCounter(evselect::ECounter::Timeslices);
+      fEvSelectingMonitor.IncrementCounter(evselect::ECounter::EventsTotal, events.size());
+      for (auto& event : events) {
+        fEvSelectingMonitor.StartTimer(evselect::ETimer::EventReconstruction);
+        event.fSelectionTriggers = ReconstructEvent(event);
+        fEvSelectingMonitor.StopTimer(evselect::ETimer::EventReconstruction);
       }
+      auto v0FinderMonitor = fV0Finder->GetMonitor();
+      fEvSelectingMonitor.IncrementCounter(evselect::ECounter::LambdaCandidates,
+                                           v0FinderMonitor.GetCounterValue(kfp::ECounter::KfpLambdaCandidates));
     }
-    // ***** DEBUG: END
 
     // --- Filter data for output
     if (Opts().HasOutput(RecoData::DigiTimeslice)) {
@@ -459,8 +483,18 @@ void Reco::Finalize()
     fStsHitFinder->Finalize();
   }
   if (fTracking) {
+    L_(info) << "Track finding in a timeslice:";
     fTracking->Finalize();
   }
+  if (fTrackingEvent) {
+    L_(info) << "Track finding in digi events:";
+    fTrackingEvent->Finalize();
+  }
+  if (fbReconstructDigiEvents) {
+    fV0Finder->Finalize();
+    L_(info) << fEvSelectingMonitor.ToString();
+  }
+
 
   if (Opts().Profiling() >= ProfilingSummary) {
     L_(info) << MakeReportSubtimers("Run Summary", fTimesliceTimesAcc) << "\n"
@@ -487,6 +521,95 @@ void Reco::PrintTimings(xpu::timings& timings)
   }
 }
 
+CbmEventTriggers Reco::ReconstructEvent(const DigiEvent& digiEvent)
+{
+  CbmEventTriggers triggers(0);
+  RecoResults recoEvent;
+  //* BMON hit reconstruction
+  {
+    fEvSelectingMonitor.StartTimer(evselect::ETimer::BmonHitFinder);
+    auto [calDigis, calMonitor]          = (*fBmonCalibrator)(digiEvent.fBmon);
+    auto [hits, hitMonitor, digiIndices] = (*fBmonHitFinder)(calDigis);
+    fEvSelectingMonitor.StopTimer(evselect::ETimer::BmonHitFinder);
+    if (fBmonHitFinderQa != nullptr) {
+      fBmonHitFinderQa->RegisterDigis(&calDigis);
+      fBmonHitFinderQa->RegisterHits(&hits);
+      fBmonHitFinderQa->RegisterDigiIndices(&digiIndices);
+      fBmonHitFinderQa->Exec();
+    }
+    recoEvent.bmonHits = std::move(hits);
+  }
+
+  //* STS hit reconstruction
+  {
+    fEvSelectingMonitor.StartTimer(evselect::ETimer::StsHitFinder);
+    auto stsResults = (*fStsHitFinder)(digiEvent.fSts);
+    fEvSelectingMonitor.StopTimer(evselect::ETimer::StsHitFinder);
+    if (stsResults.hits.NElements() < 4) {  // TODO: Provide a config for cuts (testing mode for now)
+      fEvSelectingMonitor.IncrementCounter(evselect::ECounter::EventsNeStsHits);
+      return triggers;
+    }
+    recoEvent.stsHits = stsResults.hits;
+  }
+
+  //* TOF hit reconstruction
+  {
+    fEvSelectingMonitor.StartTimer(evselect::ETimer::TofHitFinder);
+    auto [caldigis, calmonitor]          = (*fTofCalibrator)(digiEvent.fTof);
+    auto [hits, hitmonitor, digiindices] = (*fTofHitFinder)(caldigis);
+    fEvSelectingMonitor.StopTimer(evselect::ETimer::TofHitFinder);
+    if (hits.NElements() < 2) {  // TODO: Provide a config for cuts (testing mode for now)
+      fEvSelectingMonitor.IncrementCounter(evselect::ECounter::EventsNeTofHits);
+      return triggers;
+    }
+    recoEvent.tofHits = std::move(hits);
+  }
+
+  //* TRD hit reconstruction
+  {
+    // FIXME: additional copy of digis, figure out how to pass 1d + 2d digis at once to hitfinder
+    fEvSelectingMonitor.StartTimer(evselect::ETimer::TrdHitFinder);
+    const auto& digis1d = digiEvent.fTrd;
+    const auto& digis2d = digiEvent.fTrd2d;
+    PODVector<CbmTrdDigi> allDigis{};
+    allDigis.reserve(digis1d.size() + digis2d.size());
+    std::copy(digis1d.begin(), digis1d.end(), std::back_inserter(allDigis));
+    std::copy(digis2d.begin(), digis2d.end(), std::back_inserter(allDigis));
+    auto trdResults = (*fTrdHitfind)(allDigis);
+    fEvSelectingMonitor.StopTimer(evselect::ETimer::TrdHitFinder);
+    recoEvent.trdHits = std::move(std::get<0>(trdResults));
+  }
+
+  //* Tracking
+  {
+    fEvSelectingMonitor.StartTimer(evselect::ETimer::TrackFinder);
+    TrackingChain::Input_t input{.stsHits = recoEvent.stsHits,
+                                 .tofHits = recoEvent.tofHits,
+                                 .trdHits = recoEvent.trdHits};
+    TrackingChain::Output_t output = fTrackingEvent->Run(input);
+    recoEvent.tracks               = std::move(output.tracks);
+    recoEvent.trackStsHitIndices   = std::move(output.stsHitIndices);
+    recoEvent.trackTofHitIndices   = std::move(output.tofHitIndices);
+    recoEvent.trackTrdHitIndices   = std::move(output.trdHitIndices);
+    fEvSelectingMonitor.StopTimer(evselect::ETimer::TrackFinder);
+    if (recoEvent.tracks.size() < 2) {  // Reject all events with less then two tracks
+      fEvSelectingMonitor.IncrementCounter(evselect::ECounter::EventsNeTracks);
+      return triggers;
+    }
+  }
+
+  //* V0-selector
+  fEvSelectingMonitor.StartTimer(evselect::ETimer::V0Finder);
+  triggers = fV0Finder->ProcessEvent(recoEvent);
+  if (triggers.Test(CbmEventTriggers::ETrigger::Lambda)) {
+    L_(info) << "!!! Found event with potential lambda candidates";
+    fEvSelectingMonitor.IncrementCounter(evselect::ECounter::EventsSelected);
+  }
+  fEvSelectingMonitor.StopTimer(evselect::ETimer::V0Finder);
+  return triggers;
+}
+
+
 template<class Unpacker>
 auto Reco::RunUnpacker(const std::unique_ptr<Unpacker>& unpacker, const fles::Timeslice& ts) -> UnpackResult_t<Unpacker>
 {
diff --git a/algo/global/Reco.h b/algo/global/Reco.h
index 6182decdad9d289e7a654aabbb6b5db8650efe29..35cb5c34626c8dc84ba994478e83a305522cfd98 100644
--- a/algo/global/Reco.h
+++ b/algo/global/Reco.h
@@ -5,11 +5,14 @@
 
 #include "AlgoTraits.h"
 #include "SubChain.h"
+#include "evselector/RecoEventSelectorMonitor.h"
 #include "global/RecoResults.h"
 
 #include <xpu/host.h>
 
 // fwd declarations
+class CbmEventTriggers;
+
 namespace fles
 {
   class Timeslice;
@@ -20,6 +23,7 @@ namespace cbm::algo
   class HistogramSender;
   class Options;
   class TrackingChain;
+  class V0FinderChain;
 
   template<class M>
   struct UnpackMonitor;
@@ -130,13 +134,16 @@ namespace cbm::algo
 
     void Init(const Options&);
     RecoResults Run(const fles::Timeslice&);
+
+    CbmEventTriggers ReconstructEvent(const DigiEvent& event);
     void Finalize();
     void PrintTimings(xpu::timings&);
 
     void QueueProcessingExtraMetrics(const ProcessingExtraMonitor&);
 
    private:
-    bool fInitialized = false;
+    bool fInitialized            = false;
+    bool fbReconstructDigiEvents = false;
     ChainContext fContext;
     xpu::timings fTimesliceTimesAcc;
     std::shared_ptr<HistogramSender> fSender;
@@ -178,7 +185,14 @@ namespace cbm::algo
     std::unique_ptr<evbuild::EventbuildChain> fEventBuild;
 
     // Tracking
-    std::unique_ptr<TrackingChain> fTracking;
+    std::unique_ptr<TrackingChain> fTracking;       ///< Tracking in timeslice
+    std::unique_ptr<TrackingChain> fTrackingEvent;  ///< Tracking in event
+
+    // V0-finding
+    std::unique_ptr<V0FinderChain> fV0Finder;  ///< V0-finding chain (in event or a bunch of events)
+
+    // Event selection
+    evselect::Monitor fEvSelectingMonitor;  ///< Monitor for event selecting
 
     // QA
     std::unique_ptr<qa::Manager> fQaManager;
diff --git a/algo/global/RecoResults.h b/algo/global/RecoResults.h
index eb64fa2368569a398c21da0137a4678b13e46490..3bcee8be6acd66388ab8e0043313888c0f35c615 100644
--- a/algo/global/RecoResults.h
+++ b/algo/global/RecoResults.h
@@ -12,6 +12,7 @@
 #include "DigiData.h"
 #include "PartitionedSpan.h"
 #include "PartitionedVector.h"
+#include "bmon/Hit.h"
 #include "ca/core/data/CaTrack.h"
 #include "ca/core/utils/CaVector.h"
 #include "sts/Cluster.h"
@@ -26,6 +27,8 @@ namespace cbm::algo
   /// @name RecoResults
   /// @brief  Structure to keep reconstructed results: digi-events, hits and tracks
   struct RecoResults {
+    using HitId_t = std::pair<uint32_t, uint32_t>;  // Hit ID by track
+
     PODVector<CbmBmonDigi> bmonDigis;
     PODVector<CbmStsDigi> stsDigis;
     PODVector<CbmMuchDigi> muchDigis;
@@ -41,9 +44,11 @@ namespace cbm::algo
     PartitionedSpan<sts::Hit> stsHits;
     PartitionedVector<tof::Hit> tofHits;
     PartitionedVector<trd::Hit> trdHits;
+    PartitionedVector<bmon::Hit> bmonHits;
 
     ca::Vector<ca::Track> tracks;
-    ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>> trackStsHitIndices;
-    ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>> trackTofHitIndices;
+    ca::Vector<std::vector<HitId_t>> trackStsHitIndices;  // [trk][hit][(iPart, iHit)]
+    ca::Vector<std::vector<HitId_t>> trackTofHitIndices;  // [trk][hit][(iPart, iHit)]
+    ca::Vector<std::vector<HitId_t>> trackTrdHitIndices;  // [trk][hit][(iPart, iHit)]
   };
 }  // namespace cbm::algo
diff --git a/algo/kfp/CMakeLists.txt b/algo/kfp/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cc41f095bbc59bdb98f1ebcafb9ac8c09ec67927
--- /dev/null
+++ b/algo/kfp/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(interface)
diff --git a/algo/kfp/KfpV0Finder.cxx b/algo/kfp/KfpV0Finder.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..c95965bc847347caccc52b1bcf23a0eaeda8fd92
--- /dev/null
+++ b/algo/kfp/KfpV0Finder.cxx
@@ -0,0 +1,441 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   KfpV0Finder.cxx
+/// \date   01.02.2025
+/// \brief  A V0 finding algorithm (implementation)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#include "kfp/KfpV0Finder.h"
+
+#include "global/RecoResults.h"
+
+#include <algorithm>
+#include <limits>
+#include <sstream>
+
+using cbm::algo::RecoResults;
+using cbm::algo::kfp::V0Finder;
+
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+bool V0Finder::AssignMomentum(const PartitionedVector<tof::Hit>& tofHits,
+                              const std::vector<RecoResults::HitId_t>& tofHitIds, double t0, ParticleInfo& particleInfo)
+{
+  if (tofHitIds.empty()) {
+    fEventMonitor.IncrementCounter(ECounter::TracksWoTofHits);
+    return false;
+  }
+
+  double beta{0.};
+  if constexpr (kUseAverageSpeed) {
+    for (const auto& hitId : tofHitIds) {
+      beta += EstimateBeta(tofHits[hitId.first][hitId.second], t0);
+    }
+    beta /= tofHitIds.size();
+  }
+  else {
+    const auto& hitId = tofHitIds.back();
+    beta              = EstimateBeta(tofHits[hitId.first][hitId.second], t0);
+  }
+  if (beta < 0.) {
+    fEventMonitor.IncrementCounter(ECounter::TracksWNegativeTofHitTime);
+    return false;
+  }
+  else if (beta > 1.) {
+    fEventMonitor.IncrementCounter(ECounter::TracksWUnphysicalBeta);
+    return false;
+  }
+  double gamma{1. / sqrt(1. - beta * beta)};
+  particleInfo.fBeta = beta;
+  particleInfo.fQp   = particleInfo.fCharge / (gamma * beta * particleInfo.fMass);
+  return true;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0Finder::AssignPid(ParticleInfo& particleInfo)
+{
+  if (std::isnan(particleInfo.fDca)) {
+    particleInfo.fPdg = kUndefPdg;
+    fEventMonitor.IncrementCounter(ECounter::TracksWoPid);
+  }
+  else if (particleInfo.fDca > fMinPionDca) {
+    // pi-
+    particleInfo.fPdg    = -211;
+    particleInfo.fMass   = kPionMass;
+    particleInfo.fCharge = -1;
+    fEventMonitor.IncrementCounter(ECounter::PionsDca);
+  }
+  else if (particleInfo.fDca > fMinProtonDca) {
+    // proton
+    particleInfo.fPdg    = 2212;
+    particleInfo.fMass   = kProtonMass;
+    particleInfo.fCharge = 1;
+    fEventMonitor.IncrementCounter(ECounter::ProtonsDca);
+  }
+  else {
+    // primary
+    //particleInfo.fPdg = fPrimaryAssignedPdg;
+    particleInfo.fPdg = kUndefPdg;
+    fEventMonitor.IncrementCounter(ECounter::TracksWoPid);
+  }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0Finder::CollectDca(const RecoResults& recoEvent)
+{
+  const auto& stsHitIndices = recoEvent.trackStsHitIndices;
+  for (size_t iTrk = 0; iTrk < stsHitIndices.size(); ++iTrk) {
+    const auto& stsHitIndicesInTrack = stsHitIndices[iTrk];
+    if (stsHitIndicesInTrack.size() < 2) {  // DCA cannot be estimated
+      fEventMonitor.IncrementCounter(ECounter::TracksWoStsHits);
+      continue;
+    }
+    auto& particleInfo     = fvParticleInfo[iTrk];
+    auto [iPtFst, iHitFst] = stsHitIndicesInTrack[0];
+    auto [iPtSnd, iHitSnd] = stsHitIndicesInTrack[1];
+    particleInfo.fDca      = EstimateDca(recoEvent.stsHits[iPtFst][iHitFst], recoEvent.stsHits[iPtSnd][iHitSnd]);
+    AssignPid(particleInfo);
+  }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0Finder::CollectT0(gsl::span<const bmon::Hit> bmonHits)
+{
+  fvT0s.clear();
+  fvT0s.reserve(bmonHits.size());
+  std::transform(bmonHits.begin(), bmonHits.end(), std::back_inserter(fvT0s),
+                 [&](const auto& h) { return h.GetTime(); });
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+double V0Finder::EstimateDca(const sts::Hit& fst, const sts::Hit& snd) const
+{
+  double factor{(fst.Z() - fOrigin[2]) / (snd.Z() - fst.Z())};
+  double dcaX{fst.X() - fOrigin[0] - factor * (snd.X() - fst.X())};
+  double dcaY{fst.Y() - fOrigin[1] - factor * (snd.Y() - fst.Y())};
+  return std::sqrt(dcaX * dcaX + dcaY * dcaY);
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+double V0Finder::EstimateBeta(const tof::Hit& hit, double t0) const
+{
+  double t = hit.Time() - t0 - fTzeroOffset;
+  double x{hit.X() - fOrigin[0]};
+  double y{hit.Y() - fOrigin[1]};
+  double z{hit.Z() - fOrigin[2]};
+  double x2{x * x};
+  double y2{y * y};
+  double z2{z * z};
+  double r2{x2 + y2 + z2};
+  return std::sqrt(r2) / (t * kSpeedOfLight);
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+bool V0Finder::FindV0Candidates(const RecoResults& recoEvent, double t0)
+{
+  const auto& tracks = recoEvent.tracks;
+
+  // Reset temporary data structures
+  InitTrackParamVectors(tracks);
+  fpTopoReconstructor->Clear();
+  fvSelectedTrackIds.clear();
+  fvSelectedTrackIds.reserve(tracks.size());
+  fEventMonitor.ResetCounter(ECounter::TracksWoMomentum);
+  fEventMonitor.ResetCounter(ECounter::TracksSelected);
+  fEventMonitor.ResetCounter(ECounter::Pions);
+  fEventMonitor.ResetCounter(ECounter::Protons);
+  fEventMonitor.ResetCounter(ECounter::EventsLambdaCand);
+
+  // Preselect tracks
+  uint32_t nProtonCandidates{0};
+  uint32_t nPionCandidates{0};
+  uint32_t nSelectedTracks{0};
+  fEventMonitor.StartTimer(ETimer::PreselectTracks);
+  for (size_t iTrk = 0; iTrk < tracks.size(); ++iTrk) {  // Over all tracks
+    auto& particleInfo = fvParticleInfo[iTrk];
+
+    // Cut tracks with undefined dca (by PID == -2)
+    // NOTE: if fPdg == kUndefPdg, beta and QP were not estimated for this track on previous iterations as well
+    if (particleInfo.fPdg == kUndefPdg) {
+      continue;
+    }
+
+    // Reset fields of the ParticleInfo, which could be filled on the previous iteration
+    particleInfo.fBeta      = std::numeric_limits<double>::quiet_NaN();
+    particleInfo.fQp        = std::numeric_limits<double>::quiet_NaN();
+    particleInfo.fbSelected = false;
+
+    // Assign momentum to tracks
+    if (!AssignMomentum(recoEvent.tofHits, recoEvent.trackTofHitIndices[iTrk], t0, particleInfo)) {
+      fEventMonitor.IncrementCounter(ECounter::TracksWoMomentum);
+      continue;  // No momentum was assigned
+    }
+
+    // Select tracks
+    if (!SelectTrack(particleInfo)) {
+      continue;
+    }
+    fvSelectedTrackIds.push_back(iTrk);
+    particleInfo.fbSelected = true;
+
+    // Update track parameters
+    double qpVar{particleInfo.fQp * fQpAssignedUncertainty};
+    qpVar = qpVar * qpVar;
+    auto& trkParam{fvTrackParam[iTrk]};
+    trkParam.first.SetQp(particleInfo.fQp);
+    trkParam.first.SetC44(qpVar);
+    trkParam.second.SetQp(particleInfo.fQp);
+    trkParam.second.SetC44(qpVar);
+
+    switch (particleInfo.fPdg) {
+      case -211: ++nPionCandidates; break;
+      case 2212: ++nProtonCandidates; break;
+    }
+    ++nSelectedTracks;
+  }
+  fEventMonitor.StopTimer(ETimer::PreselectTracks);
+  if (!nPionCandidates || !nProtonCandidates) {
+    return false;  // no Lambda can be found
+  }
+  fEventMonitor.IncrementCounter(ECounter::EventsLambdaCand);
+  fEventMonitor.IncrementCounter(ECounter::TracksSelected, nSelectedTracks);
+  fEventMonitor.IncrementCounter(ECounter::Pions, nPionCandidates);
+  fEventMonitor.IncrementCounter(ECounter::Protons, nProtonCandidates);
+
+  // Initialize and run the KFParticleFinder
+  fEventMonitor.StartTimer(ETimer::InitKfp);
+  KFPTrackVector kfpTracksFst;
+  KFPTrackVector kfpTracksLst;
+  kfpTracksFst.Resize(nSelectedTracks);
+  kfpTracksLst.Resize(nSelectedTracks);
+  for (uint32_t iKfpTrk = 0; iKfpTrk < fvSelectedTrackIds.size(); ++iKfpTrk) {  // Over selected tracks
+    uint32_t iCaTrk{fvSelectedTrackIds[iKfpTrk]};
+    const auto& trkParam{fvTrackParam[iCaTrk]};
+    const auto& particleInfo{fvParticleInfo[iCaTrk]};
+    SetKfpTrackParameters(kfpTracksFst, iKfpTrk, iCaTrk, trkParam.first, particleInfo);
+    SetKfpTrackParameters(kfpTracksLst, iKfpTrk, iCaTrk, trkParam.second, particleInfo);
+  }
+  fpTopoReconstructor->Init(kfpTracksFst, kfpTracksLst);
+  fpTopoReconstructor->AddPV(MakeKfpPrimaryVertex(fOrigin));
+  fpTopoReconstructor->SortTracks();
+  fEventMonitor.StopTimer(ETimer::InitKfp);
+  fEventMonitor.StartTimer(ETimer::ExecKfp);
+  fpTopoReconstructor->ReconstructParticles();
+  fEventMonitor.StopTimer(ETimer::ExecKfp);
+  const auto& particles{fpTopoReconstructor->GetParticles()};
+
+  // Scan for Lambda-candidates
+  uint32_t nLambdaCandidates =
+    std::count_if(particles.begin(), particles.end(), [&](const auto& p) { return p.GetPDG() == 3122; });
+  fEventMonitor.IncrementCounter(ECounter::KfpLambdaCandidates, nLambdaCandidates);
+
+  return static_cast<bool>(nLambdaCandidates);
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0Finder::Init() { fpTopoReconstructor->SetTarget({float(fOrigin[0]), float(fOrigin[1]), float(fOrigin[2])}); }
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0Finder::InitTrackParamVectors(const ca::Vector<ca::Track>& tracks)
+{
+  fvTrackParam.clear();
+  fvTrackParam.reserve(tracks.size());
+  std::transform(tracks.begin(), tracks.end(), std::back_inserter(fvTrackParam),
+                 [&](const auto& t) { return std::make_pair(t.fParFirst, t.fParLast); });
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+KFVertex V0Finder::MakeKfpPrimaryVertex(const std::array<float, 3>& r)
+{
+  KFVertex kfVertex;
+  kfVertex.X() = r[0];
+  kfVertex.Y() = r[1];
+  kfVertex.Z() = r[2];
+  for (int iC = 0; iC < 6; ++iC) {
+    kfVertex.Covariance(iC) = 0.f;
+  }
+  kfVertex.Chi2() = -100.f;
+  return kfVertex;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+CbmEventTriggers V0Finder::Process(const RecoResults& recoEvent)
+{
+  //L_(info) << "----------------------------- EVENT --------------";
+  CbmEventTriggers res;
+  fEventMonitor.Reset();
+  fEventMonitor.StartTimer(ETimer::ProcessEvent);
+  fEventMonitor.IncrementCounter(ECounter::EventsTotal);
+  fEventMonitor.IncrementCounter(ECounter::TracksTotal, recoEvent.tracks.size());
+
+  // ----- Initialize data-structures
+  fvParticleInfo.clear();
+  fvParticleInfo.resize(recoEvent.tracks.size());
+
+  // ----- Define T0
+  // So far we cannot preselect a hit from multiple ones, we will be using all of them iteratively to find lambdas
+  fEventMonitor.StartTimer(ETimer::CollectT0);
+  CollectT0(recoEvent.bmonHits[fBmonPartitionIndex]);
+  if (fvT0s.empty()) {
+    fEventMonitor.IncrementCounter(ECounter::EventsWoTzero);
+    return res;
+  }
+  fEventMonitor.StopTimer(ETimer::CollectT0);
+
+  // ----- Estimate DCA of tracks and assign PID
+  // If a track has less then two STS hits, and undefined DCA value is stored
+  fEventMonitor.StartTimer(ETimer::CollectDca);
+  CollectDca(recoEvent);
+  fEventMonitor.StopTimer(ETimer::CollectDca);
+
+  // ----- Try to find lambdas for different T0
+  fSelectedT0 = std::numeric_limits<double>::quiet_NaN();
+  fEventMonitor.StartTimer(ETimer::FindV0Candidates);
+  for (double t0 : fvT0s) {
+    if (FindV0Candidates(recoEvent, t0)) {
+      fSelectedT0 = t0;
+      res.Set(CbmEventTriggers::ETrigger::Lambda);
+      fEventMonitor.IncrementCounter(ECounter::KfpEventsLambdaCand);
+      break;  // Lambda-candidates were found, there is no sense to scan further
+    }
+  }
+  fEventMonitor.StopTimer(ETimer::FindV0Candidates);
+  fEventMonitor.StopTimer(ETimer::ProcessEvent);
+  return res;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+bool V0Finder::SelectTrack(const ParticleInfo& particleInfo) const
+{
+  // Speed cut
+  if (particleInfo.fPdg == -211) {
+    if (particleInfo.fBeta < fMinBetaPion || particleInfo.fBeta > fMaxBetaPion) {
+      return false;
+    }
+  }
+  else if (particleInfo.fPdg == 2212) {
+    if (particleInfo.fBeta < fMinBetaProton || particleInfo.fBeta > fMaxBetaProton) {
+      return false;
+    }
+  }
+  return true;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0Finder::SetKfpTrackParameters(KFPTrackVector& trackVector, uint32_t iKfpTrk, uint32_t iCaTrk,
+                                     const ca::Track::TrackParam_t& trkParam, const ParticleInfo& particleInfo) const
+{
+  // ----- Parameter definition
+  double tx{trkParam.GetTx()};
+  double ty{trkParam.GetTy()};
+  double qp{trkParam.GetQp()};
+  double p{particleInfo.fCharge / qp};
+  double p2{p * p};
+  double t2inv{1. / (1. + tx * tx + ty * ty)};
+  double pz{std::sqrt(t2inv * p2)};
+  double px{tx * pz};
+  double py{ty * pz};
+
+  trackVector.SetParameter(trkParam.GetX(), 0, iKfpTrk);
+  trackVector.SetParameter(trkParam.GetY(), 1, iKfpTrk);
+  trackVector.SetParameter(trkParam.GetZ(), 2, iKfpTrk);
+  trackVector.SetParameter(px, 3, iKfpTrk);
+  trackVector.SetParameter(py, 4, iKfpTrk);
+  trackVector.SetParameter(pz, 5, iKfpTrk);
+
+  // Jacobian matrix for (tx, ty, qp) -> (px, py, pz)
+  std::array<std::array<double, 3>, 3> Jp;
+  Jp[2][0] = -t2inv * px;         // d(pz)/d(tx)
+  Jp[2][1] = -t2inv * py;         // d(pz)/d(ty)
+  Jp[2][2] = -pz / qp;            // d(pz)/d(qp)
+  Jp[0][0] = tx * Jp[2][0] + pz;  // d(px)/d(tx)
+  Jp[0][1] = tx * Jp[2][1];       // d(px)/d(ty)
+  Jp[0][2] = tx * Jp[2][2];       // d(px)/d(qp)
+  Jp[1][0] = ty * Jp[2][0];       // d(py)/d(tx)
+  Jp[1][1] = ty * Jp[2][1] + pz;  // d(py)/d(ty)
+  Jp[1][2] = ty * Jp[2][2];       // d(py)/d(qp)
+
+
+  // ----- Covariance matrix definition
+  // Position covariance
+  trackVector.SetCovariance(trkParam.C00(), 0, iKfpTrk);  // var(x)
+  trackVector.SetCovariance(trkParam.C01(), 1, iKfpTrk);  // cov(x, y)
+  trackVector.SetCovariance(trkParam.C11(), 2, iKfpTrk);  // var(y)
+
+  // Momentum-position covariances
+  auto MomPosCovariance = [&](const int k, const int l) constexpr->double
+  {
+    double val{0.};
+    const auto& JpA = Jp[k];
+    for (int i = 0; i < 3; ++i) {
+      val += JpA[i] * trkParam.C(i + 2, l);
+    }
+    return val;
+  };
+  trackVector.SetCovariance(MomPosCovariance(0, 0), 6, iKfpTrk);   // cov(x, px)
+  trackVector.SetCovariance(MomPosCovariance(0, 1), 7, iKfpTrk);   // cov(y, px)
+  trackVector.SetCovariance(MomPosCovariance(1, 0), 10, iKfpTrk);  // cov(x, py)
+  trackVector.SetCovariance(MomPosCovariance(1, 1), 11, iKfpTrk);  // cov(y, py)
+  trackVector.SetCovariance(MomPosCovariance(2, 0), 15, iKfpTrk);  // cov(x, pz)
+  trackVector.SetCovariance(MomPosCovariance(2, 1), 16, iKfpTrk);  // cov(y, pz)
+
+  // Momentum covariances
+  auto MomentumCovariance = [&](const int k, const int l) constexpr->double
+  {
+    double val{0.};
+    const auto& JpA = Jp[k];
+    const auto& JpB = Jp[l];
+    for (int i = 0; i < 3; ++i) {
+      double factor{0.};
+      for (int j = 0; j < 3; ++j) {
+        factor += JpB[j] * trkParam.C(i + 2, j + 2);
+      }
+      val += JpA[i] * factor;
+    }
+    return val;
+  };
+  trackVector.SetCovariance(MomentumCovariance(0, 0), 9, iKfpTrk);   // var(px)
+  trackVector.SetCovariance(MomentumCovariance(1, 0), 13, iKfpTrk);  // cov(px, py)
+  trackVector.SetCovariance(MomentumCovariance(1, 1), 14, iKfpTrk);  // var(py)
+  trackVector.SetCovariance(MomentumCovariance(2, 0), 18, iKfpTrk);  // cov(px, pz)
+  trackVector.SetCovariance(MomentumCovariance(2, 1), 19, iKfpTrk);  // cov(py, pz)
+  trackVector.SetCovariance(MomentumCovariance(2, 2), 20, iKfpTrk);  // var(pz)
+
+  // Zero covariances (with z-coordinate)
+  trackVector.SetCovariance(0.f, 3, iKfpTrk);   // cov(x,z)
+  trackVector.SetCovariance(0.f, 4, iKfpTrk);   // cov(y,z)
+  trackVector.SetCovariance(0.f, 5, iKfpTrk);   // var(z)
+  trackVector.SetCovariance(0.f, 8, iKfpTrk);   // cov(z,px)
+  trackVector.SetCovariance(0.f, 12, iKfpTrk);  // cov(z,py)
+  trackVector.SetCovariance(0.f, 17, iKfpTrk);  // var(z,pz)
+
+  // ----- Other quantities
+  // Magnetic field (NOTE: zero fom mCBM)
+  // FIXME: Provide a proper initialization for full CBM
+  for (int iF = 0; iF < 10; ++iF) {
+    trackVector.SetFieldCoefficient(0.f, iF, iKfpTrk);
+  }
+
+  trackVector.SetId(iCaTrk, iKfpTrk);
+  trackVector.SetPDG(particleInfo.fPdg, iKfpTrk);
+  trackVector.SetQ(particleInfo.fCharge, iKfpTrk);
+  trackVector.SetNPixelHits(0, iKfpTrk);
+
+  // NOTE: 0 - primary tracks, -1 - secondary tracks. Here for now we assign ALL tracks as secondary
+  trackVector.SetPVIndex(-1, iKfpTrk);
+}
diff --git a/algo/kfp/KfpV0Finder.h b/algo/kfp/KfpV0Finder.h
new file mode 100644
index 0000000000000000000000000000000000000000..9605b581b2b980b0825cb68de1689fc68da2ad5e
--- /dev/null
+++ b/algo/kfp/KfpV0Finder.h
@@ -0,0 +1,276 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   KfpV0Finder.h
+/// \date   01.02.2025
+/// \brief  A V0 finding algorithm
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "CbmEventTriggers.h"
+#include "KFParticleTopoReconstructor.h"
+#include "global/RecoResults.h"
+#include "kfp/KfpV0FinderMonitor.h"
+
+#include <limits>
+#include <memory>
+
+
+namespace cbm::algo::kfp
+{
+  /// \class V0Finder
+  /// \brief A V0-finding algorithm
+  class V0Finder {
+   public:
+    /// \struct ParticleInfo
+    /// \brief  A structure to keep temporary PID information for tracks
+    struct ParticleInfo {
+      double fMass{std::numeric_limits<double>::quiet_NaN()};  //< Estimated mass of the particle [GeV/c2]
+      double fDca{std::numeric_limits<double>::quiet_NaN()};   //< DCA to the origin [cm]
+      double fBeta{std::numeric_limits<double>::quiet_NaN()};  //< Speed of the particle [c]
+      double fQp{std::numeric_limits<double>::quiet_NaN()};    //< q/p of the particle [GeV^-1]
+      int32_t fPdg{V0Finder::kUndefPdg};                       //< PDG code
+      int32_t fCharge{0};                                      //< Charge of the particle
+      bool fbSelected{false};                                  //< The track was selected
+    };
+
+    //* Framework and physical constants (public)
+    static constexpr double kPionMass{0.13957039};      ///< Pion mass [GeV/c2]
+    static constexpr double kProtonMass{0.938272088};   ///< Proton mass [GeV/c2]
+    static constexpr double kSpeedOfLight{29.9792458};  ///< Speed of light [cm/ns]
+    static constexpr int32_t kUndefPdg{-2};             ///< PDG for tracks, which PID cannot be inferred
+
+    /// \brief Default constructor
+    V0Finder() = default;
+
+    /// \brief Copy constructor
+    V0Finder(const V0Finder&) = delete;
+
+    /// \brief Move constructor
+    V0Finder(V0Finder&&) = delete;
+
+    /// \brief Destructor
+    ~V0Finder() = default;
+
+    /// \brief Copy assignment operator
+    V0Finder& operator=(const V0Finder&) = delete;
+
+    /// \brief Move assignment operator
+    V0Finder& operator=(V0Finder&&) = delete;
+
+    /// \brief  Adds particle to reconstruction list
+    /// \param  pdg  A PDG code of the particle to be reconstructed
+    void AddDecayToReconstructionList(int pdg) { GetKFParticleFinder()->AddDecayToReconstructionList(pdg); }
+
+    /// \brief  Gets monitor data
+    const V0FinderMonitorData_t& GetEventMonitor() const { return fEventMonitor; }
+
+    /// \brief  Gets origin
+    const std::array<float, 3>& GetOrigin() const { return fOrigin; }
+
+    /// \brief  Mutable access to the KfParticleFinder of the run topology reconstructor
+    KFParticleFinder* GetKFParticleFinder() { return fpTopoReconstructor->GetKFParticleFinder(); }
+
+    /// \brief  Constant access to the KfParticleFinder of the run topology reconstructor
+    const KFParticleFinder* GetKFParticleFinder() const { return fpTopoReconstructor->GetKFParticleFinder(); }
+
+    /// \brief  Gets a vector of particle info
+    const std::vector<ParticleInfo>& GetParticleInfo() const { return fvParticleInfo; }
+
+    /// \brief  Gets selected t0
+    /// \note   NaN, if Lambda-candidate was not found
+    double GetSelectedT0() const { return fSelectedT0; }
+
+    /// \brief  Gets indices of selected tracks
+    const std::vector<uint32_t>& GetSelectedTrackIds() const { return fvSelectedTrackIds; }
+
+    /// \brief  Gets found t0s
+    const std::vector<double>& GetT0s() const { return fvT0s; }
+
+    /// \brief  Accessor to topology reconstructor
+    const std::unique_ptr<KFParticleTopoReconstructor>& GetTopoReconstructor() const { return fpTopoReconstructor; }
+
+    /// \brief  Gets track parameters
+    const auto& GetTrackAssignedParams() const { return fvTrackParam; }
+
+    /// \brief Initializes the instance (called in the beginning of the run)
+    void Init();
+
+    /// \brief  Processes a reconstructed data sample, returns a collection of fired triggers
+    CbmEventTriggers Process(const RecoResults& recoEvent);
+
+    /// \brief  Sets an address of a reference BMON diamond
+    /// \param  iPartition  An index of the BMON hit partition: used to selected the hits from the particular config
+    void SetBmonPartitionIndex(int iPartition) { fBmonPartitionIndex = iPartition; }
+
+    /// \param  dca  DCA [cm]
+    void SetMinPionDca(double dca) { fMinPionDca = dca; }
+
+    /// \brief  Sets minimal proton DCA to primary vertex
+    /// \param  dca  DCA [cm]
+    void SetMinProtonDca(double dca) { fMinProtonDca = dca; }
+
+    /// \brief  Sets origin
+    /// \param  x  X-coordinate of the origin [cm]
+    /// \param  y  Y-coordinate of the origin [cm]
+    /// \param  z  Z-coordinate of the origin [cm]
+    // FIXME: for now origin is defined as the target center, later it can be changed
+    void SetOrigin(double x, double y, double z) { fOrigin = {float(x), float(y), float(z)}; }
+
+    /// \brief  Sets minimal pion DCA to primary vertex
+    /// \brief  Sets pion velocity range
+    /// \param  vMin  Minimal velocity [cm/ns]
+    /// \param  vMax  Maximal velocity [cm/ns]
+    void SetPionVelocityRange(double vMin, double vMax)
+    {
+      fMinBetaPion = vMin / kSpeedOfLight;
+      fMaxBetaPion = vMax / kSpeedOfLight;
+    }
+
+    /// \brief  Sets proton velocity range
+    /// \param  vMin  Minimal velocity [cm/ns]
+    /// \param  vMax  Maximal velocity [cm/ns]
+    void SetProtonVelocityRange(double vMin, double vMax)
+    {
+      fMinBetaProton = vMin / kSpeedOfLight;
+      fMaxBetaProton = vMax / kSpeedOfLight;
+    }
+
+    /// \brief  Sets the assigned PDG for primary particles
+    /// \param  pdg  PDG code of the particle
+    void SetPrimaryAssignedPdg(int pdg) { fPrimaryAssignedPdg = pdg; }
+
+    /// \brief  Assignes an uncertainty to the momentum measurement
+    /// \param  uncertainty  Relative uncertainty ( = sqrt(var(q/p)) / (q/p))
+    void SetQpAssignedUncertainty(double uncertainty) { fQpAssignedUncertainty = uncertainty; }
+
+    /// \brief  Sets an offset to t0
+    /// \param  offset  An offset [ns]
+    void SetTzeroOffset(double offset) { fTzeroOffset = offset; }
+
+    /// \brief  Sets cut on the distance to the primary vertex from the decay vertex
+    /// \param  cut  Cut value [cm]
+    void SetLCut(float cut) { GetKFParticleFinder()->SetLCut(cut); }
+
+    /// \brief  Sets cut on \f$\chi^2_{prim}\f$ of each track for 2-daughter decays
+    /// \param  cut  Cut value
+    void SetChiPrimaryCut2D(float cut) { GetKFParticleFinder()->SetChiPrimaryCut2D(cut); }
+
+    /// \brief  Sets cut on \f$\chi^2_{geo}\f$ for 2-daughter decays
+    /// \param  cut  Cut value
+    void SetChi2Cut2D(float cut) { GetKFParticleFinder()->SetChi2Cut2D(cut); }
+
+    /// \brief  Sets cut on \f$l/\Delta l\f$ for 2-daughter decays
+    /// \param  cut  Cut value
+    void SetLdLCut2D(float cut) { GetKFParticleFinder()->SetLdLCut2D(cut); }
+
+   private:
+    /// \brief  Assigns momentum based on the TOF measurement
+    /// \param[in]  tofHits      Tof hits container
+    /// \param[in]  tofHitIds    Tof hit indices, used by the track
+    /// \param[in]  t0           A t0 value
+    /// \param[inout]  pidInfo   PID information for the track
+    /// \return  true  A physically reasonable momentum assigned
+    /// \return  false Momentum was not assigned, because it was nonphysical
+    /* clang-format off */
+    bool AssignMomentum(const PartitionedVector<tof::Hit>& tofHits, 
+                        const std::vector<RecoResults::HitId_t>& tofHitIds,
+                        double t0, 
+                        ParticleInfo& pidInfo);
+    /* clang-format on */
+
+    /// \brief  Assigns PID info based on the estimated DCA
+    /// \param  dca  DCA of track to origin
+    /// \return (mass, charge, pid)
+    void AssignPid(ParticleInfo& info);
+
+    /// \brief  Collects a vector of DCA
+    /// \param  recoEvent  Instance of a reconstructed event
+    void CollectDca(const RecoResults& recoEvent);
+
+    /// \brief  Collects T0 values among the BMON hits
+    /// \param  bmonHits  A span of BMON hits
+    ///
+    /// If multiple T0-s are found, the routine will run multiple times, until V0-candidates are found
+    void CollectT0(gsl::span<const bmon::Hit> bmonHits);
+
+    /// \brief  Estimates speed of particle, using TOF measurement
+    /// \param  tofHit   A TOF hit
+    /// \param  t0       An t0 value
+    /// \return Speed of particle [c]
+    double EstimateBeta(const tof::Hit& tofHit, double t0) const;
+
+    /// \brief  Estimate DCA of a track to origin
+    /// \param  fst  first  STS hit
+    /// \param  snd  second STS hit
+    /// \return dca [cm]
+    double EstimateDca(const sts::Hit& fst, const sts::Hit& snd) const;
+
+    /// \brief  Tries to find V0-candidates for a given t0
+    /// \param  recoEvent  Instance of a reconstructed event
+    /// \param  t0         An estimated t0 value
+    /// \return true  V0-candidates found
+    /// \return false V0-candidates not found
+    bool FindV0Candidates(const RecoResults& recoEvent, double t0);
+
+    /// \brief  Initializes copies of track parameter vectors
+    /// \param  tracks  A container of tracks
+    void InitTrackParamVectors(const ca::Vector<ca::Track>& tracks);
+
+    /// \brief   Makes a KF vertex
+    /// \param   r  coordinates of PV [cm]
+    static KFVertex MakeKfpPrimaryVertex(const std::array<float, 3>& r);
+
+    /// \brief  Applies selection cut on the track
+    /// \param  particleInfo  Particle collected information
+    /// \return true  Track is selected
+    /// \return false Track is rejected
+    bool SelectTrack(const ParticleInfo& particleInfo) const;
+
+    /// \brief  Sets KFP track parameters
+    /// \param[inout]  kfpTrkVector  Reference to the KFP track vector
+    /// \param[in]     iKfpTrk       Index of the KFP track
+    /// \param[in]     iCaTrk        Index of the CA track
+    /// \param[in]     trkParam      Track parameters
+    /// \param[in]     particleInfo  Particle information
+    void SetKfpTrackParameters(KFPTrackVector& kfpTrkVector, uint32_t iKfpTrk, uint32_t iCaTrk,
+                               const ca::Track::TrackParam_t& trkParam, const ParticleInfo& particleInfo) const;
+
+
+    //* Framework and physical constants (private)
+    static constexpr bool kUseAverageSpeed{false};  ///< If an average speed of tof hits is used
+
+    V0FinderMonitorData_t fEventMonitor;  ///< Main monitor data instance
+
+    //* Different run-time cuts and flags (TODO: define in a config)
+    double fTzeroOffset{0.};             ///< Offset for T0
+    double fMinPionDca{1.5};             ///< Minimum DCA to PV for pions
+    double fMinProtonDca{0.5};           ///< Minimum DCA to PV for protons
+    double fQpAssignedUncertainty{0.1};  ///< Assigned relative uncertainty for q/p estimation
+    double fMinBetaProton{0.};           ///< Minimal proton velocity (beta) [c]
+    double fMaxBetaProton{1.};           ///< Maximal proton velocity (beta) [c]
+    double fMinBetaPion{0.};             ///< Minimal proton velocity (beta) [c]
+    double fMaxBetaPion{1.};             ///< Maximal proton velocity (beta) [c]
+    int fPrimaryAssignedPdg{321};        ///< Assigned PDG hypothesis for primary particles
+
+    //* Run-time variables, provided by framework
+    int fBmonPartitionIndex{-1};  ///< Index of selected partition in BMON hit vector
+
+    //* Temporary data arrays (NOTE: keep them here for QA)
+    std::array<float, 3> fOrigin{0.f, 0.f, 0.f};                   ///< Coordinates of origin [cm]
+    std::vector<double> fvT0s;                                     ///< Found t0s [ns] (in event)
+    std::vector<ParticleInfo> fvParticleInfo;                      ///< PID info of tracks (in event)
+    std::vector<uint32_t> fvSelectedTrackIds;                      ///< IDs of selected tracks (in event)
+    double fSelectedT0{std::numeric_limits<double>::quiet_NaN()};  ///< A t0 value selected by the lambda-finder
+
+    /// \brief A copy of track parameters (first, last)
+    std::vector<std::pair<ca::Track::TrackParam_t, ca::Track::TrackParam_t>> fvTrackParam;
+
+    //* Auxilary variables
+
+    /// \brief An instance of the topology reconstructor
+    std::unique_ptr<KFParticleTopoReconstructor> fpTopoReconstructor{std::make_unique<KFParticleTopoReconstructor>()};
+  };
+}  // namespace cbm::algo::kfp
diff --git a/algo/kfp/KfpV0FinderChain.cxx b/algo/kfp/KfpV0FinderChain.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..96dc60a593ee94df51cfde7a736e4f1c15c541a8
--- /dev/null
+++ b/algo/kfp/KfpV0FinderChain.cxx
@@ -0,0 +1,160 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   KfpV0FinderChain.cxx
+/// \date   01.02.2025
+/// \brief  A chain for V0 finding (implementation)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#include "kfp/KfpV0FinderChain.h"
+
+#include "global/ParFiles.h"
+#include "kfp/KfpV0FinderConfig.h"
+#include "log/AlgoFairloggerCompat.h"
+#include "yaml/Yaml.h"
+
+#include <sstream>
+
+using cbm::algo::V0FinderChain;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+V0FinderChain::V0FinderChain(const std::unique_ptr<qa::Manager>& qaManager)
+  : fpFinderQa(qaManager != nullptr ? std::make_unique<kfp::V0FinderQa>(qaManager, "V0Finder") : nullptr)
+{
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0FinderChain::Finalize()
+{
+  GetMonitor();  // A hack to update the run monitor
+  L_(info) << fMonitorRun.ToString();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+cbm::algo::kfp::V0FinderMonitorData_t V0FinderChain::GetMonitor()
+{
+  cbm::algo::kfp::V0FinderMonitorData_t monitorData = fMonitorTimeslice;
+  fMonitorTimeslice.Reset();
+  fMonitorRun.AddMonitorData(monitorData);
+  return monitorData;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0FinderChain::Init()
+try {
+  L_(info) << "kfp::V0FinderChain: initializing the V0-finder chain ...";
+
+  // ----- Read configuration file
+  ParFiles parFiles(Opts().RunId());
+  auto config = yaml::ReadFromFile<kfp::V0FinderConfig>(Opts().ParamsDir() / parFiles.kfp.V0FinderConfig);
+  L_(info) << config.ToString();
+
+  //* Check the V0 type
+  if (config.reconstructPdg != 3122) {  // At the moment only Lambda analysis is possible
+    std::stringstream msg;
+    msg << "kfp::V0FinderChain: at the moment only lambda finding is possible. Provided PDG: " << config.reconstructPdg;
+    throw std::runtime_error(msg.str());
+  }
+
+  //* Define daughter particles
+  auto& particles{config.cuts.particles};
+  int iPion{-1};
+  int iProton{-1};
+  for (int iParticle = 0; iParticle < int(particles.size()); ++iParticle) {
+    const auto& particle  = particles[iParticle];
+    auto CheckOutParticle = [&](int pdg, int& iParticleToFind) {
+      if (particle.pdg == pdg) {
+        if (iParticleToFind == -1) {
+          iParticleToFind = iParticle;
+        }
+        else {
+          std::stringstream msg;
+          msg << "kfp::V0FinderChain: entry for pdg= " << pdg << " is defined more then one time in the ";
+          msg << "config.cuts.particles";
+          throw std::runtime_error(msg.str());
+        }
+      }
+    };
+    CheckOutParticle(-211, iPion);
+    CheckOutParticle(2212, iProton);
+  }
+  if (iProton == -1 || iPion == -1) {
+    std::stringstream msg;
+    msg << "kfp::V0FinderChain: config cuts/particles: either pion or proton settings are not found";
+    throw std::runtime_error(msg.str());
+  }
+  const auto& pion{particles[iPion]};
+  const auto& proton{particles[iProton]};
+
+  // ----- Define a BMON diamond
+  if (fBmonDefinedAddresses.empty()) {
+    throw std::runtime_error("kfp::V0FinderChain: BMON available addresses were not set");
+  }
+  int iBmonPartitionSelect = -1;
+  for (int iPart = 0; iPart < static_cast<int>(fBmonDefinedAddresses.size()); ++iPart) {
+    if (config.bmonAddress == fBmonDefinedAddresses[iPart]) {
+      iBmonPartitionSelect = iPart;
+      break;
+    }
+  }
+  if (iBmonPartitionSelect < 0) {
+    std::stringstream msg;
+    msg << "kfp::V0FinderChain: a reference BMON address ( " << std::hex << config.bmonAddress << std::dec
+        << " ) differs from ones, provided by hitfinder. Please check your configuration";
+    throw std::runtime_error(msg.str());
+  }
+
+  // ----- Set the V0-finder properties
+  // TODO: In future, there are will be a several instances of the V0Finder, each for a particular thread
+  {
+    //* Set particle PID properties
+    fFinder.SetBmonPartitionIndex(iBmonPartitionSelect);
+    fFinder.SetMinPionDca(pion.minDca);
+    fFinder.SetMinProtonDca(proton.minDca);
+    fFinder.SetPionVelocityRange(pion.minVelocity, pion.maxVelocity);
+    fFinder.SetProtonVelocityRange(proton.minVelocity, proton.maxVelocity);
+
+    //* Set KFParticleFinder properties
+    auto& kfpCuts{config.cuts.kfp};
+    fFinder.SetLdLCut2D(kfpCuts.minDecayLDL);
+    fFinder.SetLCut(kfpCuts.minDecayLength);
+    fFinder.SetChi2Cut2D(kfpCuts.maxChi2NdfGeo);
+    fFinder.SetChiPrimaryCut2D(kfpCuts.maxChi2NdfPrim);
+
+    //* Set other properties
+    fFinder.SetTzeroOffset(config.tZeroOffset);
+    fFinder.SetQpAssignedUncertainty(config.qpAssignedUncertainty);
+    fFinder.AddDecayToReconstructionList(config.reconstructPdg);
+    fFinder.SetPrimaryAssignedPdg(config.primaryAssignedPdg);
+
+    //* Init the V0 finder
+    fFinder.Init();
+  }
+
+  if (fpFinderQa != nullptr) {
+    fpFinderQa->Init();
+  }
+
+  L_(info) << "kfp::V0FinderChain: initializing the V0-finder chain ... done";
+}
+catch (const std::exception& err) {
+  L_(info) << "kfp::V0FinderChain: initializing the V0-finder chain ... failed. Reason: " << err.what();
+  throw std::runtime_error("initialization of V0-finder chain failed");
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+V0FinderChain::EventOutput V0FinderChain::ProcessEvent(const RecoResults& recoEvent)
+{
+  EventOutput res = fFinder.Process(recoEvent);
+  if (fpFinderQa != nullptr) {
+    fpFinderQa->Exec(recoEvent, fFinder);
+  }
+  fMonitorTimeslice.AddMonitorData(fFinder.GetEventMonitor());
+  return res;
+}
diff --git a/algo/kfp/KfpV0FinderChain.h b/algo/kfp/KfpV0FinderChain.h
new file mode 100644
index 0000000000000000000000000000000000000000..95f10ebbab5e8c6c6086e8ce00d5a1c3ad4fa2ab
--- /dev/null
+++ b/algo/kfp/KfpV0FinderChain.h
@@ -0,0 +1,81 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   KfpV0FinderChain.h
+/// \date   01.02.2025
+/// \brief  A chain for V0 finding
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "CbmEventTriggers.h"
+#include "PODVector.h"
+#include "base/SubChain.h"
+#include "global/RecoResults.h"
+#include "kfp/KfpV0Finder.h"
+#include "kfp/KfpV0FinderMonitor.h"
+#include "kfp/KfpV0FinderQa.h"
+
+namespace cbm::algo
+{
+  namespace qa
+  {
+    class Manager;
+  }
+}  // namespace cbm::algo
+
+namespace cbm::algo
+{
+  /// \class V0FinderChain
+  /// \brief A chain for the V0 finder
+  class V0FinderChain : public SubChain {
+   public:
+    using EventOutput = CbmEventTriggers;
+
+    /// \brief Default constructor
+    V0FinderChain() = default;
+
+    /// \brief Constructor from parameters
+    /// \param pQaManager   A QA-manager
+    V0FinderChain(const std::unique_ptr<qa::Manager>& qaManager);
+
+    /// \brief Copy constructor
+    V0FinderChain(const V0FinderChain&) = delete;
+
+    /// \brief Move constructor
+    V0FinderChain(V0FinderChain&&) = delete;
+
+    /// \brief Destructor
+    ~V0FinderChain() = default;
+
+    /// \brief Copy assignment operator
+    V0FinderChain& operator=(const V0FinderChain&) = delete;
+
+    /// \brief Move assignment operator
+    V0FinderChain& operator=(V0FinderChain&&) = delete;
+
+    /// \brief Finalizes the instance (called in the end of the run)
+    void Finalize();
+
+    /// \brief Gets a monitor
+    kfp::V0FinderMonitorData_t GetMonitor();
+
+    /// \brief Sets BMON diamond addresses array
+    /// \note  The addresses must be taken from bmon::Hitfind::GetDiamondAddresses()
+    void SetBmonDefinedAddresses(const PODVector<uint32_t>& addresses) { fBmonDefinedAddresses = addresses; }
+
+    /// \brief Initializes the instance (called in the beginning of the run)
+    void Init();
+
+    /// \brief  Processes an event, returns a collection of fired triggers
+    EventOutput ProcessEvent(const RecoResults& recoEvent);
+
+   private:
+    kfp::V0Finder fFinder;                                 ///< Instance of the V0-finding algorithm
+    kfp::V0FinderMonitor fMonitorRun;                      ///< Monitor per run
+    kfp::V0FinderMonitorData_t fMonitorTimeslice;          ///< Monitor per timeslice
+    std::unique_ptr<kfp::V0FinderQa> fpFinderQa{nullptr};  ///< QA module
+    PODVector<uint32_t> fBmonDefinedAddresses;             ///< Available addresses of BMON
+  };
+}  // namespace cbm::algo
diff --git a/algo/kfp/KfpV0FinderConfig.cxx b/algo/kfp/KfpV0FinderConfig.cxx
index 54e08ec3234d4fd4126cfc612e07155bc7dcebf2..d74e4d8b2ca7f53755e941dcde73030ca27fbb4c 100644
--- a/algo/kfp/KfpV0FinderConfig.cxx
+++ b/algo/kfp/KfpV0FinderConfig.cxx
@@ -9,6 +9,7 @@
 
 #include "KfpV0FinderConfig.h"
 
+#include <iomanip>
 #include <sstream>
 
 using cbm::algo::kfp::Cuts;
@@ -58,12 +59,17 @@ std::string Cuts::ToString() const
 //
 std::string V0FinderConfig::ToString() const
 {
+  using std::dec;
+  using std::hex;
+  using std::setfill;
+  using std::setw;
   std::stringstream msg;
   msg << "\n-------------- KFP V0-finder Configuration: ------------------------------------------------";
   msg << "\nGOAL V0 (PDG): " << reconstructPdg;
   msg << '\n' << cuts.ToString();
   msg << "\nOTHER PARAMETERS:";
   msg << "\n\tt0-offset:                " << tZeroOffset << " [ns]";
+  msg << "\n\tReference BMON:           0x" << hex << setw(8) << setfill('0') << bmonAddress << dec;
   msg << "\n\tq/p relative uncertainty: " << qpAssignedUncertainty;
   msg << "\n\tassigned PDG of primary:  " << primaryAssignedPdg;
   msg << "\n--------------------------------------------------------------------------------------------";
diff --git a/algo/kfp/KfpV0FinderConfig.h b/algo/kfp/KfpV0FinderConfig.h
index dacc3705d843874c33d196909dd85b2ac655294e..4bd992e0cb406145ad29495f89acfc00827b6622 100644
--- a/algo/kfp/KfpV0FinderConfig.h
+++ b/algo/kfp/KfpV0FinderConfig.h
@@ -69,6 +69,7 @@ namespace cbm::algo::kfp
   /// \brief  Configuration for the V0 finder
   struct V0FinderConfig {
     Cuts cuts;                     ///< Different selection cuts
+    uint32_t bmonAddress;  ///< Address of BMON diamond (if multiple alternative are present, only one must be selected)
     double tZeroOffset;            ///< Offset for T0 [ns]
     double qpAssignedUncertainty;  ///< Assigned relative uncertainty for q/p estimation
     int primaryAssignedPdg;        ///< Assigned PDG hypothesis for primary particles
@@ -76,6 +77,7 @@ namespace cbm::algo::kfp
 
     CBM_YAML_PROPERTIES(
       yaml::Property(&V0FinderConfig::cuts, "cuts", "Different selection cuts"),
+      yaml::Property(&V0FinderConfig::bmonAddress, "bmon_address", "Address of reference BMON diamond"),
       yaml::Property(&V0FinderConfig::tZeroOffset, "t0_offset", "The t0 offset [ns]"),
       yaml::Property(&V0FinderConfig::qpAssignedUncertainty, "qa_uncertainty", "Assigned relative uncertainty for q/p"),
       yaml::Property(&V0FinderConfig::primaryAssignedPdg, "primary_pdg", "Assigned PDG code for primary tracks"),
diff --git a/algo/kfp/KfpV0FinderMonitor.h b/algo/kfp/KfpV0FinderMonitor.h
new file mode 100644
index 0000000000000000000000000000000000000000..d60d5b135478e2e6d379a26b69e8755d9b2bd96b
--- /dev/null
+++ b/algo/kfp/KfpV0FinderMonitor.h
@@ -0,0 +1,108 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   KfpV0FinderMonitor.h
+/// \date   03.02.2025
+/// \brief  A monitor for the V0Finder
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "CaMonitor.h"
+
+namespace cbm::algo::kfp
+{
+  /// \enum  ECounter
+  /// \brief Counter keys for the V0FinderMonitor
+  enum class ECounter
+  {
+    TracksTotal,                ///< Total number of tracks
+    TracksSelected,             ///< Tracks, which satisfy topology PID applicability
+    TracksInfiniteParam,        ///< Tracks, which have infinite parameters
+    TracksWoTofHits,            ///< Tracks, which have no TOF hits
+    TracksWNegativeTofHitTime,  ///< Tracks, the last TOF hit of which has a negative time (it's time is less then the t0)
+    TracksWoStsHits,            ///< Tracks, which have no STS hits
+    TracksWoPid,                ///< Tracks, which has undefined PID
+    TracksWoMomentum,           ///< Tracks, which has no momentum
+    TracksWUnphysicalBeta,      ///< Tracks with beta > 1
+    PionsDca,                   ///< Number of raw pion-candidates
+    ProtonsDca,                 ///< Number of raw proton-candidates
+    Pions,                      ///< Number of pion-candidates
+    Protons,                    ///< Number of proton-candidates
+    EventsTotal,                ///< Total number of events
+    EventsWoTzero,              ///< Number of events with undefined t-zero
+    EventsLambdaCand,           ///< Events with at least one pion and one proton candidate
+    KfpEventsLambdaCand,        ///< Events with lambda-candidates in KF-particle
+    KfpLambdaCandidates,        ///< Number of lambda-candidates
+    END
+  };
+
+  /// \enum  ETimer
+  /// \brief Timer keys for the V0FinderMonitor
+  /* clang-format off */
+  enum class ETimer
+  {
+    ProcessEvent,           ///< Processing of a single event
+      CollectT0,            ///< Collecting T0s
+      CollectDca,           ///< Estimating DCAs
+      FindV0Candidates,     ///< V0-finder procedure for a given t0
+        PrepareContainers,  ///< Prepare data containers
+        PreselectTracks,    ///< Track preselection
+        InitKfp,            ///< Init KFParticleFinder inside the event
+        ExecKfp,            ///< Run KFParticleFinder inside the event
+    END
+  };
+  /* clang-format on */
+
+  /// \brief Specification of ca::MonitorData for the V0Finder
+  using V0FinderMonitorData_t = ca::MonitorData<ECounter, ETimer>;
+
+  /// \class Monitor
+  /// \brief A monitor for the V0Finder
+  class V0FinderMonitor : public ca::Monitor<ECounter, ETimer> {
+   public:
+    /// \brief Default constructor
+    V0FinderMonitor() : ca::Monitor<ECounter, ETimer>("V0 finder monitor")
+    {
+      SetCounterName(ECounter::TracksTotal, "all tracks");
+      SetCounterName(ECounter::TracksSelected, "pre-selected tracks");
+      SetCounterName(ECounter::TracksInfiniteParam, "tracks satisfying PID selection");
+      SetCounterName(ECounter::TracksWoTofHits, "tracks w/o TOF hits");
+      SetCounterName(ECounter::TracksWNegativeTofHitTime, "tracks w/ negative TOF time");
+      SetCounterName(ECounter::TracksWoStsHits, "tracks w/o STS hits");
+      SetCounterName(ECounter::TracksWoPid, "tracks w/o PID");
+      SetCounterName(ECounter::TracksWoMomentum, "tracks w/o momentum");
+      SetCounterName(ECounter::TracksWUnphysicalBeta, "tracks w/ beta > 1");
+      SetCounterName(ECounter::PionsDca, "raw pion candidates");
+      SetCounterName(ECounter::ProtonsDca, "raw proton candidates");
+      SetCounterName(ECounter::Pions, "pion candidates");
+      SetCounterName(ECounter::Protons, "proton candidates");
+      SetCounterName(ECounter::EventsTotal, "all events");
+      SetCounterName(ECounter::EventsWoTzero, "events w/o t0");
+      SetCounterName(ECounter::EventsLambdaCand, "events passed to KFP");
+      SetCounterName(ECounter::KfpEventsLambdaCand, "events w/ lambda candidates");
+      SetCounterName(ECounter::KfpLambdaCandidates, "lambda candidates");
+
+      SetTimerName(ETimer::ProcessEvent, "event processing");
+      SetTimerName(ETimer::CollectT0, "t0 container preparation");
+      SetTimerName(ETimer::CollectDca, "DCA container preparation");
+      SetTimerName(ETimer::FindV0Candidates, "V0-candidates finding");
+      SetTimerName(ETimer::PrepareContainers, "Container initialization");
+      SetTimerName(ETimer::PreselectTracks, "Track preselection");
+      SetTimerName(ETimer::InitKfp, "KFParticleFinder initialization");
+      SetTimerName(ETimer::ExecKfp, "KFParticleFinder execution");
+
+      SetRatioKeys({ECounter::EventsTotal, ECounter::TracksTotal, ECounter::TracksSelected});
+    }
+
+   private:
+    friend class boost::serialization::access;
+    template<typename Archive>
+    void serialize(Archive& ar, const unsigned int /*version*/)
+    {
+      ar& boost::serialization::base_object<ca::Monitor<ECounter, ETimer>>(*this);
+    }
+  };
+
+}  // namespace cbm::algo::kfp
diff --git a/algo/kfp/KfpV0FinderQa.cxx b/algo/kfp/KfpV0FinderQa.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..ffc903e2a2dcc2c12efa06b4bda11db6db29ffb9
--- /dev/null
+++ b/algo/kfp/KfpV0FinderQa.cxx
@@ -0,0 +1,100 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   KfpV0FinderQa.cxx
+/// \date   13.02.2025
+/// \brief  A V0 finding algorithm QA (implementation)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#include "kfp/KfpV0FinderQa.h"
+
+#include "global/RecoResults.h"
+#include "kfp/KfpV0Finder.h"
+
+
+using cbm::algo::kfp::V0FinderQa;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0FinderQa::Init()
+{
+  using qa::CanvasConfig;
+  using qa::H1D;
+  using qa::PadConfig;
+
+  //* Histogram initialisation
+  fvphMassLambdaCand =
+    MakeObj<H1D>("kfp_mass_lambda", "Mass of #Lambda-candidates;m [GeV/c^{2}];Counts", kMassB, kMassL, kMassU);
+  fvphMassAll    = MakeObj<H1D>("kfp_mass_all", "Mass of particles;m [GeV/c^{2}];Counts", 300, 0., 1.5);
+  fvphDcaAll     = MakeObj<H1D>("kfp_dca_all", "DCA of tracks to origin;DCA [cm];Counts", kDcaB, kDcaL, kDcaU);
+  fvphBetaAll    = MakeObj<H1D>("kfp_beta_all", "Speed of tracks;#beta;Counts", kBetaB, kBetaL, kBetaU);
+  fvphBetaPion   = MakeObj<H1D>("kfp_beta_pion", "Speed of #pi-candidates;#beta;Counts", kBetaB, kBetaL, kBetaU);
+  fvphBetaProton = MakeObj<H1D>("kfp_beta_proton", "Speed of proton-candidates;#beta;Counts", kBetaB, kBetaL, kBetaU);
+  fvphMomAll     = MakeObj<H1D>("kfp_mom_all", "Momentum of tracks;p [GeV/c];Counts", kBetaB, kBetaL, kBetaU);
+  fvphMomPion    = MakeObj<H1D>("kfp_mom_pion", "Momentum of #pi-candidates;p [GeV/c];Counts", kBetaB, kBetaL, kBetaU);
+  fvphMomProton =
+    MakeObj<H1D>("kfp_mom_proton", "Momentum of proton-candidates;p [GeV/c];Counts", kBetaB, kBetaL, kBetaU);
+
+  //* Canvas initialisation
+  auto canv = CanvasConfig("kfp_lambda", "Lambda-trigger summary QA", 3, 2);
+  canv.AddPadConfig(PadConfig(fvphMassLambdaCand, "hist"));  // (0, 0)
+  canv.AddPadConfig(PadConfig(fvphDcaAll, "hist"));          // (0, 1)
+  canv.AddPadConfig(PadConfig(fvphBetaAll, "hist"));
+  canv.AddPadConfig(PadConfig(fvphBetaPion, "hist"));
+  canv.AddPadConfig(PadConfig(fvphBetaProton, "hist"));
+  AddCanvasConfig(canv);
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0FinderQa::Exec(const RecoResults& recoEvent, const V0Finder& v0Finder)
+{
+  //* Fill track distributions
+  const auto& tracks{recoEvent.tracks};
+  if (v0Finder.GetT0s().size() == 1) {
+    for (uint32_t iTrk = 0; iTrk < tracks.size(); ++iTrk) {
+      const auto& particleInfo{v0Finder.GetParticleInfo()[iTrk]};
+      const auto& trkParFst{(v0Finder.GetTrackAssignedParams()[iTrk]).first};
+      bool bPdgDefined = (particleInfo.fPdg != V0Finder::kUndefPdg);
+      fvphDcaAll->Fill(bPdgDefined ? particleInfo.fDca : -999);
+      fvphBetaAll->Fill(particleInfo.fBeta);
+      if (bPdgDefined) {
+        if (particleInfo.fPdg == -211) {
+          fvphBetaPion->Fill(particleInfo.fBeta);
+          fvphMomPion->Fill(trkParFst.GetP());
+        }
+        else if (particleInfo.fPdg == 2212) {
+          fvphBetaProton->Fill(particleInfo.fBeta);
+          fvphMomProton->Fill(trkParFst.GetP());
+        }
+      }
+    }
+  }
+  else {
+    for (uint32_t iTrk = 0; iTrk < tracks.size(); ++iTrk) {
+      const auto& particleInfo{v0Finder.GetParticleInfo()[iTrk]};
+      bool bPdgDefined = (particleInfo.fPdg != V0Finder::kUndefPdg);
+      fvphDcaAll->Fill(bPdgDefined ? particleInfo.fDca : -999);
+      fvphBetaAll->Fill(-999);
+      if (bPdgDefined) {
+        if (particleInfo.fPdg == -211) {
+          fvphBetaPion->Fill(-999);
+          fvphMomPion->Fill(-999);
+        }
+        else if (particleInfo.fPdg == 2212) {
+          fvphBetaProton->Fill(-999);
+        }
+      }
+    }
+  }
+
+  //* Fill particle distributions
+  const auto& particles = v0Finder.GetTopoReconstructor()->GetParticles();
+  for (const auto& particle : particles) {
+    fvphMassAll->Fill(particle.GetMass());
+    if (particle.GetPDG() == 3122) {
+      fvphMassLambdaCand->Fill(particle.GetMass());
+    }
+  }
+}
diff --git a/algo/kfp/KfpV0FinderQa.h b/algo/kfp/KfpV0FinderQa.h
new file mode 100644
index 0000000000000000000000000000000000000000..35b914e182bf5e1890a3ce2bd0f10b6598f40e63
--- /dev/null
+++ b/algo/kfp/KfpV0FinderQa.h
@@ -0,0 +1,84 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   KfpV0FinderQa.h
+/// \date   13.02.2025
+/// \brief  A V0 finding algorithm QA
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "qa/QaTaskHeader.h"
+
+namespace cbm::algo
+{
+  class RecoResults;
+
+  namespace qa
+  {
+    class H1D;
+    class H2D;
+  }  // namespace qa
+
+  namespace kfp
+  {
+    class V0Finder;
+  }
+}  // namespace cbm::algo
+
+namespace cbm::algo::kfp
+{
+  /// \class V0FinderQa
+  /// \brief A QA-task for the V0-finding algorithm
+  class V0FinderQa : public qa::TaskHeader {
+   public:
+    /// \brief Constructor
+    /// \param pManager  Pointer to the QA manager
+    /// \param name      Name of the QA
+    V0FinderQa(const std::unique_ptr<qa::Manager>& pManager, std::string_view name) : qa::TaskHeader(pManager, name) {}
+
+    /// \brief Copy constructor
+    V0FinderQa(const V0FinderQa&) = delete;
+
+    /// \brief Move constructor
+    V0FinderQa(V0FinderQa&&) = delete;
+
+    /// \brief Copy assignment operator
+    V0FinderQa& operator=(const V0FinderQa&) = delete;
+
+    /// \brief Move assignment operator
+    V0FinderQa& operator=(V0FinderQa&&) = delete;
+
+    /// \brief Executes the task, fills the histograms
+    /// \param recoEvent  A reconstructed event instance
+    /// \param v0Finder   A V0-finder instance
+    void Exec(const RecoResults& recoEvent, const V0Finder& v0Finder);
+
+    /// \brief Initialized the task
+    void Init();
+
+   private:
+    //* Constants
+    static constexpr int kMassB    = 200;   ///< Lambda-candidate mass: number of bins
+    static constexpr double kMassL = 1.08;  ///< Lambda-candidate mass: lower bound [GeV/c2]
+    static constexpr double kMassU = 1.18;  ///< Lambda-candidate mass: upper bound [GeV/c2]
+    static constexpr int kDcaB     = 240;   ///< DCA to origin: number of bins
+    static constexpr double kDcaL  = 0.;    ///< DCA to origin: lower bound [cm]
+    static constexpr double kDcaU  = 12.;   ///< DCA to origin: upper bound [cm]
+    static constexpr int kBetaB    = 240;   ///< Speed of particle: number of bins
+    static constexpr double kBetaL = 0.;    ///< Speed of particle: lower bound [c]
+    static constexpr double kBetaU = 1.2;   ///< Speed of particle: upper bound [c]
+
+    //* Histograms
+    qa::H1D* fvphMassLambdaCand{nullptr};  ///< Mass of Lambda-candidates
+    qa::H1D* fvphMassAll{nullptr};         ///< Mass of all particles in the topology
+    qa::H1D* fvphDcaAll{nullptr};          ///< DCA of particles to origin
+    qa::H1D* fvphBetaAll{nullptr};         ///< Speed of all particles
+    qa::H1D* fvphBetaPion{nullptr};        ///< Speed of pion-candidates
+    qa::H1D* fvphBetaProton{nullptr};      ///< Speed of proton-candidates
+    qa::H1D* fvphMomAll{nullptr};          ///< Speed of all particles
+    qa::H1D* fvphMomPion{nullptr};         ///< Speed of pion-candidates
+    qa::H1D* fvphMomProton{nullptr};       ///< Speed of proton-candidates
+  };
+}  // namespace cbm::algo::kfp
diff --git a/algo/kfp/interface/CMakeLists.txt b/algo/kfp/interface/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4beacabb176c8fdd9f21d310a7ffde164c2c6bd5
--- /dev/null
+++ b/algo/kfp/interface/CMakeLists.txt
@@ -0,0 +1,38 @@
+if(NOT CBM_ONLINE_STANDALONE)
+  ### CbmKFParticleOnlineInterface
+  # Just reusing the KFParticle library, compiled in the external
+  add_library(CbmKFParticleOnlineInterface INTERFACE)
+  target_include_directories(CbmKFParticleOnlineInterface INTERFACE)
+  target_compile_definitions(CbmKFParticleOnlineInterface 
+    INTERFACE DO_TPCCATRACKER_EFF_PERFORMANCE NonhomogeneousField CBM USE_TIMERS)
+  target_link_libraries(CbmKFParticleOnlineInterface INTERFACE KFParticle)
+  install(TARGETS CbmKFParticleOnlineInterface DESTINATION lib)
+else()
+  # Creating a replacement of the KFParticle library for a standalone mode
+  set(KFP_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../external/KFParticle/KFParticle)
+
+  set(SRCS
+    ${KFP_SOURCE_DIR}/KFParticle.cxx
+    ${KFP_SOURCE_DIR}/KFParticleBase.cxx
+    ${KFP_SOURCE_DIR}/KFParticleBaseSIMD.cxx
+    ${KFP_SOURCE_DIR}/KFParticleDatabase.cxx
+    ${KFP_SOURCE_DIR}/KFParticleFinder.cxx
+    ${KFP_SOURCE_DIR}/KFParticlePVReconstructor.cxx
+    ${KFP_SOURCE_DIR}/KFParticleSIMD.cxx
+    ${KFP_SOURCE_DIR}/KFParticleTopoReconstructor.cxx
+    ${KFP_SOURCE_DIR}/KFPEmcCluster.cxx
+    ${KFP_SOURCE_DIR}/KFPTrack.cxx
+    ${KFP_SOURCE_DIR}/KFPTrackVector.cxx
+    ${KFP_SOURCE_DIR}/KFPVertex.cxx
+    ${KFP_SOURCE_DIR}/KFVertex.cxx
+  )
+  
+  add_library(CbmKFParticleOnlineInterface SHARED ${SRCS})
+  target_include_directories(CbmKFParticleOnlineInterface PUBLIC ${KFP_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
+  target_compile_definitions(CbmKFParticleOnlineInterface PUBLIC NonhomogeneousField CBM CBM_ONLINE)
+  target_link_libraries(CbmKFParticleOnlineInterface 
+                 PUBLIC Vc::Vc 
+                        ROOT::Core
+                        )
+  install(TARGETS CbmKFParticleOnlineInterface DESTINATION lib)
+endif()
diff --git a/algo/kfp/interface/RootTypesDef.h b/algo/kfp/interface/RootTypesDef.h
new file mode 100644
index 0000000000000000000000000000000000000000..e6eaa5500553524aaf4192d2a17108cd81b30da8
--- /dev/null
+++ b/algo/kfp/interface/RootTypesDef.h
@@ -0,0 +1,18 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   RootTypesDef.h
+/// \date   11.02.2025
+/// \brief  A compatibility header for the KFParticle code
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#if __has_include(<RtypesCore.h>)
+#include <RtypesCore.h>
+#else
+using Bool_t  = bool;
+using Int_t   = int;
+using Float_t = float;
+#endif
diff --git a/algo/qa/QaData.cxx b/algo/qa/QaData.cxx
index 520ed4e522e022e742179e2ecb4c5f564b95da4d..6c42b3a5237df1cad00eacd1d9b9a579cf661f6d 100644
--- a/algo/qa/QaData.cxx
+++ b/algo/qa/QaData.cxx
@@ -57,6 +57,7 @@ try {
               << " with inconsistent flags (see HistogramMetadata::CheckFlags for detailes)";
           throw std::runtime_error(msg.str());
         }
+        L_(info) << " - task: " << task.fsName << ", histogram: " << h.GetName();
         vHistCfgs.emplace_back(h.GetName() + "!" + h.GetMetadataString(), task.fsName);
       };
       fsTaskNames += fmt::format("{} ", task.fsName);
diff --git a/algo/qa/hitfind/BmonHitfindQa.h b/algo/qa/hitfind/BmonHitfindQa.h
index 020f62fa821a2c8ec298c8aa758684aee023cc21..b2a8b3e30e709d099ef1059aaa5a619864d02c54 100644
--- a/algo/qa/hitfind/BmonHitfindQa.h
+++ b/algo/qa/hitfind/BmonHitfindQa.h
@@ -34,8 +34,6 @@ namespace cbm::algo::bmon
 {
   /// \class HitfindQa
   /// \brief A QA module for the BMON hit-finder
-  /// \param pManager  Pointer to the QA manager
-  /// \param name      Name of the QA (directory)
   class HitfindQa : public qa::TaskHeader {
    public:
     /// \brief Constructor
@@ -44,7 +42,6 @@ namespace cbm::algo::bmon
     HitfindQa(const std::unique_ptr<qa::Manager>& pManager, std::string_view name) : qa::TaskHeader(pManager, name) {}
 
     /// \brief Constructor from the configuration object
-    /// \param config  QA configuration object
     HitfindQa() = default;
 
     /// \brief Copy constructor
diff --git a/core/data/CbmEventTriggers.h b/core/data/CbmEventTriggers.h
index ce1b8de535825650cfb3e6cbf77661bbf44c36c5..a2c252df56bab630e79c7e776830b70e0fbbcd99 100644
--- a/core/data/CbmEventTriggers.h
+++ b/core/data/CbmEventTriggers.h
@@ -9,28 +9,37 @@
 
 #pragma once
 
+#include <cstdint>
+#include <string>
+
 #if !defined(NO_ROOT) && !XPU_IS_HIP_CUDA
 #include <Rtypes.h>  // for ClassDef
 #endif
 
+#include <boost/serialization/access.hpp>
+
 /// \class CbmEventTriggers
 /// \brief Class to store different triggers for a given event
 class CbmEventTriggers {
  public:
   /// \enum   ETrigger
-  /// \brief  Defines a trigger address
-  enum class ETrigger : uint8_t
+  /// \brief  Defines a trigger bitmask
+  enum class ETrigger : uint32_t
   {
-    Lambda = 0b00000001,  ///< Lambda-trigger
-    Ks     = 0b00000010   ///< Ks-trigger
+    Lambda = 0x00000001,  ///< Lambda-trigger
+    Ks     = 0x00000002   ///< Ks-trigger
   };
 
   //using Trigger_t = std::underlying_type_t<ETrigger>;
-  using Trigger_t = uint8_t;
+  using Trigger_t = uint32_t;
 
   /// \brief Default constructor
   CbmEventTriggers() = default;
 
+  /// \brief A constructor from integer
+  /// \param bitmap  A bitmap of the triggers
+  explicit CbmEventTriggers(uint32_t bitmap) { fBitmap = bitmap; }
+
   /// \brief Copy constructor
   CbmEventTriggers(const CbmEventTriggers&) = default;
 
@@ -46,33 +55,47 @@ class CbmEventTriggers {
   /// \brief Move assignment operator
   CbmEventTriggers& operator=(CbmEventTriggers&&) = default;
 
+  /// \brief Gets a bitmap
+  /// \return bitmap  (integer)
+  Trigger_t GetBitmap() const { return fBitmap; }
+
   /// \brief Sets a trigger
   /// \param key  Trigger key
-  void Set(ETrigger key) { fTriggers |= static_cast<Trigger_t>(key); }
+  void Set(ETrigger key) { fBitmap |= static_cast<Trigger_t>(key); }
 
   /// \brief Resets a trigger
   /// \param key  Trigger key
-  void Reset(ETrigger key) { fTriggers &= ~static_cast<Trigger_t>(key); }
+  void Reset(ETrigger key) { fBitmap &= ~static_cast<Trigger_t>(key); }
+
+  /// \brief Resets all the triggers
+  void ResetAll() { fBitmap = 0; }
 
   /// \brief Tests a particular single trigger
   /// \param key  Trigger key
-  bool Test(ETrigger key) const { return static_cast<bool>(fTriggers & static_cast<Trigger_t>(key)); }
+  bool Test(ETrigger key) const { return static_cast<bool>(fBitmap & static_cast<Trigger_t>(key)); }
 
-  /// \brief Tests, if ALL the triggers in the mask are on
-  /// \param mask  Trigger mask
-  bool TestAll(Trigger_t mask) const { return mask == (fTriggers & mask); }
+  /// \brief Tests, if ALL the triggers in the bitmask are on
+  /// \param bitmask  Trigger bitmask
+  bool TestAll(Trigger_t bitmask) const { return bitmask == (fBitmap & bitmask); }
 
-  /// \brief Tests, if ANY of the triggers in the mask are on
-  /// \param mask  Trigger mask
-  bool TestAny(Trigger_t mask) const { return static_cast<bool>(fTriggers | mask); }
+  /// \brief Tests, if ANY of the triggers in the bitmask are on
+  /// \param bitmask  Trigger bitmask
+  bool TestAny(Trigger_t bitmask) const { return static_cast<bool>(fBitmap | bitmask); }
 
   /// \brief String representation of the class content
   std::string ToString() const;
 
  private:
-  Trigger_t fTriggers;
+  Trigger_t fBitmap{0};  ///< bitmap storing the triggers according to ETrigger
+
+  friend class boost::serialization::access;
+  template<class Archive>
+  void serialize(Archive& ar, const unsigned int /*version*/)
+  {
+    ar& fBitmap;
+  }
 
 #if !defined(NO_ROOT) && !XPU_IS_HIP_CUDA
-  ClassDefNV(CbmEventTriggers, 1);
+  ClassDefNV(CbmEventTriggers, 2);
 #endif
 };
diff --git a/core/data/global/CbmDigiEvent.h b/core/data/global/CbmDigiEvent.h
index 242740e57133ed58695947df108315761048f528..ec90985bbd4561a9c2a9ccfdef496caaa32b7ee7 100644
--- a/core/data/global/CbmDigiEvent.h
+++ b/core/data/global/CbmDigiEvent.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2021-22 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+/* Copyright (C) 2021-25 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
    SPDX-License-Identifier: GPL-3.0-only
    Authors: Volker Friese [committer] */
 
@@ -6,6 +6,7 @@
 #define CBMDIGIEVENT_H 1
 
 #include "CbmDigiData.h"
+#include "CbmEventTriggers.h"
 
 #include <boost/serialization/access.hpp>
 
@@ -14,29 +15,32 @@
  ** @brief Collection of digis from all detector systems within one event
  ** @author Volker Friese <v.friese@gsi.de>
  ** @since 7.12.2022
- ** @version 2.0
+ ** @version 3.0
  **/
 class CbmDigiEvent {
 
 public:
-  CbmDigiData fData;  ///< Event data
-  uint64_t fNumber;   ///< Event identifier
-  double fTime;       ///< Event trigger time [ns]
-
-
-  friend class boost::serialization::access;
-  /** @brief BOOST serializer**/
-  template<class Archive>
-  void serialize(Archive& ar, const unsigned int /*version*/)
-  {
-    ar& fData;
-    ar& fNumber;
-    ar& fTime;
-  }
+ CbmDigiData fData;                    ///< Event data
+ uint64_t fNumber;                     ///< Event identifier
+ double fTime;                         ///< Event trigger time [ns]
+ CbmEventTriggers fSelectionTriggers;  ///< Event selection triggers
+
+ friend class boost::serialization::access;
+ /** @brief BOOST serializer**/
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int version)
+ {
+   ar& fData;
+   ar& fNumber;
+   ar& fTime;
+   if (version >= 3) {
+     ar& fSelectionTriggers;
+   }
+ }
 
   // --- ROOT serializer
 #ifndef NO_ROOT
-  ClassDefNV(CbmDigiEvent, 2);
+ ClassDefNV(CbmDigiEvent, 3);
 #endif
 
   /** @brief Clear content **/
@@ -45,6 +49,7 @@ public:
     fData.Clear();
     fNumber = 0;
     fTime   = 0.;
+    fSelectionTriggers.ResetAll();
   }
 };
 
diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt
index d0843683605e1ffeec46e52a02158a289405d0ab..9328a585c1d7b5ed3640fea9f1126a30a34820bd 100644
--- a/external/CMakeLists.txt
+++ b/external/CMakeLists.txt
@@ -70,6 +70,7 @@ if(DOWNLOAD_EXTERNALS)
   Include(InstallYamlCpp.cmake)
   Include(InstallPoolSTL.cmake)
 
+
   if (NOT CBM_ONLINE_STANDALONE) # Not required for online standalone
 
     Include(InstallKFParticle.cmake)
@@ -86,8 +87,22 @@ if(DOWNLOAD_EXTERNALS)
       Include(InstallQa.cmake)
     endif()
 
+  else()
+
+    # Download KFParticle source from the repository, but do not install it
+    download_project_if_needed(PROJECT kfparticle_source
+      GIT_REPOSITORY "https://github.com/szharko/KFParticle.git"
+      GIT_TAG "d088e3e019ac588a0528aa560ceaca247580d881" # CBM_ONLINE directive to turn off dependencies on ROOT
+      SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/KFParticle
+      CONFIGURE_COMMAND ""
+      BUILD_COMMAND ""
+      INSTALL_COMMAND ""
+    )
+  
   endif()
 
+  
+
 else()
   # Define targets which are needed by CbmRoot but are not available
   # whithout the external packages
diff --git a/external/InstallParameter.cmake b/external/InstallParameter.cmake
index 77e3f424dc53079799a63e8be1dd5d1b94720f0d..7d02e04add0d89dfae88aea1d0ef2465fa3d1ee3 100644
--- a/external/InstallParameter.cmake
+++ b/external/InstallParameter.cmake
@@ -1,5 +1,7 @@
 set(PARAMETER_VERSION cf6a880a56a8695b485efe358231270e9179cb82) # 2025/02/15
 set(PARAMETER_SRC_URL "https://git.cbm.gsi.de/CbmSoft/cbmroot_parameter.git")
+#set(PARAMETER_VERSION 96b2189ac86b3d49c32f63da76e68f66544bba55) # 2025/05/02, BMON hitfinder parameters for online
+#set(PARAMETER_SRC_URL "https://git.cbm.gsi.de/s.zharko/cbmroot_parameter.git")
 
 download_project_if_needed(PROJECT         Parameter_source
                            GIT_REPOSITORY  ${PARAMETER_SRC_URL}
diff --git a/macro/KF/configs/mcbm_kfpf_lambda.yaml b/macro/KF/configs/mcbm_kfpf_lambda.yaml
index 01e77b1855b37c5f3f763f6f1175faf7ff89af12..fc5599d3723479b6fc379a60f56e3d0d5983131c 100644
--- a/macro/KF/configs/mcbm_kfpf_lambda.yaml
+++ b/macro/KF/configs/mcbm_kfpf_lambda.yaml
@@ -7,11 +7,11 @@
 # \brief  Standard configuration for the lambda finding in mCBM
 # \author Sergei Zharko <s.zharko@gsi.de>
 
-reconstruct_pdg: 3122  # Lambda -> pi- p
-
-t0_offset: 0.12      # Time offset for T0 [ns]
-qa_uncertainty: 0.1  # Assigned relative uncertainty for the q/p estimation
-primary_pdg: 321     # Assigned PDG code for primary tracks
+reconstruct_pdg: 3122     # Lambda -> pi- p
+bmon_address: 0x00202806  # Address of reference BMON diamond
+t0_offset: 0.12           # Time offset for T0 [ns]
+qa_uncertainty: 0.1       # Assigned relative uncertainty for the q/p estimation
+primary_pdg: 321          # Assigned PDG code for primary tracks
 
 cuts:  # Different cuts
   kfp:  # KFParticleFinder specific cuts
diff --git a/services/histserv/app/Application.cxx b/services/histserv/app/Application.cxx
index 5eb5362f051fc3f34fc1108e50bd17e1adf6e4f2..a4978c303efe8ae9925b2a65f017b8303c796a37 100644
--- a/services/histserv/app/Application.cxx
+++ b/services/histserv/app/Application.cxx
@@ -800,17 +800,21 @@ bool Application::PrepareCanvas(uint32_t uCanvIdx)
       if ("nullptr" != sName) {
         TObject* pObj = fArrayHisto[FindHistogram(sName)];
 
-        if (nullptr != dynamic_cast<TProfile2D*>(pObj)) {
-          dynamic_cast<TProfile2D*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
+        if (auto* pHistP2 = dynamic_cast<TProfile2D*>(pObj)) {
+          pHistP2->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
         }  // if( nullptr != dynamic_cast< TProfile *>( pObj ) )
-        else if (nullptr != dynamic_cast<TProfile*>(pObj)) {
-          dynamic_cast<TProfile*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
+        else if (auto* pHistP1 = dynamic_cast<TProfile*>(pObj)) {
+          pHistP1->SetLineColor(uObjIdx + 1);
+          pHistP1->SetMarkerColor(uObjIdx + 1);
+          pHistP1->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
         }  // if( nullptr != dynamic_cast< TProfile *>( pObj ) )
-        else if (nullptr != dynamic_cast<TH2*>(pObj)) {
-          dynamic_cast<TH2*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
+        else if (auto* pHistH2 = dynamic_cast<TH2*>(pObj)) {
+          pHistH2->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
         }  // if( nullptr != dynamic_cast< TH2 *>( pObj ) )
-        else if (nullptr != dynamic_cast<TH1*>(pObj)) {
-          dynamic_cast<TH1*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
+        else if (auto* pHistH1 = dynamic_cast<TH1*>(pObj)) {
+          pHistH1->SetLineColor(uObjIdx + 1);
+          pHistH1->SetMarkerColor(uObjIdx + 1);
+          pHistH1->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
         }  // if( nullptr != dynamic_cast< TH1 *>( pObj ) )
         else
           LOG(warning) << "  Unsupported object type for " << sName << " when preparing canvas " << conf.GetName();