diff --git a/src/jio/core/JIO.js b/src/jio/core/JIO.js
index f75c5a96aeaf783fc6913317c8e0b901e6eaf865..83348030eaad2d1806719c826e1d82dfb687e325 100644
--- a/src/jio/core/JIO.js
+++ b/src/jio/core/JIO.js
@@ -20,7 +20,7 @@ function JIO(storage_spec, options) {
   enableRestParamChecker(that, shared, options);
   enableJobMaker(that, shared, options);
   enableJobRetry(that, shared, options);
-  // enableJobChecker(that, shared, options);
+  enableJobChecker(that, shared, options);
   enableJobQueue(that, shared, options);
   enableJobRecovery(that, shared, options);
   enableJobTimeout(that, shared, options);
diff --git a/src/jio/core/globals.js b/src/jio/core/globals.js
index 344f8c20e9b5bb881908ba8fdb4e8e628f0474a0..9cc3337ca3fedd708ab1d80372bd49f1d87b642a 100644
--- a/src/jio/core/globals.js
+++ b/src/jio/core/globals.js
@@ -1,4 +1,5 @@
-/*jslint indent: 2, maxlen: 80, sloppy: true */
+/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
+/*global uniqueJSONStringify */
 
 var defaults = {}, constants = {};
 
@@ -330,6 +331,135 @@ constants.http_action = {
 constants.content_type_re =
   /^([a-z]+\/[a-zA-Z0-9\+\-\.]+)(?:\s*;\s*charset\s*=\s*([a-zA-Z0-9\-]+))?$/;
 
+/**
+ * Function that does nothing
+ */
 constants.emptyFunction = function () {
   return;
 };
+
+defaults.job_rule_actions = {};
+
+/**
+ * Adds some job rule actions
+ */
+(function () {
+
+  /**
+   * Compare two jobs and test if they use the same storage description
+   *
+   * @param  {Object} a The first job to compare
+   * @param  {Object} b The second job to compare
+   * @return {Boolean} True if equal, else false
+   */
+  function sameStorageDescription(a, b) {
+    return uniqueJSONStringify(a.storage_spec) ===
+      uniqueJSONStringify(b.storage_spec);
+  }
+
+  /**
+   * Guess if the method is a writer or a reader.
+   *
+   * @param  {String} method The method name
+   * @return {String} "writer", "reader" or "unknown"
+   */
+  function methodType(method) {
+    switch (method) {
+    case "post":
+    case "put":
+    case "putAttachment":
+    case "remove":
+    case "removeAttachment":
+    case "repair":
+      return 'writer';
+    case "get":
+    case "getAttachment":
+    case "allDocs":
+    case "check":
+      return 'reader';
+    default:
+      return 'unknown';
+    }
+  }
+
+  /**
+   * Compare two jobs and test if they are writers
+   *
+   * @param  {Object} a The first job to compare
+   * @param  {Object} b The second job to compare
+   * @return {Boolean} True if equal, else false
+   */
+  function areWriters(a, b) {
+    return methodType(a.method) === 'writer' &&
+      methodType(b.method) === 'writer';
+  }
+
+  /**
+   * Compare two jobs and test if they are readers
+   *
+   * @param  {Object} a The first job to compare
+   * @param  {Object} b The second job to compare
+   * @return {Boolean} True if equal, else false
+   */
+  function areReaders(a, b) {
+    return methodType(a.method) === 'reader' &&
+      methodType(b.method) === 'reader';
+  }
+
+  /**
+   * Compare two jobs and test if their methods are the same
+   *
+   * @param  {Object} a The first job to compare
+   * @param  {Object} b The second job to compare
+   * @return {Boolean} True if equal, else false
+   */
+  function sameMethod(a, b) {
+    return a.method === b.method;
+  }
+
+  /**
+   * Compare two jobs and test if their document ids are the same
+   *
+   * @param  {Object} a The first job to compare
+   * @param  {Object} b The second job to compare
+   * @return {Boolean} True if equal, else false
+   */
+  function sameDocumentId(a, b) {
+    return a.kwargs._id === b.kwargs._id;
+  }
+
+  /**
+   * Compare two jobs and test if their kwargs are equal
+   *
+   * @param  {Object} a The first job to compare
+   * @param  {Object} b The second job to compare
+   * @return {Boolean} True if equal, else false
+   */
+  function sameParameters(a, b) {
+    return uniqueJSONStringify(a.kwargs) ===
+      uniqueJSONStringify(b.kwargs);
+  }
+
+  /**
+   * Compare two jobs and test if their options are equal
+   *
+   * @param  {Object} a The first job to compare
+   * @param  {Object} b The second job to compare
+   * @return {Boolean} True if equal, else false
+   */
+  function sameOptions(a, b) {
+    return uniqueJSONStringify(a.options) ===
+      uniqueJSONStringify(b.options);
+  }
+
+  defaults.job_rule_actions = {
+    "sameStorageDescription": sameStorageDescription,
+    "areWriters": areWriters,
+    "areReaders": areReaders,
+    "sameMethod": sameMethod,
+    "sameDocumentId": sameDocumentId,
+    "sameParameters": sameParameters,
+    "sameOptions": sameOptions
+  };
+
+}());
diff --git a/src/jio/features/jobChecker.js b/src/jio/features/jobChecker.js
new file mode 100644
index 0000000000000000000000000000000000000000..f6e44d66f0923867ffa959fcee86522af340aeea
--- /dev/null
+++ b/src/jio/features/jobChecker.js
@@ -0,0 +1,135 @@
+/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, unparam: true */
+/*global arrayInsert, indexOf, deepClone */
+
+// creates
+// - some defaults job rule actions
+
+function enableJobChecker(jio, shared, options) {
+
+  // dependencies
+  // - shared.job_queue JobWorkspace
+
+  // creates
+  // - shared.job_rules Array
+
+  // uses 'job' events
+
+  shared.job_check_method_names = [undefined, "ok", "wait", "update", "deny"];
+
+  function addJobRule(job_rule) {
+    var i, old_position, before_position, after_position;
+    // job_rule = {
+    //   code_name: string
+    //   conditions: [string, ...]
+    //   action: 'wait',
+    //   after: code_name
+    //   before: code_name
+    // }
+    if (typeof job_rule !== 'object' || job_rule === null) {
+      // wrong job rule
+      return;
+    }
+    if (typeof job_rule.code_name !== 'string') {
+      // wrong code name
+      return;
+    }
+    if (!Array.isArray(job_rule.conditions)) {
+      // wrong conditions
+      return;
+    }
+    if (indexOf(job_rule.action, shared.job_check_method_names) === -1) {
+      // wrong action
+      return;
+    }
+
+    if (typeof job_rule.after !== 'string') {
+      job_rule.after = '';
+    }
+    if (typeof job_rule.before !== 'string') {
+      job_rule.before = '';
+    }
+
+    for (i = 0; i < shared.job_rules.length; i += 1) {
+      if (shared.job_rules[i].code_name === job_rule.after) {
+        after_position = i + 1;
+      }
+      if (shared.job_rules[i].code_name === job_rule.before) {
+        before_position = i;
+      }
+      if (shared.job_rules[i].code_name === job_rule.code_name) {
+        old_position = i;
+      }
+    }
+
+    job_rule = {
+      "code_name": job_rule.code_name,
+      "conditions": job_rule.conditions,
+      "action": job_rule.action || "ok"
+    };
+
+    if (before_position === undefined) {
+      before_position = shared.job_rules.length;
+    }
+    if (after_position > before_position) {
+      before_position = undefined;
+    }
+    if (job_rule.action !== "ok" && before_position !== undefined) {
+      arrayInsert(shared.job_rules, before_position, job_rule);
+    }
+    if (old_position !== undefined) {
+      if (old_position >= before_position) {
+        old_position += 1;
+      }
+      shared.job_rules.splice(old_position, 1);
+    }
+  }
+
+  if (options.job_management !== false) {
+
+    shared.job_rules = [{
+      "code_name": "readers update",
+      "conditions": [
+        "sameStorageDescription",
+        "areReaders",
+        "sameMethod",
+        "sameParameters",
+        "sameOptions"
+      ],
+      "action": "update"
+    }, {
+      "code_name": "writers update",
+      "conditions": [
+        "sameStorageDescription",
+        "areWriters",
+        "sameMethod",
+        "sameParameters"
+      ],
+      "action": "update"
+    }, {
+      "code_name": "writers wait",
+      "conditions": [
+        "sameStorageDescription",
+        "areWriters",
+        "sameDocumentId"
+      ],
+      "action": "wait"
+    }];
+
+    var i;
+    if (Array.isArray(options.job_rules)) {
+      for (i = 0; i < options.job_rules.length; i += 1) {
+        addJobRule(deepClone(options.job_rules[i]));
+      }
+    }
+
+    shared.on('job', function (param) {
+      // XXX
+      return;
+    });
+
+  }
+
+  jio.jobRules = function () {
+    return deepClone(shared.job_rules);
+  };
+}