Hey @aghiad322,
Thank you for your code. It is still very unclear to me what you are doing on a higher more abstract level, and on a concrete level, where exactly you want to detect something. Find below your commented code and at the end a few shots into the dark from me regarding what you are trying to do.
I also just saw now that you posted in the wrong forum. I moved your topic and marked it as C++ and 2025 since you said you are not using the 2026 SDK.
I hope this help and cheers,
Ferdinand
// I wrote this blind without compiling it. So ,there might be some syntax issues in the code I wrote,
// but it should still illustrate the concepts well enough.
#include "maxon/weakrawptr.h"
using namespace cinema;
using namespace maxon;
struct NexusMember
{
    // While we could consider a pointer somewhat conceptually a weak reference, it is factually 
    // not one, as the pointed object being deleted does not invalidate the pointer. This can lead 
    // to dangling pointers and access violation crashes.
    BaseObject* object = nullptr; // weak ref, don’t own it
    Bool isChild = false;         // "include children" flag
    // This is better.
    WeakRawPtr<BaseTag> _tagWeakPtr;
    // Gets the tag pointed to by this member, or nullptr if it has been deleted.
    BaseTag* GetTag() const
    {
        return _tagWeakPtr.Get();
    }
    // Sets the tag for this member.
    void Set(const BaseTag* tag)
    {
        _tagWeakPtr = _tagWeakPtr.Set(tag);
    }
};
struct NexusGroup
{
    Vector color;
    // No, do not use the std library. Cinema 4D modules are by default compiled without exception 
    // handling for performance reasons, and the standard library uses exceptions for error handling,
    // making its behavior undefined in such builds. std::vector::push_back() can throw 
    // std::bad_alloc, for example.
    std::vector<String> links;
    std::vector<NexusMember> members;
    Int32 dirtyLevel = 0;
    // MUST be:
    BaseArray<String> links;
    BaseArray<NexusMember> members;
};
class NexusRegistry : public SceneHookData
{
public:
    static NexusRegistry* Get(BaseDocument* doc);
    static NodeData* Alloc();
    virtual Bool Message(GeListNode* node, Int32 type, void* data);
    virtual EXECUTIONRESULT Execute(BaseSceneHook* node, BaseDocument* doc,
                                    BaseThread* bt, Int32 priority, EXECUTIONFLAGS flags);
   
