diff --git a/.arclint b/.arclint
--- a/.arclint
+++ b/.arclint
@@ -62,7 +62,8 @@
     "xhpast": {
       "type": "xhpast",
       "include": "(\\.php$)",
-      "standard": "phutil.xhpast"
+      "standard": "phutil.xhpast",
+      "xhpast.php-version": "5.5.0"
     }
   }
 }
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -216,7 +216,16 @@
     'ArcanistGoTestResultParserTestCase' => 'unit/parser/__tests__/ArcanistGoTestResultParserTestCase.php',
     'ArcanistHLintLinter' => 'lint/linter/ArcanistHLintLinter.php',
     'ArcanistHLintLinterTestCase' => 'lint/linter/__tests__/ArcanistHLintLinterTestCase.php',
+    'ArcanistHardpoint' => 'hardpoint/ArcanistHardpoint.php',
+    'ArcanistHardpointEngine' => 'hardpoint/ArcanistHardpointEngine.php',
+    'ArcanistHardpointFutureList' => 'hardpoint/ArcanistHardpointFutureList.php',
+    'ArcanistHardpointList' => 'hardpoint/ArcanistHardpointList.php',
     'ArcanistHardpointLoader' => 'loader/ArcanistHardpointLoader.php',
+    'ArcanistHardpointObject' => 'hardpoint/ArcanistHardpointObject.php',
+    'ArcanistHardpointQuery' => 'hardpoint/ArcanistHardpointQuery.php',
+    'ArcanistHardpointRequest' => 'hardpoint/ArcanistHardpointRequest.php',
+    'ArcanistHardpointRequestList' => 'hardpoint/ArcanistHardpointRequestList.php',
+    'ArcanistHardpointTask' => 'hardpoint/ArcanistHardpointTask.php',
     'ArcanistHelpWorkflow' => 'toolset/workflow/ArcanistHelpWorkflow.php',
     'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule.php',
     'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase.php',
@@ -393,6 +402,7 @@
     'ArcanistRuntime' => 'runtime/ArcanistRuntime.php',
     'ArcanistRuntimeConfigurationSource' => 'config/source/ArcanistRuntimeConfigurationSource.php',
     'ArcanistScalarConfigOption' => 'config/option/ArcanistScalarConfigOption.php',
+    'ArcanistScalarHardpoint' => 'hardpoint/ArcanistScalarHardpoint.php',
     'ArcanistScriptAndRegexLinter' => 'lint/linter/ArcanistScriptAndRegexLinter.php',
     'ArcanistSelfClassReferenceXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistSelfClassReferenceXHPASTLinterRule.php',
     'ArcanistSelfClassReferenceXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistSelfClassReferenceXHPASTLinterRuleTestCase.php',
@@ -472,6 +482,7 @@
     'ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase.php',
     'ArcanistVariableVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistVariableVariableXHPASTLinterRule.php',
     'ArcanistVariableVariableXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistVariableVariableXHPASTLinterRuleTestCase.php',
+    'ArcanistVectorHardpoint' => 'hardpoint/ArcanistVectorHardpoint.php',
     'ArcanistVersionWorkflow' => 'toolset/workflow/ArcanistVersionWorkflow.php',
     'ArcanistWeldWorkflow' => 'workflow/ArcanistWeldWorkflow.php',
     'ArcanistWhichWorkflow' => 'workflow/ArcanistWhichWorkflow.php',
@@ -1161,7 +1172,16 @@
     'ArcanistGoTestResultParserTestCase' => 'PhutilTestCase',
     'ArcanistHLintLinter' => 'ArcanistExternalLinter',
     'ArcanistHLintLinterTestCase' => 'ArcanistExternalLinterTestCase',
+    'ArcanistHardpoint' => 'Phobject',
+    'ArcanistHardpointEngine' => 'Phobject',
+    'ArcanistHardpointFutureList' => 'Phobject',
+    'ArcanistHardpointList' => 'Phobject',
     'ArcanistHardpointLoader' => 'Phobject',
