diff --git a/Control/AthToolSupport/AsgTools/AsgTools/SgTEventMeta.h b/Control/AthToolSupport/AsgTools/AsgTools/SgTEventMeta.h
index 2fbc0a931d0580b5261cf44fd96c56bcf0637568..567184dfae0d516dd942bcae2a6c33dc621b09ae 100644
--- a/Control/AthToolSupport/AsgTools/AsgTools/SgTEventMeta.h
+++ b/Control/AthToolSupport/AsgTools/AsgTools/SgTEventMeta.h
@@ -13,6 +13,9 @@
 #   error "This header should not be used in Athena"
 #endif // XAOD_STANDALONE
 
+#include <memory>
+#include <vector>
+
 // Forward declaration(s):
 namespace xAOD {
    class TEvent;
diff --git a/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/ATLAS_CHECK_THREAD_SAFETY b/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/ATLAS_CHECK_THREAD_SAFETY
new file mode 100644
index 0000000000000000000000000000000000000000..e6ccd312a2759d50dedd9b132eec468edec1bc13
--- /dev/null
+++ b/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/ATLAS_CHECK_THREAD_SAFETY
@@ -0,0 +1 @@
+PhysicsAnalysis/D3PDTools/AnaAlgorithm
diff --git a/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AlgorithmWorkerData.h b/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AlgorithmWorkerData.h
index 714967b7b8bd3e73bdf740edbda481b14d43023e..d99eb32b1c54f8478e599fd3d02e8dc1f0a066b7 100644
--- a/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AlgorithmWorkerData.h
+++ b/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AlgorithmWorkerData.h
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
 */
 
 /// @author Nils Krumnack
@@ -10,8 +10,10 @@
 #define ANA_ALGORITHM__ALGORITHM_WORKER_DATA_H
 
 #ifndef ROOTCORE
+#ifndef __CPPCHECK__
 #error only include this header in AnalysisBase
 #endif
+#endif
 
 namespace asg
 {
diff --git a/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AnaAlgorithm.h b/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AnaAlgorithm.h
index a44020341c7e7fc19cf7bba7218051a97abc8aa3..93ee4e57c2a05bcbd5af55a262a6ecae910d3f12 100644
--- a/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AnaAlgorithm.h
+++ b/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AnaAlgorithm.h
@@ -1,6 +1,6 @@
 // Dear emacs, this is -*- c++ -*-
 /*
-  Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
 */
 
 /// @author Nils Krumnack
@@ -29,6 +29,7 @@
 class TH1;
 class TH2;
 class TH3;
+class TEfficiency;
 class TTree;
 class ISvcLocator;
 
@@ -108,15 +109,24 @@ namespace EL
 #ifdef XAOD_STANDALONE
     /// Type of the metadata store pointer in standalone mode
     typedef asg::SgTEventMeta* MetaStorePtr_t;
+    typedef const asg::SgTEventMeta* ConstMetaStorePtr_t;
 #else
     /// Type of the metadata store pointer in standalone mode
     typedef ServiceHandle< StoreGateSvc >& MetaStorePtr_t;
+    typedef const ServiceHandle< StoreGateSvc >& ConstMetaStorePtr_t;
 #endif // XAOD_STANDALONE
 
+    ///@{
     /// Accessor for the input metadata store
-    MetaStorePtr_t inputMetaStore() const;
+    ConstMetaStorePtr_t inputMetaStore() const;
+    MetaStorePtr_t inputMetaStore();
+    ///@}
+
+    ///@{
     /// Accessor for the output metadata store
-    MetaStorePtr_t outputMetaStore() const;
+    ConstMetaStorePtr_t outputMetaStore() const;
+    MetaStorePtr_t outputMetaStore();
+    ///@}
 
 #ifdef XAOD_STANDALONE
     /// \brief get the (main) event store for this algorithm
@@ -139,13 +149,23 @@ namespace EL
     ::StatusCode book (const TH1& hist);
 
 
+    /// \brief book the given histogram
+    /// \par Guarantee
+    ///   strong
+    /// \par Failures
+    ///   histogram booking error
+  public:
+    ::StatusCode book (const TEfficiency& hist);
+
+
     /// \brief get the histogram with the given name
     /// \par Guarantee
     ///   strong
     /// \par Failures
     ///   histogram not found
   public:
-    TH1 *hist (const std::string& name) const;
+    template<typename T=TH1>
+    T *hist (const std::string& name) const;
 
 
     /// \brief get the 2-d histogram with the given name
@@ -166,6 +186,20 @@ namespace EL
     TH3 *hist3d (const std::string& name) const;
 
 
+    /// \brief get the efficiency histogram with the given name
+    ///
+    /// This exists with two names, since the originally chosen name doesn't
+    /// match the name used in Athena.
+    ///
+    /// \par Guarantee
+    ///   strong
+    /// \par Failures
+    ///   histogram not found
+  public:
+    TEfficiency *histeff (const std::string& name) const;
+    TEfficiency *efficiency (const std::string& name) const;
+
+
     /// \brief the histogram worker interface
     /// \par Guarantee
     ///   strong
@@ -519,11 +553,11 @@ namespace EL
 
     /// \brief Object accessing the input metadata store
   private:
-    mutable MetaStore_t m_inputMetaStore;
+    MetaStore_t m_inputMetaStore;
 
     /// \brief Object accessing the output metadata store
   private:
-    mutable MetaStore_t m_outputMetaStore;
+    MetaStore_t m_outputMetaStore;
 
 #ifdef XAOD_STANDALONE
     /// \brief the value of \ref histogramWorker
diff --git a/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AnaAlgorithm.icc b/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AnaAlgorithm.icc
index 518ebcd747c5ce38b758c645981b119a84cddee6..6bd7c6da4a0a8083b992662933234747d1d3e161 100644
--- a/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AnaAlgorithm.icc
+++ b/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AnaAlgorithm.icc
@@ -16,4 +16,27 @@
 
 namespace EL
 {
+#ifdef XAOD_STANDALONE
+  template<typename T> T *AnaAlgorithm ::
+  hist (const std::string& name) const
+  {
+    T *result = dynamic_cast<T*>(hist<TObject>(name));
+    if (result == nullptr)
+      throw std::runtime_error ("histogram not of the right type: " + name + " " + typeid(T).name());
+    return result;
+  }
+
+
+
+  inline TEfficiency *AnaAlgorithm ::
+  efficiency (const std::string& name) const
+  {
+    return histeff (name);
+  }
+
+
+
+  template<> TObject *AnaAlgorithm ::
+  hist<TObject> (const std::string& name) const;
+#endif
 }
diff --git a/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AnaAlgorithmConfig.h b/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AnaAlgorithmConfig.h
index 69065c73d5aa08b44c89158a825631e266e903d1..b5ae1be731e48d8bb05dd37794a6377b081733cb 100644
--- a/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AnaAlgorithmConfig.h
+++ b/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AnaAlgorithmConfig.h
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
 */
 
 /// @author Nils Krumnack
@@ -10,8 +10,10 @@
 #define ANA_ALGORITHM__ANA_ALGORITHM_CONFIG_H
 
 #ifndef ROOTCORE
+#ifndef __CPPCHECK__
 #error only include this header in AnalysisBase
 #endif
+#endif
 
 #include <AnaAlgorithm/Global.h>
 
diff --git a/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AnaReentrantAlgorithmConfig.h b/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AnaReentrantAlgorithmConfig.h
index 49133783353ba93f17d5a0e90c8a656ed23996e6..c2b106c5b6825f59cfdd8713b4a0f2717e71cc98 100644
--- a/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AnaReentrantAlgorithmConfig.h
+++ b/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/AnaReentrantAlgorithmConfig.h
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
 */
 
 /// @author Nils Krumnack
@@ -10,8 +10,10 @@
 #define ANA_ALGORITHM__ANA_REENTRANT_ALGORITHM_CONFIG_H
 
 #ifndef ROOTCORE
+#ifndef __CPPCHECK__
 #error only include this header in AnalysisBase
 #endif
+#endif
 
 #include <AnaAlgorithm/Global.h>
 
diff --git a/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/IHistogramWorker.h b/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/IHistogramWorker.h
index c1861d04df3baf90bd77e6e9af7f7dc1037a6aeb..f86cfa284b4eab0097aa7a7fb1ea7701b47edb5e 100644
--- a/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/IHistogramWorker.h
+++ b/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/IHistogramWorker.h
@@ -58,7 +58,7 @@ namespace EL
     ///   object not found
     /// \post result != 0
   public:
-    virtual TH1 *getOutputHist (const std::string& name) const = 0;
+    virtual TObject *getOutputHist (const std::string& name) const = 0;
   };
 }
 
diff --git a/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/PythonConfigBase.h b/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/PythonConfigBase.h
index 721b389cc0e275d007138e9ac34c09b0abecf2c2..11e349e2a3bed374f412534352abe3055d8b2ab3 100644
--- a/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/PythonConfigBase.h
+++ b/PhysicsAnalysis/D3PDTools/AnaAlgorithm/AnaAlgorithm/PythonConfigBase.h
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
 */
 
 /// @author Nils Krumnack
@@ -10,8 +10,10 @@
 #define ANA_ALGORITHM__PYTHON_CONFIG_BASE_H
 
 #ifndef ROOTCORE
+#ifndef __CPPCHECK__
 #error only include this header in AnalysisBase
 #endif
+#endif
 
 #include <AnaAlgorithm/Global.h>
 
diff --git a/PhysicsAnalysis/D3PDTools/AnaAlgorithm/Root/AnaAlgorithm.cxx b/PhysicsAnalysis/D3PDTools/AnaAlgorithm/Root/AnaAlgorithm.cxx
index 0ab827e9add99b3f30d98b1738444a7747d31dd4..7791426adeeba5a7ac57015690b6263c123214bf 100644
--- a/PhysicsAnalysis/D3PDTools/AnaAlgorithm/Root/AnaAlgorithm.cxx
+++ b/PhysicsAnalysis/D3PDTools/AnaAlgorithm/Root/AnaAlgorithm.cxx
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
 */
 
 /// @author Nils Krumnack
@@ -16,6 +16,7 @@
 #include <TH1.h>
 #include <TH2.h>
 #include <TH3.h>
+#include <TEfficiency.h>
 #include <stdexcept>
 
 #ifdef XAOD_STANDALONE
@@ -68,7 +69,16 @@ namespace EL
 
 
 