    // The same applies here, the Maxon API alternatives to unordered_map are:
    //
    //   * HashSet (just a hash set of fixed type values without keys)
    //   * HashMap(a hash map of fixed key and value type),
    //   * DataDictionary (a hash map of arbitrary key and value types).
    std::unordered_map<Int32, NexusGroup> groups;
    maxon::BaseArray<BaseTag*> pendingRegistration;
    void ProcessPendingTags(BaseDocument* doc);
    void RebuildRegistry(BaseDocument* doc);
    void UpdateGroupFromTag(BaseDocument* doc, BaseTag* tag);
    void RemoveObjectFromGroups(BaseObject* obj);
    void RemoveTagFromGroup(BaseTag* tag);
    const NexusGroup* GetGroup(Int32 hash) const;
};
// -------------------------------------------------------------------------------------------------
void NexusRegistry::UpdateGroupFromTag(BaseDocument* doc, BaseTag* tag)
{
    if (!tag) return;
    BaseObject* obj = tag->GetObject();
    if (!obj) return;
    BaseContainer* tagContainer = tag->GetDataInstance();
    String id = tagContainer->GetString(ID);
    // I do not know of which nature this HashID is, but our API has multiple hash functions. 
    Int32 hashedID = HashID(id);
    
    // There is for once StringTemplate::GetHashCode(). I.e., you could do this:
    const HashInt = id.GetHashCode();
    // But all scene elements already have a marker on them which uniquely identifies them (if that
    // is what you are after).
    // Uniquely identifies over its time of creation and the machine it was created on. I.e., this
    // value is persistent across sessions and unique across machines.
    const GeMarker uuid = tag->GetMarker();
    Bool includeChildren = tagContainer->GetBool(CHILDREN);
    Int32 oldIdHash = tagContainer->GetInt32(NEXUS_TAG_PREV_ID, NOTOK);
    if (oldIdHash != NOTOK && oldIdHash != hashedID)
    {
        auto it = groups.find(oldIdHash);
        if (it != groups.end())
        {
            auto& members = it->second.members;
            // std::vector::erase is not noexcept, this can crash Cinema 4D, unless you compile your
            // module with exception handling enabled (which we do not recommend for performance
            // reasons). I am not going to repeat this comment in similar places below.
            members.erase(std::remove_if(members.begin(), members.end(),
                [obj](const NexusMember& m) { return m.object == obj; }),
                members.end());
            it->second.dirtyLevel++;
            if ((Int32)members.size() == 0)
                groups.erase(it);
        }
    }
    // Update new group
    NexusGroup& group = groups[hashedID];
    // Remove duplicates of this object first
    group.members.erase(std::remove_if(group.members.begin(), group.members.end(),
        [obj](const NexusMember& m) { return m.object == obj; }),
        group.members.end());
    group.members.push_back({ obj, includeChildren });
    ((Nexus*)tag)->UpdateInfo(doc, tag);
    // Store current ID for next update
    tagContainer->SetInt32(NEXUS_TAG_PREV_ID, hashedID);
}
// -------------------------------------------------------------------------------------------------
void NexusRegistry::ProcessPendingTags(BaseDocument* doc)
{
    if (pendingRegistration.IsEmpty())
        return;
    Int32 i = 0;
    while (i < pendingRegistration.GetCount())
    {
        BaseTag* tag = pendingRegistration[i];
        BaseObject* op = tag->GetObject();
        if (op)
        {
            UpdateGroupFromTag(doc, tag);
            // This is not how our error system works. Functions of type Result<T> are our exception
            // handling equivalent. You need iferr statements to catch and/or propagate errors. See
            // code below.
            maxon::ResultRef eraseresult = pendingRegistration.Erase(i, 1);
        }
        else
        {
            i++;
        }
    }
}
// -------------------------------------------------------------------------------------------------
// See https://developers.maxon.net/docs/cpp/2026_0_0/page_maxonapi_error_overview.html for a more in detail
// explanation of our error handling system.
// So we have some class with a field whose type has a function of return type Result<T>, e.g., your
// NexusRegistry. We have now three ways to handle errors when calling such functions:
// Ignoring errors (not recommended):
void NexusRegistry::AddItem(const String name)
{
    links.Append(name) iferr_ignore("I do not care about errors here."); // Append is of type Result<void>
}
// Handling errors locally, i.e., within a function that itself is not of type Result<T>.
Bool NexusRegistry::RemoveItem(const String name)
{
    // The scope handler for this function so that we can terminate errors when the are thrown.
    iferr_scope_handler
    {
        // Optional, print some debug output to the console, #err is the error object.
        DiagnosticOutput("Error in @: @", MAXON_FUNCTIONNAME, err);
        // We just return false to indicate failure. If we would have to do cleanup/unwinding, we
        // would do it here.
        return false;
    };
    const Int32 i = links.FindIndex(name);
    // We call our Result<T> function and propagate any error to the scope handler if an error 
    // occurs. The iferr_return keyword basically unpacks a Result<T> into its T value, or jumps 
    // to the error handler in the current or higher scope and propagates the error.
    const String item = links.Erase(i) iferr_return;
    
    return true;
}
// And the same thing in green but we propagate errors further up the call chain, i.e., our function
// is itself of type Result<T>. It now also does not make too much sense to return a Bool, so our 
// return type is now Result<void>.
Result<void> NexusRegistry::RemoveItem(const String name)
{
    // Here we just use the default handler which will just return the #err object to the caller.
    iferr_scope;
    const Int32 i = links.FindIndex(name);
    const String item = links.Erase(i) iferr_return;
    return OK; // Result<void> functions return OK on success.
}
// -------------------------------------------------------------------------------------------------
EXECUTIONRESULT NexusRegistry::Execute(BaseSceneHook* node, BaseDocument* doc,
                                       BaseThread* bt, Int32 priority, EXECUTIONFLAGS flags)
{
    ProcessPendingTags(doc);
    return EXECUTIONRESULT::OK;
}
// -------------------------------------------------------------------------------------------------
// So, all in all, this does not shed too much light on what you are doing for me :) The main questions 
// is if you implement your tags yourself, i.e., the items stored in your NexusGroup::members.
// When you implement a node yourself, you can override its ::Read, ::Write, and ::CopyTo functions
// to handle the node serialization and copying yourself. See https://tinyurl.com/2v4ajn58 for a
// modern example for that. So for your, let's call it NexusTag, you would do something like this:
class NexusTag : public TagData
{
    Bool CopyTo(NodeData* dest, const GeListNode* snode, GeListNode* dnode, COPYFLAGS flags, 
                AliasTrans* trn) const
    {
        // This is a copy and paste event. There is no dedicated event for CTRL + DRAG you seem
        // to after.
        if (flags & PRIVATE_CLIPBOARD_COPY)
        {
            // Do something special with the destination node #dnode or call home to you registry.
        }
        else
        {
            // Do something different.
        }
        // We should always call the base class implementation, unless we want interrupt the copy.
        return SUPER::CopyTo(dest, snode, dnode, flags, trn);
    }
};
// ---
// Another way could using a MessageData hook and monitoring the EVMSG_CHANGE events, i.e., when
// something in a scene changed. This is usually how render engines synchronize scene graphs. I am
// not going to exemplify this here, as this is a lot of work.But you can have a look at this thread
// which is the most simple example we have for this (in Python, but is more or less the same in C++):
// https://developers.maxon.net/forum/topic/14124/how-to-detect-a-new-light-and-pram-change
// Here you do not have to own the tag implementation. But you could not detect how something has
// been inserted, only that it has been inserted.
// ---
// Yet another thing which could help are event notifications. I.e., you hook yourself into the copy
// event of any node (you do not have to own the node implementation for this) and get notified when
// a copy occurs. But event notifications are private for a reason, as you can easily crash Cinema
// with them. You will find some material on the forum, but they are intentionally not documented.
// https://tinyurl.com/2jj8xa6s
// ---
// Finally, with NodeData::Init you can also react in your node to it being cloned.
Bool NexusTag::Init(GeListNode* node, Bool isCloneInit)
{
    if (isCloneInit)
    {
        // Do something special when this is a clone operation.
    }
    return true;
}