def trigger(on, workitem)

      tree = tree()
      err = h.applied_workitem['fields']['__error__']

      handler = on == 'on_error' ? local_on_error(err) : h[on]

      if on == 'on_error' && handler.to_s.match(/^!(.+)$/)
        handler = $1
      end

      if handler.is_a?(Hash) # on_re_apply

        wi = handler['workitem']
        fi = handler['fields']
        me = handler['merge_in_fields']

        workitem =
          if wi == 'applied' || me
            h.applied_workitem
          elsif wi
            wi
          else
            workitem
          end

        workitem['fields'] = fi if fi
        workitem['fields'].merge!(me) if me
      end

      if h.trigger && t = workitem['fields']["__#{h.trigger}__"]
        #
        # the "second take"...

        handler = t
        tree = h.supplanted['original_tree']
        workitem = h.supplanted['applied_workitem']
      end

      if
        on == 'on_error' &&
        handler.respond_to?(:match) &&
        handler.match(/([,\*]|\d+[wdhmsMy]? *:)/)
      then
        return schedule_retries(handler, err)
      end

      new_tree =
        case handler
          when Hash then handler['tree']
          when Array then handler
          when HandlerEntry then [ handler.action, {}, [] ]
          else [ handler.to_s, {}, [] ]
        end

      handler = handler.action if handler.is_a?(HandlerEntry)
      handler = handler.strip if handler.respond_to?(:strip)

      if handler =~ /^can(cel|do)$/ && (on == 'on_cancel' || h.on_cancel == nil)
        handler = handler == 'cancel' ? 'undo' : 'redo'
      end

      h.on_reply = nil if on == 'on_reply'
        # preventing cascades

      case handler

        when 'redo', 'retry'
          #
          # retry with the same tree as before

          new_tree = tree

        when 'undo', 'pass', ''
          #
          # let's forget it

          h.state = on == 'on_cancel' ? 'cancelled' : 'failed'

          reply_to_parent(workitem); return

        when 'cancel'
          #
          # let's trigger on the on_cancel

          trigger('on_cancel', workitem); return

        when 'cando'
          #
          # trigger on_cancel, then redo

          h.on_reply = tree

          trigger('on_cancel', workitem); return

        when 'raise'
          #
          # re-raise

          raise Ruote.constantize(err['class']), err['message'], err['trace']

        when /^(?:store|bury) *: *(.+)/

          h.state = 'failed'
          set_vf($1, err, :workitem => workitem)

          reply_to_parent(workitem); return

        when CommandExpression::REGEXP
          #
          # a command like 'jump to shark'...

          hh = handler.split(' ')
          command = hh.first
          step = hh.last

          h.state = nil
          workitem['fields'][CommandMixin::F_COMMAND] = [ command, step ]

          reply(workitem); return

        #else
        #
        #  if h.trigger
        #    #
        #    # do not accept participant or subprocess names
        #    # for "second trigger"
        #
        #    h.state = 'failed'
        #    reply_to_parent(workitem)
        #    return
        #  end
          #
          # actually, let's not care about that, let's trust people.
      end

      workitem = h.applied_workitem if on == 'on_error'

      #
      # supplant this expression with new tree

      r = try_unpersist

      raise(
        "failed to remove exp to supplant " +
        "#{Ruote.to_storage_id(h.fei)} #{tree.first}"
      ) if r.respond_to?(:keys)

      if new_tree[0].match(/^!(.+)$/); new_tree[0] = $1; end
      new_tree[1]['_triggered'] = on

      attributes.each { |k, v|
        new_tree[1][k] = v if (k.match(/^on_/) && k != on) || k == 'tag'
      }
        #
        # let the triggered tree have the same on_ attributes as the original
        # expression, so that on_cancel/on_error/on_x effects still apply
        #
        # 'tag' is copied as well. (so that 'left_tag' is emitted too)
        #
        # Should 'timeout', should other common attributes be copied as well?

      @context.storage.put_msg(
        'apply',
        { 'fei' => h.fei,
          'parent_id' => h.parent_id,
          'tree' => new_tree,
          'workitem' => workitem,
          'variables' => h.variables,
          'trigger' => on,
          'on_reply' => h.on_reply,
          'supplanted' => {
            'tree' => tree,
            'original_tree' => original_tree,
            'applied_workitem' => h.applied_workitem,
            'variables' => h.variables
          }})
    end