-  AnaAlgorithm::MetaStorePtr_t AnaAlgorithm::inputMetaStore() const
+  AnaAlgorithm::ConstMetaStorePtr_t AnaAlgorithm::inputMetaStore() const
+  {
+#ifdef XAOD_STANDALONE
+     return &m_inputMetaStore;
+#else
+     return m_inputMetaStore;
+#endif // XAOD_STANDALONE
+  }
+
+  AnaAlgorithm::MetaStorePtr_t AnaAlgorithm::inputMetaStore()
   {
 #ifdef XAOD_STANDALONE
      return &m_inputMetaStore;
@@ -79,7 +89,16 @@ namespace EL
 
 
 
-  AnaAlgorithm::MetaStorePtr_t AnaAlgorithm::outputMetaStore() const
+  AnaAlgorithm::ConstMetaStorePtr_t AnaAlgorithm::outputMetaStore() const
+  {
+#ifdef XAOD_STANDALONE
+     return &m_outputMetaStore;
+#else
+     return m_outputMetaStore;
+#endif // XAOD_STANDALONE
+  }
+
+  AnaAlgorithm::MetaStorePtr_t AnaAlgorithm::outputMetaStore()
   {
 #ifdef XAOD_STANDALONE
      return &m_outputMetaStore;
@@ -110,8 +129,17 @@ namespace EL
 
 
 
-  TH1 *AnaAlgorithm ::
-  hist (const std::string& name) const
+  ::StatusCode AnaAlgorithm ::
+  book (const TEfficiency& hist)
+  {
+    histogramWorker()->addOutput (hist.Clone());
+    return ::StatusCode::SUCCESS;
+  }
+
+
+
+  template<> TObject *AnaAlgorithm ::
+  hist<TObject> (const std::string& name) const
   {
     return histogramWorker()->getOutputHist (name);
   }
@@ -121,10 +149,7 @@ namespace EL
   TH2 *AnaAlgorithm ::
   hist2d (const std::string& name) const
   {
-    TH2 *hist = dynamic_cast<TH2*>(histogramWorker()->getOutputHist (name));
-    if (hist == nullptr)
-      throw std::runtime_error ("histogram not a 2d-histogram: " + name);
-    return hist;
+    return hist<TH2>(name);
   }
 
 
@@ -132,10 +157,15 @@ namespace EL
   TH3 *AnaAlgorithm ::
   hist3d (const std::string& name) const
   {
-    TH3 *hist = dynamic_cast<TH3*>(histogramWorker()->getOutputHist (name));
-    if (hist == nullptr)
-      throw std::runtime_error ("histogram not a 3d-histogram: " + name);
-    return hist;
+    return hist<TH3>(name);
+  }
+
+
+
+  TEfficiency *AnaAlgorithm ::
+  histeff (const std::string& name) const
+  {
+    return hist<TEfficiency>(name);
   }
 
 
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/CMakeLists.txt b/PhysicsAnalysis/D3PDTools/EventLoop/CMakeLists.txt
index 5c543a240cfbb08a139f2c24d58f1afe111f87ab..54c2664774cb9dbcf2d22b2c4cd9d20b27ff855b 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/CMakeLists.txt
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/CMakeLists.txt
@@ -28,7 +28,7 @@ atlas_add_root_dictionary( EventLoop
    EventLoop/LLDriver.h EventLoop/LSFDriver.h EventLoop/LocalDriver.h
    EventLoop/OutputStream.h EventLoop/MetricsSvc.h EventLoop/SoGEDriver.h
    EventLoop/StatusCode.h EventLoop/TorqueDriver.h
-   EventLoop/VomsProxySvc.h EventLoop/SlurmDriver.h
+   EventLoop/VomsProxySvc.h EventLoop/SlurmDriver.h EventLoop/WorkerConfig.h
    Root/LinkDef.h
    EXTERNAL_PACKAGES ROOT )
 
@@ -53,11 +53,16 @@ atlas_add_test (EventLoop_gt_OutputStreamData
       INCLUDE_DIRS ${ROOT_INCLUDE_DIRS}
       LINK_LIBRARIES ${ROOT_LIBRARIES} EventLoop AsgTestingLib )
 
-atlas_add_test (EventLoop_gt_SubmitDirManager
+      atlas_add_test (EventLoop_gt_SubmitDirManager
       SOURCES test/gt_SubmitDirManager.cxx
       INCLUDE_DIRS ${ROOT_INCLUDE_DIRS}
       LINK_LIBRARIES ${ROOT_LIBRARIES} EventLoop AsgTestingLib )
 
+atlas_add_test (EventLoop_gt_DirectInputModule
+      SOURCES test/gt_DirectInputModule.cxx
+      INCLUDE_DIRS ${Boost_INCLUDE_DIRS} ${ROOT_INCLUDE_DIRS}
+      LINK_LIBRARIES ${Boost_LIBRARIES} ${ROOT_LIBRARIES} EventLoop AsgTestingLib )
+
 # Install files from the package:
 atlas_install_scripts( scripts/el_retrieve scripts/el_resubmit scripts/el_wait scripts/el_build_docker )
 atlas_install_data( data/*.root data/Dockerfile data/docker_analysis_setup.sh data/*.yml )
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/BatchInputModule.h b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/BatchInputModule.h
new file mode 100644
index 0000000000000000000000000000000000000000..5b7cd00f38709394fdcb2a4f9f3976674ea7c5b0
--- /dev/null
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/BatchInputModule.h
@@ -0,0 +1,44 @@
+/*
+  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Nils Krumnack
+
+
+#ifndef EVENT_LOOP__BATCH_INPUT_MODULE_H
+#define EVENT_LOOP__BATCH_INPUT_MODULE_H
+
+#include <EventLoop/Module.h>
+
+namespace EL
+{
+  struct BatchSample;
+  struct BatchSegment;
+
+  namespace Detail
+  {
+    /// @brief the @ref IInputModule implementation for the batch driver
+
+    class BatchInputModule final : public Module
+    {
+      /// Public Members
+      /// ==============
+
+    public:
+
+      BatchSample *sample = nullptr;
+      BatchSegment *segment = nullptr;
+
+
+
+      /// Inherited Members
+      /// =================
+
+    public:
+
+      StatusCode processInputs (ModuleData& data, IInputModuleActions& actions) override;
+    };
+  }
+}
+
+#endif
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/DirectInputModule.h b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/DirectInputModule.h
new file mode 100644
index 0000000000000000000000000000000000000000..da68e8c4991d62b33796f1a167d089ec6e355fe3
--- /dev/null
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/DirectInputModule.h
@@ -0,0 +1,44 @@
+/*
+  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Nils Krumnack
+
+
+#ifndef EVENT_LOOP__DIRECT_INPUT_MODULE_H
+#define EVENT_LOOP__DIRECT_INPUT_MODULE_H
+
+#include <EventLoop/Module.h>
+#include <optional>
+#include <vector>
+
+namespace EL
+{
+  namespace Detail
+  {
+    /// @brief the @ref IInputModule implementation for the direct driver
+
+    class DirectInputModule final : public Module
+    {
+      /// Public Members
+      /// ==============
+
+    public:
+
+      std::vector<std::string> fileList;
+      std::optional<uint64_t> skipEvents;
+      std::optional<uint64_t> maxEvents;
+
+
+
+      /// Inherited Members
+      /// =================
+
+    public:
+
+      StatusCode processInputs (ModuleData& data, IInputModuleActions& actions) override;
+    };
+  }
+}
+
+#endif
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/GridReportingModule.h b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/GridReportingModule.h
index 993a386164e2f559917403578c119bbcf3382c9f..62f4b740d029583baf15059d3b7fd86cb03cd458 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/GridReportingModule.h
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/GridReportingModule.h
@@ -10,6 +10,7 @@
 #define EVENT_LOOP_GRID__GRID_REPORTING_MODULE_H
 
 #include <EventLoop/Module.h>
+#include <vector>
 
 namespace EL
 {
@@ -21,11 +22,19 @@ namespace EL
     class GridReportingModule final : public Module
     {
       /// the panda error code for bad input files
-    public:
       static constexpr int EC_BADINPUT = 223;
 
+      /// the list of files we processed
+      std::vector<std::string> m_files;
+
+      /// the number of events we processed
+      unsigned m_eventsProcessed = 0;
+
     public:
-      virtual void reportInputFailure (ModuleData& data);
+      virtual ::StatusCode onNewInputFile (ModuleData& data) override;
+      virtual ::StatusCode onExecute (ModuleData& data) override;
+      virtual ::StatusCode postFileClose (ModuleData& data) override;
+      virtual void reportInputFailure (ModuleData& data) override;
     };
   }
 }
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/IInputModuleActions.h b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/IInputModuleActions.h
new file mode 100644
index 0000000000000000000000000000000000000000..8cef35220b860ae6befe2b26506e95d81d924749
--- /dev/null
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/IInputModuleActions.h
@@ -0,0 +1,82 @@
+/*
+  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Nils Krumnack
+
+
+#ifndef EVENT_LOOP__I_INPUT_MODULE_ACTIONS_H
+#define EVENT_LOOP__I_INPUT_MODULE_ACTIONS_H
+
+#include <Rtypes.h>
+#include <optional>
+#include <string>
+
+class StatusCode;
+
+namespace EL
+{
+  struct EventRange;
+
+  namespace Detail
+  {
+    struct ModuleData;
+
+
+    /// @brief the actions that @ref Module::processInputs can perform
+    ///
+    /// The main reason to have this interface is that it is much easier to
+    /// write an input module if it can call functions to perform actions
+    /// directly (as opposed to e.g. returning which file to open next, etc.).
+    ///
+    /// There is a canonical implementation of this interface that is used in
+    /// the worker, but I'm using an abstract interface to decouple the input
+    /// modules from the actual worker implementation.  In addition, this makes
+    /// it easier to write tests for input modules, or to employ a decorator
+    /// pattern, should the need arise.
+    ///
+    /// This interface is not frozen, but reflects the need of the input modules
+    /// I have defined.
+
+    class IInputModuleActions
+    {
+    public:
+
+      /// @brief standard virtual destructor
+      virtual ~IInputModuleActions () noexcept = default;
+
+
+      /// \brief process the given event range
+      ///
+      /// This will update `eventRange` if the end is set to eof
+      ///
+      /// \par Guarantee
+      ///   basic
+      /// \par Failures
+      ///   file can't be opened\n
+      ///   event range exceeds length of file\n
+      ///   processing failures
+      virtual ::StatusCode processEvents (EventRange& eventRange) = 0;
+
+
+      /// \brief open the given input file without processing it
+      ///
+      /// This is mostly to allow the driver to query the number of
+      /// events in the input file without processing it, usually to
+      /// determine the range of events to process.
+      ///
+      /// \par Guarantee
+      ///   basic
+      /// \par Failures
+      ///   file can't be opened
+      virtual ::StatusCode openInputFile (const std::string& inputFileUrl) = 0;
+
+
+      /// \brief the number of events in the input file
+      /// \pre inputFile() != 0
+      [[nodiscard]] virtual Long64_t inputFileNumEntries () const = 0;
+    };
+  }
+}
+
+#endif
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/Job.h b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/Job.h
index 040d064bfd30a65838a204c52eceb76b0d94d54b..4527f771ca585d472fd2bfd45296e02b973e1e7a 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/Job.h
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/Job.h
@@ -23,11 +23,14 @@
 
 #include <EventLoop/Global.h>
 
-#include <vector>
+
 #include <AnaAlgorithm/Global.h>
 #include <EventLoop/JobConfig.h>
 #include <SampleHandler/SampleHandler.h>
 #include <SampleHandler/MetaObject.h>
+#include <vector>
+#include <memory>
+class AnaReentrantAlgorithmConfig;
 
 namespace asg
 {
@@ -218,6 +221,19 @@ namespace EL
     static const std::string optSkipEvents;
 
 
+    /// \brief a python configuration file that will be executed on
+    /// the worker
+    ///
+    /// This allows to inspect the meta-data of the first input file
+    /// and configure the job accordingly.
+    ///
+    /// EXPERIMENTAL: This feature is currently (23 Feb 23) new and
+    /// experimental and details of its implementation and usage may
+    /// still change.
+  public:
+    static const std::string optWorkerConfigFile;
+
+
     /// description: the name of the option for selecting the number
     ///   of files per batch job.  (only BatchDriver and derived
     ///   drivers).
@@ -463,6 +479,9 @@ namespace EL
     static const std::string optOfficial;
     static const std::string optVoms;
 
+    /// whether to use grid reporting even when not running on the grid
+    static const std::string optGridReporting;
+
     /// these options are defined in \ref SH::MetaNames
     /// \{
 
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/MessageCheck.h b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/MessageCheck.h
index 81c2639d850aa5814b99b1d64b83f5d29672c8bd..abbfd15579523da5081edc67ec5e734b43d489f1 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/MessageCheck.h
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/MessageCheck.h
@@ -11,7 +11,8 @@
 
 #include <EventLoop/Global.h>
 
-#include <AsgTools/MessageCheck.h>
+#include <AsgMessaging/MessageCheck.h>
+#include <exception>
 
 namespace EL
 {
@@ -20,7 +21,7 @@ namespace EL
   namespace Detail
   {
     /// \brief print out the currently evaluated exception
-    void report_exception ();
+    void report_exception (std::exception_ptr eptr);
   }
 }
 
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/Module.h b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/Module.h
index 0dd63c214d502edbee3758855c818b71de465666..53d92ec4c5e3fca9296da2928af53c1faf3a0fa3 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/Module.h
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/Module.h
@@ -17,6 +17,9 @@ namespace EL
 {
   namespace Detail
   {
+    class IInputModuleActions;
+
+
     /// \brief the base class for EventLoop instrumentation module
     ///
     /// These are **internal** modules for EventLoop that allow to
@@ -71,6 +74,15 @@ namespace EL
       virtual ::StatusCode onInitialize (ModuleData& data);
 
 
+      /// \brief process all input files
+      ///
+      /// This deviates slightly from the usual pattern for module functions in
+      /// that I pass in the possible actions as an argument.  See @ref
+      /// IInputModuleActions for details.
+    public:
+      virtual StatusCode processInputs (ModuleData& data, IInputModuleActions& actions);
+
+
       /// \brief action after processing first event
       ///
       /// This is mostly meant to set up benchmarks that record
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/ModuleData.h b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/ModuleData.h
index 2b9db442f356b95601261c458dbbfdc5ef9a089f..cf3efe724ec4b02eefb71cab8b11d1da6dd28465 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/ModuleData.h
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/ModuleData.h
@@ -106,6 +106,13 @@ namespace EL
       std::map<std::string,Detail::OutputStreamData> m_outputs;
 
 
+
+      /// \brief explicit constructor for dependency reduction
+      ModuleData () noexcept;
+
+      /// \brief explicit destructor for dependency reduction
+      ~ModuleData () noexcept;
+
       /// \brief add the given output object to the histogram output stream
       /// \par Guarantee
       ///   basic
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/OutputStreamData.h b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/OutputStreamData.h
index bf7aa0b46e7990ac1d3dd34e3022f9c00e558947..217c0438cb5254dfc00496df0c28dd0a3e327dd8 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/OutputStreamData.h
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/OutputStreamData.h
@@ -144,7 +144,7 @@ namespace EL
       /// \par Guarantee
       ///   no-fail
     public:
-      TH1 *getOutputHist (const std::string& name) const noexcept;
+      TObject *getOutputHist (const std::string& name) const noexcept;
 
 
       /// \brief get the output tree with the given name, or nullptr
@@ -171,7 +171,7 @@ namespace EL
 
       /// \brief the output histogram map
     private:
-      std::unordered_map<std::string,TH1*> m_outputHistMap;
+      std::unordered_map<std::string,TObject*> m_outputHistMap;
 
       /// \brief the output tree map
     private:
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/Worker.h b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/Worker.h
index 89f2816e2aa5e8063622810a925eca1f55988cc8..86ac5348f4a031432634ab9fa621d2265446484e 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/Worker.h
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/Worker.h
@@ -7,6 +7,7 @@
 
 #include <EventLoop/Global.h>
 
+#include <EventLoop/IInputModuleActions.h>
 #include <EventLoop/IWorker.h>
 #include <EventLoop/ModuleData.h>
 #include <EventLoop/OutputStreamData.h>
@@ -14,7 +15,7 @@
 
 namespace EL
 {
-  class Worker final : public IWorker, private Detail::ModuleData
+  class Worker final : public IWorker, private Detail::ModuleData, private Detail::IInputModuleActions
   {
     //
     // public interface
@@ -69,7 +70,7 @@ namespace EL
     ///   object not found
     /// \post result != 0
   public:
-    TH1 *getOutputHist (const std::string& name) const final override;
+    TObject *getOutputHist (const std::string& name) const final override;
 
 
     /// effects: get the output file that goes into the dataset with
@@ -238,14 +239,6 @@ namespace EL
   public:
     ::StatusCode gridExecute (const std::string& sampleName);
 
-
-  private:
-    void gridNotifyJobFinished(uint64_t eventsProcessed,
-                           const std::vector<std::string>& fileList);
-
-  private:
-    void gridAbort();
-
   private:    
     enum GridErrorCodes {
       EC_FAIL = 220,
@@ -321,6 +314,15 @@ namespace EL
     ::StatusCode initialize ();
 
 
+    /// \brief process all the inputs
+    ///
+    /// This method ought to be called after @ref initialize and before @ref
+    /// finalize.  It will rely on the defined modules to steer it to the files
+    /// and events it ought to process.
+  protected:
+    ::StatusCode processInputs ();
+
+
     /// \brief finalize the worker
     ///
     /// This method ought to be called after all events have been
@@ -346,7 +348,7 @@ namespace EL
     ///   event range exceeds length of file\n
     ///   processing failures
   protected:
-    ::StatusCode processEvents (EventRange& eventRange);
+    ::StatusCode processEvents (EventRange& eventRange) override;
 
 
     /// \brief open the given input file without processing it
@@ -360,7 +362,7 @@ namespace EL
     /// \par Failures
     ///   file can't be opened
   protected:
-    ::StatusCode openInputFile (std::string inputFileUrl);
+    ::StatusCode openInputFile (const std::string& inputFileUrl) override;
 
 
     /// effects: add another output file
@@ -387,7 +389,7 @@ namespace EL
     ///   no-fail
     /// \pre inputFile() != 0
   protected:
-    Long64_t inputFileNumEntries () const;
+    Long64_t inputFileNumEntries () const override;
 
 
     /// \brief the number of events that have been processed
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/WorkerConfig.h b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/WorkerConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..4e616fe748ec654226b9074dba242c4ff9ca3a2c
--- /dev/null
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/WorkerConfig.h
@@ -0,0 +1,80 @@
+/*
+  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Nils Krumnack
+
+
+#ifndef EVENT_LOOP__WORKER_CONFIG_H
+#define EVENT_LOOP__WORKER_CONFIG_H
+
+#include <EventLoop/Global.h>
+
+#include <AsgTools/SgTEventMeta.h>
+#include <TObject.h>
+
+namespace EL
+{
+  namespace Detail
+  {
+    class ModuleData;
+  }
+
+  class PythonConfigBase;
+
+
+  class WorkerConfig final : public TObject
+  {
+    /// Public Members
+    /// ==============
+
+  public:
+
+    /// \brief access the meta store in the input file
+    [[nodiscard]] const asg::SgTEventMeta *metaStore() const noexcept;
+
+
+    /// \brief add the given component
+    void add (const PythonConfigBase& config);
+
+
+
+    /// Internal/Detail Members
+    /// =======================
+
+  public:
+
+    WorkerConfig (Detail::ModuleData *val_data) noexcept;
+    ~WorkerConfig () noexcept;
+
+
+
+    /// Private Members
+    /// ===============
+
+  private:
+
+    Detail::ModuleData *m_data = nullptr; //!
+    asg::SgTEventMeta m_metaStore; //!
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpragmas"
+#pragma GCC diagnostic ignored "-Wunknown-pragmas"
+#pragma GCC diagnostic ignored "-Winconsistent-missing-override"
+    ClassDef(WorkerConfig, 1);
+#pragma GCC diagnostic pop
+  };
+
+
+
+  /// Inline/Template Functions
+  /// =========================
+
+  [[nodiscard]] inline const asg::SgTEventMeta *WorkerConfig ::
+  metaStore() const noexcept
+  {
+    return &m_metaStore;
+  }
+}
+
+#endif
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/WorkerConfigModule.h b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/WorkerConfigModule.h
new file mode 100644
index 0000000000000000000000000000000000000000..6352226f42d0e1898d876c7db1cea2ae7646458f
--- /dev/null
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/EventLoop/WorkerConfigModule.h
@@ -0,0 +1,31 @@
+/*
+  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Nils Krumnack
+
+
+#ifndef EVENT_LOOP__WORKER_CONFIG_MODULE_H
+#define EVENT_LOOP__WORKER_CONFIG_MODULE_H
+
+#include <EventLoop/Global.h>
+
+#include <EventLoop/Module.h>
+
+namespace EL
+{
+  namespace Detail
+  {
+    /// \brief a \ref Module implementation for running user
+    /// configuration on the worker node
+
+    class WorkerConfigModule final : public Module
+    {
+    public:
+
+      virtual StatusCode onInitialize (ModuleData& data) override;
+    };
+  }
+}
+
+#endif
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/Root/Algorithm.cxx b/PhysicsAnalysis/D3PDTools/EventLoop/Root/Algorithm.cxx
index e0be18661d1bc7d284e91c7f1899c05577735492..0d6f4694cc3b6d7d810dad7a26bd6a561e12c3ab 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/Root/Algorithm.cxx
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/Root/Algorithm.cxx
@@ -64,7 +64,7 @@ namespace EL
   hist (const std::string& name) const
   {
     RCU_READ_INVARIANT (this);
-    return wk()->getOutputHist (name);
+    return dynamic_cast<TH1*>(wk()->getOutputHist (name));
   }
 
 
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/Root/AlgorithmStateModule.cxx b/PhysicsAnalysis/D3PDTools/EventLoop/Root/AlgorithmStateModule.cxx
index 769eec9d08f74ff770f7bf7abfa88b8efde88222..dee879da225667fac29213bb98116ef69d41c4d9 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/Root/AlgorithmStateModule.cxx
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/Root/AlgorithmStateModule.cxx
@@ -20,6 +20,7 @@
 #include <EventLoop/Worker.h>
 #include <RootCoreUtils/Assert.h>
 #include <TTree.h>
+#include <exception>
 
 //
 // method implementations
@@ -47,7 +48,7 @@ namespace EL
             }
           } catch (...)
           {
-            report_exception ();
+            report_exception (std::current_exception());
             ANA_MSG_ERROR ("executing " << funcName << " on algorithm " << alg->getName());
             return StatusCode::FAILURE;
           }
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/Root/BatchDriver.cxx b/PhysicsAnalysis/D3PDTools/EventLoop/Root/BatchDriver.cxx
index f6d161f2aac10765e8009f498c4ec8cf549e6c28..9afd0685c8aede8fd50f20956f6dc458aedad229 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/Root/BatchDriver.cxx
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/Root/BatchDriver.cxx
@@ -50,7 +50,7 @@ namespace EL
     /// guarantee: basic
     /// failures: out of memory II
     void fillJob (BatchJob& myjob, const Job& job,
-		  const std::string& submitDir)
+                  const std::string& submitDir)
     {
       myjob.job = job;
       myjob.location = submitDir;
@@ -64,7 +64,7 @@ namespace EL
     /// guarantee: basic
     /// failures: out of memory II
     void fillSample (BatchSample& mysample,
-		     const SH::Sample& sample, const SH::MetaObject& meta)
+                     const SH::Sample& sample, const SH::MetaObject& meta)
     {
       mysample.name = sample.name();
       mysample.meta = *sample.meta();
@@ -82,7 +82,7 @@ namespace EL
     /// failures: segment misconfiguration
     /// requires: !segments.empty()
     void addSample (BatchJob& job, BatchSample sample,
-		    const std::vector<BatchSegment>& segments)
+                    const std::vector<BatchSegment>& segments)
     {
       RCU_REQUIRE (!segments.empty());
 
@@ -92,7 +92,7 @@ namespace EL
       RCU_ASSERT (segments[0].begin_event == 0);
       for (std::size_t iter = 0, end = segments.size(); iter != end; ++ iter)
       {
-	BatchSegment segment = segments[iter];
+        BatchSegment segment = segments[iter];
 
         segment.sampleName = sample.name;
         {
@@ -106,19 +106,19 @@ namespace EL
           segment.segmentName = myname.str();
         }
 
-	segment.sample = job.samples.size();
-	segment.job_id = job.segments.size();
-	if (iter+1 < end)
-	{
-	  segment.end_file  = segments[iter+1].begin_file;
-	  segment.end_event = segments[iter+1].begin_event;
-	} else
-	{
-	  segment.end_file = sample.files.size();
-	  segment.end_event = 0;
-	}
-	RCU_ASSERT (segment.begin_file < segment.end_file || (segment.begin_file == segment.end_file && segment.begin_event <= segment.end_event));
-	job.segments.push_back (segment);
+        segment.sample = job.samples.size();
+        segment.job_id = job.segments.size();
+        if (iter+1 < end)
+        {
+          segment.end_file  = segments[iter+1].begin_file;
+          segment.end_event = segments[iter+1].begin_event;
+        } else
+        {
+          segment.end_file = sample.files.size();
+          segment.end_event = 0;
+        }
+        RCU_ASSERT (segment.begin_file < segment.end_file || (segment.begin_file == segment.end_file && segment.begin_event <= segment.end_event));
+        job.segments.push_back (segment);
       }
 
       sample.end_segments = job.segments.size();
@@ -137,19 +137,19 @@ namespace EL
     /// requires: numJobs == rint (numJobs)
     /// postcondition: !segments.empty()
     void splitSampleByFile (const BatchSample& sample,
-			    std::vector<BatchSegment>& segments,
-			    double numJobs)
+                            std::vector<BatchSegment>& segments,
+                            double numJobs)
     {
       // RCU_REQUIRE (!sample.files.empty());
       RCU_REQUIRE (numJobs == rint (numJobs));
       RCU_REQUIRE (numJobs <= sample.files.size());
 
       for (std::size_t index = 0, end = numJobs;
-	   index < end; ++ index)
+           index < end; ++ index)
       {
-	BatchSegment segment;
-	segment.begin_file = rint (index * (sample.files.size() / numJobs));
-	segments.push_back (segment);
+        BatchSegment segment;
+        segment.begin_file = rint (index * (sample.files.size() / numJobs));
+        segments.push_back (segment);
       }
     }
 
@@ -165,9 +165,9 @@ namespace EL
     /// requires: numJobs == rint (numJobs)
     /// postcondition: !segments.empty()
     void splitSampleByEvent (const BatchSample& sample,
-			     std::vector<BatchSegment>& segments,
-			     const std::vector<Long64_t>& eventsFile,
-			     double numJobs)
+                             std::vector<BatchSegment>& segments,
+                             const std::vector<Long64_t>& eventsFile,
+                             double numJobs)
     {
       RCU_REQUIRE (!sample.files.empty());
       RCU_REQUIRE (sample.files.size() == eventsFile.size());
@@ -175,29 +175,29 @@ namespace EL
 
       Long64_t eventsSum = 0;
       for (std::vector<Long64_t>::const_iterator nevents = eventsFile.begin(),
-	     end = eventsFile.end(); nevents != end; ++ nevents)
+             end = eventsFile.end(); nevents != end; ++ nevents)
       {
-	RCU_ASSERT_SOFT (*nevents >= 0);
-	eventsSum += *nevents;
+        RCU_ASSERT_SOFT (*nevents >= 0);
+        eventsSum += *nevents;
       }
       if (numJobs > eventsSum)
-	numJobs = eventsSum;
+        numJobs = eventsSum;
       Long64_t eventsMax
-	= sample.meta.castDouble (Job::optEventsPerWorker);
+        = sample.meta.castDouble (Job::optEventsPerWorker);
       if (eventsMax > 0)
-	numJobs = ceil (double (eventsSum) / eventsMax);
+        numJobs = ceil (double (eventsSum) / eventsMax);
       eventsMax = Long64_t (ceil (double (eventsSum) / numJobs));
 
       BatchSegment segment;
       while (std::size_t (segment.begin_file) < eventsFile.size())
       {
-	while (segment.begin_event < eventsFile[segment.begin_file])
-	{
-	  segments.push_back (segment);
-	  segment.begin_event += eventsMax;
-	}
-	segment.begin_event -= eventsFile[segment.begin_file];
-	++ segment.begin_file;
+        while (segment.begin_event < eventsFile[segment.begin_file])
+        {
+          segments.push_back (segment);
+          segment.begin_event += eventsMax;
+        }
+        segment.begin_event -= eventsFile[segment.begin_file];
+        ++ segment.begin_file;
       }
       RCU_ASSERT (segments.size() == numJobs);
     }
@@ -213,7 +213,7 @@ namespace EL
     /// requires: !sample.files.empty()
     /// postcondition: !segments.empty()
     void splitSample (const BatchSample& sample,
-		      std::vector<BatchSegment>& segments)
+                      std::vector<BatchSegment>& segments)
     {
       // RCU_REQUIRE (!sample.files.empty());
 
@@ -223,36 +223,36 @@ namespace EL
       //   it will balance out things slightly if we only have a few
       //   files and multiple files per job.
       const double filesPerWorker
-	= sample.meta.castDouble (Job::optFilesPerWorker, 1);
+        = sample.meta.castDouble (Job::optFilesPerWorker, 1);
       if (filesPerWorker < 1)
       {
-	std::ostringstream msg;
-	msg << "invalid number of files per worker: " << filesPerWorker;
-	RCU_THROW_MSG (msg.str());
+        std::ostringstream msg;
+        msg << "invalid number of files per worker: " << filesPerWorker;
+        RCU_THROW_MSG (msg.str());
       }
       double numJobs = ceil (sample.files.size() / filesPerWorker);
 
       const TObject *meta
-	= sample.meta.get (SH::MetaFields::numEventsPerFile);
+        = sample.meta.get (SH::MetaFields::numEventsPerFile);
       if (meta)
       {
-	const SH::MetaVector<Long64_t> *const meta_nentries
-	  = dynamic_cast<const SH::MetaVector<Long64_t> *>(meta);
-	RCU_ASSERT_SOFT (meta == meta_nentries);
-	RCU_ASSERT_SOFT (meta_nentries->value.size() == sample.files.size());
-	splitSampleByEvent (sample, segments, meta_nentries->value, numJobs);
+        const SH::MetaVector<Long64_t> *const meta_nentries
+          = dynamic_cast<const SH::MetaVector<Long64_t> *>(meta);
+        RCU_ASSERT_SOFT (meta == meta_nentries);
+        RCU_ASSERT_SOFT (meta_nentries->value.size() == sample.files.size());
+        splitSampleByEvent (sample, segments, meta_nentries->value, numJobs);
       } else
       {
-	splitSampleByFile (sample, segments, numJobs);
+        splitSampleByFile (sample, segments, numJobs);
       }
 
 
       if (segments.empty())
       {
-	// rationale: this isn't really the proper thing to do.  if a
-	//   sample is empty I should just run the job locally.
-	BatchSegment empty;
-	segments.push_back (empty);
+        // rationale: this isn't really the proper thing to do.  if a
+        //   sample is empty I should just run the job locally.
+        BatchSegment empty;
+        segments.push_back (empty);
       }
 
       RCU_PROVIDE (!segments.empty());
@@ -264,23 +264,23 @@ namespace EL
     /// guarantee: basic
     /// failures: out of memory II
     void fillFullJob (BatchJob& myjob, const Job& job,
-		      const std::string& location,
-		      const SH::MetaObject& meta)
+                      const std::string& location,
+                      const SH::MetaObject& meta)
     {
       fillJob (myjob, job, location);
       *myjob.job.options() = meta;
 
       for (std::size_t sampleIndex = 0, end = job.sampleHandler().size();
-	   sampleIndex != end; ++ sampleIndex)
+           sampleIndex != end; ++ sampleIndex)
       {
-	BatchSample mysample;
-	fillSample (mysample, *job.sampleHandler()[sampleIndex], meta);
+        BatchSample mysample;
+        fillSample (mysample, *job.sampleHandler()[sampleIndex], meta);
 
-	std::vector<BatchSegment> subsegments;
-	splitSample (mysample, subsegments);
-	myjob.njobs_old.push_back (subsegments.size());
+        std::vector<BatchSegment> subsegments;
+        splitSample (mysample, subsegments);
+        myjob.njobs_old.push_back (subsegments.size());
 
-	addSample (myjob, mysample, subsegments);
+        addSample (myjob, mysample, subsegments);
       }
     }
   }
@@ -443,15 +443,15 @@ namespace EL
     case Detail::ManagerStep::batchJobStatusResubmit:
     case Detail::ManagerStep::batchJobStatusRetrieve:
       {
-	for (std::size_t job = 0; job != data.batchJob->segments.size(); ++ job)
-	{
-	  std::ostringstream completedFile;
-	  completedFile << data.submitDir << "/status/completed-" << job;
+        for (std::size_t job = 0; job != data.batchJob->segments.size(); ++ job)
+        {
+          std::ostringstream completedFile;
+          completedFile << data.submitDir << "/status/completed-" << job;
           const bool hasCompleted =
             (gSystem->AccessPathName (completedFile.str().c_str()) == 0);
 
-	  std::ostringstream failFile;
-	  failFile << data.submitDir << "/status/fail-" << job;
+          std::ostringstream failFile;
+          failFile << data.submitDir << "/status/fail-" << job;
           const bool hasFail =
             (gSystem->AccessPathName (failFile.str().c_str()) == 0);
 
@@ -552,8 +552,18 @@ namespace EL
 
     // <path of build dir>/x86_64-slc6-gcc62-opt (comes from CMake, we need this)
     const char *WORKDIR_DIR         = getenv ("WorkDir_DIR");
-    if (WORKDIR_DIR == nullptr)
-      RCU_THROW_MSG ("could not find environment variable $WorkDir_DIR");
+    // As a backup, keep the CMAKE_PREFIX_PATH
+    std::string CMAKE_DIR_str ( getenv ("CMAKE_PREFIX_PATH") );
+    if (WORKDIR_DIR == nullptr){
+      msgEventLoop::ANA_MSG_INFO ("Could not find environment variable $WorkDir_DIR");
+      // Instead, build from the first path in CMAKE_PREFIX_PATH
+      if (CMAKE_DIR_str.find(":") != std::string::npos){
+        // Erase everything from the colon onwards
+        CMAKE_DIR_str.erase( CMAKE_DIR_str.find(":") , std::string::npos );
+      }
+      // Provide the remainder of the string to the workdir
+      WORKDIR_DIR = CMAKE_DIR_str.data();
+    }
 
     if(!data.sharedFileSystem)
     {
@@ -724,7 +734,7 @@ namespace EL
 
     RCU_ASSERT (data.batchJob->njobs_old.size() == data.batchJob->samples.size());
     for (std::size_t sample = 0, end = data.batchJob->samples.size();
-	 sample != end; ++ sample)
+         sample != end; ++ sample)
     {
       const BatchSample& mysample (data.batchJob->samples[sample]);
 
@@ -734,62 +744,62 @@ namespace EL
       {
         ANA_MSG_VERBOSE ("merge files for sample " << data.batchJob->samples[sample].name);
 
-	bool complete = true;
-	std::vector<std::string> input;
-	for (std::size_t segment = mysample.begin_segments,
-	       end = mysample.end_segments; segment != end; ++ segment)
-	{
-	  const BatchSegment& mysegment = data.batchJob->segments[segment];
+        bool complete = true;
+        std::vector<std::string> input;
+        for (std::size_t segment = mysample.begin_segments,
+               end = mysample.end_segments; segment != end; ++ segment)
+        {
+          const BatchSegment& mysegment = data.batchJob->segments[segment];
 
-	  const std::string hist_file = origHistOutput->targetURL
+          const std::string hist_file = origHistOutput->targetURL
             (mysegment.sampleName, mysegment.segmentName, ".root");
 
           ANA_MSG_VERBOSE ("merge segment " << segment << " completed=" << (data.batchJobSuccess.find(segment)!=data.batchJobSuccess.end()) << " fail=" << (data.batchJobFailure.find(segment)!=data.batchJobFailure.end()) << " unknown=" << (data.batchJobUnknown.find(segment)!=data.batchJobUnknown.end()));
 
-	  input.push_back (hist_file);
-
-	  if (data.batchJobFailure.find(segment)!=data.batchJobFailure.end())
-	  {
-	    std::ostringstream message;
-	    message << "subjob " << segment << "/" << mysegment.fullName
-		    << " failed";
-	    RCU_THROW_MSG (message.str());
-	  }
-	  else if (data.batchJobSuccess.find(segment)==data.batchJobSuccess.end())
-	    complete = false, result = false;
-	}
-	if (complete)
-	{
-	  RCU::hadd (output.str(), input);
-
-	  // Merge output data directories
-	  for (Job::outputIter out = data.batchJob->job.outputBegin(),
-		 end = data.batchJob->job.outputEnd(); out != end; ++ out)
-	    {
-	      output.str("");
-	      output << data.submitDir << "/data-" << out->label();
-
-	      if(gSystem->AccessPathName(output.str().c_str()))
-		gSystem->mkdir(output.str().c_str(),true);
-
-
-	      output << "/" << data.batchJob->samples[sample].name << ".root";
-
-	      std::vector<std::string> input;
-	      for (std::size_t segment = mysample.begin_segments,
-		     end = mysample.end_segments; segment != end; ++ segment)
-		{
-		  const BatchSegment& mysegment = data.batchJob->segments[segment];
-
-		  const std::string infile =
-		    data.submitDir + "/fetch/data-" + out->label() + "/" + mysegment.fullName + ".root";
-
-		  input.push_back (infile);
-		}
-
-	      RCU::hadd(output.str(), input);
-	    }
-	}
+          input.push_back (hist_file);
+
+          if (data.batchJobFailure.find(segment)!=data.batchJobFailure.end())
+          {
+            std::ostringstream message;
+            message << "subjob " << segment << "/" << mysegment.fullName
+                    << " failed";
+            RCU_THROW_MSG (message.str());
+          }
+          else if (data.batchJobSuccess.find(segment)==data.batchJobSuccess.end())
+            complete = false, result = false;
+        }
+        if (complete)
+        {
+          RCU::hadd (output.str(), input);
+
+          // Merge output data directories
+          for (Job::outputIter out = data.batchJob->job.outputBegin(),
+                 end = data.batchJob->job.outputEnd(); out != end; ++ out)
+            {
+              output.str("");
+              output << data.submitDir << "/data-" << out->label();
+
+              if(gSystem->AccessPathName(output.str().c_str()))
+                gSystem->mkdir(output.str().c_str(),true);
+
+
+              output << "/" << data.batchJob->samples[sample].name << ".root";
+
+              std::vector<std::string> input;
+              for (std::size_t segment = mysample.begin_segments,
+                     end = mysample.end_segments; segment != end; ++ segment)
+                {
+                  const BatchSegment& mysegment = data.batchJob->segments[segment];
+
+                  const std::string infile =
+                    data.submitDir + "/fetch/data-" + out->label() + "/" + mysegment.fullName + ".root";
+
+                  input.push_back (infile);
+                }
+
+              RCU::hadd(output.str(), input);
+            }
+        }
       }
     }
     return result;
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/Root/BatchInputModule.cxx b/PhysicsAnalysis/D3PDTools/EventLoop/Root/BatchInputModule.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..9749d3a5665e2fcbba020c47cc34fb2bdc9e91cb
--- /dev/null
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/Root/BatchInputModule.cxx
@@ -0,0 +1,55 @@
+/*
+  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Nils Krumnack
+
+
+//
+// includes
+//
+
+#include <EventLoop/BatchInputModule.h>
+
+#include <EventLoop/BatchSample.h>
+#include <EventLoop/BatchSegment.h>
+#include <EventLoop/EventRange.h>
+#include <EventLoop/IInputModuleActions.h>
+#include <EventLoop/MessageCheck.h>
+#include <RootCoreUtils/Assert.h>
+
+//
+// method implementations
+//
+
+namespace EL
+{
+  namespace Detail
+  {
+    StatusCode BatchInputModule ::
+    processInputs (ModuleData& /*data*/, IInputModuleActions& actions)
+    {
+      using namespace msgEventLoop;
+
+      Long64_t beginFile = segment->begin_file;
+      Long64_t endFile   = segment->end_file;
+      Long64_t lastFile  = segment->end_file;
+      RCU_ASSERT (beginFile <= endFile);
+      Long64_t beginEvent = segment->begin_event;
+      Long64_t endEvent   = segment->end_event;
+      if (endEvent > 0) endFile += 1;
+
+      for (Long64_t file = beginFile; file != endFile; ++ file)
+      {
+        RCU_ASSERT (std::size_t(file) < sample->files.size());
+        EventRange eventRange;
+        eventRange.m_url = sample->files[file];
+        eventRange.m_beginEvent = (file == beginFile ? beginEvent : 0);
+        eventRange.m_endEvent = (file == lastFile ? endEvent : EventRange::eof);
+        ANA_CHECK (actions.processEvents (eventRange));
+      }
+
+      return StatusCode::SUCCESS;
+    }
+  }
+}
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/Root/DirectInputModule.cxx b/PhysicsAnalysis/D3PDTools/EventLoop/Root/DirectInputModule.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..81a7c49780e1c3af84de2288dbf83b9bba46b742
--- /dev/null
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/Root/DirectInputModule.cxx
@@ -0,0 +1,78 @@
+/*
+  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Nils Krumnack
+
+
+//
+// includes
+//
+
+#include <EventLoop/DirectInputModule.h>
+
+#include <EventLoop/IInputModuleActions.h>
+#include <EventLoop/EventRange.h>
+#include <EventLoop/MessageCheck.h>
+#include <RootCoreUtils/Assert.h>
+
+//
+// method implementations
+//
+
+namespace EL
+{
+  namespace Detail
+  {
+    StatusCode DirectInputModule ::
+    processInputs (ModuleData& /*data*/, IInputModuleActions& actions)
+    {
+      using namespace msgEventLoop;
+      Long64_t toSkip = this->skipEvents.value_or (0);
+      std::optional<Long64_t> toProcess;
+      if (this->maxEvents.has_value())
+        toProcess = this->maxEvents.value();
+      for (const std::string& fileName : fileList)
+      {
+        // open the input file to inspect it
+        ANA_CHECK (actions.openInputFile (fileName));
+        ANA_MSG_DEBUG ("Opened input file: " << fileName);
+
+        EventRange eventRange;
+        eventRange.m_url = fileName;
+        eventRange.m_endEvent = actions.inputFileNumEntries();
+
+        if (toSkip > 0)
+        {
+          if (toSkip >= eventRange.m_endEvent)
+          {
+            toSkip -= eventRange.m_endEvent;
+            ANA_MSG_INFO ("File " << fileName << " has only " << eventRange.m_endEvent << " events, skipping it.");
+            continue;
+          }
+          eventRange.m_beginEvent = toSkip;
+          toSkip = 0u;
+        }
+
+        if (toProcess.has_value())
+        {
+          if (eventRange.m_endEvent >= eventRange.m_beginEvent + toProcess.value())
+          {
+            eventRange.m_endEvent = eventRange.m_beginEvent + toProcess.value();
+            toProcess = 0u;
+          } else
+          {
+            toProcess.value() -= eventRange.m_endEvent - eventRange.m_beginEvent;
+          }
+        }
+        ANA_CHECK (actions.processEvents (eventRange));
+        if (toProcess.has_value() && toProcess.value() == 0u)
+        {
+          ANA_MSG_INFO ("Reached maximum number of events, stopping.");
+          break;
+        }
+      }
+      return StatusCode::SUCCESS;
+    }
+  }
+}
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/Root/Driver.cxx b/PhysicsAnalysis/D3PDTools/EventLoop/Root/Driver.cxx
index b0285da0a63ecd4196298fc7d1837ef002ddf41e..84560f2a42b86d6b40dc2f2c66c4acef2f0c7eb7 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/Root/Driver.cxx
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/Root/Driver.cxx
@@ -212,7 +212,7 @@ namespace EL
     }
     std::string to = location;
     while (!to.empty() && to[to.size()-1] == '/')
-      to = to.substr (0,to.size()-1);
+      to.resize (to.size()-1);
     {
       SH::SampleHandler sh;
       sh.load (location + "/hist");
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/Root/GridReportingModule.cxx b/PhysicsAnalysis/D3PDTools/EventLoop/Root/GridReportingModule.cxx
index 738c7d7450c4e76e41d30022d43114e934b7af8a..5ee0e17d05cc1766e4557eaa08f7a414a672cb66 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/Root/GridReportingModule.cxx
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/Root/GridReportingModule.cxx
@@ -13,6 +13,9 @@
 #include <EventLoop/GridReportingModule.h>
 
 #include <EventLoop/MessageCheck.h>
+#include <EventLoop/ModuleData.h>
+#include <algorithm>
+#include <fstream>
 
 //
 // method implementations
@@ -22,6 +25,48 @@ namespace EL
 {
   namespace Detail
   {
+    ::StatusCode GridReportingModule ::
+    onNewInputFile (ModuleData& data)
+    {
+      if (std::find (m_files.begin(), m_files.end(), data.m_inputFileUrl) == m_files.end())
+        m_files.push_back (data.m_inputFileUrl);
+      return ::StatusCode::SUCCESS;
+    }
+
+
+
+    ::StatusCode GridReportingModule ::
+    onExecute (ModuleData& /*data*/)
+    {
+      ++m_eventsProcessed;
+      return ::StatusCode::SUCCESS;
+    }
+
+
+
+    ::StatusCode GridReportingModule ::
+    postFileClose (ModuleData& /*data*/)
+    {
+      using namespace msgEventLoop;
+
+      // createJobSummary
+      std::ofstream summaryfile("../AthSummary.txt");
+      if (summaryfile.is_open()) {
+        unsigned int nFiles = m_files.size();
+        summaryfile << "Files read: " << nFiles << std::endl;
+        for (auto& file : m_files)
+          summaryfile << "  " << file << std::endl;
+        summaryfile << "Events Read:    " << m_eventsProcessed << std::endl;
+        summaryfile.close();
+      }
+      else {
+        ANA_MSG_WARNING ("Failed to write summary file.");
+      }
+      return ::StatusCode::SUCCESS;
+    }
+
+
+
     void GridReportingModule ::
     reportInputFailure (ModuleData& /*data*/)
     {
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/Root/Job.cxx b/PhysicsAnalysis/D3PDTools/EventLoop/Root/Job.cxx
index 0799640f1c3b4ace6bf248632ca815c13beb34b7..437c4252142066eca2c5fc0e1850c26082fda9f5 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/Root/Job.cxx
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/Root/Job.cxx
@@ -37,6 +37,7 @@ namespace EL
   const std::string Job::optAlgorithmTimer = "nc_EventLoop_AlgorithmTimer";
   const std::string Job::optMaxEvents = "nc_EventLoop_MaxEvents";
   const std::string Job::optSkipEvents = "nc_EventLoop_SkipEvents";
+  const std::string Job::optWorkerConfigFile = "nc_EventLoop_WorkerConfigFile";
   const std::string Job::optFilesPerWorker = "nc_EventLoop_FilesPerWorker";
   const std::string Job::optEventsPerWorker = "nc_EventLoop_EventsPerWorker";
   const std::string Job::optWorkerPostClosedOutputsExecutable = "nc_EventLoop_WorkerPostClosedOutputsExecutable";
@@ -86,6 +87,7 @@ namespace EL
   const std::string Job::optGridCpuTimePerEvent = "nc_cpuTimePerEvent";
   const std::string Job::optGridMaxWalltime = "nc_maxWalltime";
   const std::string Job::optGridAvoidVP = "nc_avoidVP";
+  const std::string Job::optGridReporting = "nc_gridReporting";
   const std::string Job::optBatchSharedFileSystem = "nc_sharedFileSystem";
   const std::string Job::optBatchSlurmExtraConfigLines = "nc_SlurmExtraConfig";
   const std::string Job::optBatchSlurmWrapperExec = "nc_SlurmWrapperExec";
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/Root/LinkDef.h b/PhysicsAnalysis/D3PDTools/EventLoop/Root/LinkDef.h
index 0e12dff5f665550367b25f3e54fe91e03443e893..63a969859280f35a20d8e92219621866b382f3ea 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/Root/LinkDef.h
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/Root/LinkDef.h
@@ -35,6 +35,7 @@
 #pragma link C++ class EL::TorqueDriver+;
 #pragma link C++ class EL::VomsProxySvc+;
 #pragma link C++ class EL::IWorker+;
+#pragma link C++ class EL::WorkerConfig+;
 
 #pragma link C++ class std::pair<Long64_t,Long64_t>+;
 #pragma link C++ class std::vector<std::pair<Long64_t,Long64_t> >+;
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/Root/LocalDriver.cxx b/PhysicsAnalysis/D3PDTools/EventLoop/Root/LocalDriver.cxx
index c963edbbdb491f7ba4cf141623658b3da3644b94..33480e0f81b14adf8dd0c3bded15a805e8f7b2a5 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/Root/LocalDriver.cxx
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/Root/LocalDriver.cxx
@@ -85,7 +85,7 @@ namespace EL
           if (gSystem->MakeDirectory (basedirName.str().c_str()) != 0)
             RCU_THROW_MSG ("failed to create directory " + basedirName.str());
         }
-        auto submitSingle = [&, this] (std::size_t index) noexcept -> StatusCode
+        auto submitSingle = [&] (std::size_t index) noexcept -> StatusCode
         {
           try
           {
@@ -129,7 +129,7 @@ namespace EL
           bool abort = false;
           while (threads.size() < unsigned (numParallelProcs))
           {
-            threads.emplace_back ([&,this] () noexcept
+            threads.emplace_back ([&] () noexcept
             {
               std::unique_lock<std::mutex> lock (mutex);
               while (indexIter != data.batchJobIndices.end() && !abort)
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/Root/MessageCheck.cxx b/PhysicsAnalysis/D3PDTools/EventLoop/Root/MessageCheck.cxx
index 0bfee6cc4e4187577ffb6642f368f3d588f23c10..7cb80f43e1a925d59b81ea5e90cdbd60dd809cfb 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/Root/MessageCheck.cxx
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/Root/MessageCheck.cxx
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
 */
 
 /// @author Nils Krumnack
@@ -24,12 +24,14 @@ namespace EL
 
   namespace Detail
   {
-    void report_exception ()
+    void report_exception (std::exception_ptr eptr)
     {
       using namespace msgEventLoop;
       try
       {
-        throw;
+        if (eptr) {
+            std::rethrow_exception(eptr);
+        }
       } catch (std::exception& e)
       {
         ANA_MSG_ERROR ("caught exception: " << e.what());
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/Root/Module.cxx b/PhysicsAnalysis/D3PDTools/EventLoop/Root/Module.cxx
index f69757c491b42dbba7077a86e093e3b85c39b88d..28d1e206afdecbcede7050731cd7ee9934dd1ad0 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/Root/Module.cxx
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/Root/Module.cxx
@@ -80,6 +80,12 @@ namespace EL
       return ::StatusCode::SUCCESS;
     }
 
+    ::StatusCode Module ::
+    processInputs (ModuleData& /*data*/, IInputModuleActions& /*actions*/)
+    {
+      return ::StatusCode::SUCCESS;
+    }
+
     ::StatusCode Module ::
     onFinalize (ModuleData& /*data*/)
     {
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/Root/ModuleData.cxx b/PhysicsAnalysis/D3PDTools/EventLoop/Root/ModuleData.cxx
index f7275dec2a96893bbad1ecf344ed11c164d76e43..b5cefe823f55469a709759e735d1361816cd610b 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/Root/ModuleData.cxx
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/Root/ModuleData.cxx
@@ -14,6 +14,7 @@
 
 #include <EventLoop/OutputStreamData.h>
 #include <RootCoreUtils/Assert.h>
+#include <TTree.h>
 
 //
 // method implementations
@@ -23,6 +24,12 @@ namespace EL
 {
   namespace Detail
   {
+    ModuleData :: ModuleData () noexcept = default;
+
+    ModuleData :: ~ModuleData () noexcept = default;
+
+
+
     void ModuleData ::
     addOutput (std::unique_ptr<TObject> output)
     {
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/Root/OutputStreamData.cxx b/PhysicsAnalysis/D3PDTools/EventLoop/Root/OutputStreamData.cxx
index 9f0d435794a383d17813083bcbd05cc9adaae0d9..dbc38932ddc0331393f689878b579bef17f59ef5 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/Root/OutputStreamData.cxx
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/Root/OutputStreamData.cxx
@@ -222,12 +222,10 @@ namespace EL
       {
         TH1 *const hist = dynamic_cast<TH1*> (outputObject.get());
 
+        m_outputHistMap[outputObject->GetName()] = outputObject.get();
         m_output.emplace_back (std::move (outputObject));
         if (hist)
-        {
-          m_outputHistMap[hist->GetName()] = hist;
           hist->SetDirectory (nullptr);
-        }
       }
     }
 
@@ -285,7 +283,7 @@ namespace EL
 
 
 
-    TH1 *OutputStreamData ::
+    TObject *OutputStreamData ::
     getOutputHist (const std::string& name) const noexcept
     {
       RCU_READ_INVARIANT (this);
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/Root/Worker.cxx b/PhysicsAnalysis/D3PDTools/EventLoop/Root/Worker.cxx
index 25aae16ac20f0cd1e2fa726a1d41578551c1671c..06643b06e2093ae89b0528b2c071ace365a04fdd 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoop/Root/Worker.cxx
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/Root/Worker.cxx
@@ -20,9 +20,11 @@
 #include <AnaAlgorithm/IAlgorithmWrapper.h>
 #include <EventLoop/AlgorithmStateModule.h>
 #include <EventLoop/AlgorithmTimerModule.h>
+#include <EventLoop/BatchInputModule.h>
 #include <EventLoop/BatchJob.h>
 #include <EventLoop/BatchSample.h>
 #include <EventLoop/BatchSegment.h>
+#include <EventLoop/DirectInputModule.h>
 #include <EventLoop/Driver.h>
 #include <EventLoop/EventCountModule.h>
 #include <EventLoop/EventRange.h>
@@ -37,6 +39,7 @@
 #include <EventLoop/StopwatchModule.h>
 #include <EventLoop/PostClosedOutputsModule.h>
 #include <EventLoop/TEventModule.h>
+#include <EventLoop/WorkerConfigModule.h>
 #include <RootCoreUtils/Assert.h>
 #include <RootCoreUtils/RootUtils.h>
 #include <RootCoreUtils/ThrowMsg.h>
@@ -55,6 +58,7 @@
 #include <fstream>
 #include <memory>
 #include <xAODRootAccess/LoadDictionaries.h>
+#include <exception>
 
 //
 // method implementations
@@ -113,12 +117,12 @@ namespace EL
 
 
 
-  TH1 *Worker ::
+  TObject *Worker ::
   getOutputHist (const std::string& name) const
   {
     RCU_READ_INVARIANT (this);
 
-    TH1 *result = m_histOutput->getOutputHist (name);
+    TObject *result = m_histOutput->getOutputHist (name);
     if (result == nullptr) RCU_THROW_MSG ("unknown output histogram: " + name);
     return result;
   }
@@ -383,10 +387,13 @@ namespace EL
       m_modules.push_back (std::make_unique<Detail::TEventModule> ());
     m_modules.push_back (std::make_unique<Detail::LeakCheckModule> ());
     m_modules.push_back (std::make_unique<Detail::StopwatchModule> ());
+    if (metaData()->castBool (Job::optGridReporting, false))
+      m_modules.push_back (std::make_unique<Detail::GridReportingModule>());
     if (metaData()->castBool (Job::optAlgorithmTimer, false))
       m_modules.push_back (std::make_unique<Detail::AlgorithmTimerModule> ());
     m_modules.push_back (std::make_unique<Detail::FileExecutedModule> ());
     m_modules.push_back (std::make_unique<Detail::EventCountModule> ());
+    m_modules.push_back (std::make_unique<Detail::WorkerConfigModule> ());
     m_modules.push_back (std::make_unique<Detail::AlgorithmStateModule> ());
     m_modules.push_back (std::make_unique<Detail::PostClosedOutputsModule> ());
 
@@ -416,6 +423,20 @@ namespace EL
 
 
 
+  ::StatusCode Worker ::
+  processInputs ()
+  {
+    using namespace msgEventLoop;
+
+    RCU_CHANGE_INVARIANT (this);
+
+    for (auto& module : m_modules)
+      ANA_CHECK (module->processInputs (*this, *this));
+    return ::StatusCode::SUCCESS;
+  }
+
+
+
   ::StatusCode Worker ::
   finalize ()
   {
@@ -541,7 +562,7 @@ namespace EL
 
 
   ::StatusCode Worker ::
-  openInputFile (std::string inputFileUrl)
+  openInputFile (const std::string& inputFileUrl)
   {
     using namespace msgEventLoop;
 
@@ -575,7 +596,7 @@ namespace EL
       inputFile = SH::openFile (inputFileUrl, *metaData());
     } catch (...)
     {
-      Detail::report_exception ();
+      Detail::report_exception (std::current_exception());
     }
     if (inputFile.get() == 0)
     {
@@ -664,7 +685,7 @@ namespace EL
       }
     } catch (...)
     {
-      Detail::report_exception ();
+      Detail::report_exception (std::current_exception());
       ANA_MSG_ERROR ("while calling execute() on algorithm " << iter->m_algorithm->getName());
       return ::StatusCode::FAILURE;
     }
@@ -684,7 +705,7 @@ namespace EL
       }
     } catch (...)
     {
-      Detail::report_exception ();
+      Detail::report_exception (std::current_exception());
       ANA_MSG_ERROR ("while calling postExecute() on algorithm " << iter->m_algorithm->getName());
       return ::StatusCode::FAILURE;
     }
@@ -760,47 +781,20 @@ namespace EL
       ANA_CHECK (addOutputStream (out->label(), std::move (data)));
     }
 
-    ANA_CHECK (initialize ());
-
-    Long64_t maxEvents
-      = metaData()->castDouble (Job::optMaxEvents, -1);
-    Long64_t skipEvents
-      = metaData()->castDouble (Job::optSkipEvents, 0);
-
-    std::vector<std::string> files = sample->makeFileList();
-    for (const std::string& fileName : files)
     {
-      EventRange eventRange;
-      eventRange.m_url = fileName;
-      if (skipEvents == 0 && maxEvents == -1)
-      {
-        ANA_CHECK (processEvents (eventRange));
-      } else
-      {
-        // just open the input file to inspect it
-        ANA_CHECK (openInputFile (fileName));
-        eventRange.m_endEvent = inputFileNumEntries();
-
-        if (skipEvents != 0 && skipEvents >= eventRange.m_endEvent)
-        {
-          skipEvents -= eventRange.m_endEvent;
-          continue;
-        }
-        eventRange.m_beginEvent = skipEvents;
-        skipEvents = 0;
-
-        if (maxEvents != -1)
-        {
-          if (eventRange.m_endEvent > eventRange.m_beginEvent + maxEvents)
-            eventRange.m_endEvent = eventRange.m_beginEvent + maxEvents;
-          maxEvents -= eventRange.m_endEvent - eventRange.m_beginEvent;
-          assert (maxEvents >= 0);
-        }
-        ANA_CHECK (processEvents (eventRange));
-        if (maxEvents == 0)
-          break;
-      }
+      auto module = std::make_unique<Detail::DirectInputModule> ();
+      module->fileList = sample->makeFileList();
+      Long64_t maxEvents = metaData()->castDouble (Job::optMaxEvents, -1);
+      if (maxEvents != -1)
+        module->maxEvents = maxEvents;
+      Long64_t skipEvents = metaData()->castDouble (Job::optSkipEvents, 0);
+      if (skipEvents != 0)
+        module->skipEvents = skipEvents;
+      addModule (std::move (module));
     }
+
+    ANA_CHECK (initialize ());
+    ANA_CHECK (processInputs ());
     ANA_CHECK (finalize ());
     return ::StatusCode::SUCCESS;
   }
@@ -866,25 +860,15 @@ namespace EL
         ANA_CHECK (addOutputStream (out->label(), std::move (data)));
       }
 
-      Long64_t beginFile = segment->begin_file;
-      Long64_t endFile   = segment->end_file;
-      Long64_t lastFile  = segment->end_file;
-      RCU_ASSERT (beginFile <= endFile);
-      Long64_t beginEvent = segment->begin_event;
-      Long64_t endEvent   = segment->end_event;
-      if (endEvent > 0) endFile += 1;
-
-      ANA_CHECK (initialize ());
-
-      for (Long64_t file = beginFile; file != endFile; ++ file)
       {
-        RCU_ASSERT (std::size_t(file) < sample->files.size());
-        EventRange eventRange;
-        eventRange.m_url = sample->files[file];
-        eventRange.m_beginEvent = (file == beginFile ? beginEvent : 0);
-        eventRange.m_endEvent = (file == lastFile ? endEvent : EventRange::eof);
-        ANA_CHECK (processEvents (eventRange));
+        auto module = std::make_unique<Detail::BatchInputModule> ();
+        module->sample = sample;
+        module->segment = segment;
+        addModule (std::move (module));
       }
+
+      ANA_CHECK (initialize ());
+      ANA_CHECK (processInputs ());
       ANA_CHECK (finalize ());
 
       std::ostringstream job_name;
@@ -893,7 +877,7 @@ namespace EL
       return ::StatusCode::SUCCESS;
     } catch (...)
     {
-      Detail::report_exception ();
+      Detail::report_exception (std::current_exception());
       return ::StatusCode::FAILURE;
     }
   }
@@ -933,6 +917,8 @@ namespace EL
     ANA_CHECK (xAOD::LoadDictionaries());
 
     mo = dynamic_cast<SH::MetaObject*>(f->Get(sampleName.c_str()));
+    if (!mo)
+      mo = dynamic_cast<SH::MetaObject*>(f->Get("defaultMetaObject"));
     if (!mo) {
       ANA_MSG_ERROR ("Could not read in sample meta object");
       return ::StatusCode::FAILURE;
@@ -972,6 +958,7 @@ namespace EL
 
     const std::string location = ".";
 
+    mo->setBool (Job::optGridReporting, true);
     setMetaData (mo);
     setOutputHist (location);
     setSegmentName ("output");
@@ -998,11 +985,8 @@ namespace EL
 
     setJobConfig (std::move (*jobConfig));
 
-    addModule (std::make_unique<Detail::GridReportingModule> ());
-    ANA_CHECK (initialize());
-
-    std::vector<std::string> fileList; 
     {
+      auto module = std::make_unique<Detail::DirectInputModule> ();
       std::ifstream infile("input.txt");
       while (infile) {
         std::string sLine;
@@ -1011,53 +995,27 @@ namespace EL
         while (ssLine) {
           std::string sFile;
           if (!getline(ssLine, sFile, ',')) break;
-          fileList.push_back(sFile);
+          module->fileList.push_back(sFile);
         }
       }
-    } 
-    if (fileList.size() == 0) {
-      ANA_MSG_ERROR ("no input files provided");
-      //User was expecting input after all.
-      gSystem->Exit(EC_BADINPUT);
-    }
-
-    for (const std::string& file : fileList)
-    {
-      EventRange eventRange;
-      eventRange.m_url = file;
-
-      ANA_CHECK (processEvents (eventRange));
+      if (module->fileList.size() == 0) {
+        ANA_MSG_ERROR ("no input files provided");
+        //User was expecting input after all.
+        gSystem->Exit(EC_BADINPUT);
+      }
+      addModule (std::move (module));
     }
 
+    ANA_CHECK (initialize());
+    ANA_CHECK (processInputs ());
     ANA_CHECK (finalize ());
 
     int nEvents = eventsProcessed();
-    int nFiles = fileList.size();
     ANA_MSG_INFO ("Loop finished.");
-    ANA_MSG_INFO ("Read " << nEvents << " events in " << nFiles << " files.");
-
-    gridNotifyJobFinished(eventsProcessed(), fileList);
+    ANA_MSG_INFO ("Read/processed " << nEvents << " events.");
 
     ANA_MSG_INFO ("EventLoop Grid worker finished");
     ANA_MSG_INFO ("Saving output");
     return ::StatusCode::SUCCESS;
   }
-
-  void Worker::gridNotifyJobFinished(uint64_t eventsProcessed,
-                                     const std::vector<std::string>& fileList) {
-    // createJobSummary
-    std::ofstream summaryfile("../AthSummary.txt");
-    if (summaryfile.is_open()) {
-      unsigned int nFiles = fileList.size();
-      summaryfile << "Files read: " << nFiles << std::endl;
-      for (unsigned int i = 0; i < nFiles; i++) {
-        summaryfile << "  " << fileList.at(i) << std::endl;
-      }      
-      summaryfile << "Events Read:    " << eventsProcessed << std::endl;
-      summaryfile.close();
-    }
-    else {
-      //cout << "Failed to write summary file.\n";
-    } 
-  }
 }
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/Root/WorkerConfig.cxx b/PhysicsAnalysis/D3PDTools/EventLoop/Root/WorkerConfig.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..553bde6eba4c58651826db41f535d37567f02afe
--- /dev/null
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/Root/WorkerConfig.cxx
@@ -0,0 +1,69 @@
+/*
+  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Nils Krumnack
+
+
+//
+// includes
+//
+
+#include <EventLoop/WorkerConfig.h>
+
+#include <AnaAlgorithm/AnaAlgorithmWrapper.h>
+#include <AnaAlgorithm/AnaReentrantAlgorithmWrapper.h>
+#include <AnaAlgorithm/PythonConfigBase.h>
+#include <AsgTools/SgTEventMeta.h>
+#include <EventLoop/AsgServiceWrapper.h>
+#include <EventLoop/AsgToolWrapper.h>
+#include <EventLoop/ModuleData.h>
+
+#include <EventLoop/MessageCheck.h>
+
+//
+// method implementations
+//
+
+ClassImp (EL::WorkerConfig)
+
+namespace EL
+{
+  WorkerConfig ::
+  WorkerConfig (Detail::ModuleData *val_data) noexcept
+    : m_data (val_data),
+      m_metaStore (asg::SgTEventMeta::InputStore, nullptr)
+  {}
+
+
+
+  // FIX ME: something bad happens in the linker step if this is not
+  // here and instead rely on the implicit destructor, which I don't
+  // want to debug.
+  WorkerConfig :: ~WorkerConfig () noexcept = default;
+
+
+
+  void WorkerConfig ::
+  add (const EL::PythonConfigBase& config)
+  {
+    using namespace msgEventLoop;
+    ANA_MSG_INFO ("in add");
+    // no invariant used
+    std::unique_ptr<IAlgorithmWrapper> alg;
+    if (config.componentType() == "AnaAlgorithm")
+      alg = std::make_unique<AnaAlgorithmWrapper> (AnaAlgorithmConfig (config));
+    else if (config.componentType() == "AnaReentrantAlgorithm")
+      alg = std::make_unique<AnaReentrantAlgorithmWrapper> (AnaReentrantAlgorithmConfig (config));
+    else if (config.componentType() == "AsgTool")
+      alg = std::make_unique<AsgToolWrapper> (asg::AsgToolConfig (config));
+    else if (config.componentType() == "AsgService")
+      alg = std::make_unique<AsgServiceWrapper> (asg::AsgServiceConfig (config));
+    else
+    {
+      ANA_MSG_ERROR ("unknown component type: \"" << config.componentType() << "\"");
+      throw std::runtime_error ("unknown component type: \"" + config.componentType() + "\"");
+    }
+    m_data->m_algs.emplace_back (std::move (alg));
+  }
+}
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/Root/WorkerConfigModule.cxx b/PhysicsAnalysis/D3PDTools/EventLoop/Root/WorkerConfigModule.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..301d19132ab467fb2ae3d7ab1e4cd8a84258c76c
--- /dev/null
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/Root/WorkerConfigModule.cxx
@@ -0,0 +1,49 @@
+/*
+  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Nils Krumnack
+
+
+//
+// includes
+//
+
+#include <EventLoop/WorkerConfigModule.h>
+
+#include <EventLoop/Job.h>
+#include <EventLoop/MessageCheck.h>
+#include <EventLoop/ModuleData.h>
+#include <EventLoop/WorkerConfig.h>
+#include <EventLoop/Worker.h>
+#include <PathResolver/PathResolver.h>
+#include <SampleHandler/MetaObject.h>
+#include <TPython.h>
+
+//
+// method implementations
+//
+
+namespace EL
+{
+  namespace Detail
+  {
+    StatusCode WorkerConfigModule ::
+    onInitialize (ModuleData& data)
+    {
+      using namespace msgEventLoop;
+      std::string configFile = data.m_worker->metaData()->castString (Job::optWorkerConfigFile, "");
+      if (!configFile.empty())
+      {
+        configFile = PathResolverFindDataFile (configFile);
+        TPython::LoadMacro (configFile.c_str());
+        WorkerConfig config (&data);
+        TPython::Bind (&config, "workerConfig");
+        TPython::Eval ("fillWorkerConfig (workerConfig)");
+        TPython::Bind (nullptr, "workerConfig");
+      }
+
+      return StatusCode::SUCCESS;
+    }
+  }
+}
diff --git a/PhysicsAnalysis/D3PDTools/EventLoop/test/gt_DirectInputModule.cxx b/PhysicsAnalysis/D3PDTools/EventLoop/test/gt_DirectInputModule.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..f0f48513e5813ec93219667d339e74100a48819b
--- /dev/null
+++ b/PhysicsAnalysis/D3PDTools/EventLoop/test/gt_DirectInputModule.cxx
@@ -0,0 +1,201 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+/// @author Nils Krumnack
+
+
+
+//
+// includes
+//
+
+#include <AsgTesting/UnitTest.h>
+#include <EventLoop/IInputModuleActions.h>
+#include <EventLoop/ModuleData.h>
+#include <EventLoop/DirectInputModule.h>
+#include <EventLoop/EventRange.h>
+#include <EventLoop/MessageCheck.h>
+#include <TH1F.h>
+#include <TKey.h>
+#include <TSystem.h>
+#include <TTree.h>
+#include <filesystem>
+
+//
+// unit test
+//
+
+namespace EL
+{
+  namespace Detail
+  {
+    struct TestActions final : public IInputModuleActions
+    {
+      std::unordered_map<std::string,unsigned> inputFiles;
+      std::optional<Long64_t> inputFileNumEntries_result;
+      std::vector<EventRange> toProcessFiles;
+      std::size_t processIndex = 0u;
+
+      virtual ::StatusCode processEvents (EventRange& eventRange) override
+      {
+        using namespace msgEventLoop;
+
+        // do the standard behavior
+        ANA_CHECK (openInputFile (eventRange.m_url));
+        if (eventRange.m_endEvent == EventRange::eof)
+          eventRange.m_endEvent = inputFileNumEntries();
+
+        if (processIndex >= toProcessFiles.size())
+          return StatusCode::FAILURE;
+        EXPECT_EQ (eventRange.m_url, toProcessFiles[processIndex].m_url);
+        EXPECT_EQ (eventRange.m_beginEvent, toProcessFiles[processIndex].m_beginEvent);
+        EXPECT_EQ (eventRange.m_endEvent, toProcessFiles[processIndex].m_endEvent);
+        processIndex += 1u;
+        return StatusCode::SUCCESS;
+      }
+
+      virtual ::StatusCode openInputFile (const std::string& inputFileUrl) override
+      {
+        using namespace msgEventLoop;
+
+        if (inputFileUrl.empty())
+        {
+          inputFileNumEntries_result.reset();
+          return StatusCode::SUCCESS;
+        }
+        auto iter = inputFiles.find (inputFileUrl);
+        if (iter == inputFiles.end())
+        {
+          ANA_MSG_ERROR ("unknown input file: " << inputFileUrl);
+          return StatusCode::FAILURE;
+        }
+        inputFileNumEntries_result = iter->second;
+        return StatusCode::SUCCESS;
+      }
+
+      [[nodiscard]] virtual Long64_t inputFileNumEntries () const override
+      {
+        if (!inputFileNumEntries_result.has_value())
+          throw std::logic_error ("no input file open");
+        return inputFileNumEntries_result.value();
+      }
+    };
+
+
+
+    class DirectInputModuleTest : public ::testing::Test  {
+    public:
+      DirectInputModuleTest()
+      {
+        actions.inputFiles["empty.root"] = 0u;
+        actions.inputFiles["test1.root"] = 10u;
+        actions.inputFiles["test2.root"] = 20u;
+      }
+
+      ModuleData data;
+      TestActions actions;
+    };
+
+
+
+    TEST_F (DirectInputModuleTest, simpleTest)
+    {
+      auto module = std::make_unique<DirectInputModule> ();
+      module->fileList = {"test1.root", "test2.root"};
+
+      actions.toProcessFiles =
+      {
+        {"test1.root", 0, 10},
+        {"test2.root", 0, 20},
+      };
+
+      ASSERT_SUCCESS (module->processInputs (data, actions));
+    }
+
+
+
+    TEST_F (DirectInputModuleTest, skipLimitTest)
+    {
+      auto module = std::make_unique<DirectInputModule> ();
+      module->fileList = {"test1.root", "test2.root"};
+      module->skipEvents = 5u;
+      module->maxEvents = 10u;
+
+      actions.toProcessFiles =
+      {
+        {"test1.root", 5, 10},
+        {"test2.root", 0, 5},
+      };
+
+      ASSERT_SUCCESS (module->processInputs (data, actions));
+    }
+
+
+
+    TEST_F (DirectInputModuleTest, skipFileTest)
+    {
+      auto module = std::make_unique<DirectInputModule> ();
+      module->fileList = {"test1.root", "test2.root"};
+      module->skipEvents = 15u;
+
+      actions.toProcessFiles =
+      {
+        {"test2.root", 5, 20},
+      };
+
+      ASSERT_SUCCESS (module->processInputs (data, actions));
+    }
+
+
+
+    TEST_F (DirectInputModuleTest, limitFileTest)
+    {
+      auto module = std::make_unique<DirectInputModule> ();
+      module->fileList = {"test1.root", "test2.root"};
+      module->maxEvents = 5u;
+
+      actions.toProcessFiles =
+      {
+        {"test1.root", 0, 5},
+      };
+
+      ASSERT_SUCCESS (module->processInputs (data, actions));
+    }
+
+
+
+    TEST_F (DirectInputModuleTest, emptyFileTest)
+    {
+      auto module = std::make_unique<DirectInputModule> ();
+      module->fileList = {"empty.root", "test1.root", "test2.root"};
+
+      actions.toProcessFiles =
+      {
+        {"empty.root", 0, 0},
+        {"test1.root", 0, 10},
+        {"test2.root", 0, 20},
+      };
+      ASSERT_SUCCESS (module->processInputs (data, actions));
+    }
+
+
+
+    TEST_F (DirectInputModuleTest, skipToEmptyTest)
+    {
+      auto module = std::make_unique<DirectInputModule> ();
+      module->fileList = {"test1.root", "empty.root", "test2.root"};
+      module->skipEvents = 10u;
+
+      actions.toProcessFiles =
+      {
+        {"empty.root", 0, 0},
+        {"test2.root", 0, 20},
+      };
+
+      ASSERT_SUCCESS (module->processInputs (data, actions));
+    }
+  }
+}
+
+ATLAS_GOOGLE_TEST_MAIN
diff --git a/PhysicsAnalysis/D3PDTools/EventLoopAlgs/Root/NTupleSvc.cxx b/PhysicsAnalysis/D3PDTools/EventLoopAlgs/Root/NTupleSvc.cxx
index 1c7183c063c7139ecd82d022b13d2fd490a38897..5ec8cc68e921e70f9fa022de25df9a10b53279bd 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoopAlgs/Root/NTupleSvc.cxx
+++ b/PhysicsAnalysis/D3PDTools/EventLoopAlgs/Root/NTupleSvc.cxx
@@ -108,7 +108,7 @@ namespace EL
       while (!line.empty() && isspace (line[0]))
 	line = line.substr (1);
       while (!line.empty() && isspace (line[line.size()-1]))
-	line = line.substr (0, line.size()-1);
+	line.pop_back();
       if (!line.empty() && line[0] != '#')
 	copyBranch (line);
     }
diff --git a/PhysicsAnalysis/D3PDTools/EventLoopAlgs/test/gt_DuplicateChecker.cxx b/PhysicsAnalysis/D3PDTools/EventLoopAlgs/test/gt_DuplicateChecker.cxx
index 543b417bdf114b6e4d795becdae463e2b5f820da..b97c20eee088c7da2351fb2353019bc9a2af489e 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoopAlgs/test/gt_DuplicateChecker.cxx
+++ b/PhysicsAnalysis/D3PDTools/EventLoopAlgs/test/gt_DuplicateChecker.cxx
@@ -17,6 +17,7 @@
 #include <AsgTools/StatusCode.h>
 #include <RootCoreUtils/Assert.h>
 #include <RootCoreUtils/ShellExec.h>
+#include <RootCoreUtils/UnitTestDir.h>
 #include <SampleHandler/SampleLocal.h>
 #include <EventLoop/DirectDriver.h>
 #include <EventLoop/Job.h>
@@ -150,26 +151,26 @@ TEST (DuplicateCheckerTest, all_tests)
   xAOD::TReturnCode::enableFailure();
   xAOD::Init ().ignore();
 
-  std::string prefix = "DuplicateCheckerSubmit";
+  RCU::UnitTestDir dir ("EventLoopAlgs", "DuplicateChecker");
 
-  RCU::Shell::exec ("rm -rf " + prefix + "[123]");
+  std::string prefix = dir.path() + "/";
 
-  if (makeXAOD ("test1.root", 1, 0).isFailure())
+  if (makeXAOD (prefix + "test1.root", 1, 0).isFailure())
   {
     FAIL() << "failed to make test file";
   }
-  if (makeXAOD ("test2.root", 1, 8000).isFailure())
+  if (makeXAOD (prefix + "test2.root", 1, 8000).isFailure())
   {
     FAIL() << "failed to make test file";
   }
-  if (makeXAOD ("test3.root", 2, 0).isFailure())
+  if (makeXAOD (prefix + "test3.root", 2, 0).isFailure())
   {
     FAIL() << "failed to make test file";
   }
   std::unique_ptr<SH::SampleLocal> sample (new SH::SampleLocal ("sample"));
-  sample->add ("test1.root");
-  sample->add ("test2.root");
-  sample->add ("test3.root");
+  sample->add (prefix + "test1.root");
+  sample->add (prefix + "test2.root");
+  sample->add (prefix + "test3.root");
   SH::SampleHandler sh;
   sh.add (sample.release());
 
@@ -183,15 +184,15 @@ TEST (DuplicateCheckerTest, all_tests)
 
     {
       DirectDriver driver;
-      driver.submit (job, prefix + "1");
-      checkHistograms (prefix + "1", 30000, 26000, true);
+      driver.submit (job, prefix + "submit1");
+      checkHistograms (prefix + "submit1", 30000, 26000, true);
     }
     {
       LocalDriver driver;
-      driver.submit (job, prefix + "2");
-      checkHistograms (prefix + "2", 30000, 27000, false);
+      driver.submit (job, prefix + "submit2");
+      checkHistograms (prefix + "submit2", 30000, 27000, false);
     }
-    RCU::Shell::exec ("cmp " + prefix + "1/duplicates " + prefix + "2/duplicates");
+    RCU::Shell::exec ("cmp " + prefix + "submit1/duplicates " + prefix + "submit2/duplicates");
   }
 
   {
@@ -199,14 +200,14 @@ TEST (DuplicateCheckerTest, all_tests)
     std::unique_ptr<DuplicateChecker> alg (new DuplicateChecker);
     alg->setEventInfoName ("MyEventInfo");
     alg->setOutputTreeName ("summary");
-    alg->addKnownDuplicatesFile (prefix + "1/duplicates");
+    alg->addKnownDuplicatesFile (prefix + "submit1/duplicates");
     job.algsAdd (alg.release());
     job.sampleHandler (sh);
 
     {
       LocalDriver driver;
-      driver.submit (job, prefix + "3");
-      checkHistograms (prefix + "3", 30000, 26000, true);
+      driver.submit (job, prefix + "submit3");
+      checkHistograms (prefix + "submit3", 30000, 26000, true);
     }
   }
 }
diff --git a/PhysicsAnalysis/D3PDTools/EventLoopGrid/CMakeLists.txt b/PhysicsAnalysis/D3PDTools/EventLoopGrid/CMakeLists.txt
index 17925a4c51022f8ed47760cf39abd57dabf7381d..4aba3551a76fda6950560ca0a4a8b5aa7273946b 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoopGrid/CMakeLists.txt
+++ b/PhysicsAnalysis/D3PDTools/EventLoopGrid/CMakeLists.txt
@@ -16,7 +16,7 @@ find_package( ROOT COMPONENTS Core RIO PyROOT Tree )
 
 # Library in the package:
 atlas_add_root_dictionary( EventLoopGrid EventLoopGridCintDict
-   ROOT_HEADERS EventLoopGrid/GridDriver.h
+   ROOT_HEADERS
    EventLoopGrid/PrunDriver.h
    Root/LinkDef.h
    EXTERNAL_PACKAGES ROOT )
diff --git a/PhysicsAnalysis/D3PDTools/EventLoopGrid/EventLoopGrid/GridDriver.h b/PhysicsAnalysis/D3PDTools/EventLoopGrid/EventLoopGrid/GridDriver.h
deleted file mode 100644
index 9e574e0dc56aeed241789caaacb498cedb82e594..0000000000000000000000000000000000000000
--- a/PhysicsAnalysis/D3PDTools/EventLoopGrid/EventLoopGrid/GridDriver.h
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
-  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
-*/
-
-/// @author Alexander Madsen
-/// @author Nils Krumnack
-
-
-
-#ifndef EVENT_LOOP_GRID_DRIVER_H
-#define EVENT_LOOP_GRID_DRIVER_H
-
-#include "EventLoop/Driver.h"
-
-namespace SH {
-  class SampleGrid;
-  class MetaObject;
-}
-
-namespace EL {
-
-  std::string getRootCoreConfig ();
-
-  /// \brief a \ref Driver to submit jobs via ganga
-
-  class GridDriver final : public Driver {
-
-  public:
-
-    GridDriver ();
-
-    void testInvariant () const;
-
-    static void status(const std::string& location);
-
-    static void status();
-
-    static void kill(const std::string& location);
-
-    static void killAll();
-
-    static std::string listActive();
-
-    static void reset();
-
-    static bool startServer();
-
-    static bool stopServer();
-
-    std::string outputSampleName;
-    bool express;
-    bool mergeOutput;
-    std::string destSE;
-    std::string site;
-    std::string cloud;
-    int memory;
-    int maxCpuCount;
-    int nFiles;
-    int nFilesPerJob;
-    int nJobs;
-    int nMinorRetries;
-    int nMajorRetries;
-    float nGBPerJob;
-    std::string workDir;
-    std::string extFile;         //To be made obsolete, please do not use!
-    std::string excludedSite;
-    std::string rootVer; 
-    std::string cmtConfig; 
-
-    static std::string gangaLogFile;
-
-    //These fields are currently not supported
-    //std::string excludeFile;
-    //bool noSubmit;              //Should maybe enable this again
-    //int maxNFilesPerJob;        //no clear way to do this in ganga
-    //int maxFileSize;            //no clear way to do this in ganga
-    //std::string spaceToken;     //users can only output to scratchdisk
-    //std::string useChirpServer; //ganga has this possibility
-    //bool enableRetries;         //done by tasks 
-
-    //std::string downloadIncludeFileMask;
-    //std::string downloadExcludeFileMask;
-
-    //These functions are kept for backwards compatibility.
-    //They should be considered obsolete and will be removed in a future version.
-    void gather(const std::string location) const;
-    SH::SampleGrid* createSampleFromDQ2(const std::string& dataset) const;
-
-  protected:
-    virtual ::StatusCode
-    doManagerStep (Detail::ManagerData& data) const override;
-
-  private:
-    ::StatusCode doRetrieve (Detail::ManagerData& data) const;
-
-
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wpragmas"
-#pragma GCC diagnostic ignored "-Wunknown-pragmas"
-#pragma GCC diagnostic ignored "-Winconsistent-missing-override"
-    ClassDef(EL::GridDriver, 1);
-#pragma GCC diagnostic pop
-  };
-}
-
-#endif
diff --git a/PhysicsAnalysis/D3PDTools/EventLoopGrid/Root/GridDriver.cxx b/PhysicsAnalysis/D3PDTools/EventLoopGrid/Root/GridDriver.cxx
deleted file mode 100644
index 2853c5f5a656c977d8fd109021a1ce1f8b4bf8f7..0000000000000000000000000000000000000000
--- a/PhysicsAnalysis/D3PDTools/EventLoopGrid/Root/GridDriver.cxx
+++ /dev/null
@@ -1,1227 +0,0 @@
-/*
-  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
-*/
-
-/// @author Alexander Madsen
-/// @author Nils Krumnack
-
-
-
-#include "EventLoopGrid/GridDriver.h"
-#include "EventLoop/Algorithm.h"
-#include "EventLoop/Job.h"
-#include "EventLoop/ManagerData.h"
-#include "EventLoop/ManagerStep.h"
-#include "EventLoop/MessageCheck.h"
-#include "EventLoop/OutputStream.h"
-#include <PathResolver/PathResolver.h>
-#include "RootCoreUtils/Assert.h"
-#include "RootCoreUtils/StringUtil.h"
-#include <RootCoreUtils/ThrowMsg.h>
-#include "RootCoreUtils/hadd.h"
-#include "SampleHandler/MetaFields.h"
-#include "SampleHandler/MetaObject.h"
-#include "SampleHandler/Sample.h"
-#include "SampleHandler/SampleGrid.h"
-#include "SampleHandler/SampleHandler.h"
-#include "TFile.h"
-#include "TList.h"
-#include "TRegexp.h"
-#include "TSystem.h"
-#include "TTree.h"
-#include "TObjString.h"
-#include "TObjArray.h"
-#include "TROOT.h"
-#include <algorithm>
-#include <fstream>
-#include <iomanip>
-#include <iterator>
-#include <list>
-#include <map>
-#include <memory>
-#include <sstream>
-#include <string>
-#include <vector>
-
-ClassImp(EL::GridDriver)
-
-using namespace EL::msgEventLoop;
-
-namespace EL {
-
-  std::string getRootCoreConfig ()
-  {
-    boost::regex expr (".*-.*-.*-.*");
-
-     const char *ROOTCORECONFIG = getenv ("ROOTCORECONFIG");
-     if (ROOTCORECONFIG)
-     {
-       if (RCU::match_expr (expr, ROOTCORECONFIG))
-	 return ROOTCORECONFIG;
-       ANA_MSG_ERROR ("invalid value \"" << ROOTCORECONFIG
-                      << "\" for $ROOTCORECONFIG");
-     }
-     const char *rootCmtConfig = getenv ("rootCmtConfig");
-     if (rootCmtConfig)
-     {
-       if (RCU::match_expr (expr, rootCmtConfig))
-	 return rootCmtConfig;
-       ANA_MSG_ERROR ("invalid value \"" << rootCmtConfig
-                      << "\" for $rootCmtConfig");
-     }
-     ANA_MSG_WARNING ("no valid value for cmt config found.");
-     ANA_MSG_WARNING ("using \"x86_64-slc6-gcc47-opt\" instead.");
-     ANA_MSG_WARNING ("consider updating to a more recent RootCore version");
-     ANA_MSG_WARNING ("or analysis release");
-     return "x86_64-slc6-gcc47-opt";
-   }
-
-
-
-   std::string GridDriver::gangaLogFile = "";
-
-   namespace {
-
-     const int dummyIdCompleted = -0xDEAD;
-     const int dummyIdFailed = -0xFA11; 
-
-     const char* cvmfsATLASLocalRootBase = 
-       "/cvmfs/atlas.cern.ch/repo/ATLASLocalRootBase";    
-     const char* shAtlasLocalSetup = 
-       ". ${ATLAS_LOCAL_ROOT_BASE}/user/atlasLocalSetup.sh";    
-     const char* gangaDir = 
-       gSystem->ExpandPathName("~/gangadir-EL");
-
-     struct GangaTrfDef {
-       std::string sampleName;
-       std::string inDS;
-       std::string outDS;
-       std::string rootVer;
-       std::string inputSdBox;
-       std::string inputFiles;
-       std::string outputFiles;
-       std::string downloadDir;
-       std::string downloadMask;
-       std::string userArea;
-     }; 
-
-     struct EnvReqs {
-       bool needGanga; 
-       bool needPanda; 
-       bool needProxy; 
-       bool needRootCore; 
-       bool needDQ2; 
-       EnvReqs() : needGanga(false),
-		   needPanda(false),
-		   needProxy(false),
-		   needRootCore(false),
-		   needDQ2(false) {}
-     };
-
-     /*
-     bool readJobInfo(const std::string location, bool printInfo = true);
-     */
-
-     /*
-     bool readJobInfo(const std::string location, Int_t &nEventsTotal, 
-		      Int_t &nFilesTotal, Float_t &realTimeTotal,
-		      Float_t &cpuTimeTotal, Float_t &eventsPerRealSecondTotal,
-		      Float_t &eventsPerCpuSecondTotal, bool printInfo);
-     */
-
-     /*
-     bool readJobInfo(const std::string location, Int_t &nEventsTotal, 
-		      Int_t &nFilesTotal, bool  printInfo = false);
-     */
-
-     std::string getStrValues(const std::string &inLines, const std::string &key);
-
-     const std::string pyList(const std::string &list);
-
-     bool checkEnvironment(EnvReqs args);
-
-     const std::string getNickname();
-
-     bool sendGangaCmd(const std::string &cmd);
-
-     bool sendGangaCmd(const std::string &cmd, std::string &out);
-
-     const std::string gangaTrfCmd(const GangaTrfDef &args, 
-				   const GridDriver &driver);
-
-     int readTaskID(const std::string& location);
-     bool writeTaskID(const std::string& location, const int taskId);
-
-   } //namespace
- } // namespace EL
-
- void EL::GridDriver::testInvariant() const {
-   RCU_INVARIANT (this != 0);
- }
-
- EL::GridDriver::GridDriver() :  express(false),
-				 mergeOutput(false),				
-				 memory(-1),
-				 maxCpuCount(-1),
-				 nFiles(0),
-				 nFilesPerJob(0),
-				 nJobs(-1),
-				 nMinorRetries(4),
-				 nMajorRetries(4),
-				 nGBPerJob(13),
-				 rootVer(gROOT->GetVersion()),
-				 cmtConfig(getRootCoreConfig()) {
-   RCU_NEW_INVARIANT (this);
- }
-
-
-::StatusCode EL::GridDriver ::
-doManagerStep (Detail::ManagerData& data) const
-{
-  ANA_CHECK (Driver::doManagerStep (data));
-  switch (data.step)
-  {
-  case Detail::ManagerStep::submitJob:
-    {
-      //Create input sandbox dir with ELG files
-      //TODO: some error checks here 
-      const std::string jobELGDir   = data.submitDir + "/elg";
-      const std::string taskIdFile  = jobELGDir + "/taskID";
-      const std::string dsContFile  = jobELGDir + "/outputDQ2container";
-      const std::string sandboxDir  = jobELGDir + "/inputsandbox";
-      const std::string downloadDir = jobELGDir + "/download";
-      const std::string runShFile   = sandboxDir + "/runjob.sh";
-      //const std::string runShOrig   = "$ROOTCOREBIN/data/EventLoopGrid/runjob.sh"; 
-      const std::string runShOrig = PathResolverFindCalibFile("EventLoopGrid/runjob.sh");
-      const std::string mergeShFile = sandboxDir + "/elg_merge";
-      //const std::string mergeShOrig = 
-      //  "$ROOTCOREBIN/user_scripts/EventLoopGrid/elg_merge";
-      const std::string mergeShOrig = PathResolverFindCalibFile("EventLoopGrid/elg_merge");
-      const std::string jobDefFile  = sandboxDir + "/jobdef.root";
-      gSystem->Exec(Form("mkdir -p %s", jobELGDir.c_str()));
-      gSystem->Exec(Form("mkdir -p %s", sandboxDir.c_str()));
-      gSystem->Exec(Form("mkdir -p %s", downloadDir.c_str()));
-      gSystem->Exec(Form("cp %s %s", runShOrig.c_str(), runShFile.c_str()));
-      gSystem->Exec(Form("chmod +x %s", runShFile.c_str()));
-      gSystem->Exec(Form("cp %s %s", mergeShOrig.c_str(), mergeShFile.c_str()));
-      gSystem->Exec(Form("chmod +x %s", mergeShFile.c_str()));
-
-      if (gSystem->Exec("ps aux | grep `whoami` | grep -e [g]anga > /dev/null") == 0) {
-        if (not stopServer()) {
-          ANA_MSG_ERROR ("Could not stop currently running ganga service");
-          return ::StatusCode::FAILURE;
-        }
-        gSystem->Exec("sleep 3");
-      }
-
-      ANA_MSG_INFO ("Please wait while grid jobs are created, ");
-      ANA_MSG_INFO ("this can take several minutes...");
-
-      EnvReqs env;
-      env.needGanga = true; 
-      env.needPanda = true; 
-      env.needProxy = true; 
-      env.needRootCore = true; 
-  
-      if (not checkEnvironment(env))
-      {
-        ANA_MSG_ERROR ("some issue with the environment?");
-        return ::StatusCode::FAILURE;
-      }
-
-      const std::string nickname = getNickname();
-      if (nickname[0] == '\0') {
-        ANA_MSG_ERROR ("Failed to get grid nickname from panda"); 
-        return ::StatusCode::FAILURE;
-      }
-
-      TList outputs;
-
-      {//Save the Algorithms and sample MetaObjects to be sent with the jobs 
-        TFile f(jobDefFile.c_str(), "RECREATE"); 
-        f.WriteTObject(&data.job->jobConfig(), "jobConfig", "SingleKey");      
-
-        for (EL::Job::outputIter out = data.job->outputBegin(),
-               end = data.job->outputEnd(); out != end; ++out) {
-          outputs.Add(out->Clone());
-        }      
-        f.WriteTObject(&outputs, "outputs", "SingleKey");
-        for (SH::SampleHandler::iterator sample = data.job->sampleHandler().begin();
-             sample != data.job->sampleHandler().end();  ++sample) {
-          SH::MetaObject meta(*(*sample)->meta());
-          meta.fetchDefaults(data.options);
-          f.WriteObject(&meta, (*sample)->name().c_str());
-          //f.WriteObject((*sample)->meta(), (*sample)->name().c_str());
-        }
-        f.Close();      
-      }
-      SH::MetaObject meta(data.options);
-    
-      std::map<std::string, SH::SampleHandler> outMap; // <label,samples>     
-      std::list<std::string> outDSs; //Created dq2 datasets for output 
-
-      int iJob = 0;
-
-      std::stringstream gangaCmd;
-
-      for (SH::SampleHandler::iterator sample = data.job->sampleHandler().begin();
-           sample != data.job->sampleHandler().end(); ++sample) {
-
-        TString inDS;
-        TString outDS;
-      
-        iJob++;
-
-        //If SampleGrid, loop over childs if SampleComposite
-        inDS = (*sample)->meta()->castString("nc_grid", (*sample)->name(), SH::MetaObject::CAST_NOCAST_DEFAULT);
-      
-        // Determine outDS name
-        if (not outputSampleName.empty()) 
-          outDS = outputSampleName;
-        else  //TODO: Decide on a good default here
-          outDS = "user.%nickname%.%in:name%";
-
-        outDS.ReplaceAll("%in:name%", (*sample)->name());
-        outDS.ReplaceAll("%nickname%", nickname);
-        std::stringstream ss((*sample)->name());
-        std::string item;
-        int field = 0;
-        while(std::getline(ss, item, '.')) {
-          std::stringstream sskey;
-          sskey << "%in:name[" << ++field << "]%";
-          outDS.ReplaceAll(sskey.str(), item);
-        }	
-        while (outDS.Index("%in:") != -1) {
-          int i1 = outDS.Index("%in:");
-          int i2 = outDS.Index("%", i1+1);
-          TString metaName = outDS(i1+4, i2-i1-4);
-          outDS.ReplaceAll("%in:"+metaName+"%", 
-                           (*sample)->meta()->castString(std::string(metaName.Data()), "", SH::MetaObject::CAST_NOCAST_DEFAULT));
-        }
-        outDS.ReplaceAll("/", "");
-        if (!outDS.BeginsWith(Form("user.%s", nickname.c_str()))) {
-          //ERROR: Malformed outDS
-          ANA_MSG_ERROR ("Bad output dataset name: " << outDS);
-          ANA_MSG_ERROR ("It should begin with " << Form("user.%s", nickname.c_str()));
-          return ::StatusCode::FAILURE;
-        }
-      
-        std::string outputfilenames = "hist-output.root";
-        {
-          TIter itr(&outputs);
-          TObject *obj = 0;
-          while ((obj = itr())) {
-            EL::OutputStream *os = dynamic_cast<EL::OutputStream*>(obj);
-            const std::string name = os->label() + ".root";
-            outputfilenames += "," + name;
-          }
-        }
-      
-        gSystem->Exec(Form("mkdir -p %s", 
-                           (downloadDir + "/" + (*sample)->name()).c_str()));
-
-        GangaTrfDef trfDef;
-        trfDef.sampleName = (*sample)->name();
-        trfDef.inDS = inDS.Data();
-        trfDef.outDS = outDS.Data();
-        trfDef.inputSdBox = 
-          extFile + (extFile.empty() ? "" : ",") + jobDefFile + "," + runShFile;
-        trfDef.inputFiles = 
-          (*sample)->meta()->castString("nc_grid_filter", "*.root*", SH::MetaObject::CAST_NOCAST_DEFAULT);
-        trfDef.outputFiles = outputfilenames;    
-        trfDef.downloadDir = downloadDir;
-        trfDef.downloadMask = "hist-output.root";
-        trfDef.userArea = jobELGDir;
-        trfDef.rootVer = rootVer; 
-
-        gangaCmd << std::endl << "# Transform " << iJob << std::endl;
-        gangaCmd << gangaTrfCmd(trfDef, *this);
-        gangaCmd << std::endl;
-
-        outDSs.push_back(std::string(outDS.Data()));	
-      }
-
-      std::string taskName = data.submitDir;
-      if (taskName.rfind('/') != std::string::npos)
-        taskName = taskName.substr(taskName.rfind('/')+1);
-      std::stringstream submitCmd;
-      submitCmd   
-        << "# This file was generated by EventLoop GridDriver "
-        << "for EL job " << taskName << "\n"
-        << "t = AtlasTask()\n"
-        << "t.name = '" << taskName << "'\n" 
-        << "t.comment += 'location:" << data.submitDir << "'\n"
-        << "t.float = 200\n"
-        << "app = Athena()\n"
-        << "app.athena_compile = True\n"
-        << "app.option_file = 'runjob.sh'\n"
-        << "app.atlas_exetype = 'EXE'\n"
-        << "app.useRootCore = True\n"
-        << "app.append_to_user_area = ['.root','.cxx','.C','.h']\n"
-        << "app.user_area_path = '" << jobELGDir << "'\n"
-        << "app.atlas_release='None'\n"     
-        << "app.atlas_cmtconfig = '" << meta.castString (Job::optCmtConfig, cmtConfig) << "'\n" 
-        << "app.prepare()\n"
-        << "app.atlas_release=''\n"
-        << "app.atlas_dbrelease=''\n"
-        << "config.Athena.EXE_MAXFILESIZE=2097152\n"
-        << "config.Configuration.autoGenerateJobWorkspace = False\n"
-        << "print\n"
-        << "print \"TaskID: %d\" % t.id\n"
-        << "print \"TaskContainer: %s\" % t.getContainerName()\n"
-        << gangaCmd.str()
-        << "t.run()\n" 
-        << "import time\n"
-        << "time.sleep(60)\n";
-
-      {
-        std::ofstream of((jobELGDir + "/submit.py").c_str()); 
-        of << submitCmd.str();
-        of.close();
-      }
-
-      std::string curDir = gSystem->pwd();
-      gSystem->cd(sandboxDir.c_str());
-      std::string gangaMsg;
-      bool ok = sendGangaCmd(submitCmd.str(), gangaMsg);
-      gSystem->cd(curDir.c_str());
-
-      if (not ok) {
-        ANA_MSG_ERROR ("Could not start ganga service - job submission failed");
-        return ::StatusCode::FAILURE;
-      }
-
-      ANA_MSG_INFO ("Done. Call EL::GridDriver::status(\"" << data.submitDir
-                    << "\") to follow the progress of your jobs.");
-
-      int taskId = -1;
-      std::string container = getStrValues(gangaMsg, "TaskContainer: ");
-      std::istringstream(getStrValues(gangaMsg, "TaskID: ")) >> taskId;
-      RCU_ASSERT(taskId >= 0);
-      writeTaskID(data.submitDir, taskId);
-
-      {
-        std::ofstream of(dsContFile.c_str());
-        of << container;
-        of.close();
-      }
-  
-      std::stringstream queryOutDsCmd;
-      queryOutDsCmd << "for tr in tasks(" << taskId << ").transforms: "
-                    << "print \"%(in)s: %(out)s \" % "
-                    << "{\"in\": tr.application.options, "
-                    << "\"out\": tr.getContainerName()}\n";
-      sendGangaCmd(queryOutDsCmd.str(), gangaMsg);
-
-      for (SH::SampleHandler::iterator sample = data.job->sampleHandler().begin();
-           sample != data.job->sampleHandler().end(); ++sample) {
-        std::string sampleOutDs = getStrValues(gangaMsg, (*sample)->name() + ": ");
-        if (*sampleOutDs.rbegin() == '\n')
-          sampleOutDs  = sampleOutDs.substr(0, sampleOutDs.size()-1);
-        if (*sampleOutDs.rbegin() == ' ')
-          sampleOutDs  = sampleOutDs.substr(0, sampleOutDs.size()-1);
-
-        //Create a sample for each output and add it to that output's handler 
-        for (EL::Job::outputIter out=data.job->outputBegin();
-             out != data.job->outputEnd(); ++out) {
-          SH::SampleGrid * mysample = new SH::SampleGrid((*sample)->name());
-          mysample->meta()->setString("nc_grid", sampleOutDs);
-          mysample->meta()->setString("nc_grid_filter", "*" + out->label() + ".root*");
-          outMap[out->label()].add(mysample);
-        }
-        SH::SampleGrid * mysample=new SH::SampleGrid((*sample)->name());
-        mysample->meta()->setString("nc_grid", sampleOutDs);
-        mysample->meta()->setString("nc_grid_filter", "hist-output.root");
-        //mysample->setMetaDouble("norigfiles", nFilesInInputDS);
-        outMap["hist"].add(mysample);                	          
-      }
-
-      //Print the created dq2 datasets
-      //This is broken as Tasks appends extra fluff to the ds names
-      //outDSs.sort();
-      //outDSs.unique();
-      //cout << "The following dq2 output datasets will be created:" << endl;
-      //for (list<std::string>::iterator it=outDSs.begin(); it!=outDSs.end(); ++it) {
-      //  cout << *it << endl;
-      //}
-
-      // //Print link to Analysis Task Monitor
-      // std::stringstream url;
-      // url << "https://dashb-atlas-prodsys-prototype.cern.ch/templates/task-analysis/#user=default";
-      // url << "&table=Mains"; 
-      // url << "&p=1";
-      // url << "&records=" << outDSs.size() + 1;
-      // url << "&activemenu=1";
-      // url << "&timerange=lastMonth";
-      // //url << "&from=";
-      // //url << "&till=";
-      // //url << "&refresh=0";
-      // url << "&pattern=";
-      // url << container << "/+%7C%7C+";
-      // //for (list<std::string>::iterator it=outDSs.begin(); it!=outDSs.end(); ++it) {    
-      // //  url << *it << "/+%7C%7C+";
-      // //}
-      // cout << "You can follow the job progress using the Analysis Task Monitor:"; 
-      // cout << endl << url.str() << endl;
-      // cout << "(Please note: this service is still under development!)\n";
-    
-      //Save the output Sample Handlers
-      for (EL::Job::outputIter output=data.job->outputBegin(),
-             end=data.job->outputEnd(); output != end; ++output)
-      {
-        outMap[output->label()].fetch(data.job->sampleHandler());
-        outMap[output->label()].save(data.submitDir + "/output-" + output->label());
-      };
-      outMap["hist"].fetch(data.job->sampleHandler());
-      outMap["hist"].save(data.submitDir + "/output-hist");
-    }
-    data.submitted = true;
-    break;
-
-  case Detail::ManagerStep::doRetrieve:
-    {
-      ANA_CHECK (doRetrieve (data));
-    }
-    break;
-
-  default:
-    (void) true; // safe to do nothing
-  }
-  return ::StatusCode::SUCCESS;
-}
-
- 
-::StatusCode EL::GridDriver::doRetrieve(Detail::ManagerData& data) const {
-  RCU_READ_INVARIANT(this);
-
-  const std::string hist_sh_dir    = data.submitDir + "/output-hist";
-  const std::string jobDownloadDir = data.submitDir + "/elg/download";
-  const std::string dsContFile     = data.submitDir + "/elg/outputDQ2container";
-
-  EnvReqs env;
-  env.needGanga = true; 
-  env.needProxy = true; 
-  if (not checkEnvironment(env)) {
-    //In case of failure, errors are already reported by checkEnvironment()
-    return false; 
-  }
-
-  SH::SampleHandler sh;
-  sh.load(hist_sh_dir);
-
-  int taskId = readTaskID(data.submitDir); 
-  if (taskId == dummyIdCompleted) {
-    ANA_MSG_INFO ("Job has already been completed and output downloaded");
-    return true;
-  }
-  if (taskId == dummyIdFailed) {
-    ANA_MSG_INFO ("The job has failed permanently and will not be tried again");
-    return true;
-  }
-  RCU_ASSERT(taskId >= 0); //At this point, taskId must be that of a real task
-
-  std::stringstream cmd;
-  cmd << "for [x,y] in [[u,t] for t in tasks(" << taskId << ").transforms " 
-      << "for u in t.units]: print "
-      << "\"elgunit: %s %s %s\" % (y.application.options, x.name, x.status)";
- 
-  std::string out;
-  bool ok = sendGangaCmd(cmd.str(), out);
-
-  if (not ok) {
-    ANA_MSG_ERROR ("Could not start ganga server");
-    return false;
-  }
-    
-  const std::string unitList = getStrValues(out, "elgunit: ");
-
-  if (unitList.empty()) {
-    ANA_MSG_ERROR ("Could not get the list of task partitions from ganga");
-    ANA_MSG_ERROR ("Possibly the task registry has been lost");
-    ANA_MSG_ERROR ("or the job submission failed");
-    return false;
-  }
-
-  bool isIncomplete = false; //At least one unit is not completed 
-  bool isRunning = false;    //At least one unit is still running
-  bool isFailed = false;     //At least one unit is failed
-  int nTotalUnits = 0;
-  int nTotalUnitsCompleted = 0;
-  int nTotalTransformsCompleted = 0;
-
-  for (SH::SampleHandler::iterator sample = sh.begin();
-       sample != sh.end(); ++sample) {      
-
-    const std::string mergedHistFile 
-      = data.submitDir + "/hist-" + (*sample)->name() + ".root";
-
-    bool transformCompleted = true;
-    std::string filesToMerge;
-
-    std::istringstream units(unitList);
-    int nUnits = 0;
-    std::string entry;
-    while (getline(units, entry)) {
-      std::string sampleName, unitName, unitStatus;
-      std::istringstream s(entry);
-      s >> sampleName;
-      s >> unitName;
-      s >> unitStatus;
-
-      if ((*sample)->name() != sampleName) continue;
-      nUnits++;
-      bool unitCompleted = (unitStatus == "completed");
-      bool unitFailed    = (unitStatus == "failed");
-      bool unitBad       = (unitStatus == "bad");
-      nTotalUnits++;
-      if (unitCompleted) nTotalUnitsCompleted++;
-      if (unitFailed || unitBad) isFailed = true;
-      if (not unitCompleted && not unitFailed && not unitBad) isRunning=true;
-      if (not unitCompleted) {
-	isIncomplete = true;
-	transformCompleted = false;
-	continue;
-      }
-	
-      const std::string unitDir = 
-	jobDownloadDir + "/" + (*sample)->name() + "/" + unitName;	
-      const std::string findCmd 
-	= "find " + unitDir + " -name \"*.hist-output.root*\" |  tr '\n' ' '";
-      filesToMerge += gSystem->GetFromPipe(findCmd.c_str()).Data();		
-    }
-    if (transformCompleted) nTotalTransformsCompleted++;
-    if (nUnits == 0) transformCompleted = false;
-    if (transformCompleted) {
-      if (gSystem->Exec(Form("[ -f %s ]", mergedHistFile.c_str()))) {
-	if (filesToMerge.empty()) { //Debug
-	  ANA_MSG_ERROR ("Did not find files to merge");
-	  ANA_MSG_ERROR ("ganga response was " << out);
-	  ANA_MSG_ERROR ("Stopping here");
-	  return false;
-	}
-
-        std::istringstream iss(filesToMerge);
-        std::set<std::string> input((std::istream_iterator<std::string>(iss)), 
-                                    std::istream_iterator<std::string>());	  
-	RCU::hadd(mergedHistFile.c_str(), std::vector<std::string>(input.begin(), input.end()));
-
-	gSystem->Exec(Form("rm %s", filesToMerge.c_str())); 	  
-      }	
-    }
-  }
-
-  ANA_MSG_INFO (nTotalTransformsCompleted << "/" << sh.size() 
-                << " samples processed (" << nTotalUnitsCompleted << "/"
-                << nTotalUnits << " jobs completed)");
-
-  if (isRunning) return false;
-
-  //At this point, task should be either completed or failed
-  if (isFailed != isIncomplete) {
-    std::cerr << "At this point, task should be either completed or failed\n"
-              << "somehow it's not" << std::endl;
-  }
-
-  if (isFailed) {
-    std::cerr << "The job has failed and has reached maximum number of retries.";
-    std::cerr << std::endl << "It will not be tried again. Sorry." << std::endl;
-  }
-
-  std::stringstream removeCmd;
-  removeCmd << "tasks(" << taskId << ").remove(remove_jobs=True)";
-  if (sendGangaCmd(removeCmd.str())) {
-    writeTaskID(data.submitDir, isFailed ? dummyIdFailed : dummyIdCompleted);
-  } else {
-    std::cerr << "Warning: The job has stopped running but could not be removed ";
-    std::cerr << "from ganga. Please try calling retrieve() again later to ";
-    std::cerr << "clean up the job records." << std::endl;
-  }
-
-  env.needProxy = true;
-  env.needDQ2 = true;
-  if (checkEnvironment(env)) {
-    std::string container;
-    std::ifstream f(dsContFile.c_str());
-    f >> container;
-    f.close();
-    std::cout << "The following DQ2 containers have been created:" << std::endl;
-    gSystem->Exec(("dq2-ls \"" + 
-		   container.substr(0,container.length()-1) +
-		   ".*/\"").c_str());
-  }  
-
-  data.retrieved = true;
-  data.completed = true;
-  return ::StatusCode::SUCCESS;
-}
-
-void EL::GridDriver::status(const std::string& location) {
-  const std::string hist_sh_dir = location + "/output-hist";
-  const std::string dsContFile  = location + "/elg/outputDQ2container";
-  SH::SampleHandler sh;
-  sh.load(hist_sh_dir);
-
-  EnvReqs env;
-  env.needGanga = true;
-  env.needProxy = true;
-  if (not checkEnvironment(env)) return; 
-
-  int taskId = readTaskID(location); 
-  if (taskId == dummyIdCompleted) {
-    ANA_MSG_INFO ("Job is completed completed and output downloaded");
-    return;
-  } else if (taskId == dummyIdFailed) {
-    ANA_MSG_INFO ("The job has failed permanently and will not be tried again");
-    return;
-  }
-  RCU_ASSERT(taskId >= 0); 
-  
-  std::stringstream cmd;
-  cmd << "for [x,y] in [[u,t] for t in tasks(" << taskId << ").transforms " 
-      << "for u in t.units]: print "
-      << "\"unit: %s %s\" % (x.name, x.status)";
-  std::string out;
-  bool ok = sendGangaCmd(cmd.str(), out);
-  if (not ok) {
-    ANA_MSG_ERROR ("Could not start ganga server");
-    return;
-  }
-  const std::string unitList = getStrValues(out, "unit: ");
-  if (unitList.empty()) {
-    ANA_MSG_ERROR ("Could not get the list of task partitions from ganga");
-    ANA_MSG_ERROR ("Possibly the task registry has been lost");
-    return;
-  }
-  ANA_MSG_INFO (unitList);
-}
-
-void EL::GridDriver::status() {
-  std::istringstream tasks(listActive());
-  std::string location;
-  while (getline(tasks, location)) {
-    ANA_MSG_INFO (location);
-    status(location);
-  } 
-}
-
-void EL::GridDriver::kill(const std::string& location) {
-  EnvReqs env;
-  env.needGanga = true;
-  env.needProxy = true;
-  if (not checkEnvironment(env)) return; 
-
-  std::stringstream removeCmd;
-  int taskId = readTaskID(location);
-  if (taskId > 0) {
-    removeCmd << "tasks(" << taskId << ").pause()";
-    removeCmd << "tasks(" << taskId << ").remove(remove_jobs=True)";
-    if (sendGangaCmd(removeCmd.str())) {
-      writeTaskID(location, dummyIdFailed);
-    } else {
-      std::cerr << "Error: Could not remove the job at " << location << std::endl;
-    }
-  } else if (taskId == dummyIdCompleted) {
-    ANA_MSG_INFO ("The job is already completed!");
-  }
-}
-
-void EL::GridDriver::killAll() {
-  std::istringstream tasks(listActive());
-  std::string location;
-  ANA_MSG_INFO ("Killing jobs:");
-  while (getline(tasks, location)) {
-    ANA_MSG_INFO (location);
-    kill(location);
-  }
-}
-
-std::string EL::GridDriver::listActive() {
-  std::string gangaMsg;
-  sendGangaCmd("for t in tasks: print t.comment", gangaMsg);
-  return getStrValues(gangaMsg, "location:");
-}
-
-bool EL::GridDriver::startServer() {
-  return sendGangaCmd("");
-}
-
-bool EL::GridDriver::stopServer() {
-  EnvReqs env;
-  env.needGanga = true;
-  if (not checkEnvironment(env)) {
-    ANA_MSG_INFO ("Failed to set up ganga");
-    return false;
-  }
-  const std::string gangaBin = gSystem->GetFromPipe("command -v ganga").Data();
-  const std::string gangaInstall = gangaBin.substr(0, gangaBin.find("/bin/ganga"));
-  const std::string gangaPythonDir = gangaInstall + "/python";
-  std::stringstream cmd;
-  cmd << "export PYTHONPATH=$PYTHONPATH:" <<  gangaPythonDir << "\n"
-      << "python << EOL\n"
-      << "from GangaService.Lib.ServiceAPI.ServiceAPI import GangaService\n"
-      << "gs = GangaService()\n"
-      << "gs.gangadir = \"" << gangaDir << "\"\n"
-      << "gs.killServer()\n"
-      << "EOL\n";
-  return (gSystem->Exec(cmd.str().c_str()) == 0);
-}
-
-void EL::GridDriver::reset() {
-  gSystem->Exec(Form("rm -rf %s", gangaDir));
-}
-
-namespace EL { 
-  namespace {
-
-    //The functions in this namespace could be split into a separate "utilities"
-    //module. In the interest of one day merging this package with the main EL
-    //package, they are kept here to keep the number of EventLoopGrid related 
-    //files to a minimum.
-
-    /*
-    //TODO: This function is currently not used as the sample meta data
-    //are not filled correctly during submission
-    bool readJobInfo(const std::string location,
-		     Int_t   &nEventsTotal,
-		     Int_t   &nFilesTotal,
-		     Float_t &realTimeTotal,
-		     Float_t &cpuTimeTotal,
-		     Float_t &eventsPerRealSecondTotal, 
-		     Float_t &eventsPerCpuSecondTotal,
-		     bool    printInfo) {
-  
-      TFile f(location.c_str());
-
-      if (f.IsZombie() || not f.IsOpen()) {
-	cerr << "Failed to oen file " << location << endl;
-	return false;
-      }
-
-      TTree *tree = (TTree*)f.Get("EventLoopGridJobInfo");
-    
-      if (tree == 0) {
-	cerr << "Could not read tree 'EventLoopGridJobInfo' ";
-	cerr << "in file " << location << ". ";
-	cerr << "File is corrupt or is not an EventLoopGrid histogram collection";
-	cerr << endl;
-	return false;
-      }    
-
-      nEventsTotal = 0;
-      nFilesTotal = 0;
-      realTimeTotal = 0;
-      cpuTimeTotal = 0;
-      eventsPerRealSecondTotal = 0;
-      eventsPerCpuSecondTotal = 0;
-    
-      Int_t   nEvents = 0;
-      Int_t   nFiles = 0;
-      Float_t realTime = 0;
-      Float_t cpuTime = 0;
-      Float_t eventsPerRealSecond = 0;
-      Float_t eventsPerCpuSecond = 0;
-
-      TBranch *bnEvents = tree->GetBranch("eventsRead");
-      TBranch *bnFiles = tree->GetBranch("filesRead");
-      TBranch *brealTime = tree->GetBranch("loopWallTime");
-      TBranch *bcpuTime = tree->GetBranch("loopCpuTime");
-      TBranch *beventsPerRealSecond = tree->GetBranch("eventsPerWallSec");
-      TBranch *beventsPerCpuSecond = tree->GetBranch("eventsPerCpuSec");
-          
-      bnEvents->SetAddress(&nEvents);
-      bnFiles->SetAddress(&nFiles);
-      brealTime->SetAddress(&realTime);
-      bcpuTime->SetAddress(&cpuTime);
-      beventsPerRealSecond->SetAddress(&eventsPerRealSecond);
-      beventsPerCpuSecond->SetAddress(&eventsPerCpuSecond);
-    
-      if (printInfo) {
-	cout << "Job information for file " << location << ":" << endl;
-      }
-
-      for (Int_t i = 0; i < tree->GetEntriesFast(); i++) {
-	bnEvents->GetEvent(i);
-	bnFiles->GetEvent(i);
-	brealTime->GetEvent(i);
-	bcpuTime->GetEvent(i);
-	beventsPerRealSecond->GetEvent(i);
-	beventsPerCpuSecond->GetEvent(i);
-
-	if (printInfo) {
-	  cout << "  Job " << setw(3) << setfill('0') << i;
-	  cout << " Input files read: " << nFiles;
-	  cout << " Events read: " << nEvents;
-	  cout << " Loop real time: " << realTime;
-	  cout << " Loop CPU time: " << cpuTime;
-	  cout << " events per real second: " << eventsPerRealSecond;
-	  cout << " events per cpu second: " << eventsPerCpuSecond;
-	  cout << endl;	  
-	}
-
-	nEventsTotal += nEvents;
-	nFilesTotal += nFiles;
-	realTimeTotal += realTime;
-	cpuTimeTotal += cpuTimeTotal;
-      }
-    
-      eventsPerRealSecondTotal 
-	= nEventsTotal / (realTimeTotal != 0 ? realTimeTotal : 1.);
-      eventsPerCpuSecondTotal 
-	= nEventsTotal / (cpuTimeTotal != 0 ? cpuTimeTotal : 1.);
-
-      f.Close();
-
-      return true;
-    }
-    */
-
-    /*
-    bool readJobInfo(const std::string location, bool printInfo) {
-      Int_t nEventsTotal = 0, nFilesTotal = 0;
-      Float_t realTimeTotal = 0, cpuTimeTotal = 0;
-      Float_t eventsPerRealSecondTotal = 0, eventsPerCpuSecondTotal = 0;
-      return readJobInfo(location,  nEventsTotal, nFilesTotal, realTimeTotal, 
-			 cpuTimeTotal, eventsPerRealSecondTotal, 
-			 eventsPerCpuSecondTotal, printInfo);
-    } 
-    */     
-
-    /*
-    bool readJobInfo(const std::string location,
-		     Int_t &nEventsTotal, 
-		     Int_t &nFilesTotal,
-		     bool  printInfo) {
-      Float_t realTimeTotal = 0, cpuTimeTotal = 0;
-      Float_t eventsPerRealSecondTotal = 0, eventsPerCpuSecondTotal = 0;
-      return readJobInfo(location,  nEventsTotal, nFilesTotal, realTimeTotal, 
-			 cpuTimeTotal, eventsPerRealSecondTotal, 
-			 eventsPerCpuSecondTotal, printInfo);
-    }
-    */
-
-    //This function extracts substrings from a text, 
-    //It is used to parse the output from GangaService.
-    //Returns: "value" from every substring matching "key: value"
-    std::string getStrValues(const std::string &inLines, const std::string &key) {
-      std::string out;      
-      TString res(inLines);
-      TObjArray *lines = res.Tokenize("\n");
-      TIter itr(lines);          
-      TObject *obj = 0;
-      while ((obj = itr())) {
-	TString l = ((TObjString*)obj)->String();
-	if (l.Index(key.c_str()) != -1) {
-	  TString val = l(l.Index(key.c_str())+key.length(), l.Length());
-	  out += val.Data();
-	  out += "\n";
-	}
-      }
-      delete lines;
-      return out;
-    }
-
-    //This function takes a string like "var1 var2 var3" and formats it as a
-    //python list of strings. It is used in the creation of python commands.
-    //Returns: A string of format "['var1','var2','var3']".
-    const std::string pyList(const std::string &list) {
-      if (list[0] == '\0') return "";
-      TString s(("['" + list + "']").c_str());
-      s.ReplaceAll(" ", "");
-      s.ReplaceAll(",", "','");
-      return s.Data();
-    }
-
-    //This function performs a number of checks on the environment we are
-    //running in. If necessary, it tries to set up the requested components.
-    bool checkEnvironment(EnvReqs env) {
-    
-      if (env.needProxy) env.needPanda = true;
-
-      // if (env.needRootCore && not gSystem->Getenv("ROOTCOREBIN")) {
-      // 	cerr << "Error: Must set up RootCore first" << endl;
-      // 	return false;
-      // }
-
-      env.needPanda &= bool(gSystem->Exec("command -v prun >/dev/null"));
-      env.needGanga &= bool(gSystem->Exec("command -v ganga >/dev/null"));
-
-      bool needALRB = (env.needPanda || env.needGanga);
-      needALRB &= ( ! gSystem->Getenv("ATLAS_LOCAL_ROOT_BASE"));
-
-      if (needALRB) {
-	if (gSystem->Exec(Form("[ -d %s ]", cvmfsATLASLocalRootBase)) == 0)
-	  gSystem->Setenv("ATLAS_LOCAL_ROOT_BASE", cvmfsATLASLocalRootBase);      
-	else {
-	  ANA_MSG_ERROR ("variable ATLASLocalRootBase is not set");
-          ANA_MSG_ERROR ("and CVMFS ATLASLocalRootBase is not available -");
-          ANA_MSG_ERROR ("can't set up ATLAS software on this computer.");
-	  return false;
-	}
-      }
-
-      if (env.needPanda || env.needGanga) {      
-	try {	
-	  std::stringstream cmd;
-	  cmd << "set > __env1" << std::endl;
-	  cmd << shAtlasLocalSetup << " > /dev/null 2>&1" << std::endl;
-	  if (env.needPanda) 
-	    cmd << "localSetupPandaClient --noAthenaCheck >/dev/null 2>&1"<<std::endl;
-	  if (env.needGanga) 
-	    cmd << "localSetupGanga > /dev/null 2>&1" << std::endl;
-	  if (env.needDQ2)
-	    cmd << "localSetupDQ2Client --quiet" << std::endl;
-	  cmd << "set > __env2" << std::endl;
-	  cmd << "grep -v -Fx -f __env1 __env2" << std::endl;
-	  cmd << "rm -f __env1 __env2" << std::endl;
-	  std::string env = gSystem->GetFromPipe(cmd.str().c_str()).Data();
-          std::istringstream ssEnv(env);
-	  std::string line;
-	  while(getline(ssEnv, line)) {
-	    TString s(line.c_str());
-	    gSystem->Setenv(TString(s(0,s.Index("="))).Data(),	
-			    TString(s(s.Index("=")+1, s.Length())).Data());
-	  }
-	}
-	catch (...) {
-	  ANA_MSG_ERROR ("Error setting up ATLAS software, see log for details");
-	  gSystem->Exec("rm -f __env1 __env2");  
-	  return false;
-	}                  
-      }
-    
-      if (env.needPanda && bool(gSystem->Exec("command -v prun>/dev/null"))) {
-	ANA_MSG_ERROR ("Error setting up panda!!");
-	return false;
-      }
-
-      for (int retry = 0; env.needProxy && retry < 3; retry++) {
-	const char pyCheckProxy[] = 
-	  "from pandatools import PsubUtils;"
-	  "PsubUtils.checkGridProxy('',False,False,None);";
-	env.needProxy &= 
-	  bool(gSystem->Exec(Form("python -c \"%s\"", pyCheckProxy)));    
-      }
-
-      if (env.needProxy) {
-	ANA_MSG_ERROR ("Failed to set up grid proxy");
-	return false;
-      }
-
-      return true;
-    }
-
-    //This function returns the users grid nickname. 
-    //It is the callers responsibility to ensure that panda tools 
-    //and voms proxy are available. 
-    const std::string getNickname() {
-      const char pyGetNickName[] =
-	"from pandatools import PsubUtils;"
-	"print (PsubUtils.getNickname());";
-      return gSystem->GetFromPipe(Form("python -c \"%s\" 2>/dev/null", 
-				       pyGetNickName)).Data();    
-    }
-    
-    //This function sends a command to the ganga service.
-    //Returns: true if the command was sent succesfully, otherwise false.
-    bool sendGangaCmd(const std::string &cmd, std::string &out) {
-
-      EnvReqs env;
-      env.needGanga = true;
-      env.needProxy = true;
-      if (not checkEnvironment(env)) return false;
-
-      const std::string gangaCfg = "RUNTIME_PATH=GangaAtlas:GangaPanda";
-      const std::string gangaArg = "-o[Configuration]" + gangaCfg;
-      const std::string gangaBin = gSystem->GetFromPipe("command -v ganga").Data();
-      const std::string gangaInstall = gangaBin.substr(0, gangaBin.find("/bin/ganga"));
-      const std::string gangaPythonDir = gangaInstall + "/python";
-
-      std::stringstream sendCmd;
-      sendCmd
-	<< "export PYTHONPATH=$PYTHONPATH:" <<  gangaPythonDir << "\n"
-	<< "python << EOL\n"
-	<< "from GangaService.Lib.ServiceAPI.ServiceAPI import GangaService\n"
-	<< "import time\n"
-	<< "gs = GangaService()\n"
-	<< "gs.gangadir = \"" << gangaDir << "\"\n"
-	<< "gs.gangacmd = \"" << gangaBin << " " << gangaArg << "\"\n"
-	<< "gs.timeout = 24*60\n"      
-	<< "if not gs.startServer():\n"
-	<< "    gs.killServer()\n" 
-	<< "    if not gs.startServer():\n"
-	<< "        time.sleep(30)\n"      
-	<< "        if not gs.startServer():\n"
-	<< "            gs.killServer()\n" 
-	<< "            time.sleep(60)\n"      
-	<< "            if not gs.startServer():\n"
-	<< "                print (\"Failed to start server\")\n" 
-	<< "                exit(0)\n"
-	<< "print (gs.sendCmd(\"\"\"\n"
-	<< cmd
-	<< "\"\"\"))\n"
-	<< "EOL\n";    
-
-      out = gSystem->GetFromPipe(sendCmd.str().c_str()).Data();
-
-      if (out.find("Failed to start server") != std::string::npos) return false;
-
-      if (not GridDriver::gangaLogFile.empty()) {
-        std::ofstream f(GridDriver::gangaLogFile.c_str(), std::ios::out | std::ios::app);
-	f << "COMMAND:" << std::endl << cmd << std::endl;
-	f << "RESPONSE:" << std::endl << out << std::endl;
-	f.close();
-      }
-
-      std::istringstream stream(out);
-      std::string line;
-      while (std::getline(stream, line)) {
-	if (line.find(": ERROR") != std::string::npos) {
-          std::cout << line << std::endl;
-	}
-      }
-
-      return true;
-    }    
-
-    bool sendGangaCmd(const std::string &cmd) {
-      std::string out;
-      return sendGangaCmd(cmd, out);
-    }
-
-    //This function generates python code to create a Ganga Transform
-    const std::string gangaTrfCmd(const GangaTrfDef &args, 
-				  const GridDriver &driver) {
-      RCU_REQUIRE("" != args.sampleName);
-      RCU_REQUIRE("" != args.inDS);
-      RCU_REQUIRE("" != args.outDS);
-      RCU_REQUIRE("" != args.inputSdBox);
-      RCU_REQUIRE("" != args.outputFiles);
-      RCU_REQUIRE("" != args.downloadDir);
-      
-      std::stringstream gangaCmd;
-      gangaCmd << "trf = AtlasTransform()" << std::endl;        
-      gangaCmd << "trf.name = '" 
-	       << args.outDS.substr(args.outDS.find('.', 5) + 1) << "'" << std::endl;
-      gangaCmd << "t.appendTransform(trf)" << std::endl;
-      if (*args.inDS.rbegin() == '/') {
-	gangaCmd << "trf.initializeFromContainer('" 
-		 << args.inDS << "')" << std::endl;
-      } else {
-	gangaCmd << "trf.initializeFromDatasets(" 
-		 << pyList(args.inDS) << ")" << std::endl;
-      }
-      gangaCmd << "for u in trf.units: u.inputdata.names_pattern = "
-	       << pyList(args.inputFiles) << std::endl;
-      gangaCmd << "trf.setRunLimit(" 
-	       << driver.nMinorRetries + driver.nMajorRetries << ")" << std::endl;
-      gangaCmd << "trf.setMinorRunLimit(" << driver.nMinorRetries << ")" << std::endl;
-      gangaCmd << "trf.setMajorRunLimit(" << driver.nMajorRetries << ")" << std::endl;
-      gangaCmd << "trf.application = app" << std::endl;
-      gangaCmd << "trf.application.options = '" << args.sampleName << "'" << std::endl;
-      gangaCmd << "trf.outputdata = DQ2OutputDataset()" << std::endl;
-      gangaCmd << "trf.outputdata.datasetname = '" << args.outDS << "'" << std::endl;
-      gangaCmd << "trf.outputdata.outputdata = " 
-	       << pyList(args.outputFiles) << std::endl;
-      gangaCmd << "trf.copy_output = DQ2OutputDataset()" << std::endl;
-      gangaCmd << "trf.copy_output.datasetname = '" << args.outDS << "'" << std::endl;
-      gangaCmd << "trf.copy_output.outputdata = " 
-	       << pyList(args.outputFiles) << std::endl;
-      gangaCmd << "trf.backend = Jedi()" << std::endl;    
-      gangaCmd << "trf.backend.requirements.rootver = '" 
-               << args.rootVer << "'" << std::endl;    
-      gangaCmd << "trf.local_location = '" 
-	       << args.downloadDir + "/" + args.sampleName << "'" << std::endl; 
-      gangaCmd << "trf.include_file_mask = " 
-	       << pyList(args.downloadMask) << std::endl;      
-      if (0 < driver.nFiles)
-	gangaCmd << "for u in trf.units: u.inputdata.number_of_files = "
-		 << driver.nFiles << std::endl;
-      if (0 < driver.nFilesPerJob)
-	gangaCmd << "trf.files_per_job = " << driver.nFilesPerJob << std::endl;
-      if (0 < driver.nGBPerJob)
-	gangaCmd << "trf.MB_per_job = " << (int)(driver.nGBPerJob*1000) << std::endl;
-      if (0 < driver.nJobs)
-	gangaCmd << "trf.subjobs_per_unit = " << driver.nJobs << std::endl;
-      if (not driver.destSE.empty())
-	gangaCmd << "trf.outputdata.location = '" << driver.destSE << "'" << std::endl;
-      if (not driver.site.empty())  
-	gangaCmd << "trf.backend.site = '" << driver.site << "'" << std::endl;
-      if (0 < driver.memory) 
-	gangaCmd << "trf.backend.requirements.memory = " << driver.memory << std::endl;
-      if (0 < driver.maxCpuCount)
-	gangaCmd << "trf.backend.requirements.cputime = " 
-		 << driver.maxCpuCount << std::endl;
-      if (driver.express)   
-	gangaCmd << "trf.backend.requirements.express = True" << std::endl; 
-      if (driver.mergeOutput)  
-	gangaCmd << "trf.backend.requirements.enableMerge = True" << std::endl; 
-      gangaCmd << "trf.backend.requirements.configMerge['exec'] = "
-	       << "'elg_merge jobdef.root %OUT %IN'" << std::endl;
-      if (not driver.excludedSite.empty())  
-	gangaCmd << "trf.backend.requirements.excluded_sites = " 
-		 << pyList(driver.excludedSite) << std::endl; 
-      if (not driver.cloud.empty())  
-	gangaCmd << "trf.backend.requirements.cloud = " << driver.cloud << std::endl
-		 << "trf.backend.requirements.anyCloud = False" << std::endl;
-      return gangaCmd.str();
-    }
-   
-    int readTaskID(const std::string& location) {
-      RCU_REQUIRE(not location.empty());
-      const std::string taskIdFile = location + "/elg/taskID";
-      int taskId = -1;       
-      std::ifstream f;
-      try {
-	f.open(taskIdFile.c_str());
-	f >> taskId;      
-      } catch (...) {
-	std::cerr << "Could not read taskID from " << taskIdFile << std::endl;
-      }
-      f.close();
-      return taskId;
-    }
-
-    bool writeTaskID(const std::string& location, const int taskId) {
-      RCU_REQUIRE(not location.empty());
-      const std::string taskIdFile = location + "/elg/taskID";
-      try {
-	std::ofstream f(taskIdFile.c_str());
-	f << taskId;
-	f.close();
-      } catch (...) {
-	return false;
-      }
-      return true;
-    }
-
-  } //namespace
-} //namespace EL
-
-
-
-
-//Method stubs for obsolete functions
-
-void EL::GridDriver::gather(const std::string location) const {
-  RCU_READ_INVARIANT(this);
-  std::cerr << "GridDriver::gather(): This function is obsolete and ";
-  std::cerr << "will be removed in a future version. ";
-  std::cerr << "Please use retrieve() or wait() instead." << std::endl;
-  retrieve(location); 
-}
-
-SH::SampleGrid* EL::GridDriver::createSampleFromDQ2(const std::string& dataset)
-  const {
-  RCU_READ_INVARIANT(this);
-  std::cerr << "GridDriver::createSampleFromDQ2(): This function is obsolete and ";
-  std::cerr << "will be removed in a future version. ";
-  std::cerr << "Please use SH::scanDQ2() instead." << std::endl;
-  std::string name=dataset;
-  size_t pos=0;
-  while((pos=name.find("/", pos)) != std::string::npos) 
-    name.replace(pos++, 1, "_");
-  SH::SampleGrid * mysample=new SH::SampleGrid(name);
-  mysample->meta()->setString("nc_grid", dataset);
-  mysample->meta()->setString("nc_grid_filter", "*");          
-  return mysample;
-}
diff --git a/PhysicsAnalysis/D3PDTools/EventLoopGrid/Root/LinkDef.h b/PhysicsAnalysis/D3PDTools/EventLoopGrid/Root/LinkDef.h
index 50230abf5b74cf7f75543e04fe1aa235a7a0515d..6b3f7ccfe970c4baca6a792f71c0ea3256db1238 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoopGrid/Root/LinkDef.h
+++ b/PhysicsAnalysis/D3PDTools/EventLoopGrid/Root/LinkDef.h
@@ -2,9 +2,6 @@
   Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
 */
 
-#include <EventLoopGrid/GridDriver.h>
-#include <EventLoopGrid/PrunDriver.h>
-
 #ifdef __CINT__
 
 #pragma link off all globals;
@@ -14,7 +11,6 @@
 
 #pragma link C++ namespace EL;
 
-#pragma link C++ class EL::GridDriver+;
 #pragma link C++ class EL::PrunDriver+;
 #pragma link C++ function EL::getRootCoreConfig ();
 
diff --git a/PhysicsAnalysis/D3PDTools/EventLoopGrid/Root/PrunDriver.cxx b/PhysicsAnalysis/D3PDTools/EventLoopGrid/Root/PrunDriver.cxx
index 31957fbaced9873f7f467b07308cb9fe3b133185..2dddffaafe02baef8201d357020a57bbf1212598 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoopGrid/Root/PrunDriver.cxx
+++ b/PhysicsAnalysis/D3PDTools/EventLoopGrid/Root/PrunDriver.cxx
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
 */
 
 /// @author Alexander Madsen
@@ -8,7 +8,6 @@
 
 
 #include <EventLoopGrid/PrunDriver.h>
-#include <EventLoopGrid/GridDriver.h>
 #include <EventLoop/Algorithm.h>
 #include <EventLoop/ManagerData.h>
 #include <EventLoop/ManagerStep.h>
@@ -39,6 +38,7 @@
 #include <sstream>
 #include <string>
 #include <vector>
+#include <stdexcept>
 
 #include <boost/algorithm/string.hpp>
 
@@ -61,10 +61,13 @@ namespace {
 	if (what == name[i]) { return static_cast<Enum>(i); }
       }
       RCU_ASSERT0("Failed to parse job state string");
-      throw; //compiler dummy
+      throw std::runtime_error("PrunDriver.cxx: Failed to parse job state string"); //compiler dummy
     }
   }
 
+  // When changing the values in the enum make sure
+  // corresponding values in `data/ELG_jediState.py` script
+  // are changed accordingly
   namespace Status {
     //static const int NSTATES = 3;
     enum Enum { DONE=0, PENDING=1, FAIL=2 };
@@ -86,7 +89,7 @@ namespace {
 
   struct TmpCd {
     const std::string origDir;
-    TmpCd(std::string dir)
+    TmpCd(const std::string & dir)
       : origDir(gSystem->pwd())
     {
       gSystem->cd(dir.c_str());
@@ -131,8 +134,8 @@ static JobState::Enum nextState(JobState::Enum state, Status::Enum status)
       return TABLE[i].toState;
     }
   }
-  RCU_ASSERT0("Missing state transtion rule");
-  throw; //compiler dummy 
+  RCU_ASSERT0("Missing state transition rule");
+  throw std::logic_error("PrunDriver.cxx: Missing state transition rule"); 
 }
 
 static SH::MetaObject defaultOpts()
@@ -214,26 +217,24 @@ static Status::Enum checkPandaTask(SH::Sample* const sample)
 
   static bool loaded = false;
   if (not loaded) {
-    // TString path = "$ROOTCOREBIN/python/EventLoopGrid/ELG_jediState.py";
-    // gSystem->ExpandPathName(path);
-    // TPython::LoadMacro(path.Data());
     std::string path = PathResolverFindCalibFile("EventLoopGrid/ELG_jediState.py");
     TPython::LoadMacro(path.c_str());
     loaded = true;
   }
 
-  TPython::Bind(dynamic_cast<TObject*>(sample), "ELG_SAMPLE"); 
-  std::string ret = (const char*) TPython::Eval("ELG_jediState(ELG_SAMPLE)");
-  TPython::Bind(0, "ELG_SAMPLE");   
+  TPython::Bind(dynamic_cast<TObject*>(sample), "ELG_SAMPLE");
+  int ret =  TPython::Eval("ELG_jediState(ELG_SAMPLE)");
+  TPython::Bind(0, "ELG_SAMPLE");
 
-  if (ret == "done")  return Status::DONE;
-  if (ret == "failed") return Status::FAIL;
-  if (ret == "finished") return Status::FAIL;
+  if (ret == Status::DONE) return Status::DONE;
+  if (ret == Status::FAIL) return Status::FAIL;
 
-  if (ret != "running") { sample->meta()->setString("nc_ELG_state_details", ret); }
-  if (ret.empty()) { 
-    sample->meta()->setString("nc_ELG_state_details", 
-                              "problem checking jedi task status"); 
+  // Value 90 corresponds to `running` state of the job
+  if (ret != 90) { sample->meta()->setString("nc_ELG_state_details", "task status other than done/finished/failed/running"); }
+  // Value 99 is returned if there is error in the script (import, missing ID)
+  if (ret == 99) {
+    sample->meta()->setString("nc_ELG_state_details",
+                              "problem checking jedi task status");
   }
 
   return Status::PENDING;
@@ -380,7 +381,7 @@ static void processAllInState(const SH::SampleHandler& sh, JobState::Enum state,
 }
 
 static std::string formatOutputName(const SH::MetaObject& sampleMeta,
-				    const std::string pattern)
+				    const std::string & pattern)
 {
   const std::string sampleName = sampleMeta.castString("sample_name");
   RCU_REQUIRE(not pattern.empty());
@@ -445,9 +446,15 @@ static void saveJobDef(const std::string& fileName,
     outputs.Add(o->Clone());
   file.WriteTObject(&job.jobConfig(), "jobConfig", "SingleKey");        
   file.WriteTObject(&outputs, "outputs", "SingleKey");        
+  bool haveDefault = false;
   for (SH::SampleHandler::iterator s = sh.begin(); s != sh.end(); ++s) {
     const SH::MetaObject& meta = *((*s)->meta());
     file.WriteObject(&meta, meta.castString("sample_name").c_str());
+    if (!haveDefault)
+    {
+      file.WriteObject (&meta, "defaultMetaObject");
+      haveDefault = true;
+    }
   }
 }  
 
diff --git a/PhysicsAnalysis/D3PDTools/EventLoopGrid/data/ELG_jediState.py b/PhysicsAnalysis/D3PDTools/EventLoopGrid/data/ELG_jediState.py
index 11e63092842f9e440dcf7c6dab082e92d107937c..5a0bdaaf7fc09645473f1a975142cf7767075a44 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoopGrid/data/ELG_jediState.py
+++ b/PhysicsAnalysis/D3PDTools/EventLoopGrid/data/ELG_jediState.py
@@ -3,27 +3,54 @@
 from __future__ import print_function
 
 def ELG_jediState(sample) :
+    """ Returns state of the jobs.
+
+    State returned as an int based on an enum in the PrunDriver.cxx :
+    `enum Enum { DONE=0, PENDING=1, FAIL=2 };`
+    Make sure these values are kept the same in both files.
+    Extra values are added to capture extra states, those start at 90.
+
+    """
+    from enum import IntEnum
+    class Status(IntEnum):
+        DONE = 0
+        PENDING = 1
+        FAIL = 2
+        RUNNING = 90
+        OTHER = 91
+        SCRIPT_FAIL = 99
+
 
     try:
         from pandatools import PandaToolsPkgInfo  # noqa: F401
     except ImportError:
         print ("prun needs additional setup, try:")
         print ("    lsetup panda")
-        return 99
+        return Status.SCRIPT_FAIL
 
     jediTaskID = int(sample.meta().castDouble("nc_jediTaskID", 0))
 
     if jediTaskID < 100 :
         print ("Sample " + sample.name() + " does not have a jediTaskID")
-        return ''
+        return Status.SCRIPT_FAIL
 
     from pandatools import Client
 
     taskDict = {}
     taskDict['jediTaskID'] = jediTaskID
-    ret = Client.getJediTaskDetails(taskDict, False, True)
-    if ret[0] != 0 :
+    detail = Client.getJediTaskDetails(taskDict, False, True)
+    if detail[0] != 0 :
         print ("Problem checking status of task %s with id %s" % (sample.name(), jediTaskID))
-        return ''
+        return Status.SCRIPT_FAIL
+
+    status = detail[1]['status']
+
+    if status == "done": return Status.DONE
+    elif status == "failed": return Status.FAIL
+    # Finished returning FAIL status is original
+    # behavior from the PrunDriver.cxx
+    elif status == "finished": return Status.FAIL
+    elif status == "running": return Status.RUNNING
 
-    return ret[1]['status'].encode("ascii")
+    # Value for states not considered by PrunDriver.cxx
+    return Status.OTHER
diff --git a/PhysicsAnalysis/D3PDTools/EventLoopTest/CMakeLists.txt b/PhysicsAnalysis/D3PDTools/EventLoopTest/CMakeLists.txt
index 6897f00c21d5f4cf050ba52a5a155bcb7f9422e5..a2280a5e76c626f4cd50e9e831a5127ef536b968 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoopTest/CMakeLists.txt
+++ b/PhysicsAnalysis/D3PDTools/EventLoopTest/CMakeLists.txt
@@ -34,7 +34,8 @@ atlas_add_root_dictionary( EventLoopTestLib
    EXTERNAL_PACKAGES ROOT )
 
 atlas_add_library( EventLoopTestLib
-   EventLoopTest/*.h EventLoopTest/*.ihh Root/*.cxx ${EventLoopTestDictSource}
+   Root/UnitTest.cxx Root/UnitTestAlg.cxx Root/UnitTestAlg1.cxx Root/UnitTestAlg2.cxx Root/UnitTestAlg3.cxx Root/UnitTestAlg4.cxx Root/UnitTestAlg5.cxx Root/UnitTestAlg6.cxx Root/UnitTestAlg7.cxx Root/UnitTestAlgXAOD.cxx Root/UnitTestConfig.cxx Root/UnitTestTool.cxx
+   ${EventLoopTestDictSource}
    PUBLIC_HEADERS EventLoopTest
    INCLUDE_DIRS ${ROOT_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS}
    LINK_LIBRARIES ${ROOT_LIBRARIES} ${GMOCK_LIBRARIES} RootCoreUtils
@@ -91,5 +92,4 @@ endforeach (source ${util_sources})
 
 
 # Install files from the package:
-atlas_install_scripts( scripts/el_retrieve scripts/el_wait )
-atlas_install_data( data/*.root data/*.yml )
+atlas_install_data( data/*.yml data/*.py )
diff --git a/PhysicsAnalysis/D3PDTools/EventLoopTest/EventLoopTest/EventLoopTestDict.h b/PhysicsAnalysis/D3PDTools/EventLoopTest/EventLoopTest/EventLoopTestDict.h
index 9472bf63c650c69d4025c8e2115bd789f0f5bed6..49df960545d84db27623df564568ca7c01854073 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoopTest/EventLoopTest/EventLoopTestDict.h
+++ b/PhysicsAnalysis/D3PDTools/EventLoopTest/EventLoopTest/EventLoopTestDict.h
@@ -7,5 +7,6 @@
 
 #include "EventLoopTest/UnitTestAlg5.h"
 #include "EventLoopTest/UnitTestAlg6.h"
+#include "EventLoopTest/UnitTestAlg7.h"
 
 #endif
diff --git a/PhysicsAnalysis/D3PDTools/EventLoopTest/EventLoopTest/UnitTestAlg7.h b/PhysicsAnalysis/D3PDTools/EventLoopTest/EventLoopTest/UnitTestAlg7.h
new file mode 100644
index 0000000000000000000000000000000000000000..46dbe36cb5cfaa174a89275adf76ede4437e9275
--- /dev/null
+++ b/PhysicsAnalysis/D3PDTools/EventLoopTest/EventLoopTest/UnitTestAlg7.h
@@ -0,0 +1,50 @@
+/*
+  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef EVENT_LOOP_UNIT_TEST_ALG7_H
+#define EVENT_LOOP_UNIT_TEST_ALG7_H
+
+#include <EventLoopTest/Global.h>
+
+#include <AnaAlgorithm/AnaAlgorithm.h>
+
+namespace EL
+{
+  /// \brief a \ref AnaAlgorithm for testing the configuration on the
+  /// worker node
+
+  class UnitTestAlg7 final : public AnaAlgorithm
+  {
+    //
+    // public interface
+    //
+
+  public:
+    UnitTestAlg7 (const std::string& name,
+                  ISvcLocator* pSvcLocator);
+
+
+
+    //
+    // interface inherited from Algorithm
+    //
+
+  private:
+    virtual ::StatusCode initialize () override;
+
+  private:
+    virtual ::StatusCode execute () override;
+
+  private:
+    virtual ::StatusCode finalize () override;
+
+
+
+    //
+    // private interface
+    //
+  };
+}
+
+#endif
diff --git a/PhysicsAnalysis/D3PDTools/EventLoopTest/EventLoopTest/selection.xml b/PhysicsAnalysis/D3PDTools/EventLoopTest/EventLoopTest/selection.xml
index 8cd781929c2ccee9e7baddff063dc79da7cbc5ae..46e0d98a39d80445f70bb97f7919cad8daf02a27 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoopTest/EventLoopTest/selection.xml
+++ b/PhysicsAnalysis/D3PDTools/EventLoopTest/EventLoopTest/selection.xml
@@ -3,5 +3,6 @@
    <!-- Unit test types: -->
    <class name="EL::UnitTestAlg5" />
    <class name="EL::UnitTestAlg6" />
+   <class name="EL::UnitTestAlg7" />
 
 </lcgdict>
diff --git a/PhysicsAnalysis/D3PDTools/EventLoopTest/Root/UnitTestAlg2.cxx b/PhysicsAnalysis/D3PDTools/EventLoopTest/Root/UnitTestAlg2.cxx
index dd94437076fb325bedea0fb2928231c3bdfd0f79..609d8a358e1254daad52716257a0becd0e14d65c 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoopTest/Root/UnitTestAlg2.cxx
+++ b/PhysicsAnalysis/D3PDTools/EventLoopTest/Root/UnitTestAlg2.cxx
@@ -22,6 +22,7 @@
 #include <EventLoop/Worker.h>
 #include <RootCoreUtils/Assert.h>
 #include <RootCoreUtils/ThrowMsg.h>
+#include <TEfficiency.h>
 #include <TFile.h>
 #include <TH1.h>
 #include <TTree.h>
@@ -73,6 +74,12 @@ namespace EL
 
     RCU_ASSERT_SOFT (!m_hasInitialize);
 
+    // test that we can create and then retrieve a TEfficiency
+    // histogram
+    ANA_CHECK (book (TEfficiency ("efficiency", "dummy efficiency hist", 50, 0, 50)));
+    (void) hist<TEfficiency> ("efficiency");
+    (void) histeff ("efficiency");
+
     ANA_CHECK (book (TH1F ((m_name + "2_2").c_str(), m_name.c_str(), 50, 0, 50)));
     ANA_CHECK (book (TH1F ("file_executes_2", "file executes", 1, 0, 1)));
 
diff --git a/PhysicsAnalysis/D3PDTools/EventLoopTest/Root/UnitTestAlg7.cxx b/PhysicsAnalysis/D3PDTools/EventLoopTest/Root/UnitTestAlg7.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..2146faa47b8670b010f7dd99d403a0ac37d7ecf2
--- /dev/null
+++ b/PhysicsAnalysis/D3PDTools/EventLoopTest/Root/UnitTestAlg7.cxx
@@ -0,0 +1,51 @@
+/*
+  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+*/
+
+
+//
+// includes
+//
+
+#include <EventLoopTest/UnitTestAlg7.h>
+
+#include <TH1.h>
+
+//
+// method implementations
+//
+
+namespace EL
+{
+  UnitTestAlg7 ::
+  UnitTestAlg7 (const std::string& name,
+                ISvcLocator* pSvcLocator)
+    : AnaAlgorithm (name, pSvcLocator)
+  {
+  }
+
+
+
+  ::StatusCode UnitTestAlg7 ::
+  initialize ()
+  {
+    ANA_CHECK (book (TH1F ("dummy_hist", "dummy_hist", 50, 0, 50)));
+    return ::StatusCode::SUCCESS;
+  }
+
+
+
+  ::StatusCode UnitTestAlg7 ::
+  execute ()
+  {
+    return ::StatusCode::SUCCESS;
+  }
+
+
+
+  ::StatusCode UnitTestAlg7 ::
+  finalize ()
+  {
+    return ::StatusCode::SUCCESS;
+  }
+}
diff --git a/PhysicsAnalysis/D3PDTools/EventLoopTest/data/worker_config.py b/PhysicsAnalysis/D3PDTools/EventLoopTest/data/worker_config.py
new file mode 100644
index 0000000000000000000000000000000000000000..f35e8dc2fbb2399e1b82dca579b8e9015eac98dd
--- /dev/null
+++ b/PhysicsAnalysis/D3PDTools/EventLoopTest/data/worker_config.py
@@ -0,0 +1,5 @@
+from AnaAlgorithm.DualUseConfig import createAlgorithm
+
+def fillWorkerConfig (config) :
+    alg = createAlgorithm ('EL::UnitTestAlg7', 'myalg')
+    config.add (alg)
diff --git a/PhysicsAnalysis/D3PDTools/EventLoopTest/test/gt_LeakChecks.cxx b/PhysicsAnalysis/D3PDTools/EventLoopTest/test/gt_LeakChecks.cxx
index b2e38d43fe1e6d83920bbc7e4efd181240f9cf0e..52e55bb8781284f0aa1d508996146d6e5510da10 100644
--- a/PhysicsAnalysis/D3PDTools/EventLoopTest/test/gt_LeakChecks.cxx
+++ b/PhysicsAnalysis/D3PDTools/EventLoopTest/test/gt_LeakChecks.cxx
@@ -56,9 +56,9 @@ protected:
       m_job.algsAdd( algconf );
 
       // Set up the job.
-      m_job.options()->setBool( EL::Job::optRemoveSubmitDir, true );
       m_job.options()->setBool( EL::Job::optMemFailOnLeak,
                                 GetParam().enableChecks );
+      m_job.options()->setString (EL::Job::optSubmitDirMode, "unique");
    }
 
    /// EventLoop job to be run
@@ -100,8 +100,8 @@ TEST_P( LeakCheckTests, batch ) {
    EL::LocalDriver driver;
    try
    {
-     driver.submit( m_job, outputDir.str() );
-     driver.retrieve( outputDir.str() );
+     std::string submitDir = driver.submit( m_job, outputDir.str() );
+     driver.retrieve( submitDir );
      if (shouldSucceed == false)
        FAIL() << "job succeeded when it should have failed";
    } catch (...)
diff --git a/PhysicsAnalysis/D3PDTools/EventLoopTest/test/gt_worker_config.cxx b/PhysicsAnalysis/D3PDTools/EventLoopTest/test/gt_worker_config.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..fcd492825c1ffd11e167d9b1392d94a38669edc0
--- /dev/null
+++ b/PhysicsAnalysis/D3PDTools/EventLoopTest/test/gt_worker_config.cxx
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+//
+
+#include <AsgTesting/UnitTest.h>
+#include <EventLoop/DirectDriver.h>
+#include <EventLoop/Job.h>
+#include <EventLoop/OutputStream.h>
+#include <SampleHandler/SampleHandler.h>
+#include <SampleHandler/SampleLocal.h>
+#include <TFile.h>
+
+using namespace EL;
+
+TEST (WorkerConfigTest, DISABLED_baseTest)
+{
+  Job job;
+  job.useXAOD();
+  job.outputAdd (OutputStream ("out"));
+  job.options()->setString (Job::optWorkerConfigFile, "EventLoopTest/worker_config.py");
+  job.options()->setString (Job::optSubmitDirMode, "unique");
+
+  SH::SampleHandler sh;
+  sh.setMetaString ("nc_tree", "CollectionTree");
+  auto sample = std::make_unique<SH::SampleLocal> ("mc");
+  sample->add( "file://${ASG_TEST_FILE_MC}" );
+  sh.add (std::move (sample));
+  job.sampleHandler (sh);
+
+  DirectDriver driver;
+  std::string submitDir = driver.submit (job, "submitDir");
+  std::unique_ptr<TFile> file (TFile::Open ((submitDir + "/hist-mc.root").c_str(), "READ"));
+  ASSERT_NE (file.get(), nullptr);
+  ASSERT_NE (file->Get ("dummy_hist"), nullptr);
+}
+
+// Declare the main() function.
+ATLAS_GOOGLE_TEST_MAIN
diff --git a/PhysicsAnalysis/D3PDTools/MultiDraw/Root/AlgCFlow.cxx b/PhysicsAnalysis/D3PDTools/MultiDraw/Root/AlgCFlow.cxx
index 6980923ea6995b4386d634fbbb41a1a6ba0ed916..8ed331c7944451dd2d2d87e6c56a52a3da5c35a6 100644
--- a/PhysicsAnalysis/D3PDTools/MultiDraw/Root/AlgCFlow.cxx
+++ b/PhysicsAnalysis/D3PDTools/MultiDraw/Root/AlgCFlow.cxx
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
 */
 
 //          Copyright Nils Krumnack 2011.
@@ -107,12 +107,11 @@ namespace MD
       }
     }
 
-    m_hist = hist.get();
+    m_hist = hist.release();
     m_hist->SetDirectory (0);
 
     m_values.resize (m_formulas.size());
 
-    hist.release ();
     RCU_NEW_INVARIANT (this);
   }
 
diff --git a/PhysicsAnalysis/D3PDTools/MultiDraw/Root/AlgHist.cxx b/PhysicsAnalysis/D3PDTools/MultiDraw/Root/AlgHist.cxx
index 1b953e6eae1bd044d85a78d8364589ae41c70a98..e96f38982dea08704b02dae41b88b2ec89aa497e 100644
--- a/PhysicsAnalysis/D3PDTools/MultiDraw/Root/AlgHist.cxx
+++ b/PhysicsAnalysis/D3PDTools/MultiDraw/Root/AlgHist.cxx
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
 */
 
 //          Copyright Nils Krumnack 2011.
@@ -112,7 +112,7 @@ namespace MD
     if (!val_value3.empty())
       m_formulas.push_back (val_value3);
 
-    m_hist = hist.get();
+    m_hist = hist.release();
     m_hist->SetDirectory (0);
     if (dynamic_cast<TProfile*>(m_hist) != 0)
     {
@@ -139,7 +139,6 @@ namespace MD
     else
       RCU_THROW_MSG ("invalid number of formulas");
 
-    hist.release ();
     RCU_NEW_INVARIANT (this);
   }
 
diff --git a/PhysicsAnalysis/D3PDTools/MultiDraw/Root/Formula.cxx b/PhysicsAnalysis/D3PDTools/MultiDraw/Root/Formula.cxx
index 093ed41f66d86f9e0223be519e4745e9224d9b1b..f52d63211fe8aea2cd8e5a357932032af656f166 100644
--- a/PhysicsAnalysis/D3PDTools/MultiDraw/Root/Formula.cxx
+++ b/PhysicsAnalysis/D3PDTools/MultiDraw/Root/Formula.cxx
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
 */
 
 //          Copyright Nils Krumnack 2011 - 2012.
@@ -114,8 +114,7 @@ namespace MD
     m_tree = 0;
 
     m_tree = tree;
-    std::unique_ptr<TTreeFormula> myform
-      (m_form = new TTreeFormula (m_name.c_str(), m_formula.c_str(), tree));
+    m_form = new TTreeFormula (m_name.c_str(), m_formula.c_str(), tree);
 
     m_form->SetQuickLoad (kTRUE);
     m_manager = new TTreeFormulaManager;
@@ -132,7 +131,7 @@ namespace MD
     else if (m_manager->GetMultiplicity() == -1 && m_form->GetMultiplicity() == 1)
       m_ndim = 0;
 
-    myform.release();
+    
   }
 
 
diff --git a/PhysicsAnalysis/D3PDTools/RootCoreUtils/CMakeLists.txt b/PhysicsAnalysis/D3PDTools/RootCoreUtils/CMakeLists.txt
index c6d303315f1986bdeaf93006ae4e2693e57fffa5..303147b7aed331215f991b6684050c257b8a564f 100644
--- a/PhysicsAnalysis/D3PDTools/RootCoreUtils/CMakeLists.txt
+++ b/PhysicsAnalysis/D3PDTools/RootCoreUtils/CMakeLists.txt
@@ -1,7 +1,4 @@
-# $Id: CMakeLists.txt 739245 2016-04-11 08:05:59Z krasznaa $
-################################################################################
-# Package: RootCoreUtils
-################################################################################
+# Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
 
 # Declare the package name:
 atlas_subdir( RootCoreUtils )
diff --git a/PhysicsAnalysis/D3PDTools/RootCoreUtils/Root/Assert.cxx b/PhysicsAnalysis/D3PDTools/RootCoreUtils/Root/Assert.cxx
index 5e4f62c0ec1b344a41a61061cc5349d8440a2cc6..76d055acd47213726ba2114f4ed54d0eecd94801 100644
--- a/PhysicsAnalysis/D3PDTools/RootCoreUtils/Root/Assert.cxx
+++ b/PhysicsAnalysis/D3PDTools/RootCoreUtils/Root/Assert.cxx
@@ -26,7 +26,7 @@ namespace RCU
 {
   namespace Check
   {
-    const char *typeLiteral [typeNum] =
+    const char * const typeLiteral [typeNum] =
       {
 	"assertion failed",
 	"assertion failed",
@@ -45,7 +45,7 @@ namespace RCU
 
 
 
-    bool typeAbort [typeNum] =
+    const bool typeAbort [typeNum] =
       {
 	false,
 	true,
diff --git a/PhysicsAnalysis/D3PDTools/RootCoreUtils/Root/CheckRootVersion.cxx b/PhysicsAnalysis/D3PDTools/RootCoreUtils/Root/CheckRootVersion.cxx
index af0992ca9f0186dacd224136067fe875c2bc06a1..f38d638443676b42a03acd6c12be8164cf77ded3 100644
--- a/PhysicsAnalysis/D3PDTools/RootCoreUtils/Root/CheckRootVersion.cxx
+++ b/PhysicsAnalysis/D3PDTools/RootCoreUtils/Root/CheckRootVersion.cxx
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
 */
 
 /// @author Nils Krumnack
@@ -14,6 +14,7 @@
 
 #include <cstdlib>
 #include <iostream>
+#include <atomic>
 #include <TROOT.h>
 #include <RootCoreUtils/Assert.h>
 
@@ -25,7 +26,7 @@ namespace RCU
 {
   namespace
   {
-    bool checked = false;
+    std::atomic<bool> checked {false};
   }
 
   void check_root_version ()
diff --git a/PhysicsAnalysis/D3PDTools/RootCoreUtils/Root/Message.cxx b/PhysicsAnalysis/D3PDTools/RootCoreUtils/Root/Message.cxx
index 6b84627837249d0a7d24424eb578a61004aa9f20..5805c697d8d814dbc8e21d06b4006cf29e01a661 100644
--- a/PhysicsAnalysis/D3PDTools/RootCoreUtils/Root/Message.cxx
+++ b/PhysicsAnalysis/D3PDTools/RootCoreUtils/Root/Message.cxx
@@ -61,7 +61,7 @@ namespace RCU
 
     if (mytype != MESSAGE_UNSPECIFIED)
     {
-      static const char *type_names[MESSAGE_UNSPECIFIED] =
+      static const char * const type_names[MESSAGE_UNSPECIFIED] =
 	{"message", "warning", "error", "exception", "abort"};
       str << type_names[mytype] << ":";
     }
diff --git a/PhysicsAnalysis/D3PDTools/RootCoreUtils/Root/StringUtil.cxx b/PhysicsAnalysis/D3PDTools/RootCoreUtils/Root/StringUtil.cxx
index 07c1ea8d0927aa54715a24335dd2c45806261098..2e4a255fc234d019f780d17f947719042d547b54 100644
--- a/PhysicsAnalysis/D3PDTools/RootCoreUtils/Root/StringUtil.cxx
+++ b/PhysicsAnalysis/D3PDTools/RootCoreUtils/Root/StringUtil.cxx
@@ -28,8 +28,10 @@ namespace RCU
 
     std::string result = str;
     std::string::size_type pos;
-    while ((pos = result.find (pattern)) != std::string::npos)
+    while ((pos = result.find (pattern)) != std::string::npos) {
+      // cppcheck-suppress uselessCallsSubstr
       result = result.substr (0, pos) + with + result.substr (pos + pattern.size());
+    }
     return result;
   }
 
diff --git a/PhysicsAnalysis/D3PDTools/RootCoreUtils/RootCoreUtils/ATLAS_CHECK_THREAD_SAFETY b/PhysicsAnalysis/D3PDTools/RootCoreUtils/RootCoreUtils/ATLAS_CHECK_THREAD_SAFETY
new file mode 100644
index 0000000000000000000000000000000000000000..ebf7dd1d3901449205e3d2bd884078e9dcaba6f3
--- /dev/null
+++ b/PhysicsAnalysis/D3PDTools/RootCoreUtils/RootCoreUtils/ATLAS_CHECK_THREAD_SAFETY
@@ -0,0 +1 @@
+PhysicsAnalysis/D3PDTools/RootCoreUtils
diff --git a/PhysicsAnalysis/D3PDTools/RootCoreUtils/RootCoreUtils/Assert.h b/PhysicsAnalysis/D3PDTools/RootCoreUtils/RootCoreUtils/Assert.h
index 39595ec546dedeac41ab96bcc3b0c0ca4cf14f98..069103c71ab66a2f7fa6942fb996e1e289a2f3cf 100644
--- a/PhysicsAnalysis/D3PDTools/RootCoreUtils/RootCoreUtils/Assert.h
+++ b/PhysicsAnalysis/D3PDTools/RootCoreUtils/RootCoreUtils/Assert.h
@@ -73,8 +73,8 @@ namespace RCU
 	invariant
       };
     const int typeNum = invariant + 1;
-    extern const char *typeLiteral [typeNum];
-    extern bool typeAbort [typeNum];
+    extern const char * const typeLiteral [typeNum];
+    extern const bool typeAbort [typeNum];
 
 
     /// effects: report the error and abort either by exception or
diff --git a/PhysicsAnalysis/D3PDTools/RootCoreUtils/share/ut_expression.ref b/PhysicsAnalysis/D3PDTools/RootCoreUtils/share/ut_expression.ref
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/PhysicsAnalysis/D3PDTools/RootCoreUtils/share/ut_ran_packages_false.ref b/PhysicsAnalysis/D3PDTools/RootCoreUtils/share/ut_ran_packages_false.ref
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/PhysicsAnalysis/D3PDTools/SampleHandler/Root/GridTools.cxx b/PhysicsAnalysis/D3PDTools/SampleHandler/Root/GridTools.cxx
index ee5b4e47de471ef3b5f99e42ab2807c57c386318..a7868edaf376eae57237b719e7cd379592b2be9e 100644
--- a/PhysicsAnalysis/D3PDTools/SampleHandler/Root/GridTools.cxx
+++ b/PhysicsAnalysis/D3PDTools/SampleHandler/Root/GridTools.cxx
@@ -143,7 +143,7 @@ namespace SH
           while (isspace (subresult.front()))
             subresult = subresult.substr (1);
           while (isspace (subresult.back()))
-            subresult = subresult.substr (0, subresult.size()-1);
+            subresult.pop_back();
           result.push_back (std::move (subresult));
         }
       }
diff --git a/PhysicsAnalysis/D3PDTools/SampleHandler/Root/Sample.cxx b/PhysicsAnalysis/D3PDTools/SampleHandler/Root/Sample.cxx
index fbf7b6c42fa895d7246040e70105240fc0295455..69ce9582be1196bd469ad2d705ad61ae23b8247d 100644
--- a/PhysicsAnalysis/D3PDTools/SampleHandler/Root/Sample.cxx
+++ b/PhysicsAnalysis/D3PDTools/SampleHandler/Root/Sample.cxx
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
 */
 
 //          Copyright Nils Krumnack 2011.
@@ -456,6 +456,7 @@ namespace SH
     std::unique_ptr<TFile> file (TFile::Open (fileList[0].c_str(), "READ"));
     if (file == nullptr)
       RCU_THROW_MSG ("could not open file " + fileList[0]);
+    //cppcheck-suppress nullPointerRedundantCheck 
     TObject *object = file->Get (name.c_str());
     if (object != nullptr)
       RCU::SetDirectory (object, nullptr);
diff --git a/PhysicsAnalysis/D3PDTools/SampleHandler/Root/ScanDir.cxx b/PhysicsAnalysis/D3PDTools/SampleHandler/Root/ScanDir.cxx
index cfa7d64185ac42e975b9436396a968b20216813c..5914d32fb9a17cde4a7a5165ca99491bb7713b54 100644
--- a/PhysicsAnalysis/D3PDTools/SampleHandler/Root/ScanDir.cxx
+++ b/PhysicsAnalysis/D3PDTools/SampleHandler/Root/ScanDir.cxx
@@ -279,7 +279,7 @@ namespace SH
 	  {
 	    if (iter == 0)
 	      RCU_THROW_MSG ("sample name matches entire postfix pattern: \"" + sampleName + "\"");
-	    sampleName = sampleName.substr (0, iter);
+	    sampleName.resize (iter);
 	    done = true;
 	  }
 	}
@@ -349,7 +349,7 @@ namespace SH
 	     myindex < 0)
       {
 	while (!sampleName.empty() && sampleName[sampleName.size()-1] == '/')
-	  sampleName = sampleName.substr (0, sampleName.size() - 1);
+	  sampleName.pop_back();
 	if (sampleName.empty())
 	  return sampleName;
 	if (myindex < 0)
@@ -360,7 +360,7 @@ namespace SH
 	    sampleName.clear ();
 	    return sampleName;
 	  }
-	  sampleName = sampleName.substr (0, split);
+	  sampleName.resize (split);
 	  ++ myindex;
 	}
 	if (sampleName.empty())
diff --git a/PhysicsAnalysis/D3PDTools/SampleHandler/Root/ToolsDiscovery.cxx b/PhysicsAnalysis/D3PDTools/SampleHandler/Root/ToolsDiscovery.cxx
index 448eba6553a39434f98ae27d0d597da60a0b5c0d..96e6a761fefadd16fbcb7410a989cca658a7b1da 100644
--- a/PhysicsAnalysis/D3PDTools/SampleHandler/Root/ToolsDiscovery.cxx
+++ b/PhysicsAnalysis/D3PDTools/SampleHandler/Root/ToolsDiscovery.cxx
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
 */
 
 //          Copyright Nils Krumnack 2011.
@@ -282,7 +282,7 @@ namespace SH
               std::string url = entry.replica;
               const auto split = url.find (from);
               if (split != std::string::npos)
-                url = url.substr (0, split) + to + url.substr (split + from.size());
+                url.replace(split, from.size(), to);
               usedFiles[entry.name] = url;
             }
           }
