Index: flexoentity/__init__.py
===================================================================
--- flexoentity/__init__.py	(revision a475496ba2aa7cfa1228508888257b8507a30673)
+++ flexoentity/__init__.py	(revision 376e21b3865f5979339d98773fc106b2673d35a9)
@@ -13,4 +13,5 @@
 from .id_factory import FlexOID, canonical_seed
 from .flexo_entity import FlexoEntity, EntityType, EntityState
+from .flexo_collection import FlexoCollection
 from .domain import Domain
 
@@ -22,4 +23,5 @@
     "Domain",
     "EntityState",
+    "FlexoCollection",
     "logger"
 ]
Index: flexoentity/flexo_collection.py
===================================================================
--- flexoentity/flexo_collection.py	(revision 376e21b3865f5979339d98773fc106b2673d35a9)
+++ flexoentity/flexo_collection.py	(revision 376e21b3865f5979339d98773fc106b2673d35a9)
@@ -0,0 +1,113 @@
+"""
+flexoentity.collection
+----------------------
+
+A minimal collection for FlexOEntities.
+
+- No domain rules
+- No uniqueness validation (last write wins)
+- No state or lifecycle logic
+- No typing constraints
+
+The goal is to provide a Smalltalk-like Collection object with a simple
+protocol for storing, retrieving, iterating, and serializing FlexOEntities.
+"""
+
+
+from .flexo_entity import FlexoEntity
+from .id_factory import FlexOID
+
+
+class FlexoCollection:
+    """
+    A minimal collection of FlexOEntities, keyed by FlexOID.
+
+    Examples:
+        coll = FlexoCollection()
+        coll.add(entity)
+        entity = coll.get(some_id)
+        for e in coll:
+            ...
+    """
+
+    def __init__(self, items=None):
+        self._items = {}
+
+        if items:
+            for it in items:
+                self.add(it)
+
+    def add(self, entity: FlexoEntity):
+        """Add or replace an entity by its FlexOID. Overwrites on duplicate IDs."""
+        self._items[entity.flexo_id] = entity
+
+    def remove(self, oid: FlexOID):
+        """Remove an entity by ID, ignoring if missing."""
+        self._items.pop(oid, None)
+
+    def get(self, oid: FlexOID):
+        """Return the entity or None."""
+        return self._items.get(oid)
+
+    def __contains__(self, oid: FlexOID):
+        """Check if an ID exists."""
+        return oid in self._items
+
+    def __len__(self) -> int:
+        return len(self._items)
+
+    def __iter__(self):
+        return iter(self._items.values())
+
+    def entities(self):
+        """Return all entities."""
+        return list(self._items.values())
+
+    def ids(self):
+        """Return all IDs."""
+        return list(self._items.keys())
+
+    # ------------------------------------------------------------------
+    # Smalltalk-inspired interface
+    # ------------------------------------------------------------------
+
+    def at(self, oid):
+        """Smalltalk-style accessor (raises if missing)."""
+        return self._items[oid]
+
+    def at_put(self, oid, entity):
+        """Smalltalk-style setter."""
+        self._items[oid] = entity
+
+    def includes_id(self, oid: FlexOID):
+        """Smalltalk-style membership test."""
+        return oid in self._items
+
+    def do(self, block):
+        """Smalltalk-style #do: iteration."""
+        for value in self._items.values():
+            block(value)
+
+    # ------------------------------------------------------------------
+    # Serialization helpers
+    # ------------------------------------------------------------------
+
+    def to_dict_list(self):
+        """Serialize entities to a list of dicts."""
+        return [e.to_dict() for e in self._items.values()]
+
+    @classmethod
+    def from_dict_list(cls, data):
+        """
+        Deserialize a list of dicts into a FlexoCollection.
+
+        Uses FlexoEntity.from_dict to dispatch to the correct subclass.
+        """
+        c = cls()
+        for d in data:
+            entity = FlexoEntity.from_dict(d)
+            c.add(entity)
+        return c
+
+    def __repr__(self) -> str:
+        return f"<FlexoCollection size={len(self._items)}>"
Index: flexoentity/flexo_entity.py
===================================================================
--- flexoentity/flexo_entity.py	(revision a475496ba2aa7cfa1228508888257b8507a30673)
+++ flexoentity/flexo_entity.py	(revision 376e21b3865f5979339d98773fc106b2673d35a9)
@@ -260,5 +260,4 @@
         return {
             "flexo_id": str(self.flexo_id),
-            "domain_id": self.flexo_id.domain_id,
             "fingerprint": self.fingerprint,
             "origin": self.origin,
@@ -284,4 +283,5 @@
         obj = cls(
             flexo_id=flexo_id,
+            _in_factory=True
         )
 
Index: tests/test_collection.py
===================================================================
--- tests/test_collection.py	(revision 376e21b3865f5979339d98773fc106b2673d35a9)
+++ tests/test_collection.py	(revision 376e21b3865f5979339d98773fc106b2673d35a9)
@@ -0,0 +1,19 @@
+from flexoentity import FlexoCollection, Domain
+
+
+def test_collection_basic():
+    c = FlexoCollection()
+    assert len(c) == 0
+
+    e1 = Domain.with_domain_id("WIP_TEST")
+    e2 = Domain.with_domain_id("TEST_WIP")
+
+    c.add(e1)
+    c.add(e2)
+
+    assert len(c) == 2
+    assert e1.flexo_id in c
+    assert c.get(e1.flexo_id) is e1
+
+    c.remove(e1.flexo_id)
+    assert len(c) == 1
