diff --git a/Trigger/TrigSteer/L1Decoder/python/L1DecoderConfig.py b/Trigger/TrigSteer/L1Decoder/python/L1DecoderConfig.py
index 422f28bd146681137c8776c941a84716fefb3897..bcf84590999f0a96c1412654775bcde6d0748100 100644
--- a/Trigger/TrigSteer/L1Decoder/python/L1DecoderConfig.py
+++ b/Trigger/TrigSteer/L1Decoder/python/L1DecoderConfig.py
@@ -95,9 +95,22 @@ def createMuonRoIUnpackers():
     return [muUnpacker],[muRerunUnpacker]
 
 
+from L1Decoder.L1DecoderConf import L1TriggerResultMaker
+class L1TriggerResultMaker(L1TriggerResultMaker):
+    def __init__(self, name='L1TriggerResultMaker', *args, **kwargs):
+        super(L1TriggerResultMaker, self).__init__(name, *args, **kwargs)
 
-from L1Decoder.L1DecoderConf import L1Decoder
+        # Muon RoIs
+        self.MuRoIKey = "LVL1MuonRoIs"
+        self.MuRoILinkName = "mu_roi"
+
+        # Placeholder for other L1 xAOD outputs:
+        # - CTP result
+        # - L1Topo result
+        # - L1Calo (Run3) RoIs
 
+
+from L1Decoder.L1DecoderConf import L1Decoder
 class L1Decoder(L1Decoder) :
     def __init__(self, name='L1Decoder', *args, **kwargs):
         super(L1Decoder, self).__init__(name, *args, **kwargs)
@@ -167,11 +180,12 @@ def L1DecoderCfg(flags):
     acc.merge( TrigConfigSvcCfg( flags ) )
     acc.merge( HLTPrescaleCondAlgCfg( flags ) )
 
-    # Add the algorithm producing the input RoIBResult
-    from TrigT1ResultByteStream.TrigT1ResultByteStreamConfig import RoIBResultDecoderCfg, L1TriggerResultMakerCfg
+    # Add the algorithms producing the input RoIBResult (legacy L1) / L1TriggerResult (Run-3 L1)
+    from TrigT1ResultByteStream.TrigT1ResultByteStreamConfig import RoIBResultDecoderCfg, L1TriggerByteStreamDecoderCfg
     # TODO: implement flags to allow disabling either RoIBResult or L1TriggerResult
     acc.merge( RoIBResultDecoderCfg(flags) )
-    acc.merge( L1TriggerResultMakerCfg(flags) )
+    acc.merge( L1TriggerByteStreamDecoderCfg(flags) )
+    acc.addEventAlgo( L1TriggerResultMaker() )
 
     return acc,decoderAlg
 
diff --git a/Trigger/TrigT1/TrigT1ResultByteStream/src/L1TriggerResultMaker.cxx b/Trigger/TrigSteer/L1Decoder/src/L1TriggerResultMaker.cxx
similarity index 53%
rename from Trigger/TrigT1/TrigT1ResultByteStream/src/L1TriggerResultMaker.cxx
rename to Trigger/TrigSteer/L1Decoder/src/L1TriggerResultMaker.cxx
index a11add6d1887c6310b21ca1dc3443688becb6c96..a58731242b331760d3966d99770f0c7ea8b872fb 100644
--- a/Trigger/TrigT1/TrigT1ResultByteStream/src/L1TriggerResultMaker.cxx
+++ b/Trigger/TrigSteer/L1Decoder/src/L1TriggerResultMaker.cxx
@@ -5,6 +5,19 @@
 #include "L1TriggerResultMaker.h"
 #include "xAODTrigger/TrigCompositeAuxContainer.h"
 
+namespace {
+  template<class T> void makeLink(const SG::ReadHandleKey<T>& rhk,
+                                  xAOD::TrigComposite& target,
+                                  const std::string& linkName,
+                                  const EventContext& eventContext) {
+    ElementLink<T> link = ElementLink<T>(rhk.key(), 0, eventContext);
+    target.typelessSetObjectLink(linkName,
+                                 link.key(),
+                                 ClassID_traits<T>::ID(),
+                                 /*index =*/ 0);
+  }
+}
+
 // =============================================================================
 // Standard constructor
 // =============================================================================