diff --git a/PhysicsAnalysis/D3PDTools/SampleHandler/Root/ToolsOther.cxx b/PhysicsAnalysis/D3PDTools/SampleHandler/Root/ToolsOther.cxx
index 6527045e9be1f7c1cb373936fc21965c3b67ffd3..9703cde31c062e167c3352d70cf3178a1029cf2e 100644
--- a/PhysicsAnalysis/D3PDTools/SampleHandler/Root/ToolsOther.cxx
+++ b/PhysicsAnalysis/D3PDTools/SampleHandler/Root/ToolsOther.cxx
@@ -60,6 +60,7 @@ namespace SH
 	return result;
     }
     RCU_THROW_MSG ("failed to open file: " + name);
+    //cppcheck-suppress rethrowNoCurrentException
     throw; //compiler dummy
   }
 
diff --git a/PhysicsAnalysis/D3PDTools/SampleHandler/SampleHandler/SampleGrid.h b/PhysicsAnalysis/D3PDTools/SampleHandler/SampleHandler/SampleGrid.h
index 3199dd0a349a7a85c1ed1d7f700b7185a8af9090..a7825b1f826af3c029afebc9ad2858339b83d969 100644
--- a/PhysicsAnalysis/D3PDTools/SampleHandler/SampleHandler/SampleGrid.h
+++ b/PhysicsAnalysis/D3PDTools/SampleHandler/SampleHandler/SampleGrid.h
@@ -31,7 +31,7 @@ namespace SH
   /// the files to use within.
   ///
   /// The main purpose as such is to pass it as an input sample into
-  /// the EL::GridDriver.  However, in practice one can still use this
+  /// the @ref EL::PrunDriver.  However, in practice one can still use this
   /// like a regular sample (using direct access via FAX, or
   /// pre-loading via rucio).
   ///