Hooks, Actions, and Reducers

Sym's SDK allows you to inject your own logic at key steps in Flows. There are three mechanisms for customizing the logic of a Flow, known as Handlers. The three types of Handlers are reducers, hooks, actions.


Handlers are defined in the sym.sdk.annotations module.

Handler Types

Reducers take in an Event and return a single value, and are prefixed with get_.

Hooks allow you to alter control flow by overriding default implementations of Template steps, and are prefixed with on_.

Actions allow you to subscribe to Events so as to enact various side-effects, and are prefixed with after_.

To demonstrate how handlers are used, here is some pseudocode showing what the Sym runtime would do upon receiving a new "foo" Event.

def handle_foo_event(evt):
    # Hooks can change control flow
    if event_override := hooks.on_foo(evt):
        return event_override

    # Reducers are helper functions that will return config values
    color = reducers.get_color(evt)
    foo_default_implementation(evt, color=color)

    # Actions give an opportunity for side-effects


A Reducer injects key logic into Flows by taking an Event as input, and returning a single value.

For example, when using the sym:approval Template, you need a way to let Sym know who to route a specific request to. This is exactly what the get_approvers reducer does! To implement it, you write a Python function which takes in an Event (containing the requesting user and requested Target), and return a set of User-like objects (such as email addresses, Slack handles, Slack channels or Okta groups).

from sym.sdk.annotations import reducer
from sym.sdk.integrations import pagerduty, okta, slack

# Reducers can be mandatory, and provide dynamic config values

def get_approvers(evt):
    """Returns a set of approvers, given a user and a target."""
    # The import here uses credentials defined in an Integration in Terraform
    if pagerduty.is_on_call(evt.user, schedule="id_of_eng_on_call"):
        # This is a self-approval in a DM
        return slack.user(evt.user)

    if evt.payload.fields["urgency"] == "High":
        # This is a self-approval in a channel
        return slack.channel("#break-glass", allow_self=True)

    on_call_mgrs = okta.group("OnCallManagers").members()
    # This would create a group DM for on-call managers
    return slack.group(on_call_mgrs)


Hooks are executed before the default implementation of an Event handler in a Template. They offer an opportunity to bypass, short-circuit, or alter control flow, by emitting Events.

For example, when using the sym:approval Template, you may want to auto-approve any requests that come from interns (what could go wrong!).

from sym.sdk.annotations import hook
from sym.sdk.integrations import okta
from sym.sdk.templates.approval import events

# Hooks are optional, and can change control flow by returning Events

def on_request(evt):
    """Executed after a request is made, before the default handler."""
    if "Intern" in okta.user(evt.user).profile["job_title"]:
        return events.approve()


Actions are executed after the default implementation of an Event handler in a Template. They offer an opportunity to execute additional side-effects, such as logging or notifications.

For example, when using the sym:approval Template, you may want to log every approval to a private Slack channel.

from sym.sdk.annotations import action
from sym.sdk.integrations import slack

# Actions are optional, and let you execute side-effects after an Event

def after_approve(evt):
    """Executed after an approved event has been fired."""
    message = f"{evt.user.name} has been approved for {evt.target.name}!"
    slack.send_message("#private-audit-log", message)

Did this page help you?