diff --git a/product/CMFActivity/ActivityTool.py b/product/CMFActivity/ActivityTool.py
index 3de22b73c7cfb30d9e1bfcc4fd0e9eb679f276b2..a7db8acba954fa615740217e901c67225154ddb4 100644
--- a/product/CMFActivity/ActivityTool.py
+++ b/product/CMFActivity/ActivityTool.py
@@ -434,6 +434,26 @@ Named Parameters: %r
   def getExecutionState(self):
     return self.is_executed
 
+class GroupedMessage(object):
+  __slots__ = 'object', '_message', 'result', 'exc_info'
+
+  def __init__(self, object, message):
+    self.object = object
+    self._message = message
+
+  args = property(lambda self: self._message.args)
+  kw = property(lambda self: self._message.kw)
+
+  def raised(self, exc_info=None):
+    self.exc_info = exc_info or sys.exc_info()
+    try:
+      del self.result
+    except AttributeError:
+      pass
+
+# XXX: Allowing restricted code to implement a grouping method is questionable
+#      but there already exist some.
+allow_class(GroupedMessage)
 
 # Activity Registration
 def activity_dict():
@@ -1291,21 +1311,21 @@ class ActivityTool (Folder, UniqueObject):
               active_obj = subobj.activate(activity=activity, **activity_kw)
               getattr(active_obj, alternate_method_id)(*m.args, **m.kw)
             else:
-              expanded_object_list.append([subobj, m.args, m.kw])
+              expanded_object_list.append(GroupedMessage(subobj, m))
         except:
           m.setExecutionState(MESSAGE_NOT_EXECUTED, context=self)
 
       expanded_object_list = sum(message_dict.itervalues(), [])
       try:
-        if len(expanded_object_list) > 0:
+        if expanded_object_list:
           traverse = self.getPortalObject().unrestrictedTraverse
           # FIXME: how to apply security here?
           # NOTE: The callee must update each processed item of
-          #       expanded_object_list, by appending:
-          #       - exc_info in case of error (so its length becomes 6)
-          #       - None or the result to post on the active process otherwise
-          #         (length=4)
-          #       Skipped item must not be touched (length=3).
+          #       expanded_object_list, by setting:
+          #       - 'exc_info' in case of error
+          #       - 'result' otherwise, with None or the result to post
+          #          on the active process
+          #       Skipped item must not be touched.
           traverse(method_id)(expanded_object_list)
       except:
         # In this case, the group method completely failed.
@@ -1314,7 +1334,7 @@ class ActivityTool (Folder, UniqueObject):
           m.setExecutionState(MESSAGE_NOT_EXECUTED, exc_info, log=False)
         LOG('WARNING ActivityTool', 0,
             'Could not call method %s on objects %s' %
-            (method_id, [x[0] for x in expanded_object_list]), error=exc_info)
+            (method_id, [x.obj for x in expanded_object_list]), error=exc_info)
         error_log = getattr(self, 'error_log', None)
         if error_log is not None:
           error_log.raising(exc_info)
@@ -1323,25 +1343,24 @@ class ActivityTool (Folder, UniqueObject):
         for m, expanded_object_list in message_dict.iteritems():
           result_list = []
           for result in expanded_object_list:
-            if len(result) != 4:
-              break # message marked as failed by the group_method_id
-            elif result[3] is not None:
-              result_list.append(result)
+            try:
+              if result.result is not None:
+                result_list.append(result)
+            except AttributeError:
+              exc_info = getattr(result, "exc_info", (SkippedMessage,))
+              break # failed or skipped message
           else:
             try:
               if result_list and m.active_process:
                 active_process = traverse(m.active_process)
                 for result in result_list:
-                  m.activateResult(active_process, result[3], result[0])
+                  m.activateResult(active_process, result.result, result.obj)
             except:
-              pass
+              exc_info = None
             else:
               m.setExecutionState(MESSAGE_EXECUTED, context=self)
               continue
-          exc_info = result[3:]
-          m.setExecutionState(MESSAGE_NOT_EXECUTED,
-            tuple(exc_info) if exc_info else (SkippedMessage,),
-            context=self)
+          m.setExecutionState(MESSAGE_NOT_EXECUTED, exc_info, context=self)
       if self.activity_tracking:
         activity_tracking_logger.info('invoked group messages')
 
@@ -1351,9 +1370,9 @@ class ActivityTool (Folder, UniqueObject):
         def group_method(message_list):
           try:
             for m in message_list:
-              m.append(getattr(m[0], method_id)(*m[1], **m[2]))
+              m.result = getattr(m.object, method_id)(*m.args, **m.kw)
           except Exception:
-            m += sys.exc_info()
+            m.raised()
         return group_method
     dummyGroupMethod = dummyGroupMethod()
 
diff --git a/product/CMFActivity/tests/testCMFActivity.py b/product/CMFActivity/tests/testCMFActivity.py
index 9eddf4db404f549bd2e2cb92cd0d0b99588fdcf6..ede407cd21a61f79b59ed943580da9529f298382 100644
--- a/product/CMFActivity/tests/testCMFActivity.py
+++ b/product/CMFActivity/tests/testCMFActivity.py
@@ -43,7 +43,6 @@ from AccessControl.SecurityManagement import newSecurityManager
 from zLOG import LOG
 from ZODB.POSException import ConflictError
 from DateTime import DateTime