@@ -17,18 +30,7 @@ L1TriggerResultMaker::L1TriggerResultMaker(const std::string& name, ISvcLocator*
 StatusCode L1TriggerResultMaker::initialize() {
   ATH_MSG_DEBUG("Initialising " << name());
   ATH_CHECK(m_l1TriggerResultWHKey.initialize());
-  ATH_CHECK(m_decoderTools.retrieve());
-  ATH_CHECK(m_robDataProviderSvc.retrieve());
-  return StatusCode::SUCCESS;
-}
-
-// =============================================================================
-// Implementation of AthReentrantAlgorithm::finalize
-// =============================================================================
-StatusCode L1TriggerResultMaker::finalize() {
-  ATH_MSG_DEBUG("Finalising " << name());
-  ATH_CHECK(m_robDataProviderSvc.release());
-  ATH_CHECK(m_decoderTools.release());
+  ATH_CHECK(m_muRoIKey.initialize());
   return StatusCode::SUCCESS;
 }
 
@@ -39,27 +41,22 @@ StatusCode L1TriggerResultMaker::execute(const EventContext& eventContext) const
   ATH_MSG_DEBUG("Executing " << name());
 
   // Create and record the L1TriggerResult container
-  SG::WriteHandle<xAOD::TrigCompositeContainer> handle(m_l1TriggerResultWHKey, eventContext);
+  SG::WriteHandle<xAOD::TrigCompositeContainer> l1trHandle(m_l1TriggerResultWHKey, eventContext);
   auto cont = std::make_unique<xAOD::TrigCompositeContainer>();
   auto auxcont = std::make_unique<xAOD::TrigCompositeAuxContainer>();
   cont->setStore(auxcont.get());
-  ATH_CHECK(handle.record(std::move(cont), std::move(auxcont)));
+  ATH_CHECK(l1trHandle.record(std::move(cont), std::move(auxcont)));
   ATH_MSG_DEBUG("Recorded L1TriggerResult with key " << m_l1TriggerResultWHKey.key());
 
-  // Retrieve the BS data and fill a new L1TriggerResult
-  IROBDataProviderSvc::VROBFRAG vrobf;
-  handle->push_back(new xAOD::TrigComposite);
-  for (const auto& decoderTool : m_decoderTools) {
-    vrobf.clear();
-    m_robDataProviderSvc->getROBData(eventContext, decoderTool->robIds(), vrobf, name());
-    ATH_CHECK(decoderTool->convert(vrobf, *(handle->back()), eventContext));
-    // Verify if the link was created
-    if (!handle->back()->hasObjectLink(decoderTool->linkName())) {
-      ATH_MSG_ERROR("Decoder tool " << decoderTool.name() << " failed to link the xAOD RoI object to L1TriggerResult"
-                    << " with the link name " << decoderTool->linkName());
-      return StatusCode::FAILURE;
-    }
-  }
+  // Create new L1TriggerResult in the container
+  l1trHandle->push_back(new xAOD::TrigComposite);
+
+  // Retrieve the L1 xAOD containers to verify they exist
+  auto muRoIHandle = SG::makeHandle(m_muRoIKey, eventContext);
+  ATH_CHECK(muRoIHandle.isValid());
+
+  // Link the L1 xAOD containers (actually their first elements) to L1TriggerResult
+  makeLink(m_muRoIKey, *(l1trHandle->back()), m_muRoILinkName.value(), eventContext);
 
   return StatusCode::SUCCESS;
 }
diff --git a/Trigger/TrigT1/TrigT1ResultByteStream/src/L1TriggerResultMaker.h b/Trigger/TrigSteer/L1Decoder/src/L1TriggerResultMaker.h
similarity index 58%
rename from Trigger/TrigT1/TrigT1ResultByteStream/src/L1TriggerResultMaker.h
rename to Trigger/TrigSteer/L1Decoder/src/L1TriggerResultMaker.h
index 81c9a2471252076ddfc0ffee8ec1cb4f9795ccb1..be3e56030c30e13f060a65134db6569d8cafc0c3 100644
--- a/Trigger/TrigT1/TrigT1ResultByteStream/src/L1TriggerResultMaker.h
+++ b/Trigger/TrigSteer/L1Decoder/src/L1TriggerResultMaker.h
@@ -6,16 +6,16 @@
 #define TRIGT1RESULTBYTESTREAM_L1TRIGGERRESULTMAKER_H
 
 // Trigger includes
-#include "TrigT1ResultByteStream/IL1TriggerByteStreamTool.h"
+#include "xAODTrigger/MuonRoIContainer.h"
 #include "xAODTrigger/TrigCompositeContainer.h"
 
 // Athena includes
 #include "AthenaBaseComps/AthReentrantAlgorithm.h"
