diff --git a/Control/AthenaServices/CMakeLists.txt b/Control/AthenaServices/CMakeLists.txt
index a38c8d4836cfde4cb27e30b966db0931aded5986..c05e525daa6408a9e96371a179bcc9aa400c852a 100644
--- a/Control/AthenaServices/CMakeLists.txt
+++ b/Control/AthenaServices/CMakeLists.txt
@@ -87,6 +87,12 @@ atlas_add_test( DelayedConditionsCleanerSvc_test
    EXTRA_PATTERNS "^JobOptionsSvc +INFO|^//GP:="
    ENVIRONMENT "JOBOPTSEARCHPATH=${CMAKE_CURRENT_SOURCE_DIR}/share" )
 
+atlas_add_test( ThinningCacheTool_test
+   SOURCES test/ThinningCacheTool_test.cxx
+   LINK_LIBRARIES TestTools AsgTools AthenaKernel GaudiKernel EventInfo AthenaBaseComps
+   EXTRA_PATTERNS "^JobOptionsSvc +INFO|^//GP:=" 
+   ENVIRONMENT "JOBOPTSEARCHPATH=${CMAKE_CURRENT_SOURCE_DIR}/share" )
+
 atlas_add_test( TestStopRun
    SCRIPT test/TestStopRun.sh
    EXTRA_PATTERNS "Warning in <TFile::Init>: no StreamerInfo found|SGAudSvc +INFO Finalizing|SGAudSvc +INFO Initializing|^Py:Athena +INFO executing ROOT6Setup|No duplicates have been found|duplicate entry.*ignored|^Py:ConfigurableDb WARNING|Read module info for|^ToolSvc.* INFO( finalize:)? Removing all tools|^CoreDumpSvc *INFO Handling signals|types added|including file|^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)|local .* libGaudiKernelDict.so|^Number of My|^Py:Athena +INFO using release|^StoreGateSvc +INFO Start|^Py:AthenaDsoDb +INFO could not install alias|Bindings.py:660newobj = object.__new__|Updating ROOT::Reflex::PluginService::SetDebug|^ApplicationMgr +INFO|^StoreGateSvc +INFO"
diff --git a/Control/AthenaServices/share/ThinningCacheTool_test.ref b/Control/AthenaServices/share/ThinningCacheTool_test.ref
new file mode 100644
index 0000000000000000000000000000000000000000..a79dae8c34641bd562e1e6f59a6982c7fb6f45bf
--- /dev/null
+++ b/Control/AthenaServices/share/ThinningCacheTool_test.ref
@@ -0,0 +1,17 @@
+AthenaServices/ThinningCacheTool_test
+
+
+Initializing Gaudi ApplicationMgr using job opts ../share/ThinningCacheTool_test.txt
+JobOptionsSvc        INFO Job options successfully read in from ../share/ThinningCacheTool_test.txt
+ApplicationMgr    SUCCESS 
+====================================================================================================================================
+                                                   Welcome to ApplicationMgr (GaudiCoreSvc v27r1p99)
+                                          running on karma on Thu Oct 24 11:24:46 2019
+====================================================================================================================================
+ApplicationMgr       INFO Application Manager Configured successfully
+EventLoopMgr      WARNING Unable to locate service "EventSelector" 
+EventLoopMgr      WARNING No events will be processed from external input.
+HistogramPersis...WARNING Histograms saving not required.
+ApplicationMgr       INFO Application Manager Initialized successfully
+ApplicationMgr Ready
+test1
diff --git a/Control/AthenaServices/share/ThinningCacheTool_test.txt b/Control/AthenaServices/share/ThinningCacheTool_test.txt
new file mode 100644
index 0000000000000000000000000000000000000000..31822146db0894f48015b2a1d607a62f56a0a0ba
--- /dev/null
+++ b/Control/AthenaServices/share/ThinningCacheTool_test.txt
@@ -0,0 +1,3 @@
+// job opts for ThinningCacheTool test
+
+ToolSvc.TestCacheTool.StreamName = "MyStream";
diff --git a/Control/AthenaServices/src/ThinningCacheTool.cxx b/Control/AthenaServices/src/ThinningCacheTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..0f02690c5a0f8c9059fc6e7c269b7bd488d9ac66
--- /dev/null
+++ b/Control/AthenaServices/src/ThinningCacheTool.cxx
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration.
+ */
+/**
+ * @file AthenaServices/src/ThinningCacheTool.h
+ * @author scott snyder <snyder@bnl.gov>
+ * @date Aug, 2019
+ * @brief Create ThinningCache for a given stream.
+ */
+
+
+#include "ThinningCacheTool.h"
+#include "AthContainers/ThinningDecision.h"
+#include "SGTools/DataProxy.h"
+#include "AthenaKernel/IProxyDict.h"
+#include "AthenaKernel/ExtendedEventContext.h"
+#include "GaudiKernel/ThreadLocalContext.h"
+
+
+namespace Athena {
+
+
+/**
+ * @brief Called at the end of initialize.  A no-op here.
+ */
+StatusCode ThinningCacheTool::postInitialize()
+{
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode ThinningCacheTool::preExecute()
+{
+  m_cache.clear();
+
+  // Examine all ThinningDecision objects in the store.
+  SG::ConstIterator<SG::ThinningDecision> beg;
+  SG::ConstIterator<SG::ThinningDecision> end;
+  // Will return FAILURE if no matches, so ignore.
+  evtStore()->retrieve<SG::ThinningDecision>(beg, end).ignore();
+  for (; beg != end; ++beg)
+  {
+    // See if this is for this stream.
+    std::string key = wantedKey (beg.key());
+    if (!key.empty()) {
+
+      // Get the set of hashed SG keys for this object.
+      std::vector<SG::sgkey_t> sgkeys;
+      SG::DataProxy* proxy = beg->link().proxy (true);
+      if (proxy) {
+        sgkeys.push_back (proxy->sgkey());
+        CLID pCLID = proxy->clID();
+        IProxyDict* store = proxy->store();
+        for (CLID clid : proxy->transientID()) {
+          if (clid != pCLID) {
+            sgkeys.push_back (store->stringToKey (key, clid));
+          }
+        }
+      }
+
+      // Add it to the cache.
+      m_cache.addThinning (key, sgkeys, &*beg);
+    }
+  }
+
+  // If there was any thinning for this stream, then install the cache
+  // in the EventContext.
+  if (!m_cache.empty()) {
+    m_cache.lockOwned();
+    EventContext ctx = Gaudi::Hive::currentContext();
+    Atlas::getExtendedEventContext (ctx).setThinningCache (&m_cache);
+    Gaudi::Hive::setCurrentContext (ctx);
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+
+/**
+ * @brief Called at the end of execute.
+ *        Remove ThinningCache from the EventContext and clear the cache instance.
+ */
+StatusCode ThinningCacheTool::postExecute()
+{
+  if (!m_cache.empty()) {
+    EventContext ctx = Gaudi::Hive::currentContext();
+    Atlas::getExtendedEventContext (ctx).setThinningCache (nullptr);
+    Gaudi::Hive::setCurrentContext (ctx);
+  }
+
+  m_cache.clear();
+  return StatusCode::SUCCESS;
+}
+
+
+/**
+ * @brief Called at the beginning of finalize.  A no-op here.
+ */
+StatusCode ThinningCacheTool::preFinalize()
+{
+  return StatusCode::SUCCESS;
+}
+
+
+/**
+ * @brief Test if a thinning decision object is for our stream.
+ * @param key The SG key of the @c ThinningDecision object.
+ *
+ * If this thinning decision is for our stream, then return the SG key
+ * of the object being thinned.  Otherwise, return an empty string.
+ */
+std::string ThinningCacheTool::wantedKey (const std::string& key) const
+{
+  std::string suffix = "_THINNED_" + m_streamName;
+  std::string::size_type spos = key.find (suffix);
+  if (spos != std::string::npos &&
+      (spos + suffix.size() == key.size() ||
+       key[spos + suffix.size()] == '.'))
+  {
+    return key.substr (0, spos);
+  }
+  return "";
+}
+
+
+} // namespace Athena
+
diff --git a/Control/AthenaServices/src/ThinningCacheTool.h b/Control/AthenaServices/src/ThinningCacheTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..e7092cb7142e8b10b1baa0bfbceb4e4ecc1e5c76
--- /dev/null
+++ b/Control/AthenaServices/src/ThinningCacheTool.h
@@ -0,0 +1,91 @@
+// This file's extension implies that it's C, but it's really -*- C++ -*-.
+/*
+ * Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration.
+ */
+/**
+ * @file AthenaServices/src/ThinningCacheTool.h
+ * @author scott snyder <snyder@bnl.gov>
+ * @date Aug, 2019
+ * @brief Create ThinningCache for a given stream.
+ */
+
+
+#ifndef ATHENASERVICES_THINNINGCACHETOOL_H
+#define ATHENASERVICES_THINNINGCACHETOOL_H
+
+
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "AthenaKernel/ThinningCache.h"
+#include "AthenaKernel/IAthenaOutputTool.h"
+
+
+namespace Athena {
+
+
+/**
+ * @brief Create ThinningCache for a given stream.
+ *
+ * Hooks in this class are called before and after output is written for a given
+ * stream.  The @c preExecute hook will query the event store for any thinning
+ * to be done for the stream, build a ThinningCache object for all of these,
+ * and install it in the current EventContext.  The @c postExecute hook
+ * will then clear the cache from the EventContext.
+ */
+class ThinningCacheTool : public extends<AthAlgTool, IAthenaOutputTool>
+{
+public:
+  using base_class::base_class;
+
+
+  /**
+   * @brief Called at the end of initialize.  A no-op here.
+   */
+  virtual StatusCode postInitialize() override;
+
+
+  /**
+   * @brief Called at the beginning of execute.
+   *        Find all thinning requests for this stream, build the @c ThinningCache,
+   *        and install it in the current @c EventContext.
+   */
+  virtual StatusCode preExecute() override;
+
+
+  /**
+   * @brief Called at the end of execute.
+   *        Remove ThinningCache from the EventContext and clear the cache instance.
+   */
+  virtual StatusCode postExecute() override;
+
+
+  /**
+   * @brief Called at the beginning of finalize.  A no-op here.
+   */
+  virtual StatusCode preFinalize() override;
+
+
+private:
+  /**
+   * @brief Test if a thinning decision object is for our stream.
+   * @param key The SG key of the @c ThinningDecision object.
+   *
+   * If this thinning decision is for our stream, then return the SG key
+   * of the object being thinned.  Otherwise, return an empty string.
+   */
+  std::string wantedKey (const std::string& key) const;
+
+
+  /// Name of the stream for this tool.
+  StringProperty m_streamName
+  { this, "StreamName", "", "Name of the stream being written." };
+
+  
+  /// Thinning cache instance for this stream.
+  SG::ThinningCache m_cache;
+};
+
+
+} // namespace Athena
+
+
+#endif // not ATHENASERVICES_THINNINGCACHETOOL_H
diff --git a/Control/AthenaServices/src/components/AthenaServices_entries.cxx b/Control/AthenaServices/src/components/AthenaServices_entries.cxx
index 70a66efc0e4291e19a4e2380ca7b057d4e3121a4..8e6b28eb73f8362c5ef228c41fab64111a824bd7 100644
--- a/Control/AthenaServices/src/components/AthenaServices_entries.cxx
+++ b/Control/AthenaServices/src/components/AthenaServices_entries.cxx
@@ -8,6 +8,7 @@
 #include "../MixingEventSelector.h"
 #include "../ThinningSvc.h"
 #include "../ThinningOutputTool.h"
+#include "../ThinningCacheTool.h"
 //#include "../EventDumperSvc.h"
 #include "../MemoryRescueSvc.h"
 #include "../FPEControlSvc.h"
@@ -67,6 +68,7 @@ DECLARE_COMPONENT( MetaDataSvc )
 DECLARE_COMPONENT( OutputStreamSequencerSvc )
 DECLARE_COMPONENT( AthenaOutputStreamTool )
 DECLARE_COMPONENT( ThinningOutputTool )
+DECLARE_COMPONENT( Athena::ThinningCacheTool )
 DECLARE_COMPONENT( AthenaStopperAlg )
 DECLARE_COMPONENT( AthIncFirerAlg )
 DECLARE_COMPONENT( ToyNextPassFilterAlg )
diff --git a/Control/AthenaServices/test/ThinningCacheTool_test.cxx b/Control/AthenaServices/test/ThinningCacheTool_test.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..80a356e9335e3fe3137bec9804c7d69fc39fdca5
--- /dev/null
+++ b/Control/AthenaServices/test/ThinningCacheTool_test.cxx
@@ -0,0 +1,124 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+/**
+ * @file  AthenaServices/test/ThinningCacheTool_test.cxx
+ * @author scott snyder
+ * @date Oct 2019
+ * @brief Test for ThinningCacheTool.
+ */
+
+
+#undef NDEBUG
+#include "StoreGate/StoreGateSvc.h"
+#include "AthContainers/DataVector.h"
+#include "AthContainers/ThinningDecision.h"
+#include "AthenaKernel/ExtendedEventContext.h"
+#include "AthenaKernel/IAthenaOutputTool.h"
+#include "AthenaKernel/getThinningCache.h"
+#include "AthenaKernel/CLASS_DEF.h"
+#include "TestTools/initGaudi.h"
+#include "GaudiKernel/ToolHandle.h"
+#include "GaudiKernel/ServiceHandle.h"
+#include "GaudiKernel/ThreadLocalContext.h"
+#include <iostream>
+#include <cassert>
+
+
+class Test {};
+CLASS_DEF (DataVector<Test>, 29384894, 0)
+
+
+void thin (SG::ThinningDecisionBase& dec, unsigned int mask)
+{
+  for (size_t i = 0; mask != 0; i++, mask>>=1) {
+    if ((mask & 1) != 0) {
+      dec.thin (i);
+    }
+  }
+}
+
+
+unsigned int thinning (const SG::ThinningDecisionBase& dec)
+{
+  unsigned int mask = 0;
+  for (size_t i = 0; i < dec.size(); i++) {
+    if (dec.thinned(i)) {
+      mask |= (1<<i);
+    }
+  }
+  return mask;
+}
+
+
+void test1()
+{
+  std::cout << "test1\n";
+
+  ToolHandle<IAthenaOutputTool> tool ("Athena::ThinningCacheTool/TestCacheTool");
+  assert (tool.retrieve().isSuccess());
+
+  ServiceHandle<StoreGateSvc> sg ("StoreGateSvc", "test");
+  assert (sg.retrieve().isSuccess());
+
+  assert (tool->preExecute().isSuccess());
+  assert (SG::getThinningCache() == nullptr);
+  assert (tool->postExecute().isSuccess());
+  assert (SG::getThinningCache() == nullptr);
+
+  assert( sg->record (std::make_unique<DataVector<Test> >(16), "v1", false).isSuccess() );
+  assert( sg->record (std::make_unique<DataVector<Test> >(16), "v2", false).isSuccess() );
+
+  auto d1 = std::make_unique<SG::ThinningDecision> ("v1");
+  auto d2 = std::make_unique<SG::ThinningDecision> ("v2");
+  auto d3 = std::make_unique<SG::ThinningDecision> ("v2");
+
+  thin (*d1, 0x5555);
+  thin (*d2, 0x6753);
+  thin (*d3, 0x7234);
+
+  const SG::ThinningDecisionBase* d1p = d1.get();
+  const SG::ThinningDecisionBase* d2p = d2.get();
+  const SG::ThinningDecisionBase* d3p = d3.get();
+
+  assert( sg->record (std::move (d1), "v1_THINNED_MyStream",   false).isSuccess() );
+  assert( sg->record (std::move (d2), "v2_THINNED_MyStream.a", false).isSuccess() );
+  assert( sg->record (std::move (d3), "v2_THINNED_MyStream.b", false).isSuccess() );
+
+  assert (tool->preExecute().isSuccess());
+  assert (SG::getThinningCache() != nullptr);
+
+  assert (SG::getThinningDecision ("v1") == d1p);
+  assert (SG::getThinningDecision ("v2") != nullptr);
+  assert (SG::getThinningDecision ("v2") != d2p);
+  assert (SG::getThinningDecision ("v2") != d3p);
+
+  assert (thinning (*SG::getThinningDecision ("v1")) == 0x5555);
+  assert (thinning (*SG::getThinningDecision ("v2")) == 0x6210);
+  assert (SG::getThinningDecision ("v3") == nullptr);
+
+  assert (SG::getThinningDecision ("v1")->index (7) == 3);
+  assert (SG::getThinningDecision ("v2")->index (7) == 6);
+
+  assert (tool->postExecute().isSuccess());
+  assert (SG::getThinningCache() == nullptr);
+}
+
+
+int main()
+{
+  std::cout << "AthenaServices/ThinningCacheTool_test\n";
+  ISvcLocator* svcloc = nullptr;
+  if (!Athena_test::initGaudi("ThinningCacheTool_test.txt", svcloc)) {
+    std::cerr << "This test can not be run" << std::endl;
+    return 1;
+  }  
+  assert(svcloc);
+
+  EventContext ctx;
+  Atlas::setExtendedEventContext (ctx, Atlas::ExtendedEventContext());
+  Gaudi::Hive::setCurrentContext (ctx);
+
+  test1();
+  return 0;
+}