diff --git a/entities.py b/entities.py
index 3f7468af1f56adc97afa61a23720fa86db2702bb_ZW50aXRpZXMucHk=..b46c3f860ccb67718e867908ca273255d4018b3f_ZW50aXRpZXMucHk= 100644
--- a/entities.py
+++ b/entities.py
@@ -173,6 +173,14 @@
             else:
                 clones[child] = copy_entity(child, **kwargs)
 
+    @property
+    def clone(self):
+        """The entity being cloned from this entity (the original) or None."""
+        rset = self.entity.related(self.rtype, role=self.role, limit=1)
+        if rset:
+            return rset.get_entity(0, 0)
+        return None
+
 
 class IContained(EntityAdapter):
     """Abstract adapter for entities which are part of a container.
diff --git a/hooks.py b/hooks.py
new file mode 100644
index 0000000000000000000000000000000000000000..b46c3f860ccb67718e867908ca273255d4018b3f_aG9va3MucHk=
--- /dev/null
+++ b/hooks.py
@@ -0,0 +1,58 @@
+# copyright 2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+"""cubicweb-compound specific hooks and operations"""
+
+
+from cubicweb.predicates import objectify_predicate
+from cubicweb.server import hook
+
+
+@objectify_predicate
+def match_clone_rtype(cls, req, *args, **kwargs):
+    """Return 1 if `rtype` found in context matches with the `rtype` attribute
+    of a registered IClonable adapter bound to any of the entities
+    corresponding to `eidfrom` or `eidto` eids found in context.
+    """
+    rtype = kwargs.get('rtype')
+    if rtype is None:
+        return 0
+    for eidrole in ('eidfrom', 'eidto'):
+        entity = req.entity_from_eid(kwargs[eidrole])
+        adapted = entity.cw_adapt_to('IClonable')
+        if adapted and adapted.rtype == rtype:
+            return 1
+    return 0
+
+
+class CloneEntityHook(hook.Hook):
+    """Trigger cloning of an entity upon addition of a <clone_of> relation.
+    """
+    __regid__ = 'compound.clone_entity_hook'
+    __select__ = hook.Hook.__select__ & match_clone_rtype()
+    events = ('after_add_relation', )
+
+    def __call__(self):
+        for eid in (self.eidfrom, self.eidto):
+            original = self._cw.entity_from_eid(eid)
+            adapted = original.cw_adapt_to('IClonable')
+            if adapted:
+                clone = adapted.clone
+                if clone:
+                    break
+        else:
+            raise AssertionError(
+                '{} unexpectedly selected'.format(self.__regid__))
+        adapted.clone_into(clone)
diff --git a/test/unittest_entities.py b/test/unittest_entities.py
index 3f7468af1f56adc97afa61a23720fa86db2702bb_dGVzdC91bml0dGVzdF9lbnRpdGllcy5weQ==..b46c3f860ccb67718e867908ca273255d4018b3f_dGVzdC91bml0dGVzdF9lbnRpdGllcy5weQ== 100644
--- a/test/unittest_entities.py
+++ b/test/unittest_entities.py
@@ -185,6 +185,17 @@
             self.assertEqual(alice2.name, u'alice')
             self.assertEqual([x.eid for x in alice2.knows], [bob.eid])
 
+    def test_clone_property(self):
+        """Test .clone property of IClonableAdapter."""
+        with self.admin_access.repo_cnx() as cnx:
+            bob = cnx.create_entity('Agent', name=u'bob')
+            cnx.commit()
+            self.assertIsNone(bob.cw_adapt_to('IClonable').clone)
+            bobby = cnx.create_entity('Agent', name=u'bobby', clone_of=bob)
+            cnx.commit()
+            self.assertEqual(bob.cw_adapt_to('IClonable').clone, bobby)
+            self.assertIsNone(bobby.cw_adapt_to('IClonable').clone)
+
     def test_clone_simple(self):
         with self.admin_access.repo_cnx() as cnx:
             bob = cnx.create_entity('Agent', name=u'bob')
@@ -238,6 +249,26 @@
                                                  rset=entity.as_rset())
             self.assertIsInstance(action, CloneAction)
 
+    def test_clone_hook(self):
+        with self.admin_access.repo_cnx() as cnx:
+            agent = cnx.create_entity('Agent', name=u'bob')
+            account = cnx.create_entity('OnlineAccount', reverse_account=agent)
+            bio = cnx.create_entity('Biography', reverse_biography=agent)
+            event = cnx.create_entity('Event', reverse_event=bio)
+            anecdote1 = cnx.create_entity('Anecdote', reverse_event=bio)
+            anecdote2 = cnx.create_entity('Anecdote', reverse_event=bio, narrated_by=agent)
+            cnx.commit()
+        with self.new_access(u'georges').repo_cnx() as cnx:
+            bob = cnx.find('Agent', name=u'bob').one()
+            clone = cnx.create_entity('Agent', name=u'bobby', clone_of=bob)
+            cnx.commit()
+            clone.cw_clear_all_caches()
+            # Ensure all relation (to new entities) are present on the clone.
+            rset = cnx.execute(
+                'Any X WHERE X name "bobby", X account AC, X biography B,'
+                '            B event E, E is Event, B event A, A narrated_by X')
+            self.assertTrue(rset)
+
 
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main