diff --git a/Trigger/TrigSteer/TrigOutputHandling/python/TrigOutputHandlingConfig.py b/Trigger/TrigSteer/TrigOutputHandling/python/TrigOutputHandlingConfig.py
index 5da5bd79be0b23c3074274be5589a74715ea4797..a3f488c27c9960bc8b1d789d95ba2e94150ae2f7 100644
--- a/Trigger/TrigSteer/TrigOutputHandling/python/TrigOutputHandlingConfig.py
+++ b/Trigger/TrigSteer/TrigOutputHandling/python/TrigOutputHandlingConfig.py
@@ -110,3 +110,18 @@ def TriggerBitsMakerToolCfg(name="TriggerBitsMakerTool"):
    # Extra configuration may come here
 
    return bitsmaker
+
+def DecisionSummaryMakerAlgCfg(name="DecisionSummaryMakerAlg"):
+   alg = CompFactory.DecisionSummaryMakerAlg(name)
+   from AthenaMonitoringKernel.GenericMonitoringTool import GenericMonitoringTool
+   alg.MonTool = GenericMonitoringTool('MonTool', HistPath='HLTFramework/'+name)
+   alg.MonTool.defineHistogram('RoIsDEta', path='EXPERT', type='TH1F',
+                               title='Change of RoI eta position between initial and final RoI;delta eta;N final RoIs',
+                               xbins=51, xmin=-1.02, xmax=1.02)
+   alg.MonTool.defineHistogram('RoIsDPhi', path='EXPERT', type='TH1F',
+                               title='Change of RoI phi position between initial and final RoI;delta phi;N final RoIs',
+                               xbins=51, xmin=-1.02, xmax=1.02)
+   alg.MonTool.defineHistogram('RoIsDZed', path='EXPERT', type='TH1F',
+                               title='Change of RoI z position between initial and final RoI;delta z;N final RoIs',
+                               xbins=51, xmin=-204, xmax=204)
+   return alg
diff --git a/Trigger/TrigSteer/TrigOutputHandling/src/DecisionSummaryMakerAlg.cxx b/Trigger/TrigSteer/TrigOutputHandling/src/DecisionSummaryMakerAlg.cxx
index a3225afa47d438200d70b94e0a7a5babd0aa499e..e29f9b992f75a3ddf0a203e867a76ddf62e78583 100644
--- a/Trigger/TrigSteer/TrigOutputHandling/src/DecisionSummaryMakerAlg.cxx
+++ b/Trigger/TrigSteer/TrigOutputHandling/src/DecisionSummaryMakerAlg.cxx
@@ -1,8 +1,10 @@
 /*
   Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
 */
-#include "TrigCompositeUtils/HLTIdentifier.h"
 #include "DecisionSummaryMakerAlg.h"
+#include "TrigCompositeUtils/HLTIdentifier.h"
+#include "TrigSteeringEvent/TrigRoiDescriptorCollection.h"
+#include "CxxUtils/phihelper.h"
 
 DecisionSummaryMakerAlg::DecisionSummaryMakerAlg(const std::string& name, ISvcLocator* pSvcLocator)
   : AthReentrantAlgorithm(name, pSvcLocator) {}
@@ -26,6 +28,9 @@ StatusCode DecisionSummaryMakerAlg::initialize() {
     ATH_CHECK( m_trigCostSvcHandle.retrieve() );
   }
   ATH_CHECK( m_prescaler.retrieve() );
+  if (!m_monTool.empty()) {
+    ATH_CHECK(m_monTool.retrieve());
+  }
 
   return StatusCode::SUCCESS;
 }
@@ -104,6 +109,9 @@ StatusCode DecisionSummaryMakerAlg::execute(const EventContext& context) const {
     }
   }
 
+  // Monitor RoI updates between initial and final RoI
+  monitorRoIs(passRawOutput);
+
   // Get the data from the HLTSeeding, this is where prescales were applied
   SG::ReadHandle<DecisionContainer> hltSeedingSummary( m_hltSeedingSummaryKey, context );
   const Decision* l1SeededChains = nullptr; // Activated by L1
