diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index d0f96a63d987fac94b688a2b2487926e2dd1372c..c18903ab4dfe8f42ae0b54fedad23b8cf62583ee 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -106,6 +106,7 @@ install(
     base/Prelude.h
     base/RecoParams.h
     base/SubChain.h
+    base/Types.h
     ca/simd/CaSimd.h
     ca/simd/CaSimdVc.h
     ca/simd/CaSimdPseudo.h
diff --git a/algo/base/Options.cxx b/algo/base/Options.cxx
index 79fc95edaa37741fabfef741bf3365b1b9898115..f49b7db2e9d27b0dfc4a72b4ed0bd925b59ac67d 100644
--- a/algo/base/Options.cxx
+++ b/algo/base/Options.cxx
@@ -6,9 +6,23 @@
 #include <boost/program_options.hpp>
 
 #include <iostream>
+#include <iterator>
 #include <unordered_map>
 
 using namespace cbm::algo;
+namespace po = boost::program_options;
+
+
+namespace std
+{
+  template<class T>
+  std::ostream& operator<<(std::ostream& os, const std::vector<T>& v)
+  {
+    copy(v.begin(), v.end(), std::ostream_iterator<T>(os, " "));
+    return os;
+  }
+}  // namespace std
+
 
 void validate(boost::any& v, const std::vector<std::string>& values, severity_level*, int)
 {
@@ -18,22 +32,19 @@ void validate(boost::any& v, const std::vector<std::string>& values, severity_le
     {"info", severity_level::info},   {"warning", severity_level::warning}, {"error", severity_level::error},
     {"fatal", severity_level::fatal}};
 
-  namespace po = boost::program_options;
-
   po::validators::check_first_occurrence(v);
 
   const std::string& s = po::validators::get_single_string(values);
 
   auto it = levels.find(s);
 
-  if (it == levels.end()) { throw po::validation_error(po::validation_error::invalid_option_value); }
+  if (it == levels.end()) throw po::validation_error(po::validation_error::invalid_option_value);
 
   v = it->second;
 }
 
 Options::Options(int argc, char** argv)
 {
-  namespace po = boost::program_options;
 
   po::options_description required("Required options");
   // clang-format off
@@ -56,9 +67,15 @@ Options::Options(int argc, char** argv)
       "URI specifying monitor output (e.g. file:/tmp/monitor.txt, influx1:login:8086:cbmreco_status). Prints to cout when no argument is given. Monitor is disabled when flag is not set.")
     ("log-file,L", po::value(&fLogFile)->value_name("<file>"),
       "write log messages to file")
-    ("num-ts,n", po::value<int>(&fNumTimeslices)->default_value(-1)->value_name("<num>"),
+    ("output,o", po::value(&fOutputTypes)->multitoken()->default_value({RecoData::Hit})->value_name("<types>"),
+      "comma seperated list of reconstruction output types (hit, digi, ...)")
+    ("steps", po::value(&fRecoSteps)->multitoken()->default_value({Step::LocalReco})->value_name("<steps>"),
+      "comma seperated list of reconstruction steps (upack, digitrigger, localreco, ...)")
+    ("systems,s", po::value(&fDetectors)->multitoken()->default_value({Detector::STS})->value_name("<detectors>"),
+      "comma seperated list of detectors to process (sts, mvd, ...)")
+    ("num-ts,n", po::value(&fNumTimeslices)->default_value(-1)->value_name("<num>"),
       "Stop after <num> timeslices (-1 = all)")
-    ("skip-ts,s", po::value(&fSkipTimeslices)->default_value(0)->value_name("<num>"),
+    ("skip-ts", po::value(&fSkipTimeslices)->default_value(0)->value_name("<num>"),
       "Skip first <num> timeslices")
     ("times,t", po::bool_switch(&fCollectKernelTimes)->default_value(false),
       "print kernel times")
diff --git a/algo/base/Options.h b/algo/base/Options.h
index 7ee203f890c3cbbcf4ee18c947bdb0e1aecb58be..bc67dae908892eb33c8c5a7da7dace92a22c78d3 100644
--- a/algo/base/Options.h
+++ b/algo/base/Options.h
@@ -4,7 +4,11 @@
 #ifndef CBM_ALGO_BASE_OPTIONS_H
 #define CBM_ALGO_BASE_OPTIONS_H
 
+#include "Types.h"
+
+#include <set>
 #include <string>
+#include <vector>
 
 #include "compat/Filesystem.h"
 #include "log.hpp"
@@ -28,6 +32,21 @@ namespace cbm::algo
     int NumTimeslices() const { return fNumTimeslices; }
     int SkipTimeslices() const { return fSkipTimeslices; }
 
+    const std::vector<Step>& Steps() const { return fRecoSteps; }
+    bool HasStep(Step step) const { return std::find(fRecoSteps.begin(), fRecoSteps.end(), step) != fRecoSteps.end(); }
+    const std::vector<RecoData>& OutputTypes() const { return fOutputTypes; }
+    bool HasOutput(RecoData recoData) const
+    {
+      return std::find(fOutputTypes.begin(), fOutputTypes.end(), recoData) != fOutputTypes.end();
+    }
+
+    const std::vector<Detector>& Detectors() const { return fDetectors; }
+    bool HasDetector(Detector detector) const
+    {
+      return std::find(fDetectors.begin(), fDetectors.end(), detector) != fDetectors.end();
+    }
+
+
   private:
     std::string fParamsDir;  // TODO: can we make this a std::path?
     std::string fInputLocator;
@@ -38,6 +57,9 @@ namespace cbm::algo
     bool fCollectKernelTimes = false;
     int fNumTimeslices       = -1;
     int fSkipTimeslices      = 0;
+    std::vector<Step> fRecoSteps;
+    std::vector<RecoData> fOutputTypes;
+    std::vector<Detector> fDetectors;
   };
 
 }  // namespace cbm::algo
diff --git a/algo/base/Types.h b/algo/base/Types.h
new file mode 100644
index 0000000000000000000000000000000000000000..12c5682b5305a2ae8a16e670df29b20af3e3d7d3
--- /dev/null
+++ b/algo/base/Types.h
@@ -0,0 +1,51 @@
+/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+#ifndef CBM_BASE_TYPES_H
+#define CBM_BASE_TYPES_H
+
+#include "util/EnumDict.h"
+
+namespace cbm::algo
+{
+
+  enum class Detector
+  {
+    STS,
+    TOF,
+  };
+
+  enum class Step
+  {
+    Unpack,
+    DigiTrigger,
+    LocalReco,
+  };
+
+  enum class RecoData
+  {
+    Digi,
+    Cluster,
+    Hit,
+  };
+
+}  // namespace cbm::algo
+
+CBM_ENUM_DICT(cbm::algo::Detector,
+  {"STS", Detector::STS},
+  {"TOF", Detector::TOF}
+);
+
+CBM_ENUM_DICT(cbm::algo::Step,
+  {"Unpack", Step::Unpack},
+  {"DigiTrigger", Step::DigiTrigger},
+  {"LocalReco", Step::LocalReco}
+);
+
+CBM_ENUM_DICT(cbm::algo::RecoData,
+  {"Digi", RecoData::Digi},
+  {"Cluster", RecoData::Cluster},
+  {"Hit", RecoData::Hit}
+);
+
+#endif
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index 490967f7531f9716636dadab6bc2a7463e386fd5..b8c5b8bc244bc56b9071014ba2ef5e05e921fdf7 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -18,9 +18,20 @@ using namespace cbm::algo;
 Reco::Reco() {}
 Reco::~Reco() {}
 
+void Reco::Validate(const Options& opts)
+{
+  if (!fs::exists(opts.ParamsDir())) {
+    throw std::runtime_error("ParamsDir does not exist: " + opts.ParamsDir().string());
+  }
+
+  if (opts.HasDetector(Detector::TOF)) { throw std::runtime_error("TOF not implemented yet"); }
+}
+
 void Reco::Init(const Options& opts)
 {
-  if (fInitialized) { throw std::runtime_error("Chain already initialized"); }
+  if (fInitialized) throw std::runtime_error("Chain already initialized");
+
+  Validate(opts);
 
   fContext.opts = opts;
   SetContext(&fContext);
diff --git a/algo/global/Reco.h b/algo/global/Reco.h
index 9d1bf89e73cbbee1ef81da34939572a8518fe1b7..d485da35348bc116cbf49138e5a735a07651c076 100644
--- a/algo/global/Reco.h
+++ b/algo/global/Reco.h
@@ -43,8 +43,7 @@ namespace cbm::algo
     sts::UnpackChain fUnpack;
     sts::HitfinderChain fStsHitFinder;
 
-    // Util functions
-    void AddTiming(xpu::timings& timings);
+    void Validate(const Options& opts);
   };
 }  // namespace cbm::algo