diff --git a/algo/base/Definitions.h b/algo/base/Definitions.h
index a6f3d35505f2992c9b7f4c7c10a3d2086a684f6f..57e93adc48b6fd87b334df8f68a52a39ffc2044b 100644
--- a/algo/base/Definitions.h
+++ b/algo/base/Definitions.h
@@ -47,6 +47,12 @@ namespace cbm::algo
     Track,
   };
 
+  enum class Setup
+  {
+    mCBM2022,
+    mCBM2024,
+  };
+
 }  // namespace cbm::algo
 
 CBM_ENUM_DICT(fles::Subsystem,
@@ -87,4 +93,9 @@ CBM_ENUM_DICT(cbm::algo::RecoData,
   {"Track", RecoData::Track}
 );
 
+CBM_ENUM_DICT(cbm::algo::Setup,
+  {"mCBM2022", cbm::algo::Setup::mCBM2022},
+  {"mCBM2024", cbm::algo::Setup::mCBM2024}
+);
+
 #endif
diff --git a/core/detectors/sts/CbmStsParSetSensor.cxx b/core/detectors/sts/CbmStsParSetSensor.cxx
index 71d67d3913091388e3385ab54792ce5f180065f7..2e64b3308e3f32fe281883ccab30ea7e5faad3bc 100644
--- a/core/detectors/sts/CbmStsParSetSensor.cxx
+++ b/core/detectors/sts/CbmStsParSetSensor.cxx
@@ -54,7 +54,8 @@ Bool_t CbmStsParSetSensor::getParams(FairParamList*)
 const CbmStsParSensor& CbmStsParSetSensor::GetParSensor(UInt_t address)
 {
   if (fUseGlobal) return fGlobalParams;
-  assert(fParams.count(address));
+  LOG_IF(fatal, fParams.count(address) == 0)
+    << GetName() << ": Parameters for sensor address " << std::hex << address << std::dec << " not found!";
   return fParams[address];
 }
 // --------------------------------------------------------------------------
@@ -78,11 +79,15 @@ void CbmStsParSetSensor::SetParSensor(UInt_t address, const CbmStsParSensor& par
 std::string CbmStsParSetSensor::ToString() const
 {
   std::stringstream ss;
-  if (fUseGlobal) ss << "(Global) " << fGlobalParams.ToString();
+  if (fUseGlobal)
+    ss << "(Global) " << fGlobalParams.ToString();
   else {
-    if (fParams.empty()) ss << "Empty";
+    if (fParams.empty())
+      ss << "Empty";
     else
-      ss << "Parameters for " << fParams.size() << " sensors";
+      ss << "Parameters for " << fParams.size() << " sensors:\n";
+    for (const auto& [address, par] : fParams)
+      ss << address << "\n";
   }
   return ss.str();
 }
diff --git a/reco/steer/CMakeLists.txt b/reco/steer/CMakeLists.txt
index 440086429ffe8dc62ffd1219d5841dfafc383ece..98b6c7b43a7275a893069e05b63ef7d893a3e0ba 100644
--- a/reco/steer/CMakeLists.txt
+++ b/reco/steer/CMakeLists.txt
@@ -11,6 +11,7 @@ set(SRCS
   CbmSourceDigiTimeslice.cxx
   CbmSourceDigiEvents.cxx
   CbmSourceTsArchive.cxx
+  CbmOnlineParWrite.cxx
   )
 
 
@@ -18,6 +19,8 @@ set(LIBRARY_NAME CbmRecoSteer)
 set(LINKDEF ${LIBRARY_NAME}LinkDef.h)
 set(PUBLIC_DEPENDENCIES
   CbmData
+  CbmSimSteer
+  CbmRecoTasks
   FairRoot::Base
   ROOT::Core
   ROOT::Hist
diff --git a/reco/steer/CbmOnlineParWrite.cxx b/reco/steer/CbmOnlineParWrite.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..b4d01d7238c6a3314c76bb29e03bd1a3a10b8c1c
--- /dev/null
+++ b/reco/steer/CbmOnlineParWrite.cxx
@@ -0,0 +1,346 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+
+#include "CbmOnlineParWrite.h"
+
+#include "CbmMcbmUtils.h"
+#include "CbmSetup.h"
+#include "CbmSourceDummy.h"
+#include "CbmStsAddress.h"
+#include "CbmStsParAsic.h"
+#include "CbmStsParModule.h"
+#include "CbmStsParSensor.h"
+#include "CbmStsParSensorCond.h"
+#include "CbmStsParSetSensor.h"
+#include "CbmStsSetup.h"
+#include "CbmTaskStsHitFinderParWrite.h"
+#include "CbmTaskTofClusterizerParWrite.h"
+#include "CbmTaskTrdHitFinderParWrite.h"
+
+#include <FairParAsciiFileIo.h>
+#include <FairParRootFileIo.h>
+#include <FairRootFileSink.h>
+#include <FairRunAna.h>
+#include <FairRuntimeDb.h>
+
+#include <TGeoManager.h>
+#include <TObjString.h>
+#include <TStopwatch.h>
+
+#include <iostream>
+
+using namespace cbm::algo;
+
+void CbmOnlineParWrite::AddDetectors()
+{
+  // Add detectors here
+  AddTrd();
+  AddTof();
+  AddSts();
+}
+
+// ===========================================================================
+// TRD setup
+// ===========================================================================
+void CbmOnlineParWrite::AddTrd()
+{
+  // Copied from macro/beamtime/mcbm2022/trd_hitfinder_run.C
+  fSetup->SetActive(ECbmModuleId::kTrd, kTRUE);
+  fSetup->SetActive(ECbmModuleId::kTrd2d, kTRUE);
+
+  // ----- TRD digitisation parameters -------------------------------------
+  TString geoTagTrd;
+  if (fSetup->IsActive(ECbmModuleId::kTrd)) {
+    if (fSetup->GetGeoTag(ECbmModuleId::kTrd, geoTagTrd)) {
+      TString paramFilesTrd(Form("%s/parameters/trd/trd_%s", fSrcDir.Data(), geoTagTrd.Data()));
+      std::vector<TString> paramFilesVecTrd = {"asic", "digi", "gas", "gain"};
+      for (auto parIt : paramFilesVecTrd) {
+        fParList->Add(new TObjString(Form("%s.%s.par", paramFilesTrd.Data(), parIt.Data())));
+      }
+    }
+    for (auto parFileVecIt : *fParList) {
+      std::cout << Form("TrdParams - %s - added to parameter file list", parFileVecIt->GetName()) << std::endl;
+    }
+  }
+
+  // ----- TRD task ---------------------------------------------------------
+  auto* trdHitfinderPar = new CbmTaskTrdHitFinderParWrite();
+  fRun->AddTask(trdHitfinderPar);
+}
+
+// ===========================================================================
+// TOF setup
+// ===========================================================================
+void CbmOnlineParWrite::AddTof()
+{
+  // Copied from macro/tools/tof_hitfinder_run.C
+  fSetup->SetActive(ECbmModuleId::kTof, kTRUE);
+
+  TString geoTag;
+  if (fSetup->IsActive(ECbmModuleId::kTof)) {
+    fSetup->GetGeoTag(ECbmModuleId::kTof, geoTag);
+    TObjString* tofBdfFile = new TObjString(fSrcDir + "/parameters/tof/tof_" + geoTag + ".digibdf.par");
+    fParList->Add(tofBdfFile);
+    std::cout << "-I- TOF: Using parameter file " << tofBdfFile->GetString() << std::endl;
+  }
+
+  // -----   TOF defaults ------------------------
+  Int_t calMode      = 93;
+  Int_t calSel       = 1;
+  Double_t dDeadtime = 50.;
+
+  TString TofFileFolder = fSrcDir + "/parameters/mcbm/";
+  TString cCalId        = "490.100.5.0";
+  Int_t iCalSet         = 30040500;  // calibration settings
+
+  switch (fSetupType) {
+    case Setup::mCBM2022:
+      cCalId  = "2391.5.000";
+      iCalSet = 22002500;
+      break;
+    case Setup::mCBM2024:
+      cCalId  = "2912.1";
+      iCalSet = 012032500;
+      break;
+    default: throw std::runtime_error("TOF: Unknown setup type");
+  }
+
+  TString cFname =
+    Form("%s/%s_set%09d_%02d_%01dtofClust.hst.root", TofFileFolder.Data(), cCalId.Data(), iCalSet, calMode, calSel);
+
+  auto* tofCluster = new CbmTaskTofClusterizerParWrite("Task TOF Clusterizer", 0, 1);
+  tofCluster->SetCalParFileName(cFname);
+  tofCluster->SetCalMode(calMode);
+  tofCluster->SetTotMax(20.);                 // Tot upper limit for walk corection
+  tofCluster->SetTotMin(0.);                  //(12000.);  // Tot lower limit for walk correction
+  tofCluster->SetTotMean(5.);                 // Tot calibration target value in ns
+  tofCluster->SetMaxTimeDist(1.0);            // default cluster range in ns
+  tofCluster->SetChannelDeadtime(dDeadtime);  // artificial deadtime in ns
+  tofCluster->PosYMaxScal(0.75);              //in % of length
+  fRun->AddTask(tofCluster);
+}
+
+// ===========================================================================
+// STS setup
+// ===========================================================================
+void CbmOnlineParWrite::AddSts()
+{
+  // Copied from macro/beamtime/mcbm2022/mcbm_reco.C
+
+  auto* recoSts = new CbmTaskStsHitFinderParWrite{};
+  // recoSts->SetMode(ECbmRecoMode::EventByEvent);
+
+  // recoSts->SetTimeCutDigisAbs(20.0);     // cluster finder: time cut in ns
+  // recoSts->SetTimeCutClustersAbs(20.0);  // hit finder: time cut in ns
+
+  // Sensor params
+  CbmStsParSensor sensor6cm(CbmStsSensorClass::kDssdStereo);
+  sensor6cm.SetPar(0, 6.2092);  // Extension in x
+  sensor6cm.SetPar(1, 6.2);     // Extension in y
+  sensor6cm.SetPar(2, 0.03);    // Extension in z
+  sensor6cm.SetPar(3, 5.9692);  // Active size in y
+  sensor6cm.SetPar(4, 1024.);   // Number of strips front side
+  sensor6cm.SetPar(5, 1024.);   // Number of strips back side
+  sensor6cm.SetPar(6, 0.0058);  // Strip pitch front side
+  sensor6cm.SetPar(7, 0.0058);  // Strip pitch back side
+  sensor6cm.SetPar(8, 0.0);     // Stereo angle front side
+  sensor6cm.SetPar(9, 7.5);     // Stereo angle back side
+
+  CbmStsParSensor sensor12cm(sensor6cm);  // copy all parameters, change then only the y size
+  sensor12cm.SetPar(1, 12.4);             // Extension in y
+  sensor12cm.SetPar(3, 12.1692);          // Active size in y
+
+  // --- Addresses for sensors
+  // --- They are defined in each station as sensor 1, module 1, halfladderD (2), ladder 1
+  //  Int_t GetAddress(UInt_t unit = 0, UInt_t ladder = 0, UInt_t halfladder = 0, UInt_t module = 0, UInt_t sensor = 0,
+  //                   UInt_t side = 0, UInt_t version = kCurrentVersion);
+
+  // --- Now we can define the sensor parameter set and tell recoSts to use it
+  auto sensorParSet = new CbmStsParSetSensor("CbmStsParSetSensor", "STS sensor parameters"
+                                                                   "mcbm2021");
+
+  // TODO: is it possible to read these values from a parameter file?
+  if (fSetupType == Setup::mCBM2022) {
+
+    Int_t stsAddress01 = CbmStsAddress::GetAddress(0, 0, 1, 0, 0, 0);  // U0 L0 M0  6 cm
+    Int_t stsAddress02 = CbmStsAddress::GetAddress(0, 0, 1, 1, 0, 0);  // U0 L0 M1  6 cm
+    Int_t stsAddress03 = CbmStsAddress::GetAddress(0, 1, 1, 0, 0, 0);  // U0 L1 M0  6 cm
+    Int_t stsAddress04 = CbmStsAddress::GetAddress(0, 1, 1, 1, 0, 0);  // U0 L1 M1  6 cm
+    Int_t stsAddress05 = CbmStsAddress::GetAddress(1, 0, 1, 0, 0, 0);  // U1 L0 M0  6 cm
+    Int_t stsAddress06 = CbmStsAddress::GetAddress(1, 0, 1, 1, 0, 0);  // U1 L0 M1 12 cm
+    Int_t stsAddress07 = CbmStsAddress::GetAddress(1, 1, 1, 0, 0, 0);  // U1 L1 M0  6 cm
+    Int_t stsAddress08 = CbmStsAddress::GetAddress(1, 1, 1, 1, 0, 0);  // U1 L1 M1 12 cm
+    Int_t stsAddress09 = CbmStsAddress::GetAddress(1, 2, 1, 0, 0, 0);  // U1 L2 M0  6 cm
+    Int_t stsAddress10 = CbmStsAddress::GetAddress(1, 2, 1, 1, 0, 0);  // U1 L2 M1  6 cm
+    Int_t stsAddress11 = CbmStsAddress::GetAddress(1, 2, 1, 2, 0, 0);  // U1 L2 M2  6 cm
+
+    LOG(info) << "STS address01 " << CbmStsAddress::ToString(stsAddress01);
+    LOG(info) << "STS address02 " << CbmStsAddress::ToString(stsAddress02);
+    LOG(info) << "STS address03 " << CbmStsAddress::ToString(stsAddress03);
+    LOG(info) << "STS address04 " << CbmStsAddress::ToString(stsAddress04);
+    LOG(info) << "STS address05 " << CbmStsAddress::ToString(stsAddress05);
+    LOG(info) << "STS address06 " << CbmStsAddress::ToString(stsAddress06);
+    LOG(info) << "STS address07 " << CbmStsAddress::ToString(stsAddress07);
+    LOG(info) << "STS address08 " << CbmStsAddress::ToString(stsAddress08);
+    LOG(info) << "STS address09 " << CbmStsAddress::ToString(stsAddress09);
+    LOG(info) << "STS address10 " << CbmStsAddress::ToString(stsAddress10);
+    LOG(info) << "STS address11 " << CbmStsAddress::ToString(stsAddress11);
+
+    sensorParSet->SetParSensor(stsAddress01, sensor6cm);
+    sensorParSet->SetParSensor(stsAddress02, sensor6cm);
+    sensorParSet->SetParSensor(stsAddress03, sensor6cm);
+    sensorParSet->SetParSensor(stsAddress04, sensor6cm);
+    sensorParSet->SetParSensor(stsAddress05, sensor6cm);
+    sensorParSet->SetParSensor(stsAddress06, sensor12cm);
+    sensorParSet->SetParSensor(stsAddress07, sensor6cm);
+    sensorParSet->SetParSensor(stsAddress08, sensor12cm);
+    sensorParSet->SetParSensor(stsAddress09, sensor6cm);
+    sensorParSet->SetParSensor(stsAddress10, sensor6cm);
+    sensorParSet->SetParSensor(stsAddress11, sensor6cm);
+
+    // auto *setup = CbmStsSetup::Instance();
+    // setup->Init(); // reuse setup from global setup file
+  }
+  else if (fSetupType == Setup::mCBM2024) {
+    uint32_t addr01 = 0x10008012;
+    uint32_t addr02 = 0x10018012;
+    uint32_t addr03 = 0x10008412;
+    uint32_t addr04 = 0x10018412;
+    uint32_t addr05 = 0x10008422;
+    uint32_t addr06 = 0x10018422;
+    uint32_t addr07 = 0x10008822;
+    uint32_t addr08 = 0x10018822;
+    uint32_t addr09 = 0x10028822;
+    uint32_t addr10 = 0x10008022;
+    uint32_t addr11 = 0x10018022;
+    uint32_t addr00 = 0x10000002;  // New station 0 in mCBM2024
+
+    LOG(info) << "STS address01 " << CbmStsAddress::ToString(addr01);
+    LOG(info) << "STS address02 " << CbmStsAddress::ToString(addr02);
+    LOG(info) << "STS address03 " << CbmStsAddress::ToString(addr03);
+    LOG(info) << "STS address04 " << CbmStsAddress::ToString(addr04);
+    LOG(info) << "STS address05 " << CbmStsAddress::ToString(addr05);
+    LOG(info) << "STS address06 " << CbmStsAddress::ToString(addr06);
+    LOG(info) << "STS address07 " << CbmStsAddress::ToString(addr07);
+    LOG(info) << "STS address08 " << CbmStsAddress::ToString(addr08);
+    LOG(info) << "STS address09 " << CbmStsAddress::ToString(addr09);
+    LOG(info) << "STS address10 " << CbmStsAddress::ToString(addr10);
+    LOG(info) << "STS address11 " << CbmStsAddress::ToString(addr11);
+    LOG(info) << "STS address00 " << CbmStsAddress::ToString(addr00);
+
+    sensorParSet->SetParSensor(addr01, sensor6cm);
+    sensorParSet->SetParSensor(addr02, sensor6cm);
+    sensorParSet->SetParSensor(addr03, sensor6cm);
+    sensorParSet->SetParSensor(addr04, sensor6cm);
+    sensorParSet->SetParSensor(addr05, sensor6cm);
+    sensorParSet->SetParSensor(addr06, sensor12cm);
+    sensorParSet->SetParSensor(addr07, sensor6cm);
+    sensorParSet->SetParSensor(addr08, sensor12cm);
+    sensorParSet->SetParSensor(addr09, sensor6cm);
+    sensorParSet->SetParSensor(addr10, sensor6cm);
+    sensorParSet->SetParSensor(addr11, sensor6cm);
+    sensorParSet->SetParSensor(addr00, sensor6cm);
+
+    // auto *setup = CbmStsSetup::Instance();
+    // HACK: wrong geometry in global setup file
+    // setup->Init("/home/weiglhofer/cbm/cbmroot/geometry/sts/sts_v24b_mcbm.geo.root");
+    // setup->Init();
+  }
+  else {
+    throw std::runtime_error("STS: Unknown setup type");
+  }
+
+  recoSts->UseSensorParSet(sensorParSet);
+
+  // ASIC params: #ADC channels, dyn. range, threshold, time resol., dead time,
+  // noise RMS, zero-threshold crossing rate
+  auto parAsic = new CbmStsParAsic(128, 31, 75000., 3000., 5., 800., 1000., 3.9789e-3);
+
+  // Module params: number of channels, number of channels per ASIC
+  auto parMod = new CbmStsParModule(2048, 128);
+  parMod->SetAllAsics(*parAsic);
+  recoSts->UseModulePar(parMod);
+
+  // Sensor conditions: full depletion voltage, bias voltage, temperature,
+  // coupling capacitance, inter-strip capacitance
+  auto sensorCond = new CbmStsParSensorCond(70., 140., 268., 17.5, 1.);
+  recoSts->UseSensorCond(sensorCond);
+
+  fRun->AddTask(recoSts);
+}
+
+void CbmOnlineParWrite::Run(Setup setup)
+{
+  // Copied and adjusted from macro/beamtime/mcbm2022/trd_hitfinder_run.C
+
+  static bool callOnce = true;
+  if (!callOnce) {
+    throw std::runtime_error("CbmOnlineParWrite::Run() can only be called once at the moment!");
+  }
+  callOnce = false;
+
+  fSetupType = setup;
+
+  // -----   Environment   --------------------------------------------------
+  fSrcDir = gSystem->Getenv("VMCWORKDIR");  // top source directory
+  // ------------------------------------------------------------------------
+
+  // -----   In- and output file names   ------------------------------------
+
+  // --- Load the geometry setup ----
+  // This is currently only required by the TRD (parameters)
+  cbm::mcbm::ToForceLibLoad dummy;  /// Needed to trigger loading of the library as no fct dict in ROOT6 and CLING
+  TString geoSetupTag = "";
+  try {
+    uint64_t runId = -1;
+    switch (setup) {
+      case Setup::mCBM2022: runId = 2391; break;
+      case Setup::mCBM2024: runId = 2918; break;
+      default: throw std::runtime_error("Unknown setup type");
+    }
+    geoSetupTag = cbm::mcbm::GetSetupFromRunId(runId);
+  }
+  catch (const std::invalid_argument& e) {
+    std::cout << "Error in mapping from runID to setup name: " << e.what() << std::endl;
+    return;
+  }
+
+  TString geoFile = fSrcDir + "/macro/mcbm/data/" + geoSetupTag + ".geo.root";
+  fSetup          = CbmSetup::Instance();
+  fSetup->LoadSetup(geoSetupTag);
+
+  //-----  Load Parameters --------------------------------------------------
+  fParList = new TList();
+
+  // -----   FairRunAna   ---------------------------------------------------
+  fRun = new FairRunAna();
+
+  // Dummy source required for the FairRunAna as it will crash without a source
+  auto* inputSource = new CbmSourceDummy{};
+  fRun->SetSource(inputSource);
+
+
+  // -----   Add detectors   ------------------------------------------------
+  AddDetectors();
+
+  // -----  Parameter database   --------------------------------------------
+  FairRuntimeDb* rtdb        = fRun->GetRuntimeDb();
+  FairParAsciiFileIo* parIo2 = new FairParAsciiFileIo();
+  parIo2->setAutoWritable(false);
+  parIo2->open(fParList, "in");
+  rtdb->setSecondInput(parIo2);
+  // ------------------------------------------------------------------------
+
+  // -----   Run initialisation   -------------------------------------------
+  fRun->SetGeomFile(geoFile);
+  fRun->Init();
+
+  // No need to run the event loop, parameters are written during the initialization
+
+
+  // ----- Clean up ---------------------------------------------------------
+  gGeoManager->GetListOfVolumes()->Delete();
+  gGeoManager->GetListOfShapes()->Delete();
+  delete gGeoManager;
+}
diff --git a/reco/steer/CbmOnlineParWrite.h b/reco/steer/CbmOnlineParWrite.h
new file mode 100644
index 0000000000000000000000000000000000000000..66b7c144c47e2f8a815a74c43aa8ff82b8f65de5
--- /dev/null
+++ b/reco/steer/CbmOnlineParWrite.h
@@ -0,0 +1,42 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+
+#pragma once
+
+/**
+ * @file CbmOnlineParWrite.h
+ * @brief This file contains the declaration of the CbmOnlineParWrite class.
+**/
+
+#include "Definitions.h"
+
+#include <TString.h>
+
+class FairRunAna;
+class TList;
+class CbmSetup;
+
+/**
+ * @class CbmOnlineParWrite
+ * @brief This class is responsible for writing the online parameters to configuration files.
+ * @note Currently covers TRD, TOF and STS setup.
+**/
+class CbmOnlineParWrite {
+
+ public:
+  void Run(cbm::algo::Setup setup);
+
+ private:
+  cbm::algo::Setup fSetupType = cbm::algo::Setup::mCBM2022;  // Setup
+  TString fSrcDir             = "";                          // CbmRoot Source directory
+  CbmSetup* fSetup            = nullptr;                     // Global Geometry setup
+  FairRunAna* fRun            = nullptr;                     // FairRunAna object
+  TList* fParList             = nullptr;                     // List of parameter files, opened with FairRuntimeDb
+
+  void AddDetectors();
+
+  void AddTrd();
+  void AddTof();
+  void AddSts();
+};
diff --git a/reco/steer/CbmSourceDummy.h b/reco/steer/CbmSourceDummy.h
new file mode 100644
index 0000000000000000000000000000000000000000..5a84b5e2ea29dd4d5f31b7b13e76a65a952c1d62
--- /dev/null
+++ b/reco/steer/CbmSourceDummy.h
@@ -0,0 +1,63 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+
+#pragma once
+
+#include <FairSource.h>
+
+/**
+ * @file CbmSourceDummy.h
+ * @brief Dummy input source
+**/
+
+/**
+ * @class CbmSourceDummy
+ * @brief Dummy input FAIR source. Doesn't create any branches or data.
+ * @note This is used to work around a bug in FairRunAna, which can trigger into a crash in
+ * TGeoManager if no input source is set.
+**/
+class CbmSourceDummy : public FairSource {
+
+ public:
+  CbmSourceDummy() = default;
+
+  /** @brief Destructor **/
+  virtual ~CbmSourceDummy() = default;
+
+
+  /** @brief Demanded by base class **/
+  virtual void Close() {}
+
+
+  /** @brief Demanded by base class **/
+  virtual Source_Type GetSourceType() { return kFILE; }
+
+
+  /** @brief Initialisation **/
+  virtual Bool_t Init() { return true; }
+
+
+  /** @brief Demanded by base class **/
+  virtual Bool_t InitUnpackers() { return kTRUE; }
+
+
+  /** @brief Demanded by base class **/
+  virtual Int_t ReadEvent(UInt_t = 0) { return 0; }
+
+
+  /** @brief Demanded by base class **/
+  virtual Bool_t ReInitUnpackers() { return kTRUE; }
+
+
+  /** @brief Demanded by base class **/
+  virtual void Reset() {}
+
+
+  /** @brief Demanded by base class **/
+  virtual void SetParUnpackers() {}
+
+
+  /** @brief Demanded by base class **/
+  virtual Bool_t SpecifyRunId() { return kTRUE; }
+};
diff --git a/reco/tasks/CMakeLists.txt b/reco/tasks/CMakeLists.txt
index 523766f40f565af8370c048949f423599f904516..394be81dfde62c6c028f16cd86cd4332a95c058a 100644
--- a/reco/tasks/CMakeLists.txt
+++ b/reco/tasks/CMakeLists.txt
@@ -21,6 +21,7 @@ set(SRCS
   CbmTaskTofClusterizerParWrite.cxx
   CbmTaskTrdHitFinder.cxx
   CbmTaskTrdHitFinderParWrite.cxx
+  CbmTaskStsHitFinderParWrite.cxx
   CbmTaskUnpack.cxx
 )
 
@@ -34,7 +35,9 @@ set(LIBRARY_NAME CbmRecoTasks)
 set(LINKDEF ${LIBRARY_NAME}LinkDef.h)
 set(PUBLIC_DEPENDENCIES
   CbmData
+  CbmStsBase
   CbmTofBase
+  CbmRecoSts
   FairRoot::Base
   Algo
   ROOT::Core
diff --git a/reco/tasks/CbmTaskStsHitFinderParWrite.cxx b/reco/tasks/CbmTaskStsHitFinderParWrite.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..cb27cbcfc71f78dcaade2853d8b6b96c89483b7d
--- /dev/null
+++ b/reco/tasks/CbmTaskStsHitFinderParWrite.cxx
@@ -0,0 +1,294 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+
+#include "CbmTaskStsHitFinderParWrite.h"
+
+#include "CbmAddress.h"
+#include "CbmStsModule.h"
+#include "CbmStsParSetModule.h"
+#include "CbmStsParSetSensor.h"
+#include "CbmStsParSetSensorCond.h"
+#include "CbmStsParSim.h"
+#include "CbmStsPhysics.h"
+#include "CbmStsRecoModule.h"
+#include "CbmStsSetup.h"
+#include "sts/HitfinderPars.h"
+#include "yaml/Yaml.h"
+
+#include <FairField.h>
+#include <FairRootManager.h>
+#include <FairRun.h>
+#include <FairRuntimeDb.h>
+
+#include <TGeoBBox.h>
+#include <TGeoPhysicalNode.h>
+
+#include <iomanip>
+
+// -----   Constructor   ---------------------------------------------------
+CbmTaskStsHitFinderParWrite::CbmTaskStsHitFinderParWrite() : FairTask("CbmTaskStsHitFinderParWrite", 1) {}
+// -------------------------------------------------------------------------
+
+
+// -----   Destructor   ----------------------------------------------------
+CbmTaskStsHitFinderParWrite::~CbmTaskStsHitFinderParWrite() = default;
+// -------------------------------------------------------------------------
+
+
+// -----   Initialise the cluster finding modules   ------------------------
+UInt_t CbmTaskStsHitFinderParWrite::CreateModules()
+{
+  assert(fSetup);
+
+  std::vector<cbm::algo::sts::HitfinderPars::Module> gpuModules;  // for gpu reco
+
+  LOG(info) << GetName() << ": Creating modules";
+  LOG(info) << fParSetSensor->ToString();
+
+  for (Int_t iModule = 0; iModule < fSetup->GetNofModules(); iModule++) {
+
+    // --- Setup module and sensor
+    CbmStsModule* setupModule = fSetup->GetModule(iModule);
+    assert(setupModule);
+    Int_t moduleAddress = Int_t(setupModule->GetAddress());
+    assert(setupModule->GetNofDaughters() == 1);
+    CbmStsElement* setupSensor = setupModule->GetDaughter(0);
+    assert(setupSensor);
+    Int_t sensorAddress = Int_t(setupSensor->GetAddress());
+
+    // --- Module parameters
+    const CbmStsParModule& modPar       = fParSetModule->GetParModule(moduleAddress);
+    const CbmStsParSensor& sensPar      = fParSetSensor->GetParSensor(sensorAddress);
+    const CbmStsParSensorCond& sensCond = fParSetCond->GetParSensor(sensorAddress);
+
+    // --- Calculate and set average Lorentz shift
+    // --- This will be used in hit finding for correcting the position.
+    Double_t lorentzF = 0.;
+    Double_t lorentzB = 0.;
+    if (fParSim->LorentzShift()) {
+
+      TGeoBBox* shape = dynamic_cast<TGeoBBox*>(setupSensor->GetPnode()->GetShape());
+      assert(shape);
+      Double_t dZ = 2. * shape->GetDZ();  // Sensor thickness
+
+      // Get the magnetic field in the sensor centre
+      Double_t by = 0.;
+      if (FairRun::Instance()->GetField()) {
+        Double_t local[3] = {0., 0., 0.};  // sensor centre in local C.S.
+        Double_t global[3];                // sensor centre in global C.S.
+        setupSensor->GetPnode()->GetMatrix()->LocalToMaster(local, global);
+        Double_t field[3] = {0., 0., 0.};  // magnetic field components
+        FairRun::Instance()->GetField()->Field(global, field);
+        by = field[1] / 10.;  // kG->T
+      }                       //? field present
+
+      // Calculate average Lorentz shift on sensor sides.
+      // This is needed in hit finding for correcting the cluster position.
+      auto lorentzShift = LorentzShift(sensCond, dZ, by);
+      lorentzF          = lorentzShift.first;
+      lorentzB          = lorentzShift.second;
+    }  //? Lorentz-shift correction
+
+    // --- Create reco module
+    CbmStsRecoModule recoModule{setupModule, modPar, sensPar, lorentzF, lorentzB};
+
+    // Get Transformation Matrix
+    cbm::algo::sts::HitfinderPars::ModuleTransform localToGlobal;
+    TGeoHMatrix* matrix = recoModule.getMatrix();
+    std::copy_n(matrix->GetRotationMatrix(), 9, localToGlobal.rotation.begin());
+    std::copy_n(matrix->GetTranslation(), 3, localToGlobal.translation.begin());
+
+    // Collect GPU parameters
+    cbm::algo::sts::HitfinderPars::Module gpuModulePars{
+      .address       = moduleAddress,
+      .dY            = sensPar.GetPar(3),
+      .pitch         = sensPar.GetPar(6),
+      .stereoF       = sensPar.GetPar(8),
+      .stereoB       = sensPar.GetPar(9),
+      .lorentzF      = float(lorentzF),
+      .lorentzB      = float(lorentzB),
+      .localToGlobal = localToGlobal,
+    };
+    gpuModules.emplace_back(gpuModulePars);
+  }
+
+  const CbmStsParModule& firstModulePars = fParSetModule->GetParModule(gpuModules[0].address);
+
+  CbmStsParAsic asic = firstModulePars.GetParAsic(0);
+  cbm::algo::sts::HitfinderPars::Asic algoAsic{
+    .nAdc           = asic.GetNofAdc(),
+    .dynamicRange   = float(asic.GetDynRange()),
+    .threshold      = float(asic.GetThreshold()),
+    .timeResolution = float(asic.GetTimeResol()),
+    .deadTime       = float(asic.GetDeadTime()),
+    .noise          = float(asic.GetNoise()),
+    .zeroNoiseRate  = float(asic.GetZeroNoiseRate()),
+  };
+
+  int nChannels = firstModulePars.GetNofChannels();
+
+  auto [landauValues, landauStepSize] = CbmStsPhysics::Instance()->GetLandauWidthTable();
+  std::vector<float> landauValuesF;
+  std::copy(landauValues.begin(), landauValues.end(), std::back_inserter(landauValuesF));
+  cbm::algo::sts::HitfinderPars pars{
+    .asic      = algoAsic,
+    .nChannels = nChannels,
+    .modules   = gpuModules,
+    .landauTable =
+      {
+        .values   = landauValuesF,
+        .stepSize = float(landauStepSize),
+      },
+  };
+
+  // Write to file
+  std::string filename = "StsHitfinder.yaml";
+  std::ofstream{filename} << cbm::algo::yaml::Dump{}(pars, 4);
+
+  return pars.modules.size();
+}
+// -------------------------------------------------------------------------
+
+
+// -----   Initialisation   ------------------------------------------------
+InitStatus CbmTaskStsHitFinderParWrite::Init()
+{
+
+  // --- Something for the screen
+  LOG(info) << "==========================================================";
+  LOG(info) << GetName() << ": Initialising ";
+
+  // --- Check IO-Manager
+  FairRootManager* ioman = FairRootManager::Instance();
+  assert(ioman);
+
+  // --- Simulation settings
+  assert(fParSim);  // Set from SetParContainers()
+  LOG(info) << GetName() << ": Sim settings " << fParSim->ToString();
+
+  // --- Parameters
+  InitParams();
+
+  // --- Initialise STS setup
+  fSetup = CbmStsSetup::Instance();
+  fSetup->Init(nullptr);
+  //fSetup->SetSensorParameters(fParSetSensor);
+
+  // --- Create reconstruction modules
+  UInt_t nModules = CreateModules();
+  LOG(info) << GetName() << ": Created " << nModules << " modules";
+
+  LOG(info) << GetName() << ": Initialisation successful.";
+  LOG(info) << "==========================================================";
+
+  return kSUCCESS;
+}
+// -------------------------------------------------------------------------
+
+
+// -----   Parameter initialisation   --------------------------------------
+void CbmTaskStsHitFinderParWrite::InitParams()
+{
+
+  // --- Module parameters
+  TString sourceModu = "database";
+  assert(fParSetModule);
+  if (fUserParSetModule) {
+    fParSetModule->clear();
+    *fParSetModule = *fUserParSetModule;
+    fParSetModule->setChanged();
+    fParSetModule->setInputVersion(-2, 1);
+    sourceModu = "user-defined";
+  }
+  if (fUserParModule) {  // global settings override
+    fParSetModule->clear();
+    fParSetModule->SetGlobalPar(*fUserParModule);
+    fParSetModule->setChanged();
+    fParSetModule->setInputVersion(-2, 1);
+    sourceModu = "user-defined, global";
+  }
+  LOG(info) << GetName() << ": Module parameters (" << sourceModu << ") " << fParSetModule->ToString();
+
+  // --- Sensor parameters
+  TString sourceSens = "database";
+  assert(fParSetSensor);
+  if (fUserParSetSensor) {
+    fParSetSensor->clear();
+    *fParSetSensor = *fUserParSetSensor;
+    fParSetSensor->setChanged();
+    fParSetSensor->setInputVersion(-2, 1);
+    sourceSens = "user-defined";
+  }
+  if (fUserParSensor) {  // global settings override
+    fParSetSensor->clear();
+    fParSetSensor->SetGlobalPar(*fUserParSensor);
+    fParSetSensor->setChanged();
+    fParSetSensor->setInputVersion(-2, 1);
+    sourceSens = "user-defined, global";
+  }
+  LOG(info) << GetName() << ": Sensor parameters (" << sourceSens << ")" << fParSetSensor->ToString();
+
+  // --- Sensor conditions
+  TString sourceCond = "database";
+  assert(fParSetCond);
+  if (fUserParSetCond) {
+    fParSetCond->clear();
+    *fParSetCond = *fUserParSetCond;
+    fParSetCond->setChanged();
+    fParSetCond->setInputVersion(-2, 1);
+    sourceSens = "user-defined";
+  }
+  if (fUserParCond) {  // global settings override
+    fParSetCond->clear();
+    fParSetCond->SetGlobalPar(*fUserParCond);
+    fParSetCond->setChanged();
+    fParSetCond->setInputVersion(-2, 1);
+    sourceCond = "user-defined, global";
+  }
+  LOG(info) << GetName() << ": Sensor conditions (" << sourceCond << ")" << fParSetCond->ToString();
+}
+// -------------------------------------------------------------------------
+
+// -----   Calculate the mean Lorentz shift in a sensor   ------------------
+std::pair<Double_t, Double_t> CbmTaskStsHitFinderParWrite::LorentzShift(const CbmStsParSensorCond& conditions,
+                                                                        Double_t dZ, Double_t bY)
+{
+
+  Double_t vBias  = conditions.GetVbias();  // Bias voltage
+  Double_t vFd    = conditions.GetVfd();    // Full-depletion voltage
+  Double_t eField = (vBias + vFd) / dZ;     // Electric field
+
+  // --- Integrate in 1000 steps over the sensor thickness
+  Int_t nSteps     = 1000;
+  Double_t deltaZ  = dZ / nSteps;
+  Double_t dxMeanE = 0.;
+  Double_t dxMeanH = 0.;
+  for (Int_t j = 0; j <= nSteps; j++) {
+    eField -= 2 * vFd / dZ * deltaZ / dZ;  // Electric field [V/cm]
+    Double_t muHallE = conditions.GetHallMobility(eField, 0);
+    Double_t muHallH = conditions.GetHallMobility(eField, 1);
+    dxMeanE += muHallE * (dZ - Double_t(j) * deltaZ);
+    dxMeanH += muHallH * Double_t(j) * deltaZ;
+  }
+  dxMeanE /= Double_t(nSteps);
+  dxMeanH /= Double_t(nSteps);
+  Double_t shiftF = dxMeanE * bY * 1.e-4;
+  Double_t shiftB = dxMeanH * bY * 1.e-4;
+  // The factor 1.e-4 is because bZ is in T = Vs/m**2, but muHall is in
+  // cm**2/(Vs) and z in cm.
+
+  return {shiftF, shiftB};
+}
+// -------------------------------------------------------------------------
+
+// -----   Connect parameter container   -----------------------------------
+void CbmTaskStsHitFinderParWrite::SetParContainers()
+{
+  FairRuntimeDb* db = FairRun::Instance()->GetRuntimeDb();
+  fParSim           = dynamic_cast<CbmStsParSim*>(db->getContainer("CbmStsParSim"));
+  fParSetModule     = dynamic_cast<CbmStsParSetModule*>(db->getContainer("CbmStsParSetModule"));
+  fParSetSensor     = dynamic_cast<CbmStsParSetSensor*>(db->getContainer("CbmStsParSetSensor"));
+  fParSetCond       = dynamic_cast<CbmStsParSetSensorCond*>(db->getContainer("CbmStsParSetSensorCond"));
+}
+// -------------------------------------------------------------------------
diff --git a/reco/tasks/CbmTaskStsHitFinderParWrite.h b/reco/tasks/CbmTaskStsHitFinderParWrite.h
new file mode 100644
index 0000000000000000000000000000000000000000..f64a6501f7d3b1f56bae06ede11438c6f07d8f3b
--- /dev/null
+++ b/reco/tasks/CbmTaskStsHitFinderParWrite.h
@@ -0,0 +1,148 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+#pragma once
+
+#include <FairTask.h>
+
+class CbmStsElement;
+class CbmStsParAsic;
+class CbmStsParModule;
+class CbmStsParSensor;
+class CbmStsParSensorCond;
+class CbmStsParSetModule;
+class CbmStsParSetSensor;
+class CbmStsParSetSensorCond;
+class CbmStsParSim;
+class CbmStsSensor;
+class CbmStsSetup;
+
+class CbmTaskStsHitFinderParWrite : public FairTask {
+
+ public:
+  /** @brief Constructor **/
+  CbmTaskStsHitFinderParWrite();
+
+  /** @brief Copy constructor (disabled) **/
+  CbmTaskStsHitFinderParWrite(const CbmTaskStsHitFinderParWrite&) = delete;
+
+  /** @brief Assignment operator (disabled) **/
+  CbmTaskStsHitFinderParWrite operator=(const CbmTaskStsHitFinderParWrite&) = delete;
+
+  /** @brief Destructor  **/
+  virtual ~CbmTaskStsHitFinderParWrite();
+
+  /** @brief Initialisation **/
+  virtual InitStatus Init();
+
+  /** @brief Task execution **/
+  virtual void Exec(Option_t*) {}
+
+  /** @brief End-of-run action **/
+  virtual void Finish() {}
+
+  /** @brief Define the needed parameter containers **/
+  virtual void SetParContainers();
+
+
+  /** @brief User-defined module parameters
+     ** @param parModule Module parameter object
+     **
+     ** If defined, these parameters will be used for all modules instead
+     ** of those found in the runtimeDb.
+     */
+  void UseModulePar(CbmStsParModule* modulePar) { fUserParModule = modulePar; }
+
+
+  /** @brief User-defined module parameter set
+     ** @param parModule Module parameter set object
+     **
+     ** If defined, this parameter set will be used instead of that found in the runtimeDb.
+     */
+  void UseModuleParSet(CbmStsParSetModule* moduleParSet) { fUserParSetModule = moduleParSet; }
+
+
+  /** @brief User-defined sensor condition parameters
+      ** @param parModule Sensor condition parameter object
+      **
+      ** If defined, these condition parameters will be used for all sensors instead
+      ** of those found in the runtimeDb.
+      */
+  void UseSensorCond(CbmStsParSensorCond* sensorCond) { fUserParCond = sensorCond; }
+
+  /** @brief User-defined module parameter set
+     ** @param parModule Module parameter set object
+     **
+     ** If defined, this parameter set will be used instead of that found in the runtimeDb.
+     */
+  void UseSensorCondSet(CbmStsParSetSensorCond* sensorCondSet) { fUserParSetCond = sensorCondSet; }
+
+
+  /** @brief User-defined sensor parameters
+     ** @param parModule Sensor parameter object
+     **
+     ** If defined, these parameters will be used for all sensors instead
+     ** of those found in the runtimeDb.
+     */
+  void UseSensorPar(CbmStsParSensor* sensorPar) { fUserParSensor = sensorPar; }
+
+
+  /** @brief User-defined module parameter set
+     ** @param parModule Module parameter set object
+     **
+     ** If defined, this parameter set will be used instead of that found in the runtimeDb.
+     */
+  void UseSensorParSet(CbmStsParSetSensor* sensorParSet) { fUserParSetSensor = sensorParSet; }
+
+ private:
+  /** @brief Average Lorentz Shift in a sensor
+     ** @param conditions  Sensor operating conditions
+     ** @param dZ  Sensor thickness [cm]
+     ** @param bY  y component of magnetic field in sensor centre
+     ** @return Mean Lorentz shift front side and back side [cm]
+     **
+     ** The Lorentz shift will be corrected for in hit finding.
+     **/
+  std::pair<Double_t, Double_t> LorentzShift(const CbmStsParSensorCond& conditions, Double_t dZ, Double_t bY);
+
+
+  /** @brief Instantiate reconstruction modules
+     ** @value Number of modules created
+     **/
+  UInt_t CreateModules();
+
+
+  /** @brief Get the sensor parameters
+     ** @param geoSensor Pointer to setup sensor
+     **/
+  void GetSensorParameters(CbmStsElement* geoSensor);
+
+
+  /** @brief Initialise parameters
+   **
+   ** For simulated data, the parameters for modules and sensors are retrieved from the
+   ** runtimeDb. They can be overridden by user-specified parameter sets using the respective
+   ** setters. This is necessary when processing experiment data without a prior simulation step.
+   **
+   **/
+  void InitParams();
+
+ private:
+  // --- Setup and parameters
+  CbmStsSetup* fSetup                 = nullptr;  //! Instance of STS setup
+  CbmStsParSim* fParSim               = nullptr;  ///< Simulation settings
+  CbmStsParSetModule* fParSetModule   = nullptr;  ///< Module parameters
+  CbmStsParSetSensor* fParSetSensor   = nullptr;  ///< Sensor parameters
+  CbmStsParSetSensorCond* fParSetCond = nullptr;  ///< Sensor conditions
+
+  // --- User-defined parameters, not from database
+  CbmStsParAsic* fUserParAsic       = nullptr;
+  CbmStsParModule* fUserParModule   = nullptr;
+  CbmStsParSensor* fUserParSensor   = nullptr;
+  CbmStsParSensorCond* fUserParCond = nullptr;
+
+  // --- User-defined parameter sets, not from database
+  CbmStsParSetModule* fUserParSetModule   = nullptr;
+  CbmStsParSetSensor* fUserParSetSensor   = nullptr;
+  CbmStsParSetSensorCond* fUserParSetCond = nullptr;
+};
diff --git a/reco/tasks/CbmTaskTrdHitFinderParWrite.cxx b/reco/tasks/CbmTaskTrdHitFinderParWrite.cxx
index 901d53871e030d7c9afbf784fbda53ece1987d93..75ba2be3de7f32c2faf5f68e81266dc55288328f 100644
--- a/reco/tasks/CbmTaskTrdHitFinderParWrite.cxx
+++ b/reco/tasks/CbmTaskTrdHitFinderParWrite.cxx
@@ -40,7 +40,7 @@ CbmTaskTrdHitFinderParWrite::CbmTaskTrdHitFinderParWrite() : FairTask("TrdCluste
 //_____________________________________________________________________
 void CbmTaskTrdHitFinderParWrite::SetParContainers()
 {
-  FairRuntimeDb* rtdb = FairRunAna::Instance()->GetRuntimeDb();
+  FairRuntimeDb* rtdb = FairRun::Instance()->GetRuntimeDb();
   fAsicPar            = static_cast<CbmTrdParSetAsic*>(rtdb->getContainer("CbmTrdParSetAsic"));
   fDigiPar            = static_cast<CbmTrdParSetDigi*>(rtdb->getContainer("CbmTrdParSetDigi"));
   fGeoPar             = static_cast<CbmTrdParSetGeo*>(rtdb->getContainer("CbmTrdParSetGeo"));
diff --git a/services/CMakeLists.txt b/services/CMakeLists.txt
index 7ccb2f7d10b3277be2bc8456ac2216ec2d16c636..824b7748ebeec61dfcd62fa16b14233f54f2b115 100644
--- a/services/CMakeLists.txt
+++ b/services/CMakeLists.txt
@@ -1,2 +1,3 @@
 add_subdirectory(archive_explorer)
 add_subdirectory(histserv)
+add_subdirectory(online_par_dump)
diff --git a/services/online_par_dump/Application.cxx b/services/online_par_dump/Application.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..17116d0ccb4b276302945c371a3bcfb0a28ca482
--- /dev/null
+++ b/services/online_par_dump/Application.cxx
@@ -0,0 +1,34 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+
+#include "Application.h"
+
+#include "CbmOnlineParWrite.h"
+
+#include <FairLogger.h>
+
+#include <TString.h>
+#include <TSystem.h>
+
+void Application::Run()
+{
+  // --- Logger settings ----------------------------------------------------
+  TString logLevel     = "INFO";
+  TString logVerbosity = "LOW";
+  // ------------------------------------------------------------------------
+
+  // -----   Logger settings   ----------------------------------------------
+  FairLogger::GetLogger()->SetLogScreenLevel(logLevel.Data());
+  FairLogger::GetLogger()->SetLogVerbosityLevel(logVerbosity.Data());
+
+  LOG(info) << "Starting online parameter dump for setup " << fOpts.setup << "...";
+
+  gSystem->mkdir(fOpts.outputDir.c_str(), kTRUE);
+  gSystem->cd(fOpts.outputDir.c_str());
+
+  CbmOnlineParWrite writer;
+  writer.Run(fOpts.setup);
+
+  LOG(info) << "Online parameter dump finished";
+}
diff --git a/services/online_par_dump/Application.h b/services/online_par_dump/Application.h
new file mode 100644
index 0000000000000000000000000000000000000000..79cf571acccfc19512f2fce16accf965eb6ee449
--- /dev/null
+++ b/services/online_par_dump/Application.h
@@ -0,0 +1,18 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+
+#pragma once
+
+#include "ProgramOptions.h"
+
+class Application {
+
+ public:
+  Application(ProgramOptions opts) : fOpts(opts) {}
+
+  void Run();
+
+ private:
+  ProgramOptions fOpts;
+};
diff --git a/services/online_par_dump/CMakeLists.txt b/services/online_par_dump/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f24f315bb9439758433d4770a154cd5ccf789596
--- /dev/null
+++ b/services/online_par_dump/CMakeLists.txt
@@ -0,0 +1,29 @@
+# Copyright (C) 2024 Johann Wolfgang Goethe-Universitaet Frankfurt, Frankfurt am Main
+# SPDX-License-Identifier: GPL-3.0-only
+# Authors: Felix Weiglhofer [committer]
+
+set(APP cbm-online-par-dump)
+
+set(SRCS
+  Application.cxx
+  ProgramOptions.cxx
+  main.cxx
+  )
+
+set(HEADERS
+  Application.h
+  ProgramOptions.h
+  )
+
+add_executable(${APP} ${SRCS} ${HEADERS})
+target_link_libraries(${APP}
+  PRIVATE
+    CbmRecoTasks
+    CbmRecoSteer
+    CbmBase
+    Boost::program_options
+    external::fles_monitoring
+    external::fles_logging
+  )
+
+install(TARGETS ${APP} DESTINATION bin)
diff --git a/services/online_par_dump/ProgramOptions.cxx b/services/online_par_dump/ProgramOptions.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..dd0c22fcdaee42fe9531bdfa50bf661647984ccb
--- /dev/null
+++ b/services/online_par_dump/ProgramOptions.cxx
@@ -0,0 +1,54 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+
+#include "ProgramOptions.h"
+
+#include <boost/program_options.hpp>
+
+#include <iostream>
+
+namespace po = boost::program_options;
+
+ProgramOptions::ProgramOptions(int argc, char** argv)
+{
+  po::options_description required("Required options");
+  // clang-format off
+  required.add_options()
+    ("setup,s", po::value(&setup)->value_name("<setup>")->required(),
+      "Setup: mCBM2022, mCBM2024")
+    ("outdir,o", po::value(&outputDir)->value_name("<outputDir>")->required(),
+      "Output directory for the parameter files")
+  ;
+  // clang-format on
+
+  po::options_description cmdline_options;
+  cmdline_options.add(required);
+
+  po::variables_map vm;
+  po::command_line_parser parser{argc, argv};
+  parser.options(cmdline_options);
+  try {
+    auto result = parser.run();
+    po::store(result, vm);
+  }
+  catch (const std::exception& e) {
+    std::cerr << "Error: " << e.what() << std::endl;
+    std::cerr << "Use '-h' to display all valid options." << std::endl;
+    std::exit(EXIT_FAILURE);
+  }
+
+  if (vm.count("help") > 0) {
+    std::cout << cmdline_options << std::endl;
+    std::exit(EXIT_SUCCESS);
+  }
+
+  try {
+    po::notify(vm);
+  }
+  catch (const po::required_option& e) {
+    std::cerr << "Error: " << e.what() << std::endl;
+    std::cerr << "Use '-h' to display all valid options." << std::endl;
+    std::exit(EXIT_FAILURE);
+  }
+}
diff --git a/services/online_par_dump/ProgramOptions.h b/services/online_par_dump/ProgramOptions.h
new file mode 100644
index 0000000000000000000000000000000000000000..9dab8b7ca5a5ac2e5d66f95868bd3ffbf72f0d14
--- /dev/null
+++ b/services/online_par_dump/ProgramOptions.h
@@ -0,0 +1,16 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+
+#pragma once
+
+#include "Definitions.h"
+
+#include <string>
+
+struct ProgramOptions {
+  ProgramOptions(int argc, char** argv);
+
+  cbm::algo::Setup setup;
+  std::string outputDir;
+};
diff --git a/services/online_par_dump/main.cxx b/services/online_par_dump/main.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..ee9a4e9dce7146c84615de902ea786551fbb876b
--- /dev/null
+++ b/services/online_par_dump/main.cxx
@@ -0,0 +1,13 @@
+/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+
+#include "Application.h"
+
+int main(int argc, char** argv)
+{
+  ProgramOptions opts{argc, argv};
+  Application app{opts};
+  app.Run();
+  return 0;
+}