def trigger(on, workitem)

      tree = tree()
      err = h.applied_workitem['fields']['__error__']

      handler = on == 'on_error' ? local_on_error(err) : h[on]

      if on == 'on_error' && handler.to_s.match(/^!(.+)$/)
        handler = $1
      end

      if handler.is_a?(Hash) # on_re_apply

        wi = handler['workitem']
        fi = handler['fields']
        me = handler['merge_in_fields']

        workitem =
          if wi == 'applied' || me
            h.applied_workitem
          elsif wi
            wi
          else
            workitem
          end

        workitem['fields'] = fi if fi
        workitem['fields'].merge!(me) if me
      end

      if h.trigger && t = workitem['fields']["__#{h.trigger}__"]
        #
        # the "second take"...

        handler = t
        tree = h.supplanted['original_tree']
        workitem = h.supplanted['applied_workitem']
      end

      if
        on == 'on_error' &&
        handler.respond_to?(:match) &&
        handler.match(/([,\*]|\d+[wdhmsMy]? *:)/)
      then
        return schedule_retries(handler, err)
      end

      new_tree =
        case handler
          when Hash then handler['tree']
          when Array then handler
          when HandlerEntry then [ handler.action, {}, [] ]
          else [ handler.to_s, {}, [] ]
        end

      handler = handler.action if handler.is_a?(HandlerEntry)
      handler = handler.strip if handler.respond_to?(:strip)

      if handler =~ /^can(cel|do)$/ && (on == 'on_cancel' || h.on_cancel == nil)
        handler = handler == 'cancel' ? 'undo' : 'redo'
      end

      h.on_reply = nil if on == 'on_reply'
        # preventing cascades

      case handler

        when 'redo', 'retry'
          #
          # retry with the same tree as before

          new_tree = tree

        when 'undo', 'pass', ''
          #
          # let's forget it

          h.state = on == 'on_cancel' ? 'cancelled' : 'failed'

          reply_to_parent(workitem); return

        when 'cancel'
          #
          # let's trigger on the on_cancel

          trigger('on_cancel', workitem); return

        when 'cando'
          #
          # trigger on_cancel, then redo

          h.on_reply = tree

          trigger('on_cancel', workitem); return

        when 'raise'
          #
          # re-raise

          raise Ruote.constantize(err['class']), err['message'], err['trace']

        when /^(?:store|bury) *: *(.+)/

          h.state = 'failed'
          set_vf($1, err, :workitem => workitem)

          reply_to_parent(workitem); return

        when CommandExpression::REGEXP
          #
          # a command like 'jump to shark'...

          hh = handler.split(' ')
          command = hh.first
          step = hh.last

          h.state = nil
          workitem['fields'][CommandMixin::F_COMMAND] = [ command, step ]

          reply(workitem); return

        #else
        #
        #  if h.trigger
        #    #
        #    # do not accept participant or subprocess names
        #    # for "second trigger"
        #
        #    h.state = 'failed'
        #    reply_to_parent(workitem)
        #    return
        #  end
          #
          # actually, let's not care about that, let's trust people.
      end

      workitem = h.applied_workitem if on == 'on_error'

      #
      # supplant this expression with new tree

      r = try_unpersist

      raise(
        "failed to remove exp to supplant " +
        "#{Ruote.to_storage_id(h.fei)} #{tree.first}"
      ) if r.respond_to?(:keys)

      if new_tree[0].match(/^!(.+)$/); new_tree[0] = $1; end
      new_tree[1]['_triggered'] = on

      attributes.each { |k, v|
        new_tree[1][k] = v if (k.match(/^on_/) && k != on) || k == 'tag'
      }
        #
        # let the triggered tree have the same on_ attributes as the original
        # expression, so that on_cancel/on_error/on_x effects still apply
        #
        # 'tag' is copied as well. (so that 'left_tag' is emitted too)
        #
        # Should 'timeout', should other common attributes be copied as well?

      @context.storage.put_msg(
        'apply',
        { 'fei' => h.fei,
          'parent_id' => h.parent_id,
          'tree' => new_tree,
          'workitem' => workitem,
          'variables' => h.variables,
          'trigger' => on,
          'on_reply' => h.on_reply,
          'supplanted' => {
            'tree' => tree,
            'original_tree' => original_tree,
            'applied_workitem' => h.applied_workitem,
            'variables' => h.variables
          }})
    end
'on_{error|timeout|cancel|re_apply}' triggering