diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index f4f946b5c994506c32994e72699150c1f04b4110..b13e5732d4e16058331c952faeb3744372ef740d 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -9,7 +9,9 @@ set(DEVICE_SRCS
 
 set(SRCS
   ${DEVICE_SRCS}
+  base/Options.cxx
   evbuild/EventBuilder.cxx
+  global/Reco.cxx
   trigger/TimeClusterTrigger.cxx
   evselector/DigiEventSelector.cxx
   detectors/sts/StsHitfinderChain.cxx
@@ -32,6 +34,7 @@ target_include_directories(Algo
   PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
          ${CMAKE_CURRENT_SOURCE_DIR}/base
          ${CMAKE_CURRENT_SOURCE_DIR}/evbuild
+         ${CMAKE_CURRENT_SOURCE_DIR}/global
          ${CMAKE_CURRENT_SOURCE_DIR}/trigger
          ${CMAKE_CURRENT_SOURCE_DIR}/evselector
          ${CMAKE_CURRENT_SOURCE_DIR}/detectors/sts
@@ -45,9 +48,10 @@ target_link_libraries(Algo
   PUBLIC    OnlineData
             ROOT::GenVector
             GSL
+            Boost::program_options
+            xpu
   INTERFACE FairLogger::FairLogger
             external::fles_ipc
-            xpu
 )
 target_compile_definitions(Algo PUBLIC NO_ROOT)
 xpu_attach(Algo ${DEVICE_SRCS})
diff --git a/algo/base/.gitignore b/algo/base/.gitignore
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/algo/base/ChainContext.h b/algo/base/ChainContext.h
new file mode 100644
index 0000000000000000000000000000000000000000..954e45d6f0bc0f30f5afe47de0c2fe76dae725a9
--- /dev/null
+++ b/algo/base/ChainContext.h
@@ -0,0 +1,16 @@
+/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+#ifndef CBM_ALGO_BASE_CHAINCONTEXT_H
+#define CBM_ALGO_BASE_CHAINCONTEXT_H
+
+#include "Options.h"
+
+namespace cbm::algo
+{
+  struct ChainContext {
+    Options opts;
+  };
+}  // namespace cbm::algo
+
+#endif
diff --git a/algo/base/Options.cxx b/algo/base/Options.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..ea1b73c05994e0bdaf1486e9059db5a80ce3e83c
--- /dev/null
+++ b/algo/base/Options.cxx
@@ -0,0 +1,63 @@
+/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+#include "Options.h"
+
+#include <boost/program_options.hpp>
+
+#include <iostream>
+
+using namespace cbm::algo;
+
+Options::Options(int argc, char** argv)
+{
+  namespace po = boost::program_options;
+
+  po::options_description required("Required options");
+  // clang-format off
+  required.add_options()
+    ("param-dir,p", po::value<std::string>(&fParamsDir)->value_name("<folder>")->required(),
+      "read program options from this folder")
+    ("input-locator,i", po::value<std::string>(&fInputLocator)->value_name("<locator>")->required(),
+      "URI specifying input timeslice source")
+  ;
+  // clang-format on
+
+  po::options_description generic("Other options");
+  // clang-format off
+  generic.add_options()
+    ("device,d", po::value<std::string>(&fDevice)->default_value("cpu")->value_name("<device>"),
+      "select device (cpu, cuda0, cuda1, hip0, ...)")
+    ("log-level,l", po::value<std::string>(&fLogLevel)->default_value("info")->value_name("<level>"),
+      "set log level (debug, info, warning, error, fatal)")
+    ("num-ts,n", po::value<int>(&fNumTimeslices)->default_value(-1)->value_name("<num>"),
+      "Stop after <num> timeslices (-1 = all)")
+    ("skip-ts,s", po::value<int>(&fSkipTimeslices)->default_value(0)->value_name("<num>"),
+      "Skip first <num> timeslices")
+    ("times,t", po::bool_switch(&fCollectKernelTimes)->default_value(false),
+      "print kernel times")("help,h", "produce help message")
+  ;
+  // clang-format on
+
+  po::options_description cmdline_options;
+  cmdline_options.add(required).add(generic);
+
+  po::variables_map vm;
+  po::command_line_parser parser {argc, argv};
+  parser.options(cmdline_options);
+  po::store(parser.run(), vm);
+
+  if (vm.count("help")) {
+    std::cerr << cmdline_options << std::endl;
+    std::exit(0);
+  }
+
+  try {
+    po::notify(vm);
+  }
+  catch (const po::required_option& e) {
+    std::cerr << "Error: " << e.what() << std::endl;
+    std::cerr << cmdline_options << std::endl;
+    std::exit(1);
+  }
+}
diff --git a/algo/base/Options.h b/algo/base/Options.h
new file mode 100644
index 0000000000000000000000000000000000000000..f67827abf621c702ba28e8545fe5b035b72663a8
--- /dev/null
+++ b/algo/base/Options.h
@@ -0,0 +1,39 @@
+/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+#ifndef CBM_ALGO_BASE_OPTIONS_H
+#define CBM_ALGO_BASE_OPTIONS_H
+
+#include <filesystem>
+#include <string>
+
+namespace cbm::algo
+{
+
+  class Options {
+
+  public:
+    Options() = default;
+    Options(int argc, char** argv);
+
+    std::filesystem::path ParamsDir() const { return fParamsDir; }
+    const std::string& InputLocator() const { return fInputLocator; }
+    const std::string& LogLevel() const { return fLogLevel; }
+    const std::string& Device() const { return fDevice; }
+    bool CollectKernelTimes() const { return fCollectKernelTimes; }
+    int NumTimeslices() const { return fNumTimeslices; }
+    int SkipTimeslices() const { return fSkipTimeslices; }
+
+  private:
+    std::string fParamsDir;  // TODO: can we make this a std::path?
+    std::string fInputLocator;
+    std::string fLogLevel;
+    std::string fDevice;
+    bool fCollectKernelTimes = false;
+    int fNumTimeslices       = -1;
+    int fSkipTimeslices      = 0;
+  };
+
+}  // namespace cbm::algo
+
+#endif
diff --git a/algo/base/SubChain.h b/algo/base/SubChain.h
new file mode 100644
index 0000000000000000000000000000000000000000..13bfcf63aa47e9f46cb0ccd1b8e98ebb0188a5c0
--- /dev/null
+++ b/algo/base/SubChain.h
@@ -0,0 +1,25 @@
+/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+#ifndef CBM_ALGO_BASE_SUBCHAIN_H
+#define CBM_ALGO_BASE_SUBCHAIN_H
+
+#include <gsl/pointers>
+
+#include "ChainContext.h"
+
+namespace cbm::algo
+{
+  class SubChain {
+
+  public:
+    void SetContext(ChainContext* ctx) { fContext = ctx; }
+
+    const Options& Opts() const { return gsl::make_not_null(fContext)->opts; }
+
+  private:
+    ChainContext* fContext = nullptr;
+  };
+}  // namespace cbm::algo
+
+#endif
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..d998332d4a44bb45c5e6781828ff33544f8fc839
--- /dev/null
+++ b/algo/global/Reco.cxx
@@ -0,0 +1,13 @@
+/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+#include "Reco.h"
+
+using namespace cbm::algo;
+
+Reco::Reco() {}
+Reco::~Reco() {}
+
+void Reco::Init(const Options&) {}
+void Reco::Run() {}
+void Reco::Finalize() {}
diff --git a/algo/global/Reco.h b/algo/global/Reco.h
new file mode 100644
index 0000000000000000000000000000000000000000..de4faf55007a5c3aca19233eaac7bf989e3db0f4
--- /dev/null
+++ b/algo/global/Reco.h
@@ -0,0 +1,38 @@
+/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+#ifndef CBM_ALGO_GLOBAL_RECO_H
+#define CBM_ALGO_GLOBAL_RECO_H
+
+#include "SubChain.h"
+
+namespace fles
+{
+  class Timeslice;
+}  // namespace fles
+
+namespace cbm::algo
+{
+  class Options;
+
+  class Reco : SubChain {
+  public:
+    Reco();
+    ~Reco();
+
+    Reco(const Reco&) = delete;
+    Reco& operator=(const Reco&) = delete;
+    Reco(Reco&&)                 = delete;
+    Reco& operator=(Reco&&) = delete;
+
+    void Init(const Options&);
+    void Run();
+    void Finalize();
+
+  private:
+    bool fInitialized = false;
+    ChainContext fContext;
+  };
+}  // namespace cbm::algo
+
+#endif
diff --git a/reco/app/CMakeLists.txt b/reco/app/CMakeLists.txt
index c81df5543f7b511317bfe6bf35bb78ebe481e215..dfa25c623db964e1877102f184eda26f5cb15c1b 100644
--- a/reco/app/CMakeLists.txt
+++ b/reco/app/CMakeLists.txt
@@ -1 +1,2 @@
+add_subdirectory(cbmreco)
 add_subdirectory(cbmreco_fairrun)
diff --git a/reco/app/cbmreco/CMakeLists.txt b/reco/app/cbmreco/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4254675462e19b91cdff514ecff202686c158379
--- /dev/null
+++ b/reco/app/cbmreco/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(SRCS main.cxx)
+
+add_executable(cbmreco ${SRCS})
+target_link_libraries(cbmreco Algo)
+
+install(TARGETS cbmreco DESTINATION bin)
diff --git a/reco/app/cbmreco/main.cxx b/reco/app/cbmreco/main.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..bdee3f8582ac9f39950913fcd64e99a7dc2e730f
--- /dev/null
+++ b/reco/app/cbmreco/main.cxx
@@ -0,0 +1,17 @@
+/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+#include "Options.h"
+#include "Reco.h"
+
+int main(int argc, char** argv)
+{
+  cbm::algo::Options opts {argc, argv};
+
+  cbm::algo::Reco reco {};
+  reco.Init(opts);
+  reco.Run();
+  reco.Finalize();
+
+  return 0;
+}