-import cPickle as pickle
 from Products.CMFActivity.ActivityTool import Message
 import gc
 import random
@@ -1418,9 +1417,9 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
     def setFoobar(self, object_list):
       foobar_list.append(len(object_list))
       for m in object_list:
-        obj, args, kw = m
-        obj.foobar = getattr(obj.aq_base, 'foobar', 0) + kw.get('number', 1)
-        m.append(None)
+        obj = m.object
+        obj.foobar = getattr(obj.aq_base, 'foobar', 0) + m.kw.get('number', 1)
+        m.result = None
     from Products.ERP5Type.Core.Folder import Folder
     Folder.setFoobar = setFoobar
 
@@ -2723,8 +2722,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
     def doSomething(self, message_list):
       r = []
       for m in message_list:
-        r.append((m[0].getPath(), m[1], m[2]))
-        m.append(None)
+        m.result = r.append((m.object.getPath(), m.args, m.kw))
       r.sort()
       group_method_call_list.append(r)
     activity_tool.__class__.doSomething = doSomething
@@ -2946,8 +2944,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
     def invokeGroup(self, message_list):
       r = []
       for m in message_list:
-        r.append(c.index(m[0]))
-        m.append(None)
+        m.result = r.append(c.index(m.object))
       r.sort()
       invoked.append(r)
     category_tool.__class__.invokeGroup = invokeGroup
diff --git a/product/ERP5/Tool/RuleTool.py b/product/ERP5/Tool/RuleTool.py
index d561afe6cad221e5a395bff1d864d6a2a0bb5138..5c7ccd25d068e71c31ec16da79903ea576923a29 100644
--- a/product/ERP5/Tool/RuleTool.py
+++ b/product/ERP5/Tool/RuleTool.py
@@ -144,19 +144,19 @@ class RuleTool(BaseTool):
   def updateSimulation(self, message_list):
     expandable_dict = defaultdict(list)
     for m in message_list:
-      expandable_dict[m[0]].append(m)
-    for expandable, message_list in expandable_dict.iteritems():
-      try:
+      expandable_dict[m.object].append(m)
+    try:
+      for expandable, message_list in expandable_dict.iteritems():
         kw = {}
         for m in message_list:
-          kw.update(m[2])
-          m.append(None)
+          kw.update(m.kw)
+          m.result = None
         LOG("RuleTool", INFO, "Updating simulation for %s: %r"
                               % (expandable.getPath(), kw))
         expandable._updateSimulation(**kw)
-      except Exception:
-        exc_info = sys.exc_info()
-        for m in message_list:
-          m[3:] = exc_info
+    except Exception:
+      exc_info = sys.exc_info()
+      for m in message_list:
+        m.raised(exc_info)
 
 InitializeClass(RuleTool)
diff --git a/product/ERP5Catalog/CatalogTool.py b/product/ERP5Catalog/CatalogTool.py
index 6310d72e8486e8e18f2ab8e111cb9606db8ac6bd..cd097a888e4b5021e3cd13614a980a56eeb0b77d 100644
--- a/product/ERP5Catalog/CatalogTool.py
+++ b/product/ERP5Catalog/CatalogTool.py
@@ -39,6 +39,7 @@ from Products.CMFCore.utils import UniqueObject, _getAuthenticatedUser, getToolB
 from Products.ERP5Type.Globals import InitializeClass, DTMLFile
 from Acquisition import aq_base, aq_inner, aq_parent, ImplicitAcquisitionWrapper
 from Products.CMFActivity.ActiveObject import ActiveObject
+from Products.CMFActivity.ActivityTool import GroupedMessage
 from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
 
 from AccessControl.PermissionRole import rolesForPermissionOn
@@ -789,29 +790,28 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject):
     def catalogObjectList(self, object_list, *args, **kw):
         """Catalog a list of objects"""
         m = object_list[0]
-        if type(m) is list:
-          tmp_object_list = [x[0] for x in object_list]
-          super(CatalogTool, self).catalogObjectList(tmp_object_list, **m[2])
+        if isinstance(m, GroupedMessage):
+          tmp_object_list = [x.object for x in object_list]
+          super(CatalogTool, self).catalogObjectList(tmp_object_list, **m.kw)
           if tmp_object_list:
             exc_info = sys.exc_info()
           for x in object_list:
-            if x[0] in tmp_object_list:
-              x += exc_info # failed
+            if x.object in tmp_object_list:
+              x.raised(exc_info)
             else:
-              x.append(None) # success, no result
+              x.result = None
         else:
           super(CatalogTool, self).catalogObjectList(object_list, *args, **kw)
 
     security.declarePrivate('uncatalogObjectList')
     def uncatalogObjectList(self, message_list):
       """Uncatalog a list of objects"""
-      # XXX: this is currently only a placeholder for further optimization
-      #      (for the moment, it's not faster than the dummy group method)
+      # TODO: this is currently only a placeholder for further optimization
       try:
         for m in message_list:
-          m.append(self.unindexObject(*m[1], **m[2]))
+          m.result = self.unindexObject(*m.args, **m.kw)
       except Exception:
-        m += sys.exc_info()
+        m.raised()
 
     security.declarePrivate('unindexObject')
     def unindexObject(self, object=None, path=None, uid=None,sql_catalog_id=None):