-#include "ByteStreamCnvSvcBase/IROBDataProviderSvc.h"
-#include "StoreGate/WriteHandle.h"
+#include "StoreGate/ReadHandleKey.h"
+#include "StoreGate/WriteHandleKey.h"
 
 /** @class L1TriggerResultMaker
- *  @brief Algorithm creating RoIBResult from ByteStream representation
+ *  @brief Algorithm creating L1TriggerResult and linking the relevant L1 xAOD collections to it
  **/
 class L1TriggerResultMaker : public AthReentrantAlgorithm {
 public:
@@ -24,22 +24,23 @@ public:
 
   // ------------------------- AthReentrantAlgorithm methods -------------------
   virtual StatusCode initialize() override;
-  virtual StatusCode finalize() override;
   virtual StatusCode execute(const EventContext& eventContext) const override;
 
 private:
   // ------------------------- Properties --------------------------------------
-  /// StoreGate key for the output RoIBResult
   SG::WriteHandleKey<xAOD::TrigCompositeContainer> m_l1TriggerResultWHKey {
     this, "L1TriggerResultWHKey", "L1TriggerResult", "Key of the output L1 Trigger Result"};
 
-  // ------------------------- Tool/Service handles ----------------------------
-  /// Tool performing the decoding work
-  ToolHandleArray<IL1TriggerByteStreamTool> m_decoderTools {
-    this, "DecoderTools", {}, "Array of tools performing the decoding work"};
-  /// ROBDataProvider service handle
-  ServiceHandle<IROBDataProviderSvc> m_robDataProviderSvc {
-    this, "ROBDataProviderSvc", "ROBDataProviderSvc", "ROB data provider"};
+  // Muon RoIs
+  SG::ReadHandleKey<xAOD::MuonRoIContainer> m_muRoIKey {
+    this, "MuRoIKey", "LVL1MuonRoIs", "Key of the muon RoI container to be linked to L1 Trigger Result"};
+  Gaudi::Property<std::string> m_muRoILinkName {
+    this, "MuRoILinkName", "mu_roi", "Name of the link to be created from L1 Trigger Result to muon RoI container"};
+
+  // Placeholder for other L1 xAOD outputs:
+  // - CTP result
+  // - L1Topo result
+  // - L1Calo (Run3) RoIs
 };
 
 #endif // TRIGT1RESULTBYTESTREAM_L1TRIGGERRESULTMAKER_H
diff --git a/Trigger/TrigSteer/L1Decoder/src/components/L1Decoder_entries.cxx b/Trigger/TrigSteer/L1Decoder/src/components/L1Decoder_entries.cxx
index 67bb34747cf00174112a9b96efe72cf73822fae4..e17042a57404438d23d46493f4e66b8bb35e1f28 100644
--- a/Trigger/TrigSteer/L1Decoder/src/components/L1Decoder_entries.cxx
+++ b/Trigger/TrigSteer/L1Decoder/src/components/L1Decoder_entries.cxx
@@ -18,7 +18,7 @@
 #include "../PrescalingTool.h"
 #include "../PrescalingEmulationTool.h"
 #include "../CreateFullScanRoI.h"
-
+#include "../L1TriggerResultMaker.h"
 
 DECLARE_COMPONENT( L1CaloDecoder )
 DECLARE_COMPONENT( FakeRoI )
@@ -40,3 +40,4 @@ DECLARE_COMPONENT( PrescalingTool )
 DECLARE_COMPONENT( PrescalingEmulationTool )
 DECLARE_COMPONENT( RerunRoIsUnpackingTool )
 DECLARE_COMPONENT( CreateFullScanRoI )
+DECLARE_COMPONENT( L1TriggerResultMaker )
diff --git a/Trigger/TrigT1/TrigT1ResultByteStream/TrigT1ResultByteStream/IL1TriggerByteStreamTool.h b/Trigger/TrigT1/TrigT1ResultByteStream/TrigT1ResultByteStream/IL1TriggerByteStreamTool.h
index 9717932b0bebbfb55f0bdd18286f44a5f681e7ac..300b84b6efa494099737d8df816d2adf8f20f3c2 100644
--- a/Trigger/TrigT1/TrigT1ResultByteStream/TrigT1ResultByteStream/IL1TriggerByteStreamTool.h
+++ b/Trigger/TrigT1/TrigT1ResultByteStream/TrigT1ResultByteStream/IL1TriggerByteStreamTool.h
@@ -20,40 +20,29 @@ public:
   /**
    * @brief Convert BS -> xAOD
    *
-   * The implementation should create an xAOD RoI object from the raw data, record it in the event store,
-   * and then link it to the l1TriggerResult object.
+   * The implementation should create an xAOD RoI object from the raw data and record it in the event store
+   * using a WriteHandle it declares.
    **/