@@ -168,3 +176,77 @@ StatusCode DecisionSummaryMakerAlg::execute(const EventContext& context) const {
 
   return StatusCode::SUCCESS;
 }
+
+void DecisionSummaryMakerAlg::monitorRoIs(const TrigCompositeUtils::Decision* terminusNode) const {
+  using namespace TrigCompositeUtils;
+  using RoILinkVec = std::vector<LinkInfo<TrigRoiDescriptorCollection>>;
+
+  auto printDecision = [this](const Decision* d){
+    ATH_MSG_INFO("The decision corresponds to chain(s):");
+    for (const DecisionID id : decisionIDs(d)) {
+      ATH_MSG_INFO("-- " << HLT::Identifier(id).name());
+    }
+  };
+  auto printDecisionAndRoI = [this,&printDecision](const Decision* d, const TrigRoiDescriptor& roi){
+    printDecision(d);
+    ATH_MSG_INFO("and final RoI: " << roi);
+  };
+
+  // Loop over all final RoIs
+  const RoILinkVec allFinalRoIs = findLinks<TrigRoiDescriptorCollection>(terminusNode, roiString(), TrigDefs::lastFeatureOfType);
+  for (const auto& finalRoILink : allFinalRoIs) {
+    // Get the final TrigRoiDescriptor reference
+    if (!finalRoILink.isValid() || *(finalRoILink.link)==nullptr) {
+      ATH_MSG_WARNING("Encountered invalid final RoI link");
+      printDecision(finalRoILink.source);
+      continue;
+    }
+    const TrigRoiDescriptor& finalRoI = **(finalRoILink.link);
+
+    // Skip full-scan
+    if (finalRoI.isFullscan()) {continue;}
+
+    // Get all initial RoIs associated with this final RoI (should be exactly one)
+    const RoILinkVec initialRoIs = findLinks<TrigRoiDescriptorCollection>(finalRoILink.source, initialRoIString(), TrigDefs::lastFeatureOfType);
+
+    // Warn if the number of initial RoIs differs from one
+    if (initialRoIs.empty()) {
+      ATH_MSG_WARNING("Encountered decision node with no " << initialRoIString() << " link");
+      printDecisionAndRoI(finalRoILink.source, finalRoI);
+      continue;
+    }
+    if (initialRoIs.size()>1) {
+      ATH_MSG_WARNING("Encountered decision node with multiple (" << initialRoIs.size() << ") "
+                      << initialRoIString() << " links");
+      printDecisionAndRoI(finalRoILink.source, finalRoI);
+    }
+
+    // Get the initial TrigRoiDescriptor reference
+    const auto& initialRoILink = initialRoIs.front();
+    if (!initialRoILink.isValid() || *(initialRoILink.link)==nullptr) {
+      ATH_MSG_WARNING("Encountered invalid initial RoI link");
+      printDecisionAndRoI(finalRoILink.source, finalRoI);
+      continue;
+    }
+    const TrigRoiDescriptor& initialRoI = **(initialRoILink.link);
+
+    // Skip full-scan
+    if (initialRoI.isFullscan()) {continue;}
+
+    // Fill the monitoring histograms
+    Monitored::Scalar dEta{"RoIsDEta", finalRoI.eta() - initialRoI.eta()};
+    Monitored::Scalar dPhi{"RoIsDPhi", CxxUtils::deltaPhi(finalRoI.phi(), initialRoI.phi())};
+    Monitored::Scalar dZed{"RoIsDZed", finalRoI.zed() - initialRoI.zed()};
+    Monitored::Group(m_monTool, dEta, dPhi, dZed);
+
+    // Print warnings on large updates
+    if (m_warnOnLargeRoIUpdates.value()) {
+      if (std::abs(dEta) > 1.0 || std::abs(dPhi) > 1.0 || std::abs(dZed) > 200) {
+        ATH_MSG_WARNING("Large RoI difference between initial and final RoI: "
+                        << "dEta=" << dEta << ", dPhi=" << dPhi << ", dZed=" << dZed
+                        << "initialRoI: " << initialRoI << ", finalRoI: " << finalRoI);
+        printDecision(finalRoILink.source);
+      }
+    }
+  }
+}
diff --git a/Trigger/TrigSteer/TrigOutputHandling/src/DecisionSummaryMakerAlg.h b/Trigger/TrigSteer/TrigOutputHandling/src/DecisionSummaryMakerAlg.h
index 6c126276321ff70e8c902663ffa9d2f72c63f2f6..121a6335d97fcdcf62ea8248581bac06e1cc610c 100644
--- a/Trigger/TrigSteer/TrigOutputHandling/src/DecisionSummaryMakerAlg.h
+++ b/Trigger/TrigSteer/TrigOutputHandling/src/DecisionSummaryMakerAlg.h
@@ -4,11 +4,12 @@
 #ifndef TRIGOUTPUTHANDLING_DECISIONSUMMARYMAKERALG_H
 #define TRIGOUTPUTHANDLING_DECISIONSUMMARYMAKERALG_H
 
-#include <string>
-#include "AthenaBaseComps/AthReentrantAlgorithm.h"
 #include "TrigCompositeUtils/TrigCompositeUtils.h"
 #include "TrigCostMonitor/ITrigCostSvc.h"
 #include "HLTSeeding/IPrescalingTool.h"
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+#include "AthenaMonitoringKernel/Monitored.h"
+#include <string>
 
 
 /**
@@ -26,6 +27,9 @@ public:
   virtual StatusCode finalize() override;
 
 private:
+  /// Monitor RoI updates between initial and final RoI
+  void monitorRoIs(const TrigCompositeUtils::Decision* terminusNode) const;
+
   SG::WriteHandleKey<TrigCompositeUtils::DecisionContainer> m_summaryKey{ this, "DecisionsSummaryKey", "HLTNav_Summary",
       "Location of final decision" };
 
@@ -47,12 +51,18 @@ private:
   ToolHandle<IPrescalingTool> m_prescaler{this, "Prescaler", "PrescalingTool/PrescalingTool",
     "Prescaling tool used to determine express stream prescale decisions"};
 
+  ToolHandle<GenericMonitoringTool> m_monTool { this, "MonTool", "",
+    "Monitoring tool" };
+
   Gaudi::Property< std::map< std::string, std::vector<std::string> > > m_lastStepForChain{ this, "FinalStepDecisions", {},
     "The map of chain name to names of the collections in which the final decision is found" };
 
   Gaudi::Property<bool> m_doCostMonitoring{this, "DoCostMonitoring", false,
     "Enables end-of-event cost monitoring behavior."};
 
+  Gaudi::Property<bool> m_warnOnLargeRoIUpdates{this, "WarnOnLargeRoIUpdates", true,
+    "Print warnings from RoI update monitoring if the difference between initial and final RoI is large"};
+
   Gaudi::Property<bool> m_setFilterStatus{this, "SetFilterStatus", false,
     "Enables chain-passed filter. This will cause the downstream EDMCreator to not run if no chains pass, saving CPU in rejected events. "
     "Cannot be used in jobs producing RDO output."};
diff --git a/Trigger/TriggerCommon/TriggerJobOpts/python/TriggerConfig.py b/Trigger/TriggerCommon/TriggerJobOpts/python/TriggerConfig.py
index 862a57f84e83385e66fab5252ea638ff0d320953..25207ec6dffb92f3c33efaa503abe214f0d61617 100644
--- a/Trigger/TriggerCommon/TriggerJobOpts/python/TriggerConfig.py
+++ b/Trigger/TriggerCommon/TriggerJobOpts/python/TriggerConfig.py
@@ -175,9 +175,9 @@ def triggerSummaryCfg(flags, hypos):
     Returns: ca, algorithm
     """
     acc = ComponentAccumulator()
-    DecisionSummaryMakerAlg=CompFactory.DecisionSummaryMakerAlg
     from TrigEDMConfig.TriggerEDMRun3 import recordable
-    decisionSummaryAlg = DecisionSummaryMakerAlg()
+    from TrigOutputHandling.TrigOutputHandlingConfig import DecisionSummaryMakerAlgCfg
+    decisionSummaryAlg = DecisionSummaryMakerAlgCfg()
     chainToLastCollection = OrderedDict() # keys are chain names, values are lists of collections