+    'ArcanistHardpointObject' => 'Phobject',
+    'ArcanistHardpointQuery' => 'Phobject',
+    'ArcanistHardpointRequest' => 'Phobject',
+    'ArcanistHardpointRequestList' => 'Phobject',
+    'ArcanistHardpointTask' => 'Phobject',
     'ArcanistHelpWorkflow' => 'ArcanistWorkflow',
     'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
     'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
@@ -1337,6 +1357,7 @@
     'ArcanistRubyLinterTestCase' => 'ArcanistExternalLinterTestCase',
     'ArcanistRuntimeConfigurationSource' => 'ArcanistDictionaryConfigurationSource',
     'ArcanistScalarConfigOption' => 'ArcanistConfigOption',
+    'ArcanistScalarHardpoint' => 'ArcanistHardpoint',
     'ArcanistScriptAndRegexLinter' => 'ArcanistLinter',
     'ArcanistSelfClassReferenceXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
     'ArcanistSelfClassReferenceXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
@@ -1416,6 +1437,7 @@
     'ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
     'ArcanistVariableVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
     'ArcanistVariableVariableXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
+    'ArcanistVectorHardpoint' => 'ArcanistHardpoint',
     'ArcanistVersionWorkflow' => 'ArcanistWorkflow',
     'ArcanistWeldWorkflow' => 'ArcanistArcWorkflow',
     'ArcanistWhichWorkflow' => 'ArcanistWorkflow',
diff --git a/src/hardpoint/ArcanistHardpoint.php b/src/hardpoint/ArcanistHardpoint.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistHardpoint.php
@@ -0,0 +1,32 @@
+<?php
+
+abstract class ArcanistHardpoint
+  extends Phobject {
+
+  private $hardpointKey;
+
+  public function setHardpointKey($hardpoint_key) {
+    $this->hardpointKey = $hardpoint_key;
+    return $this;
+  }
+
+  public function getHardpointKey() {
+    return $this->hardpointKey;
+  }
+
+  abstract public function isVectorHardpoint();
+
+  public function mergeHardpointValues(
+    ArcanistHardpointObject $object,
+    $old,
+    $new) {
+
+    throw new Exception(
+      pht(
+        'Hardpoint ("%s", of type "%s") does not support merging '.
+        'values.',
+        $this->getHardpointKey(),
+        get_class($this)));
+  }
+
+}
diff --git a/src/hardpoint/ArcanistHardpointEngine.php b/src/hardpoint/ArcanistHardpointEngine.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistHardpointEngine.php
@@ -0,0 +1,237 @@
+<?php
+
+final class ArcanistHardpointEngine
+  extends Phobject {
+
+  private $queries;
+  private $queryHardpointMap = array();
+
+  private $requests = array();
+
+  private $futureIterator;
+  private $waitFutures = array();
+
+  public function setQueries(array $queries) {
+    assert_instances_of($queries, 'ArcanistHardpointQuery');
+
+    $this->queries = $queries;
+    $this->queryHardpointMap = null;
+
+    return $this;
+  }
+
+  private function getQueriesForHardpoint($hardpoint) {
+    if ($this->queryHardpointMap === null) {
+      $map = array();
+
+      foreach ($this->queries as $query_key => $query) {
+        $query->setHardpointEngine($this);
+
+        $hardpoints = $query->getHardpoints();
+
+        foreach ($hardpoints as $query_hardpoint) {
+          $map[$query_hardpoint][$query_key] = $query;
+        }
+      }
+
+      $this->queryHardpointMap = $map;
+    }
+
+    return idx($this->queryHardpointMap, $hardpoint, array());
+  }
+
+  public function requestHardpoints(array $objects, array $requests) {
+    assert_instances_of($objects, 'ArcanistHardpointObject');
+
+    $results = array();
+    foreach ($requests as $request) {
+      $request = ArcanistHardpointRequest::newFromSpecification($request)
+        ->setEngine($this)
+        ->setObjects($objects);
+
+      $this->requests[] = $request;
+
+      $this->startRequest($request);
+
+      $results[] = $request;
+    }
+
+    return ArcanistHardpointRequestList::newFromRequests($results);
+  }
+
+  private function startRequest(ArcanistHardpointRequest $request) {
+    $objects = $request->getObjects();
+    $hardpoint = $request->getHardpoint();
+
+    $queries = $this->getQueriesForHardpoint($hardpoint);
+
+    $load = array();
+    foreach ($objects as $object_key => $object) {
+      if (!$object->hasHardpoint($hardpoint)) {
+        throw new Exception(
+          pht(
+            'Object (with key "%s", of type "%s") has no hardpoint "%s". '.
+            'Hardpoints on this object are: %s.',
+            $object_key,
+            phutil_describe_type($object),
+            $hardpoint,
+            $object->getHardpointList()->getHardpointListForDisplay()));
+      }
+
+      // If the object already has the hardpoint attached, we don't have to
+      // do anything. Throw the object away.
+
+      if ($object->hasAttachedHardpoint($hardpoint)) {
+        unset($objects[$object_key]);
+        continue;
+      }
+
+      $any_query = false;
+      foreach ($queries as $query_key => $query) {
+        if (!$query->canLoadObject($object)) {
+          continue;
+        }
+
+        $any_query = true;
+        $load[$query_key][$object_key] = $object;
+      }
+
+      if (!$any_query) {
+        throw new Exception(
+          pht(
+            'No query exists which can load hardpoint "%s" for object '.
+            '(with key "%s" of type "%s").',
+            $hardpoint,
+            $object_key,
+            phutil_describe_type($object)));
+      }
+    }
+
+    if (!$objects) {
+      return;
+    }
+
+    $any_object = head($objects);
+    $list = $object->getHardpointList();
+    $definition = $list->getHardpointDefinition($any_object, $hardpoint);
+
+    $is_vector = ($definition->isVectorHardpoint());
+
+    if ($is_vector) {
+      foreach ($objects as $object) {
+        $object->attachHardpoint($hardpoint, array());
+      }
+    }
+
+    $request->setHardpointDefinition($definition);
+
+    foreach ($load as $query_key => $object_map) {
+      $query = id(clone $queries[$query_key]);
+
+      $task = $request->newTask()
+        ->setQuery($query)
+        ->setObjects($object_map);
+    }
+  }
+
+  public function waitForRequests(array $wait_requests) {
+    foreach ($wait_requests as $wait_key => $wait_request) {
+      if ($wait_request->getEngine() !== $this) {
+        throw new Exception(
+          pht(
+            'Attempting to wait on a hardpoint request (with index "%s", for '.
+            'hardpoint "%s") that is part of a different engine.',
+            $wait_key,
+            $wait_request->getHardpoint()));
+      }
+    }
+
+    while (true) {
+      $any_progress = false;
+      foreach ($this->requests as $req_key => $request) {
+        $did_update = $request->updateTasks();
+        if ($did_update) {
+          $any_progress = true;
+        }
+      }
+
+      // If we made progress by directly executing requests, continue
+      // excuting them until we stop making progress. We want to queue all
+      // reachable futures before we wait on futures.
+
+      if ($any_progress) {
+        continue;
+      }
+
+      foreach ($this->requests as $request_key => $request) {
+        if ($request->isComplete()) {
+          unset($this->requests[$request_key]);
+        }
+      }
+
+      if (!$this->requests) {
+        break;
+      }
+
+      $resolved_key = $this->updateFutures();
+
+      if ($resolved_key === null) {
+        throw new Exception(
+          pht(
+            'Hardpoint engine can not resolve: no request made progress '.
+            'during the last update cycle and there are no futures '.
+            'awaiting resolution.'));
+      }
+    }
+  }
+
+  private function updateFutures() {
+    $iterator = $this->futureIterator;
+
+    $is_rewind = false;
+    $wait_futures = $this->waitFutures;
+    if ($wait_futures) {
+      if (!$this->futureIterator) {
+        $iterator = new FutureIterator(array());
+        foreach ($wait_futures as $wait_future) {
+          $iterator->addFuture($wait_future);
+        }
+        $is_rewind = true;
+        $this->futureIterator = $iterator;
+      } else {
+        foreach ($wait_futures as $wait_future) {
+          $iterator->addFuture($wait_future);
+        }
+      }
+      $this->waitFutures = array();
+    }
+
+    $resolved_key = null;
+    if ($iterator) {
+      if ($is_rewind) {
+        $iterator->rewind();
+      } else {
+        $iterator->next();
+      }
+
+      if ($iterator->valid()) {
+        $resolved_key = $iterator->key();
+      } else {
+        $this->futureIterator = null;
+      }
+    }
+
+    return $resolved_key;
+  }
+
+  public function addFutures(array $futures) {
+    assert_instances_of($futures, 'Future');
+    $this->waitFutures += mpull($futures, null, 'getFutureKey');
+
+    // TODO: We could reasonably add these futures to the iterator
+    // immediately and start them here, instead of waiting.
+
+    return $this;
+  }
+
+}
diff --git a/src/hardpoint/ArcanistHardpointFutureList.php b/src/hardpoint/ArcanistHardpointFutureList.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistHardpointFutureList.php
@@ -0,0 +1,31 @@
+<?php
+
+final class ArcanistHardpointFutureList
+  extends Phobject {
+
+  private $futures;
+  private $sendResult;
+
+  public static function newFromFutures(array $futures) {
+    assert_instances_of($futures, 'Future');
+
+    $object = new self();
+    $object->futures = $futures;
+
+    return $object;
+  }
+
+  public function getFutures() {
+    return $this->futures;
+  }
+
+  public function setSendResult($send_result) {
+    $this->sendResult = $send_result;
+    return $this;
+  }
+
+  public function getSendResult() {
+    return $this->sendResult;
+  }
+
+}
diff --git a/src/hardpoint/ArcanistHardpointList.php b/src/hardpoint/ArcanistHardpointList.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistHardpointList.php
@@ -0,0 +1,121 @@
+<?php
+
+final class ArcanistHardpointList
+  extends Phobject {
+
+  private $hardpoints = array();
+  private $attached = array();
+  private $data = array();
+
+  public function setHardpoints(array $hardpoints) {
+    assert_instances_of($hardpoints, 'ArcanistHardpoint');
+
+    $map = array();
+    foreach ($hardpoints as $idx => $hardpoint) {
+      $key = $hardpoint->getHardpointKey();
+
+      if (!strlen($key)) {
+        throw new Exception(
+          pht(
+            'Hardpoint (at index "%s") has no hardpoint key. Each hardpoint '.
+            'must have a key that is unique among hardpoints on the object.',
+            $idx));
+      }
+
+      if (isset($map[$key])) {
+        throw new Exception(
+          pht(
+            'Hardpoint (at index "%s") has the same key ("%s") as an earlier '.
+            'hardpoint. Each hardpoint must have a key that is unique '.
+            'among hardpoints on the object.'));
+      }
+
+      $map[$key] = $hardpoint;
+    }
+
+    $this->hardpoints = $map;
+
+    return $this;
+  }
+
+  public function hasHardpoint($object, $hardpoint) {
+    return isset($this->hardpoints[$hardpoint]);
+  }
+
+  public function hasAttachedHardpoint($object, $hardpoint) {
+    return isset($this->attached[$hardpoint]);
+  }
+
+  public function getHardpointDefinition($object, $hardpoint) {
+    if (!$this->hasHardpoint($object, $hardpoint)) {
+      throw new Exception(
+        pht(
+          'Hardpoint ("%s") is not registered on this object (of type "%s") '.
+          'so the definition object does not exist. Hardpoints are: %s.',
+          $hardpoint,
+          phutil_describe_type($object),
+          $this->getHardpointListForDisplay()));
+    }
+
+    return $this->hardpoints[$hardpoint];
+  }
+
+  public function getHardpoint($object, $hardpoint) {
+    if (!$this->hasHardpoint($object, $hardpoint)) {
+      throw new Exception(
+        pht(
+          'Hardpoint ("%s") is not registered on this object (of type "%s"). '.
+          'Hardpoints are: %s.',
+          $hardpoint,
+          phutil_describe_type($object),
+          $this->getHardpointListForDisplay()));
+    }
+
+    if (!$this->hasAttachedHardpoint($object, $hardpoint)) {
+      throw new Exception(
+        pht(
+          'Hardpoint data (for hardpoint "%s") is not attached.',
+          $hardpoint));
+    }
+
+    return $this->data[$hardpoint];
+  }
+
+  public function setHardpointValue($object, $hardpoint, $value) {
+    if (!$this->hasHardpoint($object, $hardpoint)) {
+      throw new Exception(
+        pht(
+          'Hardpoint ("%s") is not registered on this object (of type "%s"). '.
+          'Hardpoints are: %s.',
+          $hardpoint,
+          phutil_describe_type($object),
+          $this->getHardpointListforDisplay()));
+    }
+
+    $this->attached[$hardpoint] = true;
+    $this->data[$hardpoint] = $value;
+  }
+
+  public function attachHardpoint($object, $hardpoint, $value) {
+    if ($this->hasAttachedHardpoint($object, $hardpoint)) {
+      throw new Exception(
+        pht(
+          'Hardpoint ("%s") already has attached data.',
+          $hardpoint));
+    }
+
+    $this->setHardpointValue($object, $hardpoint, $value);
+  }
+
+  public function getHardpointListForDisplay() {
+    $list = array_keys($this->hardpoints);
+
+    if ($list) {
+      sort($list);
+      return implode(', ', $list);
+    }
+
+    return pht('<none>');
+  }
+
+}
diff --git a/src/hardpoint/ArcanistHardpointObject.php b/src/hardpoint/ArcanistHardpointObject.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistHardpointObject.php
@@ -0,0 +1,91 @@
+<?php
+
+abstract class ArcanistHardpointObject
+  extends Phobject {
+
+  private $hardpointList;
+
+  final public function getHardpoint($hardpoint) {
+    return $this->getHardpointList()->getHardpoint(
+      $this,
+      $hardpoint);
+  }
+
+  final public function attachHardpoint($hardpoint, $value) {
+    $this->getHardpointList()->attachHardpoint(
+      $this,
+      $hardpoint,
+      $value);
+
+    return $this;
+  }
+
+  final public function mergeHardpoint($hardpoint, $value) {
+    $hardpoint_list = $this->getHardpointList();
+    $hardpoint_def = $hardpoint_list->getHardpointDefinition(
+      $this,
+      $hardpoint);
+
+    $old_value = $this->getHardpoint($hardpoint);
+    $new_value = $hardpoint_def->mergeHardpointValues(
+      $this,
+      $old_value,
+      $value);
+
+    $hardpoint_list->setHardpointValue(
+      $this,
+      $hardpoint,
+      $new_value);
+
+    return $this;
+  }
+
+  final public function hasHardpoint($hardpoint) {
+    return $this->getHardpointList()->hasHardpoint($this, $hardpoint);
+  }
+
+  final public function hasAttachedHardpoint($hardpoint) {
+    return $this->getHardpointList()->hasAttachedHardpoint(
+      $this,
+      $hardpoint);
+  }
+
+  protected function newHardpoints() {
+    return array();
+  }
+
+  final protected function newHardpoint($hardpoint_key) {
+    return id(new ArcanistScalarHardpoint())
+      ->setHardpointKey($hardpoint_key);
+  }
+
+  final protected function newVectorHardpoint($hardpoint_key) {
+    return id(new ArcanistVectorHardpoint())
+      ->setHardpointKey($hardpoint_key);
+  }
+
+
+  final public function getHardpointList() {
+    if ($this->hardpointList === null) {
+      $list = $this->newHardpointList();
+
+      // TODO: Cache the hardpoint list with the class name as a key? If so,
+      // it needs to be purged when the request cache is purged.
+
+      $hardpoints = $this->newHardpoints();
+
+      // TODO: Verify the hardpoints list is structured properly.
+
+      $list->setHardpoints($hardpoints);
+
+      $this->hardpointList = $list;
+    }
+
+    return $this->hardpointList;
+  }
+
+  private function newHardpointList() {
+    return new ArcanistHardpointList();
+  }
+
+}
diff --git a/src/hardpoint/ArcanistHardpointQuery.php b/src/hardpoint/ArcanistHardpointQuery.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistHardpointQuery.php
@@ -0,0 +1,36 @@
+<?php
+
+abstract class ArcanistHardpointQuery
+  extends Phobject {
+
+  private $hardpointEngine;
+
+  final public function setHardpointEngine(ArcanistHardpointEngine $engine) {
+    $this->hardpointEngine = $engine;
+    return $this;
+  }
+
+  final public function getHardpointEngine() {
+    return $this->hardpointEngine;
+  }
+
+  abstract public function getHardpoints();
+  abstract public function canLoadObject(ArcanistHardpointObject $object);
+  abstract public function loadHardpoint(array $objects, $hardpoint);
+
+  final protected function yieldFuture(Future $future) {
+    return $this->yieldFutures(array($future))
+      ->setSendResult(true);
+  }
+
+  final protected function yieldFutures(array $futures) {
+    return ArcanistHardpointFutureList::newFromFutures($futures);
+  }
+
+  final protected function yieldRequests(array $objects, $requests) {
+    $engine = $this->getHardpointEngine();
+    $requests = $engine->requestHardpoints($objects, $requests);
+    return $requests;
+  }
+
+}
diff --git a/src/hardpoint/ArcanistHardpointRequest.php b/src/hardpoint/ArcanistHardpointRequest.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistHardpointRequest.php
@@ -0,0 +1,132 @@
+<?php
+
+final class ArcanistHardpointRequest
+  extends Phobject {
+
+  private $engine;
+  private $objects;
+  private $hardpoint;
+  private $hardpointDefinition;
+  private $tasks = array();
+  private $isComplete;
+
+  public static function newFromSpecification($spec) {
+    if ($spec instanceof ArcanistHardpointRequest) {
+      return $spec;
+    }
+
+    if (is_string($spec)) {
+      return id(new self())->setHardpoint($spec);
+    }
+
+    throw new Exception(
+      pht(
+        'Unknown Hardpoint request specification (of type "%s").',
+        phutil_describe_type($spec)));
+  }
+
+  public function setEngine(ArcanistHardpointEngine $engine) {
+    $this->engine = $engine;
+    return $this;
+  }
+
+  public function getEngine() {
+    return $this->engine;
+  }
+
+  public function setHardpoint($hardpoint) {
+    $this->hardpoint = $hardpoint;
+    return $this;
+  }
+
+  public function getHardpoint() {
+    return $this->hardpoint;
+  }
+
+  public function setObjects(array $objects) {
+    $this->objects = $objects;
+    return $this;
+  }
+
+  public function getObjects() {
+    return $this->objects;
+  }
+
+  public function newTask() {
+    $task = id(new ArcanistHardpointTask())
+      ->setRequest($this);
+
+    $this->tasks[] = $task;
+    $this->isComplete = false;
+
+    return $task;
+  }
+
+  public function isComplete() {
+    return $this->isComplete;
+  }
+
+  public function getTasks() {
+    return $this->tasks;
+  }
+
+  public function updateTasks() {
+    $any_progress = false;
+
+    foreach ($this->tasks as $task) {
+      $did_update = $task->updateTask();
+      if ($did_update) {
+        $any_progress = true;
+      }
+    }
+
+    foreach ($this->tasks as $task_key => $task) {
+      if ($task->isComplete()) {
+        unset($this->tasks[$task_key]);
+      }
+    }
+
+    if (!$this->tasks) {
+
+      // TODO: We can skip or modify this check if the hardpoint is a vector
+      // hardpoint.
+
+      $objects = $this->getObjects();
+      $hardpoint = $this->getHardpoint();
+      foreach ($objects as $object) {
+        if (!$object->hasAttachedHardpoint($hardpoint)) {
+          throw new Exception(
+            pht(
+              'Unable to load hardpoint "%s" for object (of type "%s"). '.
+              'All hardpoint query tasks resolved but none attached '.
+              'a value to the hardpoint.',
+              $hardpoint,
+              phutil_describe_type($object)));
+        }
+      }
+
+      // We may arrive here if a request is queued that can be satisfied
+      // immediately, most often because it requests hardpoints which are
+      // already attached. We don't have to do any work, so we have no tasks
+      // to update or complete and can complete the request immediately.
+      if (!$this->isComplete) {
+        $any_progress = true;
+      }
+
+      $this->isComplete = true;
+    }
+
+    return $any_progress;
+  }
+
+
+  public function setHardpointDefinition($hardpoint_definition) {
+    $this->hardpointDefinition = $hardpoint_definition;
+    return $this;
+  }
+
+  public function getHardpointDefinition() {
+    return $this->hardpointDefinition;
+  }
+
+}
diff --git a/src/hardpoint/ArcanistHardpointRequestList.php b/src/hardpoint/ArcanistHardpointRequestList.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistHardpointRequestList.php
@@ -0,0 +1,21 @@
+<?php
+
+final class ArcanistHardpointRequestList
+  extends Phobject {
+
+  private $requests;
+
+  public static function newFromRequests(array $requests) {
+    assert_instances_of($requests, 'ArcanistHardpointRequest');
+
+    $object = new self();
+    $object->requests = $requests;
+
+    return $object;
+  }
+
+  public function getRequests() {
+    return $this->requests;
+  }
+
+}
diff --git a/src/hardpoint/ArcanistHardpointTask.php b/src/hardpoint/ArcanistHardpointTask.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistHardpointTask.php
@@ -0,0 +1,246 @@
+<?php
+
+final class ArcanistHardpointTask
+  extends Phobject {
+
+  private $request;
+  private $query;
+  private $objects;
+
+  private $isComplete;
+  private $generator;
+  private $hasRewound;
+  private $sendFuture;
+
+  private $blockingRequests = array();
+  private $blockingFutures = array();
+
+  public function setRequest(ArcanistHardpointRequest $request) {
+    $this->request = $request;
+    return $this;
+  }
+
+  public function getRequest() {
+    return $this->request;
+  }
+
+  public function setQuery(ArcanistHardpointQuery $query) {
+    $this->query = $query;
+    return $this;
+  }
+
+  public function getQuery() {
+    return $this->query;
+  }
+
+  public function setObjects(array $objects) {
+    $this->objects = $objects;
+    return $this;
+  }
+
+  public function getObjects() {
+    return $this->objects;
+  }
+
+  public function isComplete() {
+    return $this->isComplete;
+  }
+
+  public function updateTask() {
+    if ($this->isComplete()) {
+      return false;
+    }
+
+    // If we're blocked by other requests, we have to wait for them to
+    // resolve.
+    if ($this->getBlockingRequests()) {
+      return false;
+    }
+
+    // If we're blocked by futures, we have to wait for them to resolve.
+    if ($this->getBlockingFutures()) {
+      return false;
+    }
+
+    $query = $this->getQuery();
+
+    // If we've previously produced a generator, iterate it.
+
+    if ($this->generator) {
+      $generator = $this->generator;
+
+      $has_send = false;
+      $send_value = null;
+
+      // If our last iteration generated a single future and it was marked to
+      // be sent back to the generator, resolve the future (it should already
+      // be ready to resolve) and send the result.
+
+      if ($this->sendFuture) {
+        $has_send = true;
+        $future = $this->sendFuture;
+        $this->sendFuture = null;
+
+        $send_value = $future->resolve();
+      }
+
+      if ($has_send && !$this->hasRewound) {
+        throw new Exception(
+          pht(
+            'Generator has never rewound, but has a value to send. This '.
+            'is invalid.'));
+      }
+
+      if (!$this->hasRewound) {
+        $this->hasRewound = true;
+        $generator->rewind();
+      } else if ($has_send) {
+        $generator->send($send_value);
+      } else {
+        $generator->next();
+      }
+
+      if ($generator->valid()) {
+        $result = $generator->current();
+
+        if ($result instanceof Future) {
+          $result = new ArcanistHardpointFutureList($result);
+        }
+
+        if ($result instanceof ArcanistHardpointFutureList) {
+          $futures = $result->getFutures();
+          $is_send = $result->getSendResult();
+
+          $this->getRequest()->getEngine()->addFutures($futures);
+
+          foreach ($futures as $future) {
+            $this->blockingFutures[] = $future;
+          }
+
+          if ($is_send) {
+            if (count($futures) === 1) {
+              $this->sendFuture = head($futures);
+            } else {
+              throw new Exception(
+                pht(
+                  'Hardpoint future list is marked to send results to the '.
+                  'generator, but the list does not have exactly one future '.
+                  '(it has %s).',
+                  phutil_count($futures)));
+            }
+          }
+
+          return true;
+        }
+
+        $is_request = ($result instanceof ArcanistHardpointRequest);
+        $is_request_list = ($result instanceof ArcanistHardpointRequestList);
+        if ($is_request || $is_request_list) {
+          if ($is_request) {
+            $request_list = array($result);
+          } else {
+            $request_list = $result->getRequests();
+          }
+
+          // TODO: Make sure these requests have already been added to the
+          // engine.
+
+          foreach ($request_list as $blocking_request) {
+            $this->blockingRequests[] = $blocking_request;
+          }
+
+          return true;
+        }
+
+        throw new Exception(
+          pht(
+            'Hardpoint generator (for query "%s") yielded an unexpected '.
+            'value. Generators may only yield "Future" or '.
+            '"ArcanistHardpointRequest" objects, got "%s".',
+            get_class($query),
+            phutil_describe_type($result)));
+      }
+
+      $this->generator = null;
+      $result = $generator->getReturn();
+
+      $this->attachResult($result);
+
+      return true;
+    }
+
+    $objects = $this->getObjects();
+    $hardpoint = $this->getRequest()->getHardpoint();
+
+    $result = $query->loadHardpoint($objects, $hardpoint);
+    if ($result instanceof Generator) {
+      $this->generator = $result;
+      $this->hasRewound = false;
+
+      // If we produced a generator, we can attempt to iterate it immediately.
+      return $this->updateTask();
+    }
+
+    $this->attachResult($result);
+
+    return true;
+  }
+
+  public function getBlockingRequests() {
+    $blocking = array();
+
+    foreach ($this->blockingRequests as $key => $request) {
+      if (!$request->isComplete()) {
+        $blocking[$key] = $request;
+      }
+    }
+
+    $this->blockingRequests = $blocking;
+
+    return $blocking;
+  }
+
+  public function getBlockingFutures() {
+    $blocking = array();
+
+    foreach ($this->blockingFutures as $key => $future) {
+      if (!$future->hasResult() && !$future->hasException()) {
+        $blocking[$key] = $future;
+      }
+    }
+
+    $this->blockingFutures = $blocking;
+
+    return $blocking;
+  }
+
+  private function attachResult($result) {
+    $objects = $this->getObjects();
+    $hardpoint = $this->getRequest()->getHardpoint();
+
+    $definition = $this->getRequest()->getHardpointDefinition();
+    $is_vector = $definition->isVectorHardpoint();
+
+    foreach ($result as $object_key => $value) {
+      if (!isset($objects[$object_key])) {
+        throw new Exception(
+          pht(
+            'Bad object key ("%s").',
+            $object_key));
+      }
+
+      $object = $objects[$object_key];
+
+      if ($is_vector) {
+        $object->mergeHardpoint($hardpoint, $value);
+      } else {
+        if (!$object->hasAttachedHardpoint($hardpoint)) {
+          $object->attachHardpoint($hardpoint, $value);
+        }
+      }
+    }
+
+    $this->isComplete = true;
+  }
+
+}
diff --git a/src/hardpoint/ArcanistScalarHardpoint.php b/src/hardpoint/ArcanistScalarHardpoint.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistScalarHardpoint.php
@@ -0,0 +1,10 @@
+<?php
+
+final class ArcanistScalarHardpoint
+  extends ArcanistHardpoint {
+
+  public function isVectorHardpoint() {
+    return false;
+  }
+
+}
diff --git a/src/hardpoint/ArcanistVectorHardpoint.php b/src/hardpoint/ArcanistVectorHardpoint.php
new file mode 100644
--- /dev/null
+++ b/src/hardpoint/ArcanistVectorHardpoint.php
@@ -0,0 +1,22 @@
+<?php
+
+final class ArcanistVectorHardpoint
+  extends ArcanistHardpoint {
+
+  public function isVectorHardpoint() {
+    return true;
+  }
+
+  public function mergeHardpointValues(
+    ArcanistHardpointObject $object,
+    $old,
+    $new) {
+
+    foreach ($new as $item) {
+      $old[] = $item;
+    }
+
+    return $old;
+  }
+
+}