diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/CMakeLists.txt b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/CMakeLists.txt
index ec08633e8e807f006210971c5d6d8746be5a4869..f7aca06d08f52a7a1b08c1f52d1d0afe0473cb61 100644
--- a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/CMakeLists.txt
+++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/CMakeLists.txt
@@ -12,11 +12,36 @@ atlas_depends_on_subdirs( PUBLIC
                           PhysicsAnalysis/DerivationFramework/DerivationFrameworkInterfaces
                           Trigger/TrigCost/EnhancedBiasWeighter
                           Trigger/TrigAnalysis/TrigAnalysisInterfaces
+                          Trigger/TrigAnalysis/TriggerMatchingTool
+                          Trigger/TrigDecisionTool/TrigDecisionTool
+                          Trigger/TrigEvent/TrigNavStructure
+                          Event/xAOD/xAODBase
+                          Event/xAOD/xAODCore
+                          Event/xAOD/xAODTrigger
+                          Event/xAOD/xAODEgamma
+                          Event/xAOD/xAODCaloEvent
+                          Event/FourMomUtils
                           PRIVATE
                           Event/xAOD/xAODEventInfo
                           Tools/PathResolver
                           External/AtlasROOT)
 
+find_package( ROOT COMPONENTS Core Tree MathCore Hist RIO pthread )
+
+atlas_add_library( 
+    DerivationFrameworkTriggerLib
+    src/*.cxx
+    NO_PUBLIC_HEADERS
+    INCLUDE_DIRS ${ROOT_INCLUDE_DIRS}
+    LINK_LIBRARIES ${ROOT_LIBRARIES} GaudiKernel AthenaBaseComps xAODBase TrigDecisionToolLib TrigNavStructure xAODCore xAODTrigger xAODCaloEvent FourMomUtils xAODEgamma
+)
+
+atlas_add_component(
+    DerivationFrameworkTrigger
+    src/components/*.cxx
+    LINK_LIBRARIES DerivationFrameworkTriggerLib
+)
+
 # Install files from the package:
 atlas_install_python_modules( python/*.py )
 atlas_install_joboptions( share/*.py )
diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/python/TriggerMatchingHelper.py b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/python/TriggerMatchingHelper.py
new file mode 100644
index 0000000000000000000000000000000000000000..8df9c400da694b09331723947ff13a5eea339cf7
--- /dev/null
+++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/python/TriggerMatchingHelper.py
@@ -0,0 +1,103 @@
+# Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+
+from DerivationFrameworkTrigger.DerivationFrameworkTriggerConf import (
+        DerivationFramework__TriggerMatchingTool)
+from DerivationFrameworkCore.DerivationFrameworkCoreConf import (
+        DerivationFramework__CommonAugmentation)
+from DerivationFrameworkCore.DerivationFrameworkMaster import (
+        DerivationFrameworkJob)
+import AthenaCommon.AppMgr as AppMgr
+from AthenaCommon.Configurable import Configurable
+import copy
+import logging
+log = logging.getLogger(__name__)
+
+class TriggerMatchingHelper(object):
+    """ Helper class for setting up shared trigger matching.
+    
+        'Shared' trigger matching uses a algorithm+tool that are shared across
+        several different derivation formats that can be run in the same trains
+        together. The tool can only be run once per event and sharing an algorithm
+        ensures that this happens correctly.
+
+        This class can technically support multiple different tool
+        configurations (set by the kwargs in the __init__ function). However
+        right now we only really expect this to be used with its default
+        settings.
+    """
+    # Register of tool and algorithm pairs
+    _register = {}
+
+    @classmethod
+    def get_alg_and_tool(cls, **kwargs):
+        properties = copy.deepcopy(
+                DerivationFramework__TriggerMatchingTool.getDefaultProperties())
+        if "name" not in kwargs or kwargs["name"] == Configurable.DefaultName:
+            kwargs["name"] = "DFTriggerMatchingTool"
+        properties.update(kwargs)
+        # We don't want to hash the ChainNames as they don't affect the physics
+        keys = sorted(k for k in properties if k != "ChainNames")
+        hashval = hash(tuple(str(properties[k]) for k in keys) )
+        if hashval not in cls._register:
+            # We need to make a new alg and tool, but first we have to check
+            # that two arguments do not collide - the name and the output prefix.
+            # The name mustn't collide because otherwise athena will just return
+            # us the same object, the output prefix mustn't collide because
+            # otherwise we'll get errors trying to write containers
+            if any(t.name() == properties["name"]
+                   for _, t in cls._register.itervalues() ):
+                raise ValueError("Attempting to create a new trigger matching tool with the same name as an existing one")
+            tool = DerivationFramework__TriggerMatchingTool(**properties)
+            AppMgr.ToolSvc += tool
+            alg = DerivationFramework__CommonAugmentation(
+                    name=properties["name"]+"_Alg",
+                    AugmentationTools = [tool])
+            cls._register[hashval] = (alg, tool)
+        return cls._register[hashval]
+
+    def __init__(self, trigger_list, add_to_df_job=False, **kwargs):
+        """ Create the helper
+
+            The trigger_list is the list of triggers that *this* derivation
+            needs matching results for.
+
+            kwargs can be used to pass extra properties to the underlying
+            DerivationFramework::TriggerMatchingTool. If you change *any*
+            properties you *must* also set the 'name' and
+            'OutputContainerPrefix' keys.
+
+            If this is a 'private' configuration (i.e. one that is to be used
+            *only* in your format) then these values should include your
+            format's name and you can just add self.tool to your kernel
+            algorithm. However, if you're using a shared algorithm and tool (one
+            that doesn't include your format's name) then you *must* either
+            include self.alg in your derivation's sequence, or set
+            'add_to_df_job' to True, which will add the algorithm directly into
+            the main DF job. You should *never* set 'add_to_df_job' to True for
+            a private configuration!
+
+            Doing anything else will likely cause the derivation framework to
+            fail when it runs multiple formats in a train. Unless you run a
+            train in your local tests, this will not show up in those tests and
+            will just break a nightly instead!
+        """
+        self.alg, self.tool = self.get_alg_and_tool(**kwargs)
+        if add_to_df_job:
+            global DerivationFrameworkJob
+            if self.alg not in DerivationFrameworkJob:
+                DerivationFrameworkJob += self.alg
+        self.tool.ChainNames += trigger_list
+        self.trigger_list = trigger_list[:]
+        self.container_prefix = self.tool.OutputContainerPrefix
+
+    def output_containers(self):
+        """ Get the full list of containers to be written out """
+        return ["{0}{1}".format(self.container_prefix, chain).replace(".", "_")
+                for chain in self.trigger_list]
+    def add_to_slimming(self, slimming_helper):
+        """ Add the created containers to the output """
+        slimming_helper.AllVariables += self.output_containers()
+        for cont in self.output_containers():
+            slimming_helper.AppendToDictionary.update({
+                cont : "xAOD::TrigCompositeContainer",
+                cont+"Aux" : "AOD::AuxContainerBase!"})
diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/python/__init__.py b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/python/__init__.py
deleted file mode 100644
index 74583d364ec2ca794156596c7254d9b234a940c6..0000000000000000000000000000000000000000
--- a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/python/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
-
diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/share/TRIG6.py b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/share/TRIG6.py
new file mode 100644
index 0000000000000000000000000000000000000000..badb4e1280668d40abf9e61fee5ced201614c0bb
--- /dev/null
+++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/share/TRIG6.py
@@ -0,0 +1,48 @@
+#********************************************************************
+# TRIG6.py
+# For removing empty collections from Trigger-Level Analysis AODs
+# Modelled on EXOT2.py
+# For running on DataScouting stream, disable AODFix since this attempts
+# to initialise some BTagging which requires AntiKt4EMTopo jets:
+# --preExec 'rec.doApplyAODFix.set_Value_and_Lock(False)'
+#********************************************************************
+from DerivationFrameworkCore.DerivationFrameworkMaster import *
+
+trig6Seq = CfgMgr.AthSequencer("TRIG6Sequence")
+
+
+#====================================================================
+# SET UP STREAM   
+#====================================================================
+from AthenaServices.Configurables import ThinningSvc, createThinningSvc
+
+streamName = derivationFlags.WriteDAOD_TRIG6Stream.StreamName
+fileName   = buildFileName( derivationFlags.WriteDAOD_TRIG6Stream )
+TRIG6Stream = MSMgr.NewPoolRootStream( streamName, fileName )
+TRIG6Stream.AcceptAlgs(["TRIG6Kernel"])
+
+
+#=======================================
+# CREATE THE DERIVATION KERNEL ALGORITHM   
+#=======================================
+# No skimming - keep all events
+
+from DerivationFrameworkCore.DerivationFrameworkCoreConf import DerivationFramework__DerivationKernel
+DerivationFrameworkJob += trig6Seq
+trig6Seq += CfgMgr.DerivationFramework__DerivationKernel(name = "TRIG6Kernel",
+                                                         SkimmingTools = [],
+                                                         ThinningTools = [])
+
+
+#====================================================================
+# Add the DS container to the output stream - no slimming done
+#====================================================================
+from DerivationFrameworkCore.SlimmingHelper import SlimmingHelper
+TRIG6SlimmingHelper = SlimmingHelper("TRIG6SlimmingHelper")
+
+TRIG6SlimmingHelper.AllVariables = [
+    "HLT_xAOD__JetContainer_TrigHLTJetDSSelectorCollection",
+    ]
+
+TRIG6SlimmingHelper.AppendContentToStream(TRIG6Stream)
+
diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/share/TRIG7.py b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/share/TRIG7.py
new file mode 100644
index 0000000000000000000000000000000000000000..97e40b0b87256c7b2180e57500e78b5b9720cf52
--- /dev/null
+++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/share/TRIG7.py
@@ -0,0 +1,247 @@
+from AthenaCommon.Constants import *
+#====================================================================
+# TRIG7.py 
+# Purpose: investigate potential for using substructure in large-R jet trigger
+# Skimming: require a high pT central ungroomed a10 jet and an HLT a10 jet trigger
+# Content: 
+#    AntiKt10Truth[Jets,TrimmedPtFrac5SmallR20Jets]
+#    AntiKt10LCTopo [Jets,TrimmedPtFrac5SmallR20Jets]
+#    AntiKt [2,4] PV0TrackJets - we have to have these to get trimmed jets, don't know why
+#    AntiKt4EMPFlow [Jets,TopoJets] - TJ asked for these
+#    CaloCalTopoClusters matched to ungroomed a10 - for playing with jet rec and jss 
+# Based loosely on JETM6, JETM15, JETM8,...
+# reductionConf flag TRIG7 in Reco_tf.py   
+#====================================================================
+
+# pt and eta cuts for jet selection
+jetptmin='250.'
+jetetamax='2.5'
+
+
+from DerivationFrameworkCore.DerivationFrameworkMaster import *
+from DerivationFrameworkJetEtMiss.JetCommon import *
+from DerivationFrameworkJetEtMiss.ExtendedJetCommon import *
+
+#====================================================================
+# DEFAULT IS NO TRUTH - NEED TO ADD IT EXPLICITLY THEN THIN IT BELOW
+#====================================================================
+#if DerivationFrameworkIsMonteCarlo:
+
+#  from DerivationFrameworkMCTruth.MCTruthCommon import addStandardTruthContents
+#  addStandardTruthContents()
+
+#====================================================================
+# TRIGGER SKIMMING TOOL - REQUIRE A JET TRIGGER TO HAVE BEEN PASSED
+#====================================================================
+from DerivationFrameworkJetEtMiss import TriggerLists
+jetTriggers = TriggerLists.jetTrig()
+
+from DerivationFrameworkTools.DerivationFrameworkToolsConf import DerivationFramework__TriggerSkimmingTool
+TRIG7TrigSkimmingTool = DerivationFramework__TriggerSkimmingTool(name = "TRIG7TrigSkimmingTool", TriggerListOR = jetTriggers)
+ToolSvc += TRIG7TrigSkimmingTool
+
+#====================================================================
+# OFFLINE SKIMMING TOOL - REQUIRE A HIGH PT CENTRAL FAT JET
+#====================================================================
+from DerivationFrameworkTools.DerivationFrameworkToolsConf import DerivationFramework__xAODStringSkimmingTool
+
+jetSelection = '(count( AntiKt10LCTopoJets.pt > '+jetptmin+'*GeV && abs(AntiKt10LCTopoJets.eta) < '+jetetamax+' ) >=1)'
+
+TRIG7OfflineSkimmingTool = DerivationFramework__xAODStringSkimmingTool( name = "TRIG7OfflineSkimmingTool", expression = jetSelection)
+ToolSvc += TRIG7OfflineSkimmingTool
+
+
+#=======================================
+# CREATE PRIVATE SEQUENCE
+#=======================================
+trig7Seq = CfgMgr.AthSequencer("TRIG7Sequence")
+DerivationFrameworkJob += trig7Seq
+
+#====================================================================
+# CREATE THE DERIVATION TRIG SKIM KERNEL ALGORITHM AND PASS TRIG SKIM TOOL  
+#====================================================================
+from DerivationFrameworkCore.DerivationFrameworkCoreConf import DerivationFramework__DerivationKernel
+trig7Seq += CfgMgr.DerivationFramework__DerivationKernel(name="TRIG7TrigSkimKernel",SkimmingTools = [TRIG7TrigSkimmingTool])
+
+#=======================================
+# RESTORE AOD-REDUCED JET COLLECTIONS - THESE JETS ARE NOT IN THE INPUT AOD 
+#=======================================
+reducedJetList = ["AntiKt2PV0TrackJets","AntiKt4PV0TrackJets","AntiKt10LCTopoJets","AntiKt10TruthJets"]
+replaceAODReducedJets(reducedJetList,trig7Seq,"TRIG7")
+addDefaultTrimmedJets(trig7Seq,"TRIG7")
+
+
+#====================================================================
+# SET UP STREAM   
+#====================================================================
+# The base name (DAOD_TRIG7 here) must match the string in 
+# DerivationFrameworkProdFlags (in DerivationFrameworkCore) 
+streamName = derivationFlags.WriteDAOD_TRIG7Stream.StreamName
+fileName   = buildFileName( derivationFlags.WriteDAOD_TRIG7Stream )
+TRIG7Stream = MSMgr.NewPoolRootStream( streamName, fileName )
+TRIG7Stream.AcceptAlgs(["TRIG7MainKernel"])
+
+#====================================================================
+# SET UP THINNING SERVICE   
+#====================================================================
+#from AthenaServices.Configurables import ThinningSvc, createThinningSvc
+#augStream = MSMgr.GetStream( streamName )
+#evtStream = augStream.GetEventStream()
+#svcMgr += createThinningSvc( svcName="TRIG7ThinningSvc", outStreams=[evtStream] )
+
+#====================================================================
+# THINNING HELPER
+#====================================================================
+
+from DerivationFrameworkCore.ThinningHelper import ThinningHelper
+TRIG7ThinningHelper = ThinningHelper( "TRIG7ThinningHelper" )
+### https://gitlab.cern.ch/atlas/athena/blob/21.2/PhysicsAnalysis/DerivationFramework/DerivationFrameworkExamples/share/TriggerContentExample.py
+TRIG7ThinningHelper.TriggerChains = '^(?!.*_[0-9]*(mu|e|xe|tau|ht|xs|te))(?!HLT_j[0-9]+_[0-9]*j[0-9]+.*)HLT_j[0-9]+_a10.*'
+TRIG7ThinningHelper.AppendToStream( TRIG7Stream )
+
+thinningTools = []
+
+
+#====================================================================
+# CLUSTER THINNING TOOL
+#====================================================================
+from DerivationFrameworkCalo.DerivationFrameworkCaloConf import DerivationFramework__JetCaloClusterThinning
+
+jetcuts = '(AntiKt10LCTopoJets.pt > '+jetptmin+'*GeV && abs(AntiKt10LCTopoJets.eta) < '+jetetamax+')'
+
+## save clusters matched to ungroomed jets with jetcuts selection
+TRIG7ClusterThinningTool = DerivationFramework__JetCaloClusterThinning(name                  = "TRIG7ClusterThinningTool",
+                                                                       ThinningService       = TRIG7ThinningHelper.ThinningSvc(),
+                                                                       SGKey                 = "AntiKt10LCTopoJets",
+                                                                       TopoClCollectionSGKey = "CaloCalTopoClusters",
+                                                                       SelectionString       = jetcuts,
+                                                                       AdditionalClustersKey = ["LCOriginTopoClusters"])
+
+ToolSvc += TRIG7ClusterThinningTool
+thinningTools.append(TRIG7ClusterThinningTool)
+
+trigjetcuts = '(HLT_xAOD__JetContainer_a10tclcwsubjesFS.pt > '+jetptmin+'*GeV && abs(HLT_xAOD__JetContainer_a10tclcwsubjesFS.eta) < '+jetetamax+')'
+
+## save clusters matched to ungroomed trigger jets passing trigjetcuts
+TRIG7TrigClusterThinningTool = DerivationFramework__JetCaloClusterThinning(name                  = "TRIG7TrigClusterThinningTool",
+                                                                           ThinningService       = TRIG7ThinningHelper.ThinningSvc(),
+                                                                           SGKey                 = "HLT_xAOD__JetContainer_a10tclcwsubjesFS",
+                                                                           TopoClCollectionSGKey = "CaloCalTopoClusters",
+                                                                           SelectionString       = trigjetcuts
+                                                           )
+ToolSvc += TRIG7TrigClusterThinningTool
+thinningTools.append(TRIG7TrigClusterThinningTool)
+
+#====================================================================
+# TRACK THINNING TOOL
+#====================================================================
+"""
+from DerivationFrameworkInDet.DerivationFrameworkInDetConf import DerivationFramework__JetTrackParticleThinning
+
+## save tracks matched to ungroomed jets passing jetcuts
+TRIG7TrackThinningTool = DerivationFramework__JetTrackParticleThinning(name      = "TRIG7TrackThinningTool",
+                                                                       ThinningService       = TRIG7ThinningHelper.ThinningSvc(),
+                                                                       JetKey                 = "AntiKt10LCTopoJets",
+                                                                       InDetTrackParticlesKey = "InDetTrackParticles",
+                                                                       SelectionString        = jetcuts)
+ToolSvc += TRIG7TrackThinningTool
+thinningTools.append(TRIG7TrackThinningTool)
+
+## save trackss matched to ungroomed trigger jets passing trigjets cuts
+TRIG7TrigTrackThinningTool = DerivationFramework__JetTrackParticleThinning(name   = "TRIG7TrigTrackThinningTool",
+                                                                           ThinningService       = TRIG7ThinningHelper.ThinningSvc(),
+                                                                           JetKey                 = "HLT_xAOD__JetContainer_a10tclcwsubjesFS",
+                                                                           InDetTrackParticlesKey = "InDetTrackParticles",
+                                                                           SelectionString        = trigjetcuts)
+
+ToolSvc += TRIG7TrigTrackThinningTool
+thinningTools.append(TRIG7TrigTrackThinningTool)
+"""
+#====================================================================
+# TRUTH THINNING TOOL
+#====================================================================
+doTruthThinning = False
+from AthenaCommon.GlobalFlags import globalflags
+if doTruthThinning and DerivationFrameworkIsMonteCarlo:  
+  from DerivationFrameworkMCTruth.DerivationFrameworkMCTruthConf import DerivationFramework__MenuTruthThinning
+  TRIG7TruthThinningTool = DerivationFramework__MenuTruthThinning(name               = "TRIG7TruthThinningTool",
+                                                                  ThinningService    = TRIG7ThinningHelper.ThinningSvc(),
+                                                                  WriteAllStable     = True)
+  ToolSvc += TRIG7TruthThinningTool
+  thinningTools.append(TRIG7TruthThinningTool)
+
+
+
+#====================================================================
+# CREATE THE OFFLINE SKIM KERNEL ALGORITHM AND PASS OFFLINE SKIM TOOL AND THINNING TOOLS
+#====================================================================
+from DerivationFrameworkCore.DerivationFrameworkCoreConf import DerivationFramework__DerivationKernel
+trig7Seq += CfgMgr.DerivationFramework__DerivationKernel( name = "TRIG7MainKernel",
+                                                          AugmentationTools = [] ,
+                                                          SkimmingTools     = [TRIG7OfflineSkimmingTool],
+                                                          ThinningTools     = thinningTools)
+
+
+
+#====================================================================
+# CONTENT LIST  
+#====================================================================
+from DerivationFrameworkCore.SlimmingHelper import SlimmingHelper
+
+TRIG7SlimmingHelper = SlimmingHelper("TRIG7SlimmingHelper")
+
+# can only put standard collections in here - anything else needs to be appended to dictionary
+TRIG7SlimmingHelper.SmartCollections = ["PrimaryVertices",
+                                        "AntiKt4EMTopoJets","AntiKt4LCTopoJets","AntiKt4EMPFlowJets",
+#                                        "BTagging_AntiKt2Track","BTagging_AntiKt4EMTopo",
+                                        "AntiKt10LCTopoTrimmedPtFrac5SmallR20Jets"]
+#                                        "TauJets"]
+#                                        "InDetTrackParticles"]
+                                      
+
+TRIG7SlimmingHelper.AllVariables = ["Kt4EMTopoOriginEventShape","Kt4LCTopoOriginEventShape","Kt4EMPFlowEventShape"]
+#                                    "TruthVertices",
+#                                    "TruthEvents", 
+#                                    "TruthParticles"]
+
+### these jets are on the fly
+TRIG7SlimmingHelper.AppendToDictionary={"AntiKt10LCTopoJets" : "xAOD::JetContainer",
+                                        "AntiKt10LCTopoJetsAux." : " xAOD::JetAuxContainer",
+                                        "AntiKt10TruthJets" : "xAOD::JetContainer",
+                                        "AntiKt10TruthJetsAux." : " xAOD::JetAuxContainer",
+                                        "HLT_xAOD__JetContainer_a10ttclcwjesFS" : "xAOD::JetContainer",
+                                        "HLT_xAOD__JetContainer_a10ttclcwjesFSAux." : "xAOD::JetAuxContainer",
+                                        "HLT_xAOD__JetContainer_a10r_tcemsubjesISFS" : "xAOD::JetContainer" ,
+                                        "HLT_xAOD__JetContainer_a10r_tcemsubjesISFSAux." : "xAOD::JetAuxContainer",
+                                        "HLT_xAOD__JetContainer_a10r_tcemsubjesFS" : "xAOD::JetContainer" ,
+                                        "HLT_xAOD__JetContainer_a10r_tcemsubjesFSAux." : "xAOD::JetAuxContainer" }
+
+### want all vars for trigger jets
+from DerivationFrameworkCore.JetTriggerContent import JetTriggerContent
+for trig in JetTriggerContent:
+    if 'HLT' in trig and not 'Aux' in trig:
+        TRIG7SlimmingHelper.AllVariables += [trig]
+
+TRIG7SlimmingHelper.AllVariables += ["AntiKt10LCTopoJets"]
+TRIG7SlimmingHelper.AllVariables += ["AntiKt10TruthJets"]
+TRIG7SlimmingHelper.AllVariables += ["HLT_xAOD__JetContainer_a10ttclcwjesFS"]
+TRIG7SlimmingHelper.AllVariables += ["HLT_xAOD__JetContainer_a10r_tcemsubjesISFS"]
+TRIG7SlimmingHelper.AllVariables += ["HLT_xAOD__JetContainer_a10r_tcemsubjesFS"]
+
+TRIG7SlimmingHelper.AppendToDictionary["CaloCalTopoClusters"]='xAOD::CaloClusterContainer'
+TRIG7SlimmingHelper.AppendToDictionary["CaloCalTopoClustersAux"]='xAOD::CaloClusterAuxContainer'
+TRIG7SlimmingHelper.ExtraVariables = ["CaloCalTopoClusters.calEta.calPhi.CENTER_MAG.calE.calM.rawEta.rawPhi.rawE.rawM"]
+
+
+addJetOutputs(TRIG7SlimmingHelper,["AntiKt10LCTopoJets",
+                                   "AntiKt10TruthJets",
+                                   "HLT_xAOD__JetContainer_a10ttclcwjesFS",
+                                   "HLT_xAOD__JetContainer_a10r_tcemsubjesISFS",
+                                   "HLT_xAOD__JetContainer_a10r_tcemsubjesFS"])
+#                                   "TRIG7"])
+
+
+TRIG7SlimmingHelper.AppendContentToStream(TRIG7Stream)
+
+#TRIG7Stream.RemoveItem("xAOD::TrigNavigation#*")
+#TRIG7Stream.RemoveItem("xAOD::TrigNavigationAuxInfo#*")
diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/BuildCombinations.h b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/BuildCombinations.h
new file mode 100644
index 0000000000000000000000000000000000000000..4ca03239c254b8ef568e30341e0593df6dbe67a9
--- /dev/null
+++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/BuildCombinations.h
@@ -0,0 +1,45 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef DERIVATIONFRAMEWORKTRIGGER_BUILDCOMBINATIONS_H
+#define DERIVATIONFRAMEWORKTRIGGER_BUILDCOMBINATIONS_H
+
+#include "RangedItr.h"
+
+namespace DerivationFramework { namespace TriggerMatchingUtils {
+
+  /**
+   * @brief Helper function for inserting an element into a sorted vector
+   * @tparam T The type stored in the vector
+   * @param[out] vec The vector to insert into
+   * @param ele The element to insert
+   * @return Whether or not the element was inserted.
+   * The element will not be inserted if it already exists.
+   */
+  template <typename T>
+    bool insertIntoSortedVector(std::vector<T>& vec, const T& ele);
+
+  /**
+   * @brief Helper function to create a sorted vector from an unsorted one.
+   * @tparam T The type of the iterator
+   * @param begin The start of the unsorted range
+   * @param end The end of the unsorted range
+   */
+  template <typename T>
+    std::vector<typename T::value_type> sorted(T begin, T end);
+
+  /**
+   * @brief Get all valid, unique combinations of distinct elements from the
+   * input ranges.
+   * @param inputs The ranges over vectors of possible elements.
+   */
+  // First, a note on the type of the argument. This is essentially a way of
+  // passing a vector of vectors but only with iterators over the vectors.
+  template <typename T>
+    std::vector<std::vector<T>> getAllDistinctCombinations(
+        std::vector<RangedItr<typename std::vector<T>::const_iterator>>& inputs);
+
+}} //> end namespace DerivationFramework::TriggerMatchingUtils
+#include "BuildCombinations.icc"
+#endif //> !DERIVATIONFRAMEWORKTRIGGER_BUILDCOMBINATIONS_H
diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/BuildCombinations.icc b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/BuildCombinations.icc
new file mode 100644
index 0000000000000000000000000000000000000000..92f6b1683b9b886d4b383c32325ce241ed5dd8d4
--- /dev/null
+++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/BuildCombinations.icc
@@ -0,0 +1,97 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+namespace DerivationFramework { namespace TriggerMatchingUtils {
+
+  template <typename T>
+    bool insertIntoSortedVector(std::vector<T>& vec, const T& ele)
+    {
+      auto itr = std::lower_bound(vec.begin(), vec.end(), ele);
+      // if no lower bound is found or the found element doesn't equal ele then
+      // insert ele at this point
+      if (itr == vec.end() || *itr != ele) {
+        vec.insert(itr, ele);
+        return true;
+      }
+      else
+        return false;
+    }
+
+  template <typename T>
+    std::vector<typename T::value_type> sorted(T begin, T end)
+    {
+      std::vector<typename T::value_type> output(begin, end);
+      std::sort(output.begin(), output.end() );
+      return output;
+    }
+  
+
+  template <typename T>
+    std::vector<std::vector<T>> getAllDistinctCombinations(
+        std::vector<RangedItr<typename std::vector<T>::const_iterator>>& inputs)
+  {
+    // This is where the output will go
+    std::vector<std::vector<T>> output;
+
+    // If inputs is empty or if any of the ranges within inputs are empty then
+    // no combinations will be found!
+    if (inputs.empty() || std::any_of(inputs.begin(), inputs.end(),
+          [] (const auto& rangedItr) { return rangedItr.exhausted(); }) )
+      return output;
+
+    // This is the current output combination being built
+    std::vector<T> currentCombination;
+
+    // This iterator points to where in the vector of ranged iterators we are
+    // looking. This can therefore often be acting as an iterator to and
+    // iterator! *itr is an iterator, **itr is a const T&!
+    // To disambiguate I will refer to itr as the outer iterator and *itr as the
+    // inner iterator.
+    auto itr = inputs.begin();
+    while (true) {
+      // Have we exhausted our current inner iterator?
+      if (itr == inputs.end() || itr->exhausted() ) {
+        // If the outer iterator has returned to the beginning then we're done
+        // and every combination has been checked.
+        if (itr == inputs.begin() )
+          break;
+        // otherwise we need to continue. To do this we
+        // - reset the current inner iterator. This is so that the next time we
+        //   return to it we look through all the elements again
+        // - step the outer iterator back
+        // - remove the last element of the current combination. That
+        //   element will be replaced by the next valid one from the list *now*
+        //   pointed to by the outer iterator.
+        if (itr != inputs.end() )
+          itr->restart();
+        --itr;
+        currentCombination.pop_back();
+      } //> end if inner iterator is exhausted
+      else {
+        // Check if the pointed-to element is already in the current combination
+        const T& currentCandidate = **itr;
+        // Now that we've accessed this value, step past it
+        ++*itr;
+        if (std::find(
+              currentCombination.begin(), 
+              currentCombination.end(), 
+              currentCandidate) == currentCombination.end() ) {
+          // If it isn't, then add it.
+          currentCombination.push_back(currentCandidate);
+          // Step the outer iterator forwards so we're looking at the next list
+          ++itr;
+          if (itr == inputs.end() ) {
+            // If we're at the end then this is a complete combination, add it
+            // to the output.
+            insertIntoSortedVector(output, sorted(
+                  currentCombination.begin(),
+                  currentCombination.end() ) );
+          }
+        } //> end if currentCandidate is in currentCombination
+      } //> end else (if inner iterator is exhausted)
+    } //> end while loop
+    return output;
+  } //> end function getAllDistinctCombinations
+
+}} //> end namespace DerivationFramework::TriggerMatchingUtils
diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/RangedItr.h b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/RangedItr.h
new file mode 100644
index 0000000000000000000000000000000000000000..a633ddd37925aeba8f0b64aa0acb244538819eaa
--- /dev/null
+++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/RangedItr.h
@@ -0,0 +1,97 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef DERIVATIONFRAMEWORKTRIGGER_RANGEDITR_H
+#define DERIVATIONFRAMEWORKTRIGGER_RANGEDITR_H
+
+#include <vector>
+#include <iterator>
+#include <type_traits>
+
+namespace DerivationFramework { namespace TriggerMatchingUtils {
+/**
+ * @brief utility class that acts wraps a bidirectional iterator.
+ * This class acts as both and range and an iterator.
+ */
+template <typename T>
+  class RangedItr {
+    protected:
+      using tr = std::iterator_traits<T>;
+      static_assert(std::is_base_of<std::bidirectional_iterator_tag, typename tr::iterator_category>::value, "Templated type must be a random access iterator");
+    public:
+      // A full implementation of this idea would allow for any input
+      // iterator type. For this version, just require at least bidirectional
+
+      using iterator_category = std::bidirectional_iterator_tag;
+      using value_type      = typename tr::value_type;
+      using reference       = typename tr::reference;
+      using pointer         = typename tr::pointer;
+      using difference_type = typename tr::difference_type;
+
+      /// Default Constructor
+      RangedItr() = default;
+
+      /// Construct from a beginning and an end
+      RangedItr(const T& begin, const T& end) : 
+        RangedItr(begin, end, begin) {}
+
+      /// Construct from a beginning, an end and a starting position
+      RangedItr(const T& begin, const T& end, const T& position) :
+        m_begin(begin), m_end(end), m_position(position) {}
+
+      /// Iterator interface
+      reference operator*() { return m_position.operator*(); }
+      pointer operator->() { return m_position.operator->(); }
+
+      RangedItr& operator++() {
+        ++m_position;
+        return *this;
+      }
+      RangedItr& operator++(int) {
+        RangedItr ret = *this;
+        ++m_position;
+        return ret;
+      }
+
+      RangedItr& operator--() {
+        --m_position;
+        return *this;
+      }
+      RangedItr& operator--(int) {
+        RangedItr ret = *this;
+        --m_position;
+        return ret;
+      }
+
+      bool operator==(const RangedItr& other) {
+        return m_begin == other.m_begin &&
+                m_end == other.m_end &&
+                m_position == other.m_position;
+      }
+
+      bool operator!=(const RangedItr& other) {
+        return !(*this == other);
+      }
+
+      /// Is this iterator exhausted?
+      bool exhausted() const { return m_position == m_end; }
+
+      /// Reset this iterator to its start
+      void restart() { m_position = m_begin; }
+
+      /// Make this act as a range
+      RangedItr begin() const { return RangedItr(m_begin, m_end, m_begin); }
+      RangedItr end() const { return RangedItr(m_begin, m_end, m_end); }
+      difference_type size() const { return std::distance(m_begin, m_end); }
+
+      /// Allow conversion back to the original iterator type
+      operator T() const { return m_position; }
+    private:
+      T m_begin;
+      T m_end;
+      T m_position;
+
+  }; //> end class RangedItr
+} } //> end namespace DerivationFramework::TriggerMatchingUtils
+#endif //> !DERIVATIONFRAMEWORKTRIGGER_RANGEDITR_H
diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/TriggerMatchingTool.cxx b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/TriggerMatchingTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..03e6757b5a6e2b24284c72c074335ba79a01353f
--- /dev/null
+++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/TriggerMatchingTool.cxx
@@ -0,0 +1,252 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "TriggerMatchingTool.h"
+#include "BuildCombinations.h"
+#include "GaudiKernel/ServiceHandle.h"
+#include "GaudiKernel/IIncidentSvc.h"
+#include "xAODCore/AuxContainerBase.h"
+#include "xAODCaloEvent/CaloClusterContainer.h"
+#include "FourMomUtils/xAODP4Helpers.h"
+#include <memory>
+#include <algorithm>
+
+namespace {
+  /// Helper typedefs for accessors/decorators, vectors of ele links
+  template <typename T>
+    using constAcc_t = SG::AuxElement::ConstAccessor<T>;
+  template <typename T>
+    using dec_t = SG::AuxElement::Decorator<T>;
+  template <typename T>
+    using vecLink_t = std::vector<ElementLink<T>>;
+} //> end anonymous namespace
+
+namespace DerivationFramework {
+
+  TriggerMatchingTool::TriggerMatchingTool(
+      const std::string& type,
+      const std::string& name,
+      const IInterface* pSvcLocator) :
+    AthAlgTool(type, name, pSvcLocator)
+  {
+    declareInterface<IAugmentationTool>(this);
+    declareProperty("ChainNames", m_chainNames, 
+        "The list of trigger chains to match.");
+    declareProperty("OnlineParticleTool", m_trigParticleTool,
+        "The tool to retrieve online particles from the navigation.");
+    declareProperty("InputElectrons",
+        m_offlineInputs[xAOD::Type::Electron] = "Electrons",
+        "Offline electron candidates for matching.");
+    declareProperty("InputPhotons",
+        m_offlineInputs[xAOD::Type::Photon] = "Photons",
+        "Offline photon candidates for matching.");
+    declareProperty("InputMuons",
+        m_offlineInputs[xAOD::Type::Muon] = "Muons",
+        "Offline muon candidates for matching.");
+    declareProperty("InputTaus",
+        m_offlineInputs[xAOD::Type::Tau] = "TauJets",
+        "Offline tau candidates for matching.");
+    declareProperty("DRThreshold", m_drThreshold = 0.1,
+        "The maximum dR between an offline and an online particle to consider "
+        "a match between them.");
+    declareProperty("Rerun", m_rerun = false,
+        "Whether to match triggers in rerun mode.");
+    declareProperty("OutputContainerPrefix", m_outputPrefix="TrigMatch_",
+        "The prefix to add to the output containers.");
+    declareProperty("CheckEmptyChainGroups", m_checkEmptyChainGroups = true,
+        "If set, discard any empty chain groups. Otherwise these will cause "
+        "a job failure.");
+    declareProperty("InputDependentConfig", m_inputDependentConfig=false,
+        "Warn when a trigger is removed (if the configuration is dependent "
+        "on the inputs, removal is not expected).");
+  }
+
+  StatusCode TriggerMatchingTool::initialize()
+  {
+    ATH_MSG_INFO( "Initializing " << name() );
+
+    // Remove any duplicates from the list of chain names
+    std::sort(m_chainNames.begin(), m_chainNames.end() );
+    m_chainNames.erase(std::unique(m_chainNames.begin(), m_chainNames.end() ), m_chainNames.end() );
+
+    ATH_CHECK( m_trigParticleTool.retrieve() );
+    return StatusCode::SUCCESS;
+  }
+
+  StatusCode TriggerMatchingTool::addBranches() const
+  {
+    if (m_firstEvent) {
+      m_firstEvent = false;
+      auto itr = m_chainNames.begin();
+      while (itr != m_chainNames.end() ) {
+        const Trig::ChainGroup* cg = m_tdt->getChainGroup(*itr);
+        if (cg->getListOfTriggers().size() == 0){
+          if (m_inputDependentConfig)
+            ATH_MSG_WARNING("Removing trigger " << (*itr) << " -- suggests a bad tool configuration (asking for triggers not in the menu)");
+          itr = m_chainNames.erase(itr);
+        } else
+          ++itr;
+      }
+    }
+
+    // Now, get all the possible offline candidates
+    std::map<xAOD::Type::ObjectType, particleVec_t> offlineCandidates;
+    for (const auto& p : m_offlineInputs) {
+      // Skip any that may have been disabled by providing an empty string to
+      // the corresponding property
+      if (p.second.empty() )
+        continue;
+      const xAOD::IParticleContainer* cont(nullptr);
+      ATH_CHECK( evtStore()->retrieve(cont, p.second) );
+      offlineCandidates.emplace(std::make_pair(
+          p.first, 
+          particleVec_t(cont->begin(), cont->end() )
+      ) );
+    }
+
+    // Store possible matches from online to offline particles here
+    // We do this as multiple chains may use the same online particles so we can
+    // reuse this information.
+    std::map<const xAOD::IParticle*, particleVec_t> matches;
+
+    // Iterate through the chains to get the matchings
+    for (const std::string& chain : m_chainNames) {
+      // Create the output
+      xAOD::TrigCompositeContainer* container(nullptr);
+      ATH_CHECK( createOutputContainer(container, chain) );
+
+      // Get the list of online combinations
+      std::vector<particleVec_t> onlineCombinations;
+      ATH_CHECK( m_trigParticleTool->retrieveParticles(onlineCombinations, chain, m_rerun) );
+
+      ATH_MSG_DEBUG(
+          onlineCombinations.size() << " combinations found for chain" << chain);
+      
+      // If no combinations were found (because the trigger was not passed) then
+      // we can skip this trigger
+      if (onlineCombinations.size() == 0)
+        continue;
+
+      using particleRange_t = TriggerMatchingUtils::RangedItr<typename particleVec_t::const_iterator>;
+      // Now build up the list of offline combinations;
+      std::vector<particleVec_t> offlineCombinations;
+      for (const particleVec_t& combination : onlineCombinations) {
+        // Here we store the possible candidates for the matching. We use the
+        // range type as a lightweight method to carry around a view of a vector
+        // without copying it (the RangedItr is essentially a combination of 3
+        // iterators).
+        std::vector<particleRange_t> matchCandidates;
+        for (const xAOD::IParticle* part : combination) {
+          const particleVec_t& possibleMatches = getCandidateMatchesFor(
+              part, offlineCandidates, matches);
+          matchCandidates.emplace_back(possibleMatches.begin(), possibleMatches.end() );
+        } //> end loop over particles
+        // Get all possible combinations of offline objects that could match to
+        // this particular online combination.
+        auto theseOfflineCombinations = 
+          TriggerMatchingUtils::getAllDistinctCombinations<const xAOD::IParticle*>(
+              matchCandidates);
+        if (msgLvl(MSG::VERBOSE) ) {
+          // Spit out some verbose information
+          ATH_MSG_VERBOSE(
+              "Checking matching for chain " << chain
+              << " with " << matchCandidates.size() << " legs");
+          std::size_t idx = 0;
+          for (const particleRange_t& range : matchCandidates)
+            ATH_MSG_VERBOSE( "Leg #" << idx++ << " has " << range.size()
+                << " offline candidates." );
+          ATH_MSG_VERBOSE(
+              "Matching generated " << theseOfflineCombinations.size()
+              << " offline combinations");
+        }
+        // Now push them back into the output. Use a specialised function for
+        // inserting into a sorted vector that ensures that we only output
+        // unique combinations
+        for (const particleVec_t& vec : theseOfflineCombinations)
+          TriggerMatchingUtils::insertIntoSortedVector(offlineCombinations, vec);
+      } //> end loop over combinations
+
+
+      // Decorate the found combinations onto trigger composites.
+      for (const particleVec_t& foundCombination : offlineCombinations) {
+        xAOD::TrigComposite* composite = new xAOD::TrigComposite();
+        container->push_back(composite);
+        static dec_t<vecLink_t<xAOD::IParticleContainer>> dec_links(
+            "TrigMatchedObjects");
+        vecLink_t<xAOD::IParticleContainer>& links = dec_links(*composite);
+        for (const xAOD::IParticle* part : foundCombination) {
+          // TODO This might be dangerous if the user provides the wrong types
+          // for the inputs or the input containers are not the owners (so that
+          // part->index() isn't the right value...). However I'm not sure what
+          // the best option is here...
+          links.emplace_back(m_offlineInputs.at(part->type()), part->index() );
+        }
+      } //> end loop over the found combinations
+    } //> end loop over chains
+    return StatusCode::SUCCESS;
+  }
+
+  StatusCode TriggerMatchingTool::createOutputContainer(
+      xAOD::TrigCompositeContainer*& container,
+      const std::string& chain) const
+  {
+    auto uniqueContainer = std::make_unique<xAOD::TrigCompositeContainer>();
+    auto aux = std::make_unique<xAOD::AuxContainerBase>();
+    uniqueContainer->setStore(aux.get() );
+    container = uniqueContainer.get();
+    std::string name = m_outputPrefix+chain;
+    // We have to replace '.' characters with '_' characters so that these are
+    // valid container names...
+    std::replace(name.begin(), name.end(), '.', '_');
+    ATH_CHECK( evtStore()->record(std::move(uniqueContainer), name) );
+    ATH_CHECK( evtStore()->record(std::move(aux), name+"Aux.") );
+    return StatusCode::SUCCESS;
+  }
+
+  const TriggerMatchingTool::particleVec_t& TriggerMatchingTool::getCandidateMatchesFor(
+      const xAOD::IParticle* part,
+      std::map<xAOD::Type::ObjectType, particleVec_t>& offlineParticles,
+      std::map<const xAOD::IParticle*, particleVec_t>& cache) const
+  {
+    // Build up all the possible matches between online and offline particles
+    auto cacheItr = cache.find(part);
+    if (cacheItr == cache.end() ) {
+      xAOD::Type::ObjectType type = part->type();
+      particleVec_t candidates;
+      if (type == xAOD::Type::CaloCluster) {
+        // If it's a calo cluster then we need to get the cluster from the
+        // egamma types.
+        static constAcc_t<vecLink_t<xAOD::CaloClusterContainer>> acc_calo("caloClusterLinks");
+        for (xAOD::Type::ObjectType egType : {
+            xAOD::Type::Electron, xAOD::Type::Photon})
+        {
+          for (const xAOD::IParticle* cand : offlineParticles[egType]) {
+            const vecLink_t<xAOD::CaloClusterContainer>& links = acc_calo(*cand);
+            if (links.size() == 0 || !links.at(0).isValid() )
+              continue;
+            const xAOD::CaloCluster* clus = *links.at(0);
+            if (matchParticles(part, clus) )
+              candidates.push_back(cand);
+          }
+        }
+      }
+      else {
+        for (const xAOD::IParticle* cand : offlineParticles[type])
+          if (matchParticles(part, cand) )
+            candidates.push_back(cand);
+      }
+      cacheItr = cache.emplace(
+          std::make_pair(part, std::move(candidates) ) ).first;
+    }
+    return cacheItr->second;
+  }
+
+  bool TriggerMatchingTool::matchParticles(
+      const xAOD::IParticle* lhs,
+      const xAOD::IParticle* rhs) const
+  {
+    return xAOD::P4Helpers::deltaR(lhs, rhs, false) < m_drThreshold;
+  }
+
+} //> end namespace DerivationFramework
diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/TriggerMatchingTool.h b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/TriggerMatchingTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..6e3be2757e7c03c469ebc6e6718474cecd9df620
--- /dev/null
+++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/TriggerMatchingTool.h
@@ -0,0 +1,125 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef DERIVATIONFRAMEWORKTRIGGER_TRIGGERMATCHINGTOOL_H
+#define DERIVATIONFRAMEWORKTRIGGER_TRIGGERMATCHINGTOOL_H
+
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "DerivationFrameworkInterfaces/IAugmentationTool.h"
+#include "GaudiKernel/ToolHandle.h"
+#include "TriggerMatchingTool/IIParticleRetrievalTool.h"
+#include "TrigDecisionTool/TrigDecisionTool.h"
+#include "xAODBase/IParticle.h"
+#include "xAODBase/IParticleContainer.h"
+#include "xAODTrigger/TrigCompositeContainer.h"
+#include <vector>
+
+namespace DerivationFramework {
+  /**
+   * @brief Tool to perform trigger matching in the derivation framework.
+   *
+   * Trigger matching in the derivation framework has to take the reverse
+   * approach to matching in analysis code. There, the matching tool is provided
+   * with a list of analysis particles to match to a given trigger. The
+   * navigation then proceeds to search out all features that match the types of
+   * those particles.
+   * In the derivation framework however, we need to get the full list of all
+   * particles that a user *could* ask for in their analysis code. To do this,
+   * we ask the navigation for all the relevant xAOD objects and then use those
+   * to query the available offline objects.
+   *
+   * @author Jon Burr
+   */
+  class TriggerMatchingTool final :
+    public AthAlgTool,
+    virtual public IAugmentationTool
+  {
+    public:
+      /// Helper typedefs
+      using particleVec_t = std::vector<const xAOD::IParticle*>;
+      /// Constructor
+      TriggerMatchingTool(
+          const std::string& type,
+          const std::string& name,
+          const IInterface* pSvcLocator);
+
+      /// Initialize the tool
+      StatusCode initialize() override;
+
+      /// Calculate the matchings
+      StatusCode addBranches() const override;
+
+    private:
+      // Properties
+      /// The list of chain names to match
+      mutable std::vector<std::string> m_chainNames;
+      // This being mutable isn't exactly the greatest thing but we can't check
+      // whether the chains actually exist before the first event. A better
+      // solution would be to filter the chains by what is available in the
+      // input file but I don't know if that's possible.
+
+      /// The tool to retrieve the online candidates
+      ToolHandle<Trig::IIParticleRetrievalTool> m_trigParticleTool
+      {"Trig::IParticleRetrievalTool/OnlineParticleTool"};
+
+      /// The input containers to use. These are keyed by xAOD object type
+      std::map<xAOD::Type::ObjectType, std::string> m_offlineInputs;
+
+      /// The DR threshold to use
+      float m_drThreshold;
+
+      /// Whether to match in rerun mode or not.
+      bool m_rerun;
+
+      /// The prefix to place at the beginning of the output containers
+      std::string m_outputPrefix;
+
+      /// If set, discard any triggers with empty chain groups (break the job
+      /// otherwise).
+      bool m_checkEmptyChainGroups;
+
+      /// If using an input-file-dependent config then we warn when triggers
+      /// are removed
+      bool m_inputDependentConfig;
+
+      /// The trig decision tool
+      ToolHandle<Trig::TrigDecisionTool> m_tdt
+      {"Trig::TrigDecisionTool/TrigDecisionTool"};
+
+      // Internal values
+      mutable bool m_firstEvent{true};
+
+      // Internal functions
+      /**
+       * @brief Create an output container for the named chain
+       * @param[out] container A pointer to the created container
+       * @param chain The name of the chain to create the container for
+       * The container will be recorded in the StoreGate using the name of the
+       * chain as a key.
+       */
+      StatusCode createOutputContainer(
+          xAOD::TrigCompositeContainer*& container,
+          const std::string& chain) const;
+
+      /**
+       * @brief Get all offline particles that could match a given online one.
+       * @param part The online particle to match against.
+       * @param offlineParticles The offline particles, key by xAOD type.
+       * @param cache Store past matches here to speed up future look ups.
+       */
+      const particleVec_t& getCandidateMatchesFor(
+          const xAOD::IParticle* part,
+          std::map<xAOD::Type::ObjectType, particleVec_t>& offlineParticles,
+          std::map<const xAOD::IParticle*, particleVec_t>& cache) const;
+
+      /**
+       * @brief Check if the dR between two particles is below threshold.
+       */
+      bool matchParticles(
+          const xAOD::IParticle* lhs,
+          const xAOD::IParticle* rhs) const;
+  }; //> end class TriggerMatchingTool
+} //> end namespace DerivationFramework
+
+#endif //> ! DERIVATIONFRAMEWORKTRIGGER_TRIGGERMATCHINGTOOL_H
diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/components/DerivationFrameworkTrigger_entries.cxx b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/components/DerivationFrameworkTrigger_entries.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..17463f8f4649a261871efa50116d44706ca5ee40
--- /dev/null
+++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkTrigger/src/components/DerivationFrameworkTrigger_entries.cxx
@@ -0,0 +1,3 @@
+#include "../TriggerMatchingTool.h"
+
+DECLARE_COMPONENT( DerivationFramework::TriggerMatchingTool )