diff --git a/doc/source/api/output.rst b/doc/source/api/output.rst
index c656b5de..ba1ed00d 100644
--- a/doc/source/api/output.rst
+++ b/doc/source/api/output.rst
@@ -284,6 +284,13 @@ methods
     :return: A list of `str` labels of workloads that were part of this run.
 
 
+.. method:: RunOutput.add_classifier(name, value, overwrite=False)
+
+   Add a classifier to the run as a whole. If a classifier with the specified
+   ``name`` already exists, a``ValueError`` will be raised, unless
+   `overwrite=True` is specified.
+
+
 :class:`RunDatabaseOutput`
 ---------------------------
 
@@ -402,7 +409,7 @@ artifacts, metadata, and configuration. It has the following attributes:
 methods
 ~~~~~~~
 
-.. method:: RunOutput.get_artifact(name)
+.. method:: JobOutput.get_artifact(name)
 
     Return the :class:`Artifact` specified by ``name`` associated with this job.
 
@@ -410,7 +417,7 @@ methods
     :return: The :class:`Artifact` with that name
     :raises HostError: If the artifact with the specified name does not exist.
 
-.. method:: RunOutput.get_artifact_path(name)
+.. method:: JobOutput.get_artifact_path(name)
 
     Return the path to the file backing the artifact specified by ``name``,
     associated with this job.
@@ -419,13 +426,20 @@ methods
     :return: The path to the artifact
     :raises HostError: If the artifact with the specified name does not exist.
 
-.. method:: RunOutput.get_metric(name)
+.. method:: JobOutput.get_metric(name)
 
    Return the :class:`Metric` associated with this job with the specified
    `name`.
 
    :return: The :class:`Metric` object for the metric with the specified name.
 
+.. method:: JobOutput.add_classifier(name, value, overwrite=False)
+
+   Add a classifier to the job. The classifier will be propagated to all
+   existing artifacts and metrics, as well as those added afterwards. If a
+   classifier with the specified ``name`` already exists, a ``ValueError`` will
+   be raised, unless `overwrite=True` is specified.
+
 
 :class:`JobDatabaseOutput`
 ---------------------------
diff --git a/wa/framework/execution.py b/wa/framework/execution.py
index ac386ba9..bd4b062a 100644
--- a/wa/framework/execution.py
+++ b/wa/framework/execution.py
@@ -249,6 +249,11 @@ class ExecutionContext(object):
     def add_event(self, message):
         self.output.add_event(message)
 
+    def add_classifier(self, name, value, overwrite=False):
+        self.output.add_classifier(name, value, overwrite)
+        if self.current_job:
+            self.current_job.add_classifier(name, value, overwrite)
+
     def add_metadata(self, key, *args, **kwargs):
         self.output.add_metadata(key, *args, **kwargs)
 
diff --git a/wa/framework/job.py b/wa/framework/job.py
index 347b4e54..127da658 100644
--- a/wa/framework/job.py
+++ b/wa/framework/job.py
@@ -181,6 +181,11 @@ class Job(object):
         if force or self.status < status:
             self.status = status
 
+    def add_classifier(self, name, value, overwrite=False):
+        if name in self.classifiers and not overwrite:
+            raise ValueError('Cannot overwrite "{}" classifier.'.format(name))
+        self.classifiers[name] = value
+
     def __str__(self):
         return '{} ({}) [{}]'.format(self.id, self.label, self.iteration)
 
diff --git a/wa/framework/output.py b/wa/framework/output.py
index 66b90a1d..682d08a2 100644
--- a/wa/framework/output.py
+++ b/wa/framework/output.py
@@ -165,6 +165,9 @@ class Output(object):
         artifact = self.get_artifact(name)
         return self.get_path(artifact.path)
 
+    def add_classifier(self, name, value, overwrite=False):
+        self.result.add_classifier(name, value, overwrite)
+
     def add_metadata(self, key, *args, **kwargs):
         self.result.add_metadata(key, *args, **kwargs)
 
@@ -410,6 +413,21 @@ class Result(Podable):
                 return artifact
         raise HostError('Artifact "{}" not found'.format(name))
 
+    def add_classifier(self, name, value, overwrite=False):
+        if name in self.classifiers and not overwrite:
+            raise ValueError('Cannot overwrite "{}" classifier.'.format(name))
+        self.classifiers[name] = value
+
+        for metric in self.metrics:
+            if name in metric.classifiers and not overwrite:
+                raise ValueError('Cannot overwrite "{}" classifier; clashes with {}.'.format(name, metric))
+            metric.classifiers[name] = value
+
+        for artifact in self.artifacts:
+            if name in artifact.classifiers and not overwrite:
+                raise ValueError('Cannot overwrite "{}" classifier; clashes with {}.'.format(name, artifact))
+            artifact.classifiers[name] = value
+
     def add_metadata(self, key, *args, **kwargs):
         force = kwargs.pop('force', False)
         if kwargs: