Bug 1389248 - Move actions spec to taskcluster docs draft
authorBrian Stack <bstack@mozilla.com>
Thu, 10 Aug 2017 13:59:05 -0700
changeset 644335 54fc8e5c64ba10b75751be23d5a1d1902fce60fa
parent 644169 5322c03f4c8587fe526172d3f87160031faa6d75
child 725580 752e0e72dd0c6fabeea42adf342a8cfb9ada1597
push id73405
push userbstack@mozilla.com
push dateThu, 10 Aug 2017 20:59:44 +0000
bugs1389248
milestone57.0a1
Bug 1389248 - Move actions spec to taskcluster docs MozReview-Commit-ID: BsYRmivUZZ7
taskcluster/docs/action-implementation.rst
taskcluster/docs/action-spec.rst
taskcluster/docs/action-uis.rst
taskcluster/docs/actions-schema.yml
taskcluster/docs/actions.rst
deleted file mode 100644
--- a/taskcluster/docs/action-implementation.rst
+++ /dev/null
@@ -1,244 +0,0 @@
-Action Task Implementation
-==========================
-
-This document shows how to define an action in-tree such that it shows up in
-supported user interfaces like Treeherder. For details on interface between
-in-tree logic and external user interfaces, see
-:doc:`the specification for actions.json <action-spec>`.
-
-There are two options for defining actions: creating a callback action, or
-creating a custom action task.  A callback action automatically defines an
-action task that will invoke a Python function of your devising.
-
-A custom action task is an arbitrary task definition that will be created
-directly.  In cases where the callback would simply call ``queue.createTask``,
-a custom action task can be more efficient.
-
-Creating a Callback Action
---------------------------
-A *callback action* is an action that calls back into in-tree logic. That is,
-you register the action with name, title, description, context, input schema and a
-python callback. When the action is triggered in a user interface,
-input matching the schema is collected, passed to a new task which then calls
-your python callback, enabling it to do pretty much anything it wants to.
-
-To create a new action you must create a file
-``taskcluster/taskgraph/actions/my-action.py``, that at minimum contains::
-
-  from registry import register_callback_action
-
-  @register_callback_action(
-      name='hello',
-      title='Say Hello',
-      symbol='hw',  # Show the callback task in treeherder as 'hw'
-      description="Simple **proof-of-concept** callback action",
-      order=10000,  # Order in which it should appear relative to other actions
-  )
-  def hello_world_action(parameters, input, task_group_id, task_id, task):
-      # parameters is an instance of taskgraph.parameters.Parameters
-      # it carries decision task parameters from the original decision task.
-      # input, task_id, and task should all be None
-      print "Hello was triggered from taskGroupId: " + taskGroupId
-
-The example above defines an action that is available in the context-menu for
-the entire task-group (result-set or push in Treeherder terminology). To create
-an action that shows up in the context menu for a task we would specify the
-``context`` parameter.
-
-
-Setting the Action Context
-..........................
-The context parameter should be a list of tag-sets, such as
-``context=[{"platform": "linux"}]``, which will make the task show up in the
-context-menu for any task with ``task.tags.platform = 'linux'``. Below is
-some examples of context parameters and the resulting conditions on
-``task.tags`` (tags used below are just illustrative).
-
-``context=[{"platform": "linux"}]``:
-  Requires ``task.tags.platform = 'linux'``.
-``context=[{"kind": "test", "platform": "linux"}]``:
-  Requires ``task.tags.platform = 'linux'`` **and** ``task.tags.kind = 'test'``.
-``context=[{"kind": "test"}, {"platform": "linux"}]``:
-  Requires ``task.tags.platform = 'linux'`` **or** ``task.tags.kind = 'test'``.
-``context=[{}]``:
-  Requires nothing and the action will show up in the context menu for all tasks.
-``context=[]``:
-  Is the same as not setting the context parameter, which will make the action
-  show up in the context menu for the task-group.
-  (i.e., the action is not specific to some task)
-
-The example action below will be shown in the context-menu for tasks with
-``task.tags.platform = 'linux'``::
-
-  from registry import register_callback_action
-
-  @register_callback_action(
-      name='retrigger',
-      title='Retrigger',
-      symbol='re-c',  # Show the callback task in treeherder as 're-c'
-      description="Create a clone of the task",
-      order=1,
-      context=[{'platform': 'linux'}]
-  )
-  def retrigger_action(parameters, input, task_group_id, task_id, task):
-      # input will be None
-      print "Retriggering: {}".format(task_id)
-      print "task definition: {}".format(task)
-
-When the ``context`` parameter is set, the ``task_id`` and ``task`` parameters
-will provided to the callback. In this case the ``task_id`` and ``task``
-parameters will be the ``taskId`` and *task definition* of the task from whose
-context-menu the action was triggered.
-
-Typically, the ``context`` parameter is used for actions that operate on
-tasks, such as retriggering, running a specific test case, creating a loaner,
-bisection, etc. You can think of the context as a place the action should
-appear, but it's also very much a form of input the action can use.
-
-
-Specifying an Input Schema
-..........................
-In call examples so far the ``input`` parameter for the callbacks has been
-``None``. To make an action that takes input you must specify an input schema.
-This is done by passing a JSON schema as the ``schema`` parameter.
-
-When designing a schema for the input it is important to exploit as many of the
-JSON schema validation features as reasonably possible. Furthermore, it is
-*strongly* encouraged that the ``title`` and ``description`` properties in
-JSON schemas is used to provide a detailed explanation of what the input
-value will do. Authors can reasonably expect JSON schema ``description``
-properties to be rendered as markdown before being presented.
-
-The example below illustrates how to specify an input schema. Notice that while
-this example doesn't specify a ``context`` it is perfectly legal to specify
-both ``input`` and ``context``::
-
-  from registry import register_callback_action
-
-  @register_callback_action(
-      name='run-all',
-      title='Run All Tasks',
-      symbol='ra-c',  # Show the callback task in treeherder as 'ra-c'
-      description="**Run all tasks** that have been _optimized_ away.",
-      order=1,
-      input={
-          'title': 'Action Options',
-          'description': 'Options for how you wish to run all tasks',
-          'properties': {
-              'priority': {
-                  'title': 'priority'
-                  'description': 'Priority that should be given to the tasks',
-                  'type': 'string',
-                  'enum': ['low', 'normal', 'high'],
-                  'default': 'low',
-              },
-              'runTalos': {
-                  'title': 'Run Talos'
-                  'description': 'Do you wish to also include talos tasks?',
-                  'type': 'boolean',
-                  'default': 'false',
-              }
-          },
-          'required': ['priority', 'runTalos'],
-          'additionalProperties': False,
-      },
-  )
-  def retrigger_action(parameters, input, task_group_id, task_id, task):
-      print "Create all pruned tasks with priority: {}".format(input['priority'])
-      if input['runTalos']:
-          print "Also running talos jobs..."
-
-When the ``schema`` parameter is given the callback will always be called with
-an ``input`` parameter that satisfies the previously given JSON schema.
-It is encouraged to set ``additionalProperties: false``, as well as specifying
-all properties as ``required`` in the JSON schema. Furthermore, it's good
-practice to provide ``default`` values for properties, as user interface generators
-will often take advantage of such properties.
-
-Once you have specified input and context as applicable for your action you can
-do pretty much anything you want from within your callback. Whether you want
-to create one or more tasks or run a specific piece of code like a test.
-
-Conditional Availability
-........................
-The decision parameters ``taskgraph.parameters.Parameters`` passed to
-the callback are also available when the decision task generates the list of
-actions to be displayed in the user interface. When registering an action
-callback the ``availability`` option can be used to specify a callable
-which, given the decision parameters, determines if the action should be available.
-The feature is illustrated below::
-
-  from registry import register_callback_action
-
-  @register_callback_action(
-      name='hello',
-      title='Say Hello',
-      symbol='hw',  # Show the callback task in treeherder as 'hw'
-      description="Simple **proof-of-concept** callback action",
-      order=2,
-      # Define an action that is only included if this is a push to try
-      available=lambda parameters: parameters.get('project', None) == 'try',
-  )
-  def try_only_action(parameters, input, task_group_id, task_id, task):
-      print "My try-only action"
-
-Properties of ``parameters``  are documented in the
-:doc:`parameters section <parameters>`. You can also examine the
-``parameters.yml`` artifact created by decisions tasks.
-
-
-Creating a Custom Action Task
-------------------------------
-
-It is possible to define an action that doesn't take a callback. Instead, you'll
-then have to provide a task template. For details on how the task template
-language works refer to :doc:`the specification for actions.json <action-spec>`,
-the example below illustrates how to create such an action::
-
-  from registry import register_task_action
-
-  @register_task_action(
-      name='retrigger',
-      title='Retrigger',
-      description="Create a clone of the task",
-      order=1,
-      context=[{'platform': 'linux'}],
-      input={
-          'title': 'priority'
-          'description': 'Priority that should be given to the tasks',
-          'type': 'string',
-          'enum': ['low', 'normal', 'high'],
-          'default': 'low',
-      },
-  )
-  def task_template_builder(parameters):
-      # The task template builder may return None to signal that the action
-      # isn't available.
-      if parameters.get('project', None) != 'try':
-        return None
-      return {
-          'created': {'$fromNow': ''},
-          'deadline': {'$fromNow': '1 hour'},
-          'expires': {'$fromNow': '14 days'},
-          'provisionerId': '...',
-          'workerType': '...',
-          'priority': '${input}',
-          'payload': {
-              'command': '...',
-              'env': {
-                  'TASK_DEFINITION': {'$json': {'eval': 'task'}}
-              },
-              ...
-          },
-          # It's now your responsibility to include treeherder routes, as well
-          # additional metadata for treeherder in task.extra.treeherder.
-          ...
-      },
-
-This kind of action is useful for creating simple derivative tasks, but is
-limited by the expressiveness of the template language. On the other hand, it
-is more efficient than an action callback as it does not involve an
-intermediate action task before creating the task the user requested.
-
-For further details on the template language, see :doc:`the specification for
-actions.json <action-spec>`.
deleted file mode 100644
--- a/taskcluster/docs/action-spec.rst
+++ /dev/null
@@ -1,242 +0,0 @@
-Action Specification
-====================
-This document specifies how actions exposed by the *decision task* are to be
-presented and triggered from Treeherder, or similar user interfaces.
-
-The *decision task* creates an artifact ``public/actions.json`` to be consumed
-by a user interface such as Treeherder. The ``public/actions.json`` file
-specifies actions that can be triggered such as:
-
- * Retrigger a task,
- * Retry specific test cases many times,
- * Obtain a loaner machine,
- * Back-fill missing tasks,
- * ...
-
-Through the ``public/actions.json`` file it is possible expose actions defined
-in-tree such that the actions can be conveniently triggered in Treeherder.
-This has two purposes:
-
- 1. Facilitate development of utility actions/tools in-tree, and,
- 2. Strongly decouple build/test configuration from Treeherder.
-
-For details on how define custom actions in-tree, refer to
-:doc:`the in-tree actions section <action-details>`. This document merely
-specifies how ``actions.json`` shall be interpreted.
-
-Actions
--------
-
-The content of ``actions.json`` is a list of actions (and variables, to be
-described later).  Each action has a ``kind`` describing how a user interface
-should trigger it.  There is currently only one kind defined: ``task``.
-
-An action with ``kind: 'task'`` specifies a task that the user interface should
-create. That is, when the action is triggered, the user interface calls the
-Taskcluster API to create a new task, with the content of that task determined
-from ``actions.json``.
-
-The task created by the action may be useful in its own right (for example,
-running a test with additional debugging), or it may simplify trigger in-tree
-scripts that create new tasks.  The latter form is called an *action task*, and
-is similar to a decision task. This allows in-tree scripts to execute
-complicated actions such as backfilling.
-
-Actions of the ``'task'`` *kind* **must** have a ``task`` property. This
-property specifies the task template to be parameterized and created in order
-to trigger the action.
-
-The template is parameterized using `JSON-e
-<https://github.com/taskcluster/json-e>`_, with the following context entries
-available:
-
-``taskGroupId``
-  the ``taskGroupId`` of task-group this is triggerd from,
-``taskId``
-  the ``taskId`` of the selected task, ``null`` if no task is
-  selected (this is the case if the action has ``context: []``),
-``task``
-  the task definition of the selected task, ``null`` if no task is
-  selected (this is the case if the action has ``context: []``), and,
-``input``
-  the input matching the ``schema`` property, ``null`` if the action
-  doesn't have a ``schema`` property.  See "Action Input" below.
-``<key>``
-  Any ``<key>`` defined in the ``variables`` property may also be referenced.
-  See "Variables" below.
-
-The following **example** demonstrates how a task template can specify
-timestamps and dump input JSON into environment variables::
-
-  {
-    "version": 1,
-    "actions": [
-      {
-        "kind": "task",
-        "name": "thing",
-        "title: "Do A Thing",
-        "description": "Do something",
-        "task": {
-          "workerType": "my-worker",
-          "payload": {
-            "created": {"$fromNow": ""},
-            "deadline": {"$fromNow": "1 hour 15 minutes"},
-            "expiration": {"$fromNow": "14 days"},
-            "image": "my-docker-image",
-            "env": {
-              "TASKID_TRIGGERED_FOR": "${taskId}",
-              "INPUT_JSON": {"$json": {"$eval": "input"}}
-            },
-            ...
-          },
-          ...
-        }
-      }
-    ],
-    "variables: {},
-  }
-
-
-MetaData
-........
-
-Each action entry must define a ``name``, ``title`` and ``description``.
-furthermore, the list of actions should be sorted by the order in which actions
-should appear in a menu.
-
-The ``name`` is used by user interfaces to identify the action. For example, a
-retrigger button might look for an action with `name = "retrigger"`.
-
-Action names must be unique for a given task, or for a taskgroup, but the same
-name may be used for actions applying to disjoint sets of tasks. For example,
-it may be helpful to define different "retrigger" actions for build tasks
-`[{jobKind: 'build'}]` and test tasks `[{jobKind: 'test'}]`, and in this case
-only one such action would apply to any given task.
-
-The ``title`` is a human readable string intended to be used as label on the
-button, link or menu entry that triggers the action. This should be short and
-concise.  Ideally, you'll want to avoid duplicates.
-
-The ``description`` property contains a human readable string describing the
-action, such as what it does, how it does it, what it is useful for. This string
-is to be render as **markdown**, allowing for bullet points, links and other
-simple formatting to explain what the action does.
-
-
-Action Context
-..............
-
-Few actions are relevant in all contexts. For this reason each action specifies
-a ``context`` property. This property specifies when an action is relevant.
-Actions *relevant* for a task should be displayed in a context menu for the
-given task. Similarly actions *not relevant* for a given task should not be
-displayed in the context menu for the given task.
-
-As a special case we say that actions for which *no relevant* contexts can
-exist, are *relevant* for the task-group. This could for example be an action
-to create tasks that was optimized away.
-
-The ``context`` property is specified as a list of *tag-sets*. A *tag-set* is a
-set of key-value pairs. A task is said to *match* a *tag-set* if ``task.tags``
-is a super-set of the *tag-set*. An action is said to be *relevant* for a given
-task, if ``task.tags`` *match* one of the *tag-sets* given in the ``context``
-property for the action.
-
-Naturally, it follows that an action with an empty list of *tag-sets* in its
-``context`` property cannot possibly be *relevant* for any task. Hence, by
-previously declared special case such an action is *relevant* for the
-task-group.
-
-**Examples**::
-
-    // Example task definitions (everything but tags eclipsed)
-    TaskA = {..., tags: {kind: 'test', platform: 'linux'}}
-    TaskB = {..., tags: {kind: 'test', platform: 'windows'}}
-    TaskC = {..., tags: {kind: 'build', platform: 'linux'}}
-
-    Action1 = {..., context: [{kind: 'test'}]}
-    // Action1 is relevant to: TaskA, TaskB
-
-    Action2 = {..., context: [{kind: 'test', platform: 'linux'}]}
-    // Action2 is relevant to: TaskA
-
-    Action3 = {..., context: [{platform: 'linux'}]}
-    // Action3 is relevant to: TaskA, TaskC
-
-    Action4 = {..., context: [{kind: 'test'}, {kind: 'build'}]}
-    // Action4 is relevant to: TaskA, TaskB, TaskC
-
-    Action5 = {..., context: [{}]}
-    // Action5 is relevant to: TaskA, TaskB, TaskC (all tasks in fact)
-
-    Action6 = {..., context: []}
-    // Action6 is relevant to the task-group
-
-
-Action Input
-............
-
-An action can take JSON input, the input format accepted by an action is
-specified using a `JSON schema <http://json-schema.org/>`_. This schema is
-specified with by the action's ``schema`` property.  For example::
-
-  {
-    "version": 1,
-    "actions": [
-      {
-        "kind": "task",
-        "name": "thing",
-        "title: "Do A Thing",
-        "description": "Do something",
-        "schema": {
-          "description": "The thing to do",
-          "title": "Thing",
-          "default": "something",
-          "type": "string"
-          "maxLength": 255
-        },
-        "task": {
-          "payload": {
-            "env": {
-              "INPUT_JSON": {"$json": {"$eval": "input"}}
-            },
-            ...
-          },
-          ...
-        }
-      }
-    ],
-    "variables: {},
-  }
-
-User interfaces for triggering actions, like Treeherder, are expected to provide
-JSON input that satisfies this schema. These interfaces are also expected to
-validate the input against the schema before attempting to trigger the action.
-
-It is perfectly legal to reference external schemas using
-constructs like ``{"$ref": "https://example.com/my-schema.json"}``, in this case
-it however strongly recommended that the external resource is available over
-HTTPS and allows CORS requests from any source.
-
-When writing schemas it is strongly encouraged that the JSON schema
-``description`` properties are used to provide detailed descriptions. It is
-assumed that consumers will render these ``description`` properties as markdown.
-
-
-Variables
----------
-
-The ``public/actions.json`` artifact has a ``variables`` property that is a
-mapping from variable names to JSON values to be used as constants.
-These variables can be referenced from task templates, but beware that they
-may overshadow builtin variables. This is mainly useful to deduplicate commonly
-used values, in order to reduce template size. This feature does not
-introduce further expressiveness.
-
-Formal Specification
---------------------
-
-The following is the JSON schema for ``actions.json``.
-
-.. literalinclude:: actions-schema.yml
-   :language: YAML
deleted file mode 100644
--- a/taskcluster/docs/action-uis.rst
+++ /dev/null
@@ -1,87 +0,0 @@
-User Interface Considerations
-=============================
-
-The actions system decouples in-tree changes from user interface changes by
-taking advantage of graceful degradation. User interfaces, when presented with
-an unfamiliar action, fall back to a usable default behavior, and can later be
-upgraded to handle that action with a more refined approach.
-
-Default Behavior
-----------------
-
-Every user interface should support the following:
-
- * Displaying a list of actions relevant to each task, and displaying
-   task-group tasks for the associated task-group.
-
- * Providing an opportuntity for the user to enter input for an action.  This
-   might be in JSON or YAML, or using a form auto-generated from the action's
-   JSON-schema.  If the action has no schema, this step should be skipped.
-   The user's input should be validated against the schema.
-
- * For ``action.kind = 'task'``, rendering the template using the JSON-e
-   library, using the variables described in :doc:`action-spec`.
-
- * Calling ``Queue.createTask`` with the resulting task, using the user's
-   Taskcluster credentials.  See the next section for some important
-   security-related concerns.
-
-Creating Tasks
---------------
-
-When executing an action, a UI must ensure that the user is authorized to
-perform the action, and that the user is not being "tricked" into executing
-an unexpected action.
-
-To accomplish the first, the UI should create tasks with the user's Taskcluster
-credentials. Do not use credentials configured as part of the service itself!
-
-To accomplish the second, use the decision tasks's ``scopes`` property as the
-`authorizedScopes
-<https://docs.taskcluster.net/manual/design/apis/hawk/authorized-scopes>`_ for
-the ``Queue.createTask`` call.  This prevents action tasks from doing anything
-the original decision task couldn't do.
-
-Specialized Behavior
---------------------
-
-The default behavior is too clumsy for day-to-day use for common actions.  User
-interfaces may want to provide a more natural interface that still takes advantage
-of the actions system.
-
-Specialized Input
-.................
-
-A user interface may provide specialized input forms for specific schemas.  The
-input generated from the form must conform to the schema.
-
-To ensure that the schema has not changed, implementers should do a deep
-comparison between a schema for which a hand-written form exists, and the
-schema required by the action. If the two differ, the default behavior should
-be used instead.
-
-Specialized Triggering
-......................
-
-A user interface may want to trigger a specific action using a dedicated UI
-element.  For example, an "start interactive session" button might be placed
-next to each failing test in a list of tests.
-
-User interfaces should look for the desired action by name. The UI should check
-that there is exactly one matching action available for the given task or
-task-graph. If multiple actions match, the UI should treat that as an error
-(helping to avoid actions being surreptitiously replaced by similarly-named,
-malicious actions).
-
-Having discovered the task, the user interface has a choice in how to provide
-its input. It can use the "specialized input" approach outlined above, providing
-a customized form if the action's schema is recognized and gracefully degrading
-if not.
-
-But if the user interface is generating the input internally, it may instead
-validate that generated input against the action's schema as given, proceeding
-if validation succeeds.  In this alternative, there is no need to do a deep
-comparison of the schema.  This approach allows in-tree changes that introduce
-backward-compatible changes to the schema, without breaking support in user
-interfaces.  Of course, if the changes are not backward-compatibile, breakage
-will ensue.
deleted file mode 100644
--- a/taskcluster/docs/actions-schema.yml
+++ /dev/null
@@ -1,197 +0,0 @@
-$schema: http://json-schema.org/draft-04/schema#
-id: https://hg.mozilla.org/mozilla-central/raw-file/tip/taskcluster/docs/actions-schema.yml
-title: Schema for Exposing Actions
-description: |
-  This document specifies the schema for the `public/actions.json` used by
-  _decision tasks_ to expose actions that can be triggered by end-users.
-
-  For the purpose of this document the _consumer_ is the user-interface that
-  displays task results to the end-user and allows end-users to trigger actions
-  defined by `public/actions.json`. A _consumer_ might be Treeherder.
-  The _end-user_ is a developer who is inspecting the results, and wish to
-  trigger actions.
-type: object
-properties:
-  version:
-    enum: [1]
-    type: integer
-  variables:
-    type: object
-    description: |
-      Mapping from variable name to constants that can be referenced using
-      `{$eval: '<variable>'}` within the task templates defined for each action.
-
-      This is useful for commonly used constants that are used in many task
-      templates. Whether it's to reduce the size of the `public/actions.json`
-      artifact by reuseing large constants, or simply to make it easier to
-      write task templates by exposing additional variables.
-
-      These will overwrite any builtin variables, such as `taskGroupId`,
-      `input`, `taskId`, `task`, and any further variables that future
-      backwards compatible iterations of this specifcation adds. Hence, you
-      should avoid declaring variables such as `input`, as it will shadow the
-      builtin `input` variable.
-    additionalProperties: true
-  actions:
-    type: array
-    description: |
-      List of actions that can be triggered.
-    items:
-      type: object
-      properties:
-        name:
-          type: string
-          maxLength: 255
-          description: |
-            The name of this action.  This is used by user interfaces to
-            identify the action. For example, a retrigger button might look for
-            an action with `name = "retrigger"`.
-
-            Action names must be unique for a given task, or for a taskgroup,
-            but the same name may be used for actions applying to disjoint sets
-            of tasks. For example, it may be helpful to define different
-            "retrigger" actions for build tasks `[{jobKind: 'build'}]` and test
-            tasks `[{jobKind: 'test'}]`, and in this case only one such action
-            would apply to any given task.
-        title:
-          type: string
-          maxLength: 255
-          description: |
-            Title text to be displayed on the button or link triggering the action.
-        description:
-          type: string
-          maxLength: 4096
-          description: |
-            Human readable description of the action in markdown.
-            Can be displayed in tooltip, popup and/or dialog when triggering
-            the action.
-        kind:
-          enum:
-            - task
-          description: |
-            Specifies the kind of action this is.
-
-            The `task` _action kind_ is triggered by creating a task, following
-            a task template.
-
-            Other kinds might be added in the future. Consumers should ignore
-            all entries featuring a `kind` property they don't recognize.
-        context:
-          type: array
-          default: []
-          items:
-            type: object
-            additionalProperties:
-              type: string
-              maxLength: 4096
-            title: tag-set
-            description: |
-              A set of key-value pairs specifying a _tag-set_.
-          description: |
-            The `context` property determines in what context the action is
-            relevant. Thus, what context the action should be presented to the
-            end-user.
-
-            The `context` property contains a set of tag-sets. A _tag-set_ is a
-            set of key-value pairs. A task is said satisfy a tag-set if
-            `task.tags` is a super-set of the given tag-set. An action is
-            relevant for a task if the task satisfies at-least one of
-            the tag-sets.
-
-            Hence, an action with `context: [{a: '1'}, {b: '2'}]` is relevant
-            for any task with `task.tags.a = '1'` or `task.tags.b = '2'`.
-            An action with `context: [{a: '1', b: '2'}]` is only relevant for
-            tasks with `task.tags.a = '1'` and `task.tags.b = '2'`.
-
-            This allows restrictions of what tasks an action is relevant for.
-            For example some tasks might not support running under a debugger.
-
-            The keen reader observes that actions with `context: [{}]` are
-            relevant for all tasks. Conversely, we have that tasks with
-            `context: []` are irrelevant for all tasks. We abuse this property
-            and define actions with `context: []` to be relevant for the
-            _task-group_ only.
-
-            That is an action with `context: []` should not be display in the
-            context-sensitive menu for a task, rather it should be display when
-            selecting the entire task-group. Presentation details are left for
-            consumer to decide.
-
-            Notice that the `context` property is optional, but defined to have
-            a default value `context: []`. Hence, if the `context` is not
-            specified consumer should take this to mean `context: []` implying
-            that the action is relevant to the task-group, rather than any
-            subset of tasks.
-        schema:
-          $ref: http://json-schema.org/schema
-          description: |
-            JSON schema for input parameters to the `task` template property.
-            Consumers shall offer a user-interface where end-users can enter
-            values that satisfy this schema. Furthermore, consumers **must**
-            validate enter values against the given schema before parameterizing
-            the `task` template property and triggering the action.
-
-            In practice it's encourage that consumers employ a facility that
-            can generate HTML forms from JSON schemas. However, if certain
-            schemas are particularly complicated or common, consumers may also
-            hand-write a user-interface for collecting the input. In this case
-            the consumer **must** do a deep comparison between the schema given
-            in the action, and the schema for which a custom user-interface have
-            been written, and fall-back to an auto-generated form if the schema
-            doesn't match.
-
-            It is assumed that the JSON schema `description` property will be
-            rendered as markdown when displayed as documentation for end-users.
-            Producers of `public/actions.json` is encouraged to provide a
-            detailed explanation of the input parameters using these
-            `description` properties. And consumers are *strongly* encouraged
-            to render `description` values as markdown.
-
-            The `schema` property is optional, and if not given the input for
-            `task` template parameterization shall be `null`.
-        task:
-          type: object
-          title: task template
-          description: |
-            Task template for triggering the action.
-
-            When an action have been selected in the appropriate context and
-            input satisfying the `schema` (if any) has been collected. The
-            action is triggered by parameterizing the task template given in
-            this property, and creating the resulting task.
-
-            The template is an object that is parameterized using
-            [JSON-e](https://github.com/taskcluster/json-e), with the above
-            variables supplied as context.
-
-            This allows for dumping `input` and `taskId` into environment
-            variables for the task to be created. The following task template
-            injects `input` and `taskId` as environment variables:
-            ```json
-            {
-              "workerType": "my-worker",
-              "payload": {
-                "created": {"$fromNow": ""},
-                "deadline": {"$fromNow": "1 hour 15 minutes"},
-                "expiration": {"$fromNow": "14 days"},
-                "image": "my-docker-image",
-                "env": {
-                  "TASKID_TRIGGERED_FOR": "${taskId}",
-                  "INPUT_JSON": {"$json": {"$eval": "input"}}
-                },
-                ...
-              },
-              ...
-            }
-            ```
-      additionalProperties: false
-      required:
-        - title
-        - description
-        - kind
-        - task
-additionalProperties: false
-required:
-  - version
-  - actions
-  - variables
--- a/taskcluster/docs/actions.rst
+++ b/taskcluster/docs/actions.rst
@@ -1,13 +1,14 @@
 Actions
 =======
 
-This section shows how to define an action in-tree such that it shows up in
-supported user interfaces like Treeherder.
+This document shows how to define an action in-tree such that it shows up in
+supported user interfaces like Treeherder. For details on interface between
+in-tree logic and external user interfaces, see `the actions.json spec`_.
 
 At a very high level, the process looks like this:
 
  * The decision task produces an artifact, ``public/actions.json``, indicating
    what actions are available.
 
  * A user interface (for example, Treeherder or the Taskcluster tools) consults
    ``actions.json`` and presents appropriate choices to the user, if necessary
@@ -15,14 +16,248 @@ At a very high level, the process looks 
    re-trigger a test case.
 
  * The user interface follows the action description to carry out the action.
    In most cases (``action.kind == 'task'``), that entails creating an "action
    task", including the provided information. That action task is responsible
    for carrying out the named action, and may create new sub-tasks if necessary
    (for example, to re-trigger a task).
 
+Action Task Implementation
+==========================
 
-.. toctree::
+There are two options for defining actions: creating a callback action, or
+creating a custom action task.  A callback action automatically defines an
+action task that will invoke a Python function of your devising.
+
+A custom action task is an arbitrary task definition that will be created
+directly.  In cases where the callback would simply call ``queue.createTask``,
+a custom action task can be more efficient.
+
+Creating a Callback Action
+--------------------------
+A *callback action* is an action that calls back into in-tree logic. That is,
+you register the action with name, title, description, context, input schema and a
+python callback. When the action is triggered in a user interface,
+input matching the schema is collected, passed to a new task which then calls
+your python callback, enabling it to do pretty much anything it wants to.
+
+To create a new action you must create a file
+``taskcluster/taskgraph/actions/my-action.py``, that at minimum contains::
+
+  from registry import register_callback_action
+
+  @register_callback_action(
+      name='hello',
+      title='Say Hello',
+      symbol='hw',  # Show the callback task in treeherder as 'hw'
+      description="Simple **proof-of-concept** callback action",
+      order=10000,  # Order in which it should appear relative to other actions
+  )
+  def hello_world_action(parameters, input, task_group_id, task_id, task):
+      # parameters is an instance of taskgraph.parameters.Parameters
+      # it carries decision task parameters from the original decision task.
+      # input, task_id, and task should all be None
+      print "Hello was triggered from taskGroupId: " + taskGroupId
+
+The example above defines an action that is available in the context-menu for
+the entire task-group (result-set or push in Treeherder terminology). To create
+an action that shows up in the context menu for a task we would specify the
+``context`` parameter.
+
+
+Setting the Action Context
+..........................
+The context parameter should be a list of tag-sets, such as
+``context=[{"platform": "linux"}]``, which will make the task show up in the
+context-menu for any task with ``task.tags.platform = 'linux'``. Below is
+some examples of context parameters and the resulting conditions on
+``task.tags`` (tags used below are just illustrative).
+
+``context=[{"platform": "linux"}]``:
+  Requires ``task.tags.platform = 'linux'``.
+``context=[{"kind": "test", "platform": "linux"}]``:
+  Requires ``task.tags.platform = 'linux'`` **and** ``task.tags.kind = 'test'``.
+``context=[{"kind": "test"}, {"platform": "linux"}]``:
+  Requires ``task.tags.platform = 'linux'`` **or** ``task.tags.kind = 'test'``.
+``context=[{}]``:
+  Requires nothing and the action will show up in the context menu for all tasks.
+``context=[]``:
+  Is the same as not setting the context parameter, which will make the action
+  show up in the context menu for the task-group.
+  (i.e., the action is not specific to some task)
+
+The example action below will be shown in the context-menu for tasks with
+``task.tags.platform = 'linux'``::
+
+  from registry import register_callback_action
+
+  @register_callback_action(
+      name='retrigger',
+      title='Retrigger',
+      symbol='re-c',  # Show the callback task in treeherder as 're-c'
+      description="Create a clone of the task",
+      order=1,
+      context=[{'platform': 'linux'}]
+  )
+  def retrigger_action(parameters, input, task_group_id, task_id, task):
+      # input will be None
+      print "Retriggering: {}".format(task_id)
+      print "task definition: {}".format(task)
+
+When the ``context`` parameter is set, the ``task_id`` and ``task`` parameters
+will provided to the callback. In this case the ``task_id`` and ``task``
+parameters will be the ``taskId`` and *task definition* of the task from whose
+context-menu the action was triggered.
+
+Typically, the ``context`` parameter is used for actions that operate on
+tasks, such as retriggering, running a specific test case, creating a loaner,
+bisection, etc. You can think of the context as a place the action should
+appear, but it's also very much a form of input the action can use.
+
+
+Specifying an Input Schema
+..........................
+In call examples so far the ``input`` parameter for the callbacks has been
+``None``. To make an action that takes input you must specify an input schema.
+This is done by passing a JSON schema as the ``schema`` parameter.
+
+When designing a schema for the input it is important to exploit as many of the
+JSON schema validation features as reasonably possible. Furthermore, it is
+*strongly* encouraged that the ``title`` and ``description`` properties in
+JSON schemas is used to provide a detailed explanation of what the input
+value will do. Authors can reasonably expect JSON schema ``description``
+properties to be rendered as markdown before being presented.
+
+The example below illustrates how to specify an input schema. Notice that while
+this example doesn't specify a ``context`` it is perfectly legal to specify
+both ``input`` and ``context``::
+
+  from registry import register_callback_action
 
-    action-spec
-    action-uis
-    action-implementation
+  @register_callback_action(
+      name='run-all',
+      title='Run All Tasks',
+      symbol='ra-c',  # Show the callback task in treeherder as 'ra-c'
+      description="**Run all tasks** that have been _optimized_ away.",
+      order=1,
+      input={
+          'title': 'Action Options',
+          'description': 'Options for how you wish to run all tasks',
+          'properties': {
+              'priority': {
+                  'title': 'priority'
+                  'description': 'Priority that should be given to the tasks',
+                  'type': 'string',
+                  'enum': ['low', 'normal', 'high'],
+                  'default': 'low',
+              },
+              'runTalos': {
+                  'title': 'Run Talos'
+                  'description': 'Do you wish to also include talos tasks?',
+                  'type': 'boolean',
+                  'default': 'false',
+              }
+          },
+          'required': ['priority', 'runTalos'],
+          'additionalProperties': False,
+      },
+  )
+  def retrigger_action(parameters, input, task_group_id, task_id, task):
+      print "Create all pruned tasks with priority: {}".format(input['priority'])
+      if input['runTalos']:
+          print "Also running talos jobs..."
+
+When the ``schema`` parameter is given the callback will always be called with
+an ``input`` parameter that satisfies the previously given JSON schema.
+It is encouraged to set ``additionalProperties: false``, as well as specifying
+all properties as ``required`` in the JSON schema. Furthermore, it's good
+practice to provide ``default`` values for properties, as user interface generators
+will often take advantage of such properties.
+
+Once you have specified input and context as applicable for your action you can
+do pretty much anything you want from within your callback. Whether you want
+to create one or more tasks or run a specific piece of code like a test.
+
+Conditional Availability
+........................
+The decision parameters ``taskgraph.parameters.Parameters`` passed to
+the callback are also available when the decision task generates the list of
+actions to be displayed in the user interface. When registering an action
+callback the ``availability`` option can be used to specify a callable
+which, given the decision parameters, determines if the action should be available.
+The feature is illustrated below::
+
+  from registry import register_callback_action
+
+  @register_callback_action(
+      name='hello',
+      title='Say Hello',
+      symbol='hw',  # Show the callback task in treeherder as 'hw'
+      description="Simple **proof-of-concept** callback action",
+      order=2,
+      # Define an action that is only included if this is a push to try
+      available=lambda parameters: parameters.get('project', None) == 'try',
+  )
+  def try_only_action(parameters, input, task_group_id, task_id, task):
+      print "My try-only action"
+
+Properties of ``parameters``  are documented in the
+:doc:`parameters section <parameters>`. You can also examine the
+``parameters.yml`` artifact created by decisions tasks.
+
+
+Creating a Custom Action Task
+------------------------------
+
+It is possible to define an action that doesn't take a callback. Instead, you'll
+then have to provide a task template. For details on how the task template
+language works refer to `the actions.json spec`_, the example below illustrates
+how to create such an action::
+
+  from registry import register_task_action
+
+  @register_task_action(
+      name='retrigger',
+      title='Retrigger',
+      description="Create a clone of the task",
+      order=1,
+      context=[{'platform': 'linux'}],
+      input={
+          'title': 'priority'
+          'description': 'Priority that should be given to the tasks',
+          'type': 'string',
+          'enum': ['low', 'normal', 'high'],
+          'default': 'low',
+      },
+  )
+  def task_template_builder(parameters):
+      # The task template builder may return None to signal that the action
+      # isn't available.
+      if parameters.get('project', None) != 'try':
+        return None
+      return {
+          'created': {'$fromNow': ''},
+          'deadline': {'$fromNow': '1 hour'},
+          'expires': {'$fromNow': '14 days'},
+          'provisionerId': '...',
+          'workerType': '...',
+          'priority': '${input}',
+          'payload': {
+              'command': '...',
+              'env': {
+                  'TASK_DEFINITION': {'$json': {'eval': 'task'}}
+              },
+              ...
+          },
+          # It's now your responsibility to include treeherder routes, as well
+          # additional metadata for treeherder in task.extra.treeherder.
+          ...
+      },
+
+This kind of action is useful for creating simple derivative tasks, but is
+limited by the expressiveness of the template language. On the other hand, it
+is more efficient than an action callback as it does not involve an
+intermediate action task before creating the task the user requested.
+
+For further details on the template language, see `the actions.json spec`_.
+
+.. _the actions.json spec: https://docs.taskcluster.net/manual/tasks/actions/spec