-  virtual StatusCode convert(const std::vector<const OFFLINE_FRAGMENTS_NAMESPACE::ROBFragment*>& vrobf,
-                             xAOD::TrigComposite& l1TriggerResult,
-                             const EventContext& eventContext) const = 0;
+  virtual StatusCode convertFromBS(const std::vector<const OFFLINE_FRAGMENTS_NAMESPACE::ROBFragment*>& vrobf,
+                                   const EventContext& eventContext) const = 0;
 
   /**
    * @brief Convert xAOD -> BS
    *
-   * The implementation should take the xAOD RoI object linked to the l1TriggerResult object,
+   * The implementation should take the xAOD RoI object from the event store using a ReadHandle it declares,
    * convert it to raw data, and fill the vrobf vector.
    **/
-  virtual StatusCode convert(const xAOD::TrigComposite& l1TriggerResult,
-                             std::vector<const OFFLINE_FRAGMENTS_NAMESPACE::ROBFragment*>& vrobf,
-                             const EventContext& eventContext) const = 0;
+  virtual StatusCode convertToBS(std::vector<const OFFLINE_FRAGMENTS_NAMESPACE::ROBFragment*>& vrobf,
+                                 const EventContext& eventContext) const = 0;
 
   /**
-   * @brief List of IDs of ROBs which the convert() methods expect in the vrobf input/output parameter
+   * @brief List of IDs of ROBs which the convert methods expect in the vrobf input/output parameter
    *
    * The implementation has to hold a Gaudi::Property<vector<uint32_t>> to declare the ROB IDs it requires/provides
    * and this method has to return the value of this property. There is no easy way to declare a Gaudi::Property here
    * in the interface, so it is delegated to the implementation.
    **/
   virtual const std::vector<uint32_t> robIds() const = 0;
-
-  /**
-   * @brief Name of the link between the xAOD RoI converted by the implementation from/to raw data
-   *
-   * The implementation has to hold a Gaudi::Property<std::string> to declare the link name it requires/provides
-   * and this method has to return the value of this property. There is no easy way to declare a Gaudi::Property here
-   * in the interface, so it is delegated to the implementation.
-   **/
-  virtual const std::string linkName() const = 0;
 };
 
 #endif // TRIGT1RESULTBYTESTREAM_IL1TRIGGERBYTESTREAMTOOL_H
diff --git a/Trigger/TrigT1/TrigT1ResultByteStream/python/TrigT1ResultByteStreamConfig.py b/Trigger/TrigT1/TrigT1ResultByteStream/python/TrigT1ResultByteStreamConfig.py
index 53851d5d59e5730a6d1dfc0d09537ae82659e9e7..f746167aab49b6828756d274d57bf8cc1b544e4f 100644
--- a/Trigger/TrigT1/TrigT1ResultByteStream/python/TrigT1ResultByteStreamConfig.py
+++ b/Trigger/TrigT1/TrigT1ResultByteStream/python/TrigT1ResultByteStreamConfig.py
@@ -14,8 +14,8 @@ def RoIBResultDecoderCfg(flags):
   acc.addEventAlgo(decoderAlg)
   return acc
 
-def L1TriggerResultMakerCfg(flags):
-  from TrigT1ResultByteStream.TrigT1ResultByteStreamConf import L1TriggerResultMaker,ExampleL1TriggerByteStreamTool
+def L1TriggerByteStreamDecoderCfg(flags):
+  from TrigT1ResultByteStream.TrigT1ResultByteStreamConf import L1TriggerByteStreamDecoderAlg,ExampleL1TriggerByteStreamTool
   from libpyeformat_helper import SourceIdentifier,SubDetector
 
   # Placeholder for real decoder tools - now it's just an example
@@ -23,14 +23,12 @@ def L1TriggerResultMakerCfg(flags):
   muctpi_robid = int(SourceIdentifier(SubDetector.TDAQ_MUON_CTP_INTERFACE, muctpi_moduleid))
   exampleTool = ExampleL1TriggerByteStreamTool(ROBIDs=[muctpi_robid],
                                                MUCTPIModuleId=muctpi_moduleid,
-                                               MuonRoIContainerWriteKey="LVL1MuonRoIs",
-                                               LinkName="mu_roi")
+                                               MuonRoIContainerWriteKey="LVL1MuonRoIs")
 
   decoderTools = [exampleTool]
 
-  decoderAlg = L1TriggerResultMaker(name="L1TriggerResultMaker",
-                                    L1TriggerResultWHKey="L1TriggerResult",
-                                    DecoderTools=decoderTools)
+  decoderAlg = L1TriggerByteStreamDecoderAlg(name="L1TriggerByteStreamDecoder",
+                                             DecoderTools=decoderTools)
 
   from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
   acc = ComponentAccumulator()
@@ -44,4 +42,4 @@ def L1ByteStreamDecodersRecExSetup(enableRun2L1=True, enableRun3L1=True):
   if enableRun2L1:
     CAtoGlobalWrapper(RoIBResultDecoderCfg,ConfigFlags)
   if enableRun3L1:
-    CAtoGlobalWrapper(L1TriggerResultMakerCfg,ConfigFlags)
+    CAtoGlobalWrapper(L1TriggerByteStreamDecoderCfg,ConfigFlags)
diff --git a/Trigger/TrigT1/TrigT1ResultByteStream/src/ExampleL1TriggerByteStreamTool.cxx b/Trigger/TrigT1/TrigT1ResultByteStream/src/ExampleL1TriggerByteStreamTool.cxx
index d0bc7d292d12e300b9d973b4d3b62b1eb7a55e68..716f047ce303e0d00c3b322d987909599acf71e6 100644
--- a/Trigger/TrigT1/TrigT1ResultByteStream/src/ExampleL1TriggerByteStreamTool.cxx
+++ b/Trigger/TrigT1/TrigT1ResultByteStream/src/ExampleL1TriggerByteStreamTool.cxx
@@ -15,14 +15,19 @@ ExampleL1TriggerByteStreamTool::ExampleL1TriggerByteStreamTool(const std::string
 : base_class(type, name, parent) {}
 
 StatusCode ExampleL1TriggerByteStreamTool::initialize() {
+  if (m_roiWriteKey.empty() == m_roiReadKey.empty()) {
+    ATH_MSG_ERROR("Exactly one of the read / write handle keys has to be set and the other has to be empty, "
+                  << "but they are \"" << m_roiReadKey.key() << "\" / \"" << m_roiWriteKey.key() << "\"");
+    return StatusCode::FAILURE;
+  }
   ATH_CHECK(m_roiWriteKey.initialize(!m_roiWriteKey.empty()));
+  ATH_CHECK(m_roiReadKey.initialize(!m_roiReadKey.empty()));
   return StatusCode::SUCCESS;
 }
 
 // BS->xAOD conversion
-StatusCode ExampleL1TriggerByteStreamTool::convert(const std::vector<const ROBF*>& vrobf,
-                                                   xAOD::TrigComposite& l1TriggerResult,
-                                                   const EventContext& eventContext) const {
+StatusCode ExampleL1TriggerByteStreamTool::convertFromBS(const std::vector<const ROBF*>& vrobf,
+                                                         const EventContext& eventContext) const {
   if (m_roiWriteKey.empty()) {
     ATH_MSG_ERROR("Conversion from BS to xAOD RoI requested but RoI WriteHandleKey is empty");
     return StatusCode::FAILURE;
@@ -36,13 +41,6 @@ StatusCode ExampleL1TriggerByteStreamTool::convert(const std::vector<const ROBF*
   ATH_CHECK(handle.record(std::move(cont), std::move(auxcont)));
   ATH_MSG_DEBUG("Recorded MuonRoIContainer with key " << m_roiWriteKey.key());
 
-  // Link the RoI container (actually its first element) to L1TriggerResult
-  ElementLink<xAOD::MuonRoIContainer> link = ElementLink<xAOD::MuonRoIContainer>(m_roiWriteKey.key(), 0, eventContext);
-  l1TriggerResult.typelessSetObjectLink(m_linkName.value(),
-                                        link.key(),
-                                        ClassID_traits<xAOD::MuonRoIContainer>::ID(),
-                                        /*index =*/ 0);
-
   // Find the ROB fragment to decode
   const eformat::helper::SourceIdentifier sid(eformat::TDAQ_MUON_CTP_INTERFACE, m_muCTPIModuleID.value());
   auto it = std::find_if(vrobf.begin(), vrobf.end(), [&sid](const ROBF* rob){return rob->rob_source_id() == sid.code();});
@@ -68,15 +66,11 @@ StatusCode ExampleL1TriggerByteStreamTool::convert(const std::vector<const ROBF*
 }
 
 /// xAOD->BS conversion
-StatusCode ExampleL1TriggerByteStreamTool::convert(const xAOD::TrigComposite& l1TriggerResult,
-                                                   std::vector<const ROBF*>& /*vrobf*/,
-                                                   const EventContext& /*eventContext*/) const {
-
+StatusCode ExampleL1TriggerByteStreamTool::convertToBS(std::vector<const ROBF*>& /*vrobf*/,
+                                                       const EventContext& eventContext) const {
   // Retrieve the RoI container
-  if (!l1TriggerResult.hasObjectLink(m_linkName.value())) {
-    ATH_MSG_ERROR("L1TriggerResult does not have a link \"" << m_linkName << "\"");
-    return StatusCode::FAILURE;
-  }
+  auto muonRoIs = SG::makeHandle(m_roiReadKey, eventContext);
+  ATH_CHECK(muonRoIs.isValid());
 
   // TODO: implement this part when new code requesting the xAOD->BS conversion is implemented (ATR-19542)
 
diff --git a/Trigger/TrigT1/TrigT1ResultByteStream/src/ExampleL1TriggerByteStreamTool.h b/Trigger/TrigT1/TrigT1ResultByteStream/src/ExampleL1TriggerByteStreamTool.h
index 1003f9401122cf8c7a03c45f428c88d4de89a3ce..dd6c72cf0c7ad6db91f6900ea9ae324357486e01 100644
--- a/Trigger/TrigT1/TrigT1ResultByteStream/src/ExampleL1TriggerByteStreamTool.h
+++ b/Trigger/TrigT1/TrigT1ResultByteStream/src/ExampleL1TriggerByteStreamTool.h
@@ -21,8 +21,8 @@
  *
  *  This example decodes Muon RoIs from MUCTPI raw data, filling the results with dummy values. Real implementations
  *  should have very similar structure and should implement the same functionality and properties. In particular,
- *  their BS->xAOD convert method should also record a new xAOD collection in the event store and link it to the
- *  L1TriggerResult object, as presented here.
+ *  the convertFromBS method should record a new xAOD collection in the event store using a WriteHandle, and the
+ *  convertToBS method should take the xAOD collection from the event store using a ReadHandle.
  **/
 class ExampleL1TriggerByteStreamTool : public extends<AthAlgTool, IL1TriggerByteStreamTool> {
 public:
@@ -34,35 +34,30 @@ public:
 
   // ------------------------- IL1TriggerByteStreamTool methods ----------------
   /// BS->xAOD conversion
-  virtual StatusCode convert(const std::vector<const OFFLINE_FRAGMENTS_NAMESPACE::ROBFragment*>& vrobf,
-                             xAOD::TrigComposite& l1TriggerResult,
-                             const EventContext& eventContext) const override;
+  virtual StatusCode convertFromBS(const std::vector<const OFFLINE_FRAGMENTS_NAMESPACE::ROBFragment*>& vrobf,
+                                   const EventContext& eventContext) const override;
   /// xAOD->BS conversion
-  virtual StatusCode convert(const xAOD::TrigComposite& l1TriggerResult,
-                             std::vector<const OFFLINE_FRAGMENTS_NAMESPACE::ROBFragment*>& vrobf,
-                             const EventContext& eventContext) const override;
+  virtual StatusCode convertToBS(std::vector<const OFFLINE_FRAGMENTS_NAMESPACE::ROBFragment*>& vrobf,
+                                 const EventContext& eventContext) const override;
   /// Declare ROB IDs for conversion
   virtual const std::vector<uint32_t> robIds() const override {return m_robIds.value();}
-  /// Declare name of the link from L1TriggerResult to the xAOD RoI
-  virtual const std::string linkName() const override {return m_linkName.value();}
 
 private:
   // ------------------------- Properties --------------------------------------
-  // The following two are required by the interface
+  // ROBIDs property required by the interface
   Gaudi::Property<std::vector<uint32_t>> m_robIds {
     this, "ROBIDs", {}, "List of ROB IDs required for conversion to/from xAOD RoI"};
-  Gaudi::Property<std::string> m_linkName {
-    this, "LinkName", "UNDEFINED_LINK_NAME", "Name of the link from L1TriggerResult to the xAOD RoI"};
+  // It is a good idea to have also the module IDs configurable in case they change at some point
+  Gaudi::Property<uint16_t> m_muCTPIModuleID {
+    this, "MUCTPIModuleId", 1, "Module ID of MUCTPI ROB with RoI information"};
 
-  /// Write key should be reset to empty string in python configuration if the tool is in xAOD->BS mode of operation
+  // Only write key should be set to non-empty string in python configuration if the tool is in BS->xAOD mode of operation
   SG::WriteHandleKey<xAOD::MuonRoIContainer> m_roiWriteKey {
-    this, "MuonRoIContainerWriteKey", "LVL1MuonRoIs", "Write handle key to MuonRoIContainer for conversion from ByteStream"};
+    this, "MuonRoIContainerWriteKey", "", "Write handle key to MuonRoIContainer for conversion from ByteStream"};
+  // Only read key should be set to non-empty string in python configuration if the tool is in xAOD->BS mode of operation
+  SG::ReadHandleKey<xAOD::MuonRoIContainer> m_roiReadKey {
+    this, "MuonRoIContainerReadKey", "", "Read handle key to MuonRoIContainer for conversion to ByteStream"};
 
-  // It is a good idea to have the module IDs configurable in case they change at some point
-  /// MUCTPI Module ID to decode
-  Gaudi::Property<uint16_t> m_muCTPIModuleID {
-    this, "MUCTPIModuleId", 1, "Module ID of MUCTPI ROB with RoI information"
-  };
 };
 
 #endif // TRIGT1RESULTBYTESTREAM_EXAMPLEL1TRIGGERBYTESTREAMTOOL_H
diff --git a/Trigger/TrigT1/TrigT1ResultByteStream/src/L1TriggerByteStreamDecoderAlg.cxx b/Trigger/TrigT1/TrigT1ResultByteStream/src/L1TriggerByteStreamDecoderAlg.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..9334a729897af2ca01d3299bf3b5fc07afe12fe7
--- /dev/null
+++ b/Trigger/TrigT1/TrigT1ResultByteStream/src/L1TriggerByteStreamDecoderAlg.cxx
@@ -0,0 +1,48 @@
+/*
+  Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "L1TriggerByteStreamDecoderAlg.h"
+
+// =============================================================================
+// Standard constructor
+// =============================================================================
+L1TriggerByteStreamDecoderAlg::L1TriggerByteStreamDecoderAlg(const std::string& name, ISvcLocator* svcLoc)
+: AthReentrantAlgorithm(name, svcLoc) {}
+
+// =============================================================================
+// Implementation of AthReentrantAlgorithm::initialize
+// =============================================================================
+StatusCode L1TriggerByteStreamDecoderAlg::initialize() {
+  ATH_MSG_DEBUG("Initialising " << name());
+  ATH_CHECK(m_decoderTools.retrieve());
+  ATH_CHECK(m_robDataProviderSvc.retrieve());
+  return StatusCode::SUCCESS;
+}
+
+// =============================================================================
+// Implementation of AthReentrantAlgorithm::finalize
+// =============================================================================
+StatusCode L1TriggerByteStreamDecoderAlg::finalize() {
+  ATH_MSG_DEBUG("Finalising " << name());
+  ATH_CHECK(m_robDataProviderSvc.release());
+  ATH_CHECK(m_decoderTools.release());
+  return StatusCode::SUCCESS;
+}
+
+// =============================================================================
+// Implementation of AthReentrantAlgorithm::execute
+// =============================================================================
+StatusCode L1TriggerByteStreamDecoderAlg::execute(const EventContext& eventContext) const {
+  ATH_MSG_DEBUG("Executing " << name());
+
+  // Retrieve the BS data and call the decoder tools
+  IROBDataProviderSvc::VROBFRAG vrobf;
+  for (const auto& decoderTool : m_decoderTools) {
+    vrobf.clear();
+    m_robDataProviderSvc->getROBData(eventContext, decoderTool->robIds(), vrobf, name());
+    ATH_CHECK(decoderTool->convertFromBS(vrobf, eventContext));
+  }
+
+  return StatusCode::SUCCESS;
+}
diff --git a/Trigger/TrigT1/TrigT1ResultByteStream/src/L1TriggerByteStreamDecoderAlg.h b/Trigger/TrigT1/TrigT1ResultByteStream/src/L1TriggerByteStreamDecoderAlg.h
new file mode 100644
index 0000000000000000000000000000000000000000..1af617f97e17d5ace60a3d770a4f994118fe430c
--- /dev/null
+++ b/Trigger/TrigT1/TrigT1ResultByteStream/src/L1TriggerByteStreamDecoderAlg.h
@@ -0,0 +1,38 @@
+/*
+  Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef TRIGT1RESULTBYTESTREAM_L1TRIGGERBYTESTREAMDECODERALG_H
+#define TRIGT1RESULTBYTESTREAM_L1TRIGGERBYTESTREAMDECODERALG_H
+
+// Trigger includes
+#include "TrigT1ResultByteStream/IL1TriggerByteStreamTool.h"
+
+// Athena includes
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+#include "ByteStreamCnvSvcBase/IROBDataProviderSvc.h"
+
+/** @class L1TriggerByteStreamDecoderAlg
+ *  @brief Algorithm calling tools to convert L1 ByteStream into xAOD collections
+ **/
+class L1TriggerByteStreamDecoderAlg : public AthReentrantAlgorithm {
+public:
+  /// Standard constructor
+  L1TriggerByteStreamDecoderAlg(const std::string& name, ISvcLocator* svcLoc);
+
+  // ------------------------- AthReentrantAlgorithm methods -------------------
+  virtual StatusCode initialize() override;
+  virtual StatusCode finalize() override;
+  virtual StatusCode execute(const EventContext& eventContext) const override;
+
+private:
+  // ------------------------- Tool/Service handles ----------------------------
+  /// Tool performing the decoding work
+  ToolHandleArray<IL1TriggerByteStreamTool> m_decoderTools {
+    this, "DecoderTools", {}, "Array of tools performing the decoding work"};
+  /// ROBDataProvider service handle
+  ServiceHandle<IROBDataProviderSvc> m_robDataProviderSvc {
+    this, "ROBDataProviderSvc", "ROBDataProviderSvc", "ROB data provider"};
+};
+
+#endif // TRIGT1RESULTBYTESTREAM_L1TRIGGERBYTESTREAMDECODERALG_H
diff --git a/Trigger/TrigT1/TrigT1ResultByteStream/src/components/TrigT1ResultByteStream_entries.cxx b/Trigger/TrigT1/TrigT1ResultByteStream/src/components/TrigT1ResultByteStream_entries.cxx
index fc6a0aecc4c6daf2460fade6b79802b32dc67c65..8c5b33ce982bc0a9dbb1ffba9df71b13349039f1 100644
--- a/Trigger/TrigT1/TrigT1ResultByteStream/src/components/TrigT1ResultByteStream_entries.cxx
+++ b/Trigger/TrigT1/TrigT1ResultByteStream/src/components/TrigT1ResultByteStream_entries.cxx
@@ -15,7 +15,7 @@
 #include "TrigT1ResultByteStream/RoIBResultByteStreamTool.h"
 
 #include "../RoIBResultByteStreamDecoderAlg.h"
-#include "../L1TriggerResultMaker.h"
+#include "../L1TriggerByteStreamDecoderAlg.h"
 #include "../ExampleL1TriggerByteStreamTool.h"
 
 // ROBF for offline
@@ -41,5 +41,5 @@ DECLARE_COMPONENT( RecRoIBResultByteStreamTool )
 DECLARE_COMPONENT( RoIBResultByteStreamTool )
 
 DECLARE_COMPONENT( RoIBResultByteStreamDecoderAlg )
-DECLARE_COMPONENT( L1TriggerResultMaker )
+DECLARE_COMPONENT( L1TriggerByteStreamDecoderAlg )
 DECLARE_COMPONENT( ExampleL1TriggerByteStreamTool )
diff --git a/Trigger/TriggerCommon/TriggerJobOpts/share/runHLT_standalone.py b/Trigger/TriggerCommon/TriggerJobOpts/share/runHLT_standalone.py
index 4e89781c122f6984fe468af53e413dbec9aa7253..dfb0a1830b776eb30687116b73edf4b50cc0b6a3 100644
--- a/Trigger/TriggerCommon/TriggerJobOpts/share/runHLT_standalone.py
+++ b/Trigger/TriggerCommon/TriggerJobOpts/share/runHLT_standalone.py
@@ -444,10 +444,13 @@ if opt.doL1Unpacking:
         from L1Decoder.L1DecoderConfig import L1Decoder
         l1decoder = L1Decoder("L1Decoder")
         l1decoder.ctpUnpacker.ForceEnableAllChains = opt.forceEnableAllChains
+        if opt.decodePhaseIL1:
+            from L1Decoder.L1DecoderConfig import L1TriggerResultMaker
+            topSequence += L1TriggerResultMaker()
+        else:
+            l1decoder.L1TriggerResult = ""
         if not opt.decodeLegacyL1:
             l1decoder.RoIBResult = ""
-        if not opt.decodePhaseIL1:
-            l1decoder.L1TriggerResult = ""
         if TriggerFlags.doTransientByteStream():
             transTypeKey = ("TransientBSOutType","StoreGateSvc+TransientBSOutKey")
             l1decoder.ExtraInputs += [transTypeKey]