diff --git a/ajax/dojo/macros.pt b/ajax/dojo/macros.pt
index a1398e3..4ad58c0 100644
--- a/ajax/dojo/macros.pt
+++ b/ajax/dojo/macros.pt
@@ -5,10 +5,7 @@
   
 
   
-
-  
 
 
diff --git a/process/README.txt b/process/README.txt
new file mode 100644
index 0000000..7b13f2b
--- /dev/null
+++ b/process/README.txt
@@ -0,0 +1,54 @@
+Business Process Management
+===========================
+
+  ($Id$)
+
+We start with the definition of a simple process:
+
+  startNode --> n01 --> endNode
+
+  >>> from cybertools.process.definition import ProcessDefinition, Node, Transition
+  >>> process = ProcessDefinition()
+  >>> n01 = Node()
+  >>> process.startNode.addTransition(n01)
+  >>> n01.addTransition(process.endNode)
+
+Now let's execute the process:
+
+  >>> from cybertools.process.execution import ProcessInstance
+  >>> instance = ProcessInstance(process)
+  >>> execution = instance.execute()
+
+As there aren't any interactions with the outside world in our process we
+don't see anything. But we can check if the process instance has reached the
+process' end node:
+
+  >>> execution.currentNode is process.endNode
+  True
+
+So let's now associate an action handler with the process' nodes:
+
+  >>> from zope.component import provideAdapter, adapts
+  >>> from zope.interface import implements
+  >>> from cybertools.process.interfaces import INode, IActionHandler
+
+  >>> class DummyHandler(object):
+  ...     implements(IActionHandler)
+  ...     adapts(INode)
+  ...     def __init__(self, context): pass
+  ...     def handle(self, execution):
+  ...         print 'working.'
+
+  >>> provideAdapter(DummyHandler)
+  >>> execution = instance.execute()
+  working.
+  >>> execution.currentNode is process.startNode
+  True
+  >>> execution.trigger()
+  working.
+  >>> execution.currentNode is n01
+  True
+  >>> execution.trigger()
+  working.
+  >>> execution.currentNode is process.endNode
+  True
diff --git a/process/__init__.py b/process/__init__.py
new file mode 100644
index 0000000..4bc90fb
--- /dev/null
+++ b/process/__init__.py
@@ -0,0 +1,4 @@
+"""
+$Id$
+"""
+
diff --git a/process/definition.py b/process/definition.py
new file mode 100644
index 0000000..a4e5030
--- /dev/null
+++ b/process/definition.py
@@ -0,0 +1,116 @@
+#
+#  Copyright (c) 2006 Helmut Merz helmutm@cy55.de
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 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 General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+
+"""
+Process definitions.
+
+$Id$
+"""
+
+from zope import component
+from zope.interface import implements
+from cybertools.process.interfaces import INode, ITransition, IProcessDefinition
+from cybertools.process.interfaces import IActionHandler
+
+
+class Node(object):
+
+    implements(INode)
+
+    def __init__(self, process=None, name=u'', title=u'', handlerName=''):
+        self._process = process
+        self._incoming = set()
+        self._outgoing = set()
+        self._handlerName = handlerName
+        self.__name__ = name
+        self.title = title
+
+    def getProcess(self): return self._process
+    def setProcess(self, prc): self._process = prc
+    process = property(getProcess, setProcess)
+
+    @property
+    def outgoing(self): return self._outgoing
+
+    @property
+    def incoming(self): return self._incoming
+
+    def getHandlerName(self): return self._handlerName
+    def setHandlerName(self, name): self._handlerName = name
+    handlerName = property(getHandlerName, setHandlerName)
+
+    def addTransition(self, destination):
+        transition = Transition(destination)
+        self.outgoing.add(transition)
+        transition.source = self
+        if transition.destination.process is None:
+            transition.destination.process = self.process
+
+    def execute(self, execution):
+        execution.currentNode = self
+        handler = component.queryAdapter(self, IActionHandler, name=self.handlerName)
+        if handler is not None:
+            handler.handle(execution) # creates work item; work item triggers execution
+        else:
+            execution.trigger()
+
+
+class Transition(object):
+
+    implements(ITransition)
+
+    def __init__(self, destination, qualifier=u''):
+        self._source = None
+        self._destination = destination
+        self._qualifier = qualifier
+
+    def getSource(self): return self._source
+    def setSource(self, node): self._source = node
+    source = property(getSource, setSource)
+
+    def getDestination(self): return self._destination
+    def setDestination(self, node): self._destination = node
+    destination = property(getDestination, setDestination)
+
+    def getQualifier(self): return self._qualifier
+    def setQualifier(self, node): self._qualifier = node
+    qualifier = property(getQualifier, setQualifier)
+
+    def take(self, execution):
+        self.destination.execute(execution)
+
+
+class ProcessDefinition(object):
+
+    implements(IProcessDefinition)
+
+    def __init__(self):
+        self._instances = set()
+        self._startNode = Node(self)
+        self._endNode = Node(self)
+
+    @property
+    def instances(self): return self._instances
+
+    def getStartNode(self): return self._startNode
+    def setStartNode(self, node): self._startNode = node
+    startNode = property(getStartNode, setStartNode)
+
+    def getEndNode(self): return self._endNode
+    def setEndNode(self, node): self._endNode = node
+    endNode = property(getEndNode, setEndNode)
diff --git a/process/execution.py b/process/execution.py
new file mode 100644
index 0000000..ed610b0
--- /dev/null
+++ b/process/execution.py
@@ -0,0 +1,74 @@
+#
+#  Copyright (c) 2006 Helmut Merz helmutm@cy55.de
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 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 General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+
+"""
+Execution of a process.
+
+$Id$
+"""
+
+from zope.interface import implements
+from cybertools.process.interfaces import IExecution, IProcessInstance
+
+
+class Execution(object):
+
+    implements(IExecution)
+
+    def __init__(self, instance):
+        self._instance = instance
+        self._currentNode = None
+        self._workItem = None
+
+    @property
+    def instance(self): return self._instance
+
+    def getCurrentNode(self): return self._currentNode
+    def setCurrentNode(self, node): self._currentNode = node
+    currentNode = property(getCurrentNode, setCurrentNode)
+
+    def getWorkItem(self): return self._workItem
+    def setWorkItem(self, item): self._workItem = item
+    workItem = property(getWorkItem, setWorkItem)
+
+    def trigger(self, transitionPattern=None):
+        outgoing = self.currentNode.outgoing
+        if outgoing:
+            transition = iter(outgoing).next()
+            transition.take(self)
+
+
+class ProcessInstance(object):
+
+    implements(IProcessInstance)
+
+    def __init__(self, process):
+        self._process = process
+        self._executions = set()
+
+    @property
+    def process(self): return self._process
+
+    @property
+    def executions(self): return self._executions
+
+    def execute(self):
+        execution = Execution(self)
+        self.executions.add(execution)
+        self.process.startNode.execute(execution)
+        return execution
diff --git a/process/interfaces.py b/process/interfaces.py
new file mode 100644
index 0000000..5bb5df0
--- /dev/null
+++ b/process/interfaces.py
@@ -0,0 +1,137 @@
+#
+#  Copyright (c) 2006 Helmut Merz helmutm@cy55.de
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 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 General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+
+"""
+Interfaces for process management.
+
+$Id$
+"""
+
+from zope.interface import Interface, Attribute
+from zope import schema
+from zope.i18nmessageid import MessageFactory
+
+_ = MessageFactory('zope')
+
+
+# process/workflow definitions
+
+class INode(Interface):
+    """ A step of a process - typically a state that lets the process wait
+        for a user interaction or an action that is executed automatically.
+    """
+
+    process = Attribute('The process this node belongs to')
+    incoming = Attribute('Transitions that lead to this node')
+    outgoing = Attribute('Transitions that lead to the next nodes')
+    handlerName = Attribute('Name of an adapter that may handle the '
+                            'execution of this node')
+
+    def addTransition(destination):
+        """ Append a transition to the destination node given
+            to the collection of outgoing transitions.
+        """
+
+    def execute(execution):
+        """ Execute a node in an execution context of a process instance;
+            if this node signifies a wait state this will create a work item.
+        """
+
+
+class ITransition(Interface):
+    """ A transition leading from one node (activity, state, action) to
+        the next.
+    """
+    source = Attribute('The node that triggered this transition')
+    destination = Attribute('The destination node of this transition')
+    qualifier = Attribute('A string giving a hint for the meaning of the '
+                          'transition. May be used by an execution context '
+                          'for deciding which transition it will transfer '
+                          'control to')
+
+    def take(execution):
+        """ Pass over the execution context from the source node to the
+            destination node.
+        """
+
+
+class IProcessDefinition(Interface):
+    """ The definition of a process or workflow.
+    """
+
+    instances = Attribute('A collection of process instances created from '
+                          'this process definition')
+    startNode = Attribute('The start node of this process')
+    endNode = Attribute('The end node of this process')
+
+
+class IActionHandler(Interface):
+    """ Will be called for handling process executions. Is typically
+        implemented as an adapter for INode.
+    """
+
+    def handle(execution):
+        """ Handles the execution of a node in the execution context given.
+        """
+
+# process execution
+
+
+class IExecution(Interface):
+    """ An execution context signifying the current state (or one of the
+        current states) of a process instance.
+    """
+
+    instance = Attribute('The process instance this execution context belongs to')
+    currentNode = Attribute('The node the process instance is currently in')
+    workItem = Attribute('The work item the process instance is currently '
+                         'waiting for; None if the current node is not in a '
+                         'waiting state')
+
+    def trigger(transitionQualifiers=None):
+        """ A callback (handler) that will may be called by an action handler.
+            This will typically lead to moving on the execution context
+            to an outgoing transition of the current node. The execution
+            context will use the transitionQualifiers to decide which outgoing
+            transition(s) to transfer control to.
+        """
+
+
+class IProcessInstance(Interface):
+    """ An executing process, i.e. an execution context that keeps track of
+        the currently active node(s) of the process.
+    """
+
+    process = Attribute('The process definition this instance is created from')
+    executions = Attribute('A collection of currently active execution contexts')
+
+    def execute():
+        """ Start the execution of the process with its start node;
+            return the execution context.
+        """
+
+
+class IWorkItem(Interface):
+    """ An instance of an activity from a process definition.
+    """
+
+    node = Attribute('The node this work item has been created from')
+    execution = Attribute('The execution context (and thus the process '
+                          'instance) that has create this work item')
+
+
diff --git a/process/tests.py b/process/tests.py
new file mode 100755
index 0000000..63fd6d9
--- /dev/null
+++ b/process/tests.py
@@ -0,0 +1,28 @@
+#! /usr/bin/python
+
+"""
+Tests for the 'cybertools.process' package.
+
+$Id$
+"""
+
+import unittest, doctest
+from zope.testing.doctestunit import DocFileSuite
+from cybertools.process.definition import ProcessDefinition
+
+class TestProcess(unittest.TestCase):
+    "Basic tests for the process package."
+
+    def testBasicStuff(self):
+        p = ProcessDefinition()
+
+
+def test_suite():
+    flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
+    return unittest.TestSuite((
+        unittest.makeSuite(TestProcess),
+        DocFileSuite('README.txt', optionflags=flags),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
diff --git a/process/work.py b/process/work.py
new file mode 100644
index 0000000..5e380e1
--- /dev/null
+++ b/process/work.py
@@ -0,0 +1,32 @@
+#
+#  Copyright (c) 2006 Helmut Merz helmutm@cy55.de
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 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 General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+
+"""
+Work items (tasks) and similar stuff.
+
+$Id$
+"""
+
+from zope.interface import implements
+from cybertools.process.interfaces import IWorkItem
+
+
+class WorkItem(object):
+
+    implements(IWorkItem)
+