diff --git a/source/RobotAPI/libraries/armem/core/MemoryID.cpp b/source/RobotAPI/libraries/armem/core/MemoryID.cpp
index 664319410949b11c7563512a6ccc22dc0c2dd3e1..61dd4836ca9f16ffd6b36342facbd2f610c75ff3 100644
--- a/source/RobotAPI/libraries/armem/core/MemoryID.cpp
+++ b/source/RobotAPI/libraries/armem/core/MemoryID.cpp
@@ -1,20 +1,45 @@
 #include "MemoryID.h"
 
-#include <SimoxUtility/algorithm/string.h>
-
 #include "error/ArMemError.h"
 
+#include <SimoxUtility/algorithm/advanced.h>
+#include <SimoxUtility/algorithm/string/string_tools.h>
+
+#include <boost/algorithm/string.hpp>
+
+#include <forward_list>
+
 
 namespace armarx::armem
 {
 
+    const std::string MemoryID::delimiter = "/";
+
+
     MemoryID::MemoryID()
     {
     }
 
     MemoryID::MemoryID(const std::string& string)
     {
-        std::vector<std::string> items = simox::alg::split(string, "/", false, false);
+        std::forward_list<std::string> items;
+        boost::split(items, string, boost::is_any_of(delimiter));
+
+        // Handle escaped /'s
+        for (auto it = items.begin(); it != items.end(); ++it)
+        {
+            while (it->size() > 0 && it->back() == '\\')
+            {
+                // The / causing the split was escaped. Merge the items together.
+                auto next = simox::alg::advanced(it, 1);
+                if (next != items.end())
+                {
+                    // "it\" + "next" => "it/next"
+                    *it = it->substr(0, it->size() - 1) + delimiter + *next;
+                    items.erase_after(it); // Invalidates `next`, but not `it`.
+                }
+            }
+        }
 
         auto it = items.begin();
         if (it == items.end())
@@ -55,19 +80,19 @@ namespace armarx::armem
     }
 
 
-    std::string MemoryID::str() const
+    std::string MemoryID::str(bool escapeDelimiters) const
     {
-        return str("/");
+        return str(delimiter, escapeDelimiters);
     }
 
-    std::string MemoryID::str(const std::string& delimeter) const
+    std::string MemoryID::str(const std::string& delimiter, bool escapeDelimiter) const
     {
-        std::vector<std::string> items = getAllItems();
+        std::vector<std::string> items = getAllItems(escapeDelimiter);
         while (items.size() > 0 && items.back().empty())
         {
             items.pop_back();
         }
-        return simox::alg::join(items, delimeter, false, false);
+        return simox::alg::join(items, delimiter, false, false);
     }
 
     std::string MemoryID::getLeafItem() const
@@ -86,52 +111,55 @@ namespace armarx::armem
     bool MemoryID::hasGap() const
     {
         bool emptyFound = false;
-        for (const std::string& name : getAllItems())
+        for (const std::string& item : getAllItems())
         {
-            if (name.empty())
+            if (item.empty())
             {
                 emptyFound = true;
             }
-            else
+            else if (emptyFound)
             {
-                if (emptyFound)
-                {
-                    return true;
-                }
+                // Found a non-empty item after an empty item.
+                return true;
             }
         }
         return false;
     }
 
+    bool MemoryID::isWellDefined() const
+    {
+        return !hasGap();
+    }
+
 
     MemoryID MemoryID::fromString(const std::string& string)
     {
         return MemoryID(string);
     }
 
-    std::vector<std::string> MemoryID::getItems() const
+    std::vector<std::string> MemoryID::getItems(bool escapeDelimiters) const
     {
         std::vector<std::string> items;
 
-        items.push_back(memoryName);
+        items.push_back(escape(memoryName, escapeDelimiters));
 
         if (!hasCoreSegmentName())
         {
             return items;
         }
-        items.push_back(coreSegmentName);
+        items.push_back(escape(coreSegmentName, escapeDelimiters));
 
         if (!hasProviderSegmentName())
         {
             return items;
         }
-        items.push_back(providerSegmentName);
+        items.push_back(escape(providerSegmentName, escapeDelimiters));
 
         if (!hasEntityName())
         {
             return items;
         }
-        items.push_back(entityName);
+        items.push_back(escape(entityName, escapeDelimiters));
 
         if (!hasTimestamp())
         {
@@ -148,11 +176,12 @@ namespace armarx::armem
         return items;
     }
 
-    std::vector<std::string> MemoryID::getAllItems() const
+    std::vector<std::string> MemoryID::getAllItems(bool escapeDelimiters) const
     {
         return
         {
-            memoryName, coreSegmentName, providerSegmentName, entityName,
+            escape(memoryName, escapeDelimiters), escape(coreSegmentName, escapeDelimiters),
+            escape(providerSegmentName, escapeDelimiters), escape(entityName, escapeDelimiters),
             timestampStr(), instanceIndexStr()
         };
     }
@@ -276,6 +305,7 @@ namespace armarx::armem
         return id;
     }
 
+
     std::string MemoryID::timestampStr() const
     {
         return hasTimestamp() ? std::to_string(timestamp.toMicroSeconds()) : "";
@@ -306,8 +336,46 @@ namespace armarx::armem
                and instanceIndex == other.instanceIndex;
     }
 
+    bool MemoryID::operator<(const MemoryID& rhs) const
+    {
+        int c = memoryName.compare(rhs.memoryName);
+        if (c != 0)
+        {
+            return c < 0;
+        }
+        // Equal memory name
+        c = coreSegmentName.compare(rhs.coreSegmentName);
+        if (c != 0)
+        {
+            return c < 0;
+        }
+        // Equal core segment ID
+        c = providerSegmentName.compare(rhs.providerSegmentName);
+        if (c != 0)
+        {
+            return c < 0;
+        }
+        // Equal provider segment ID
+        c = entityName.compare(rhs.entityName);
+        if (c != 0)
+        {
+            return c < 0;
+        }
+        // Equal entity ID
+        if (timestamp != rhs.timestamp)
+        {
+            return timestamp < rhs.timestamp;
+        }
+        // Equal entity snapshot ID
+        return instanceIndex < rhs.instanceIndex;
+    }
+
     long long MemoryID::parseInteger(const std::string& string, const std::string& semanticName)
     {
+        if (string.empty())
+        {
+            return -1;
+        }
         try
         {
             return std::stoll(string);
@@ -322,15 +390,44 @@ namespace armarx::armem
         }
     }
 
+    std::string MemoryID::escapeDelimiter(const std::string& name)
+    {
+        return simox::alg::replace_all(name, delimiter, "\\" + delimiter);
+    }
+
+    std::string MemoryID::escape(const std::string& name, bool escapeDelimiters)
+    {
+        if (escapeDelimiters)
+        {
+            return escapeDelimiter(name);
+        }
+        else
+        {
+            return name;
+        }
+    }
+
     std::ostream& operator<<(std::ostream& os, const MemoryID id)
     {
         return os << "'" << id.str() << "'";
     }
 
 
-    bool
-    contains(const MemoryID& general, const MemoryID& specific)
+    bool contains(const MemoryID& general, const MemoryID& specific)
     {
+        if (!general.isWellDefined())
+        {
+            std::stringstream ss;
+            ss << "ID `general` is not well-defined, which is required for `" << __FUNCTION__ << "()`.";
+            throw error::InvalidMemoryID(general, ss.str());
+        }
+        if (!specific.isWellDefined())
+        {
+            std::stringstream ss;
+            ss << "ID `specific` is not well-defined, which is required for `" << __FUNCTION__ << "()`.";
+            throw error::InvalidMemoryID(specific, ss.str());
+        }
+
         if (general.memoryName.empty())
         {
             return true;
diff --git a/source/RobotAPI/libraries/armem/core/MemoryID.h b/source/RobotAPI/libraries/armem/core/MemoryID.h
index 1fc22f7288b51970f298a9309cbe33b087d48f5f..09415ec4abd73185f77050c89683b326f3c3e9d0 100644
--- a/source/RobotAPI/libraries/armem/core/MemoryID.h
+++ b/source/RobotAPI/libraries/armem/core/MemoryID.h
@@ -12,11 +12,37 @@ namespace armarx::armem
     /**
      * @brief A memory ID.
      *
-     * Structure:
-     * `MemoryName/CoreSegmentName/ProviderSegmentName/EntityName/Timestamp/InstanceIndex`
+     * A memory ID is an index into the hierarchical memory structure.
+     * It specifies the keys for the different levels, starting from the
+     * memory name and ending at the instance index.
      *
-     * Example:
-     * `VisionMemory/RGBImages/PrimesenseRGB/image/1245321323/0`
+     * A memory ID need not be complete, e.g. it may specify only the memory
+     * and core segment names (thus representing a core segment ID).
+     * A memory ID that fully identifies a level starting from the memory is
+     * called well-defined.
+     * @see `isWellDefined()`
+     *
+     * Memory IDs can be encoded in strings using a delimiter:
+     * - Structure: "MemoryName/CoreSegmentName/ProviderSegmentName/EntityName/Timestamp/InstanceIndex"
+     * - Example:   "Vision/RGBImages/Primesense/image/1245321323/0"
+     * @see `str()`
+     *
+     * If an ID does not specify the lower levels, these parts can be omitted.
+     * Thus, an entity ID could look like:
+     * - Structure: "MemoryName/CoreSegmentName/ProviderSegmentName/EntityName"
+     * - Example:   "Vision/RGBImages/Primesense/image"
+     *
+     * If a name contains a "/", it will be escaped:
+     * - Example:   "Vision/RGBImages/Primesense/my\/entity\/with\/slashes"
+     *
+     * Memory IDs may be not well-defined. This can occur e.g. when preparing
+     * an entity instance ID which is still pending the timestamp.
+     * It could look like (note the missing timestamp):
+     * - Example:   "Vision/RGBImages/Primesense/image//0"
+     *
+     * These IDs are still valid and can be handled (encoded as string etc.).
+     * However, some operations may not be well-defined for non-well-defined IDs
+     * (such as `contains()`).
      */
     class MemoryID
     {
@@ -30,26 +56,37 @@ namespace armarx::armem
         int instanceIndex = -1;
 
 
-
     public:
 
+        /// Construct a default (empty) memory ID.
         MemoryID();
+        /// (Re-)Construct a memory ID from a string representation as returned by `str()`.
         explicit MemoryID(const std::string& string);
 
 
-        std::string str() const;
-        std::string str(const std::string& delimeter) const;
-        static MemoryID fromString(const std::string& string);
-
-        std::string timestampStr() const;
-        std::string instanceIndexStr() const;
-
-
-        /// Get all levels as string.
-        std::vector<std::string> getAllItems() const;
-        /// Get the levels from root to first not defined level (excluding).
-        std::vector<std::string> getItems() const;
-
+        /**
+         * @brief Indicate whether this ID is well-defined.
+         *
+         * A well-defined ID has no specified level after a non-specified level (i.e., no gaps).
+         *
+         * Well-defined examples:
+         * - "" (empty, but well-defined)
+         * - "Memory" (a memory ID)
+         * - "Memory/Core" (a core segment ID)
+         * - "Memory/Core/Provider" (a provider segment ID)
+         *
+         * Non-well-defined examples:
+         * - "Memory//Provider" (no core segment name)
+         * - "/Core" (no memory name)
+         * - "Mem/Core/Prov/entity//0" (no timestamp)
+         * - "///entity//0" (no memory, core segment and provider segment names)
+         *
+         * @return True if `*this` is a well-defined memory ID.
+         */
+        bool isWellDefined() const;
+
+
+        // Checks whether a specific level is specified.
 
         bool hasMemoryName() const
         {
@@ -113,15 +150,49 @@ namespace armarx::armem
         MemoryID withInstanceIndex(int index) const;
 
 
+        // String conversion
+
+        /**
+         * @brief Get a string representation of this memory ID.
+         *
+         * Items are separated by a delimiter. If `escapeDelimiter` is true,
+         * delimiters occuring inside names are escaped with backward slashes.
+         * This allows to reconstruct the memory ID from the result of `str()`
+         * in these cases.
+         *
+         * @param escapeDelimiter If true, escape delimiters inside names
+         * @return A string representation of this MemoryID.
+         */
+        std::string str(bool escapeDelimiters = true) const;
+
+        /// Get the timestamp as string.
+        std::string timestampStr() const;
+        /// Get the instance index as string.
+        std::string instanceIndexStr() const;
+
+        /// Alias for constructor from string.
+        static MemoryID fromString(const std::string& string);
+        /// Reconstruct a timestamp from a string as returned by `timestampStr()`.
+        static Time timestampFromStr(const std::string& timestamp);
+        /// Reconstruct an instance index from a string as returned by `instanceIndexStr()`.
+        static int instanceIndexFromStr(const std::string& index);
+
+
+        /// Get all levels as strings.
+        std::vector<std::string> getAllItems(bool escapeDelimiters = false) const;
+        /// Get the levels from root to first not defined level (excluding).
+        std::vector<std::string> getItems(bool escapeDelimiters = false) const;
+
+
+        // Other utility.
+
         /// Indicate whether this ID has a gap such as in 'Memory//MyProvider' (no core segment name).
         bool hasGap() const;
         /// Get the lowest defined level (or empty string if there is none).
         std::string getLeafItem() const;
 
 
-        static Time timestampFromStr(const std::string& timestamp);
-        static int instanceIndexFromStr(const std::string& index);
-
+        // Operators
 
         bool operator ==(const MemoryID& other) const;
         inline bool operator !=(const MemoryID& other) const
@@ -129,18 +200,59 @@ namespace armarx::armem
             return !(*this == other);
         }
 
+        bool operator< (const MemoryID& rhs) const;
+        inline bool operator> (const MemoryID& rhs) const
+        {
+            return rhs < (*this);
+        }
+        inline bool operator<=(const MemoryID& rhs) const
+        {
+            return !operator> (rhs);
+        }
+        inline bool operator>=(const MemoryID& rhs) const
+        {
+            return !operator< (rhs);
+        }
+
         friend std::ostream& operator<<(std::ostream& os, const MemoryID id);
 
 
     private:
 
         static long long parseInteger(const std::string& string, const std::string& semanticName);
+        static std::string escapeDelimiter(const std::string& name);
+        static std::string escape(const std::string& name, bool escapeDelimiters);
+
+        static const std::string delimiter;
+
+
+        // Do not allow specifying the delimiter from outside.
+        std::string str(const std::string& delimiter, bool escapeDelimiter) const;
 
     };
 
 
-    bool
-    contains(const MemoryID& general, const MemoryID& specific);
+    /**
+     * @brief Indicates whether `general` is "less specific" than, or equal to, `specific`,
+     * i.e. `general` "contains" `specific`.
+     *
+     * A memory ID A is said to be less specific than B, if B the same values as A
+     * for all levels specified in A, but potentially also specifies the lower levels.
+     *
+     * Examples:
+     * - "" contains ""
+     * - "m" contains "m" and "m/c", but not "n" and "n/c"
+     * - "m/c" contains "m/c" and "m/c/p", but not "m/d" and "m/c/q"
+     *
+     * If a memory ID has a gap (`@see MemoryID::hasGap()`), such as "m//p",
+     * the levels after the gap are ignored.
+     * - "m//p" contains "m", "m/c" and "m/c/p".
+     *
+     * @param general The less specific memory ID
+     * @param specific The more specific memory ID
+     * @return True if `general` is less specific than `specific`.
+     */
+    bool contains(const MemoryID& general, const MemoryID& specific);
 
 }
 
diff --git a/source/RobotAPI/libraries/armem/test/ArMemMemoryIDTest.cpp b/source/RobotAPI/libraries/armem/test/ArMemMemoryIDTest.cpp
index d811c053ba4c57864030eaa8b0d5a7485a6df58c..1f94d9adb9a2d023a3813c0e09edf8b41367ace1 100644
--- a/source/RobotAPI/libraries/armem/test/ArMemMemoryIDTest.cpp
+++ b/source/RobotAPI/libraries/armem/test/ArMemMemoryIDTest.cpp
@@ -26,6 +26,7 @@
 
 #include <RobotAPI/Test.h>
 #include "../core/MemoryID.h"
+#include "../core/error.h"
 
 #include <iostream>
 
@@ -33,22 +34,106 @@
 namespace armem = armarx::armem;
 
 
-BOOST_AUTO_TEST_CASE(test_memoryid_contains)
+BOOST_AUTO_TEST_CASE(test_MemoryID_contains)
 {
     armem::MemoryID general, specific;
 
-    BOOST_CHECK(armem::contains(general, specific));
+    // Both empty.
+    BOOST_TEST_CONTEXT(VAROUT(general) << " | " << VAROUT(specific))
+    {
+        BOOST_CHECK(armem::contains(general, specific));
+    }
 
+    // Diverging.
     general.memoryName = "a";
     specific.memoryName = "b";
 
-    BOOST_CHECK(not armem::contains(general, specific));
+    BOOST_TEST_CONTEXT(VAROUT(general) << " | " << VAROUT(specific))
+    {
+        BOOST_CHECK(not armem::contains(general, specific));
+        BOOST_CHECK(not armem::contains(specific, general));
+    }
 
+    // Identical.
     specific.memoryName = "a";
 
-    BOOST_CHECK(armem::contains(general, specific));
+    BOOST_TEST_CONTEXT(VAROUT(general) << " | " << VAROUT(specific))
+    {
+        BOOST_CHECK(armem::contains(general, specific));
+        BOOST_CHECK(armem::contains(specific, general));
+    }
 
-    specific.providerSegmentName = "aa";
+    // general contains specific
+    specific.coreSegmentName = "c";
+
+    BOOST_TEST_CONTEXT(VAROUT(general) << " | " << VAROUT(specific))
+    {
+        BOOST_CHECK(armem::contains(general, specific));
+        BOOST_CHECK(not armem::contains(specific, general));
+    }
+
+    // general contains specific
+    specific.providerSegmentName = "d";
+
+    BOOST_TEST_CONTEXT(VAROUT(general) << " | " << VAROUT(specific))
+    {
+        BOOST_CHECK(armem::contains(general, specific));
+        BOOST_CHECK(not armem::contains(specific, general));
+    }
+
+    // Not well-defined ID - throw an exception.
+    specific.coreSegmentName.clear();
+
+    BOOST_TEST_CONTEXT(VAROUT(general) << " | " << VAROUT(specific))
+    {
+        BOOST_CHECK_THROW(armem::contains(general, specific), armem::error::InvalidMemoryID);
+        BOOST_CHECK_THROW(armem::contains(specific, general), armem::error::InvalidMemoryID);
+    }
+}
+
+
+
+BOOST_AUTO_TEST_CASE(test_MemoryID_from_to_string)
+{
+    armem::MemoryID idIn {"Memory/Core/Prov/entity/2810381/2"};
+
+    BOOST_CHECK_EQUAL(idIn.memoryName, "Memory");
+    BOOST_CHECK_EQUAL(idIn.coreSegmentName, "Core");
+    BOOST_CHECK_EQUAL(idIn.providerSegmentName, "Prov");
+    BOOST_CHECK_EQUAL(idIn.entityName, "entity");
+    BOOST_CHECK_EQUAL(idIn.timestamp, IceUtil::Time::microSeconds(2810381));
+    BOOST_CHECK_EQUAL(idIn.instanceIndex, 2);
+
+
+    BOOST_TEST_CONTEXT(VAROUT(idIn.str()))
+    {
+        armem::MemoryID idOut(idIn.str());
+        BOOST_CHECK_EQUAL(idOut, idIn);
+    }
+
+    idIn.entityName = "KIT/Amicelli/0";  // Like an ObjectID
+    BOOST_CHECK_EQUAL(idIn.entityName, "KIT/Amicelli/0");
+
+    BOOST_TEST_CONTEXT(VAROUT(idIn.str()))
+    {
+        armem::MemoryID idOut(idIn.str());
+        BOOST_CHECK_EQUAL(idOut.entityName, "KIT/Amicelli/0");
+        BOOST_CHECK_EQUAL(idOut, idIn);
+    }
+
+    idIn = armem::MemoryID {"InThe\\/Mid/AtTheEnd\\//\\/AtTheStart/YCB\\/sugar\\/-1//2"};
+    BOOST_CHECK_EQUAL(idIn.memoryName, "InThe/Mid");
+    BOOST_CHECK_EQUAL(idIn.coreSegmentName, "AtTheEnd/");
+    BOOST_CHECK_EQUAL(idIn.providerSegmentName, "/AtTheStart");
+    BOOST_CHECK_EQUAL(idIn.entityName, "YCB/sugar/-1");
+    BOOST_CHECK_EQUAL(idIn.timestamp, IceUtil::Time::microSeconds(-1));
+    BOOST_CHECK_EQUAL(idIn.instanceIndex, 2);
+
+    BOOST_TEST_CONTEXT(VAROUT(idIn.str()))
+    {
+        armem::MemoryID idOut(idIn.str());
+        BOOST_CHECK_EQUAL(idOut, idIn);
+    }
 
-    BOOST_CHECK(armem::contains(general, specific));
 }
+