Index: flexoentity/flexo_entity.py
===================================================================
--- flexoentity/flexo_entity.py	(revision a1c4ba3c9e7340534c8c5c9f5d8ad5613cbbe9c2)
+++ flexoentity/flexo_entity.py	(revision a17e4f68f5171edbf6d5d7644d58b7e36f86d2dd)
@@ -306,4 +306,38 @@
         obj._deserialize_content(data.get("content", {}))
         return obj
+    def fork(
+        self,
+        *,
+        domain_id: str | None = None,
+        reset_state: bool = True,
+        keep_origin: bool = True,
+    ) -> "FlexoEntity":
+        """
+        Create a new independent entity derived from this one.
+        """
+        data = self.to_dict()
+        meta = data["meta"]
+
+        # Provenance
+        if keep_origin:
+            meta["origin"] = str(self.flexo_id)
+
+        # Reset lifecycle
+        if reset_state:
+            meta["version"] = 1
+            meta["state"] = EntityState.DRAFT.value
+
+        # Generate new identity
+        new_flexo_id = FlexOID.safe_generate(
+            domain_id=domain_id or self.domain_id,
+            entity_type=self.entity_type.value,
+            state=meta["state"],
+            text=self.text_seed,
+            version=meta["version"],
+        )
+
+        meta["flexo_id"] = str(new_flexo_id)
+
+        return self.__class__.from_dict(data)
 
     def to_json(self, indent: int = 2) -> str:
@@ -453,6 +487,6 @@
             )
 
-        self.origin = self.flexo_id
-        self.flexo_id = new_fid
+            self.origin = self.flexo_id
+            self.flexo_id = new_fid
 
         return self
Index: tests/test_id_lifecycle.py
===================================================================
--- tests/test_id_lifecycle.py	(revision a1c4ba3c9e7340534c8c5c9f5d8ad5613cbbe9c2)
+++ tests/test_id_lifecycle.py	(revision a17e4f68f5171edbf6d5d7644d58b7e36f86d2dd)
@@ -115,2 +115,47 @@
     with pytest.raises(RuntimeError, match="mark obsolete"):
         q.bump_version()
+
+def test_fork_creates_independent_domain(sample_domain):
+    original = sample_domain
+
+    forked = original.fork(domain_id="DST")
+
+    # ─── identity ───────────────────────────────────────────────────────
+    assert forked is not original
+    assert isinstance(forked.flexo_id, FlexOID)
+    assert forked.flexo_id != original.flexo_id
+
+    # ─── lifecycle reset ────────────────────────────────────────────────
+    assert forked.state == EntityState.DRAFT
+    assert forked.version == 1
+
+    # ─── provenance ─────────────────────────────────────────────────────
+    assert forked.origin == str(original.flexo_id)
+
+    # ─── domain reassigned ──────────────────────────────────────────────
+    assert forked.domain_id == "DST"
+
+def test_fork_does_not_mutate_original(sample_domain):
+    original = sample_domain
+    original_id = original.flexo_id
+    original_fp = original.fingerprint
+
+    _ = original.fork(domain_id="DST")
+
+    assert original.flexo_id == original_id
+    assert original.fingerprint == original_fp
+    assert original.origin is None
+
+def test_fork_does_not_shadow_state(sample_domain):
+    forked = sample_domain.fork(domain_id="DST")
+
+    # No instance attribute allowed
+    assert "state" not in forked.__dict__
+
+    # Property must still work
+    assert forked.state == EntityState.DRAFT
+
+def test_fork_without_origin(sample_domain):
+    forked = sample_domain.fork(domain_id="DST", keep_origin=False)
+
+    assert forked.origin is None
