diff --git a/source/RobotAPI/libraries/armem/CMakeLists.txt b/source/RobotAPI/libraries/armem/CMakeLists.txt
index ee5a16f2563ac08d997f1273035a16085ab1919c..b8442bdf0f1403caf899757976a230aa6a97e235 100644
--- a/source/RobotAPI/libraries/armem/CMakeLists.txt
+++ b/source/RobotAPI/libraries/armem/CMakeLists.txt
@@ -85,6 +85,7 @@ set(LIB_FILES
     client/Writer.cpp
     client/WriterComponentPlugin.cpp
 
+    client/util/MemoryListener.cpp
     client/util/SimpleReaderBase.cpp
     client/util/SimpleWriterBase.cpp
 
@@ -199,6 +200,7 @@ set(LIB_HEADERS
     client/query/detail/NameSelectorOps.h
     client/query/detail/SelectorOps.h
 
+    client/util/MemoryListener.h
     client/util/SimpleReaderBase.h
     client/util/SimpleWriterBase.h
 
diff --git a/source/RobotAPI/libraries/armem/client/Reader.cpp b/source/RobotAPI/libraries/armem/client/Reader.cpp
index 2efc1d41ff87836042522cf9ed166fda974537a8..b548e7aabf3c7f010b09b96c79a2446a4848e82c 100644
--- a/source/RobotAPI/libraries/armem/client/Reader.cpp
+++ b/source/RobotAPI/libraries/armem/client/Reader.cpp
@@ -108,48 +108,6 @@ namespace armarx::armem::client
     }
 
 
-    void
-    Reader::updated(const std::vector<MemoryID>& updatedSnapshotIDs) const
-    {
-        std::stringstream error;
-
-        for (const auto& [subscription, callbacks] : this->callbacks)
-        {
-            std::vector<MemoryID> matchingSnapshotIDs;
-
-            for (const MemoryID& updatedSnapshotID : updatedSnapshotIDs)
-            {
-                try
-                {
-                    if (contains(subscription, updatedSnapshotID))
-                    {
-                        matchingSnapshotIDs.push_back(updatedSnapshotID);
-                    }
-                }
-                catch (const armem::error::InvalidMemoryID& e)
-                {
-                    // Log to debug, but ignore otherwise
-                    error << "Error when comparing subscribed ID " << subscription
-                          << " with updated ID " << updatedSnapshotID << ":\n"
-                          << e.what() << "\n\n";
-                }
-            }
-
-            if (not matchingSnapshotIDs.empty())
-            {
-                for (auto& callback : callbacks)
-                {
-                    callback(subscription, matchingSnapshotIDs);
-                }
-            }
-        }
-        if (error.str().size() > 0)
-        {
-            ARMARX_VERBOSE << "The following issues were encountered during Reader::" << __FUNCTION__ << "(): \n\n"
-                           << error.str();
-        }
-    }
-
     data::StoreResult
     Reader::readAndStore(const data::StoreInput& input) const
     {
@@ -166,21 +124,6 @@ namespace armarx::armem::client
     }
 
 
-    void
-    Reader::subscribe(const MemoryID& id, callback callback)
-    {
-        callbacks[id].push_back(callback);
-    }
-
-    void Reader::subscribe(const MemoryID& subscriptionID, callback_updated_only callback)
-    {
-        subscribe(subscriptionID, [callback](const MemoryID&, const std::vector<MemoryID>& updatedSnapshotIDs)
-        {
-            callback(updatedSnapshotIDs);
-        });
-    }
-
-
     void
     Reader::setReadingMemory(server::ReadingMemoryInterfacePrx memory)
     {
diff --git a/source/RobotAPI/libraries/armem/client/Reader.h b/source/RobotAPI/libraries/armem/client/Reader.h
index ae2548f29c93e4a9ad2ad8bef0079861c87fb8fa..3a1726a548fb4d392eda273ef3228eaeccefb265 100644
--- a/source/RobotAPI/libraries/armem/client/Reader.h
+++ b/source/RobotAPI/libraries/armem/client/Reader.h
@@ -2,8 +2,6 @@
 
 
 // STD/STL
-#include <functional>
-#include <unordered_map>
 #include <vector>
 
 // RobotAPI
@@ -13,6 +11,7 @@
 #include <RobotAPI/libraries/armem/core/workingmemory/ice_conversions.h>
 #include <RobotAPI/libraries/armem/core/workingmemory/Memory.h>
 #include <RobotAPI/libraries/armem/core/longtermmemory/Memory.h>
+#include <RobotAPI/libraries/armem/client/util/MemoryListener.h>
 
 #include "Query.h"
 
@@ -23,18 +22,9 @@ namespace armarx::armem::client
     /**
      * @brief Reads data from a memory server.
      */
-    class Reader
+    class Reader : public util::MemoryListener
     {
 
-        using callback = std::function<void(const MemoryID& subscriptionID, const std::vector<MemoryID>& updatedSnapshotIDs)>;
-        using callback_updated_only = std::function<void(const std::vector<MemoryID>& updatedSnapshotIDs)>;
-
-        template <class CalleeT>
-        using member_callback = void(CalleeT::*)(const MemoryID& subscriptionID, const std::vector<MemoryID>& updatedSnapshotIDs);
-        template <class CalleeT>
-        using member_callback_updated_only = void(CalleeT::*)(const std::vector<MemoryID>& updatedSnapshotIDs);
-
-
     public:
 
         /**
@@ -78,36 +68,6 @@ namespace armarx::armem::client
         data::StoreResult readAndStore(const data::StoreInput& input) const;
 
 
-        void subscribe(const MemoryID& subscriptionID, callback callback);
-        void subscribe(const MemoryID& subscriptionID, callback_updated_only callback);
-        /**
-         * Subscribe with a class member function:
-         * @code
-         * reader.subscribe(entityID, this, &This::myCallback);
-         * @endcode
-         */
-        template <class CalleeT>
-        void subscribe(const MemoryID& subscriptionID, CalleeT* callee, member_callback<CalleeT> callback)
-        {
-            auto cb = [callee, callback](const MemoryID & subscriptionID, const std::vector<MemoryID>& updatedSnapshotIDs)
-            {
-                (callee->*callback)(subscriptionID, updatedSnapshotIDs);
-            };
-            subscribe(subscriptionID, cb);
-        }
-        template <class CalleeT>
-        void subscribe(const MemoryID& subscriptionID, CalleeT* callee, member_callback_updated_only<CalleeT> callback)
-        {
-            auto cb = [callee, callback](const MemoryID&, const std::vector<MemoryID>& updatedSnapshotIDs)
-            {
-                (callee->*callback)(updatedSnapshotIDs);
-            };
-            subscribe(subscriptionID, cb);
-        }
-        /// Function handling updates from the MemoryListener ice topic.
-        void updated(const std::vector<MemoryID>& updatedIDs) const;
-
-
         inline operator bool() const
         {
             return bool(memoryPrx);
@@ -117,10 +77,6 @@ namespace armarx::armem::client
 
         server::ReadingMemoryInterfacePrx memoryPrx;
 
-    private:
-
-        std::unordered_map<MemoryID, std::vector<callback>> callbacks;
-
     };
 
 }
diff --git a/source/RobotAPI/libraries/armem/client/util/MemoryListener.cpp b/source/RobotAPI/libraries/armem/client/util/MemoryListener.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..318d1294eea4f7d5fd0926c4b9d0475b831e60b6
--- /dev/null
+++ b/source/RobotAPI/libraries/armem/client/util/MemoryListener.cpp
@@ -0,0 +1,76 @@
+#include "MemoryListener.h"
+
+#include <sstream>
+
+#include <ArmarXCore/core/logging/Logging.h>
+#include <RobotAPI/libraries/armem/core/error.h>
+
+
+namespace armarx::armem::client::util
+{
+
+    MemoryListener::MemoryListener()
+    {
+    }
+
+
+    void
+    MemoryListener::updated(const std::vector<MemoryID>& updatedSnapshotIDs) const
+    {
+        std::stringstream error;
+
+        for (const auto& [subscription, callbacks] : this->callbacks)
+        {
+            std::vector<MemoryID> matchingSnapshotIDs;
+
+            for (const MemoryID& updatedSnapshotID : updatedSnapshotIDs)
+            {
+                try
+                {
+                    if (contains(subscription, updatedSnapshotID))
+                    {
+                        matchingSnapshotIDs.push_back(updatedSnapshotID);
+                    }
+                }
+                catch (const armem::error::InvalidMemoryID& e)
+                {
+                    // Log to debug, but ignore otherwise
+                    error << "Error when comparing subscribed ID " << subscription
+                          << " with updated ID " << updatedSnapshotID << ":\n"
+                          << e.what() << "\n\n";
+                }
+            }
+
+            if (not matchingSnapshotIDs.empty())
+            {
+                for (auto& callback : callbacks)
+                {
+                    callback(subscription, matchingSnapshotIDs);
+                }
+            }
+        }
+        if (error.str().size() > 0)
+        {
+            ARMARX_VERBOSE << "The following issues were encountered during MemoryListener::" << __FUNCTION__ << "(): \n\n"
+                           << error.str();
+        }
+    }
+
+
+    void
+    MemoryListener::subscribe(const MemoryID& id, callback callback)
+    {
+        callbacks[id].push_back(callback);
+    }
+
+
+    void MemoryListener::subscribe(const MemoryID& subscriptionID, callback_updated_only callback)
+    {
+        subscribe(subscriptionID, [callback](const MemoryID&, const std::vector<MemoryID>& updatedSnapshotIDs)
+        {
+            callback(updatedSnapshotIDs);
+        });
+    }
+
+
+}
diff --git a/source/RobotAPI/libraries/armem/client/util/MemoryListener.h b/source/RobotAPI/libraries/armem/client/util/MemoryListener.h
new file mode 100644
index 0000000000000000000000000000000000000000..33e1e104294fd829c45a8362f2e1652cc3962d1e
--- /dev/null
+++ b/source/RobotAPI/libraries/armem/client/util/MemoryListener.h
@@ -0,0 +1,77 @@
+#pragma once
+
+
+// STD/STL
+#include <functional>
+#include <unordered_map>
+#include <vector>
+
+// RobotAPI
+#include <RobotAPI/interface/armem/client/MemoryListenerInterface.h>
+#include <RobotAPI/libraries/armem/core/MemoryID.h>
+
+
+namespace armarx::armem::client::util
+{
+
+    /**
+     * @brief Handles update signals from the memory system and distributes it
+     * to its subsribers.
+     */
+    class MemoryListener
+    {
+
+        using callback = std::function<void(const MemoryID& subscriptionID, const std::vector<MemoryID>& updatedSnapshotIDs)>;
+        using callback_updated_only = std::function<void(const std::vector<MemoryID>& updatedSnapshotIDs)>;
+
+        template <class CalleeT>
+        using member_callback = void(CalleeT::*)(const MemoryID& subscriptionID, const std::vector<MemoryID>& updatedSnapshotIDs);
+        template <class CalleeT>
+        using member_callback_updated_only = void(CalleeT::*)(const std::vector<MemoryID>& updatedSnapshotIDs);
+
+
+    public:
+
+        MemoryListener();
+
+
+        void subscribe(const MemoryID& subscriptionID, callback callback);
+        void subscribe(const MemoryID& subscriptionID, callback_updated_only callback);
+
+        /**
+         * Subscribe with a class member function:
+         * @code
+         * reader.subscribe(entityID, this, &This::myCallback);
+         * @endcode
+         */
+        template <class CalleeT>
+        void subscribe(const MemoryID& subscriptionID, CalleeT* callee, member_callback<CalleeT> callback)
+        {
+            auto cb = [callee, callback](const MemoryID & subscriptionID, const std::vector<MemoryID>& updatedSnapshotIDs)
+            {
+                (callee->*callback)(subscriptionID, updatedSnapshotIDs);
+            };
+            subscribe(subscriptionID, cb);
+        }
+        template <class CalleeT>
+        void subscribe(const MemoryID& subscriptionID, CalleeT* callee, member_callback_updated_only<CalleeT> callback)
+        {
+            auto cb = [callee, callback](const MemoryID&, const std::vector<MemoryID>& updatedSnapshotIDs)
+            {
+                (callee->*callback)(updatedSnapshotIDs);
+            };
+            subscribe(subscriptionID, cb);
+        }
+
+
+        /// Function handling updates from the MemoryListener ice topic.
+        void updated(const std::vector<MemoryID>& updatedIDs) const;
+
+
+    protected:
+
+        std::unordered_map<MemoryID, std::vector<callback>> callbacks;
+
+    };
+
+}