diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp
index 326ebe7ed447a725fcbb17febb9813741794dc0b..c17dbcf4ead2b3ac999b43cb67202f646986ebbe 100644
--- a/indra/newview/llviewerobject.cpp
+++ b/indra/newview/llviewerobject.cpp
@@ -3090,24 +3090,33 @@ void LLViewerObject::fetchInventoryFromServer()
         delete mInventory;
         mInventory = NULL;
 
-        // Results in processTaskInv
-        LLMessageSystem* msg = gMessageSystem;
-        msg->newMessageFast(_PREHASH_RequestTaskInventory);
-        msg->nextBlockFast(_PREHASH_AgentData);
-        msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
-        msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
-        msg->nextBlockFast(_PREHASH_InventoryData);
-        msg->addU32Fast(_PREHASH_LocalID, mLocalID);
-        msg->sendReliable(mRegionp->getHost());
-
         // This will get reset by doInventoryCallback or processTaskInv
         mInvRequestState = INVENTORY_REQUEST_PENDING;
+
+        if (mRegionp && !mRegionp->getCapability("RequestTaskInventory").empty())
+        {
+            LLCoros::instance().launch("LLViewerObject::fetchInventoryFromCapCoro()",
+                                       boost::bind(&LLViewerObject::fetchInventoryFromCapCoro, mID));
+        }
+        else
+        {
+            LL_WARNS() << "Using old task inventory path!" << LL_ENDL;
+            // Results in processTaskInv
+            LLMessageSystem *msg = gMessageSystem;
+            msg->newMessageFast(_PREHASH_RequestTaskInventory);
+            msg->nextBlockFast(_PREHASH_AgentData);
+            msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
+            msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
+            msg->nextBlockFast(_PREHASH_InventoryData);
+            msg->addU32Fast(_PREHASH_LocalID, mLocalID);
+            msg->sendReliable(mRegionp->getHost());
+        }
     }
 }
 
 void LLViewerObject::fetchInventoryDelayed(const F64 &time_seconds)
 {
-    // unless already waiting, drop previous request and shedule an update
+    // unless already waiting, drop previous request and schedule an update
     if (mInvRequestState != INVENTORY_REQUEST_WAIT)
     {
         if (mInvRequestXFerId != 0)
@@ -3138,6 +3147,80 @@ void LLViewerObject::fetchInventoryDelayedCoro(const LLUUID task_inv, const F64
     }
 }
 
+//static
+void LLViewerObject::fetchInventoryFromCapCoro(const LLUUID task_inv)
+{
+    LLViewerObject *obj = gObjectList.findObject(task_inv);
+    if (obj)
+    {
+        LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
+        LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
+                                   httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("TaskInventoryRequest", httpPolicy));
+        LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
+        std::string url = obj->mRegionp->getCapability("RequestTaskInventory") + "?task_id=" + obj->mID.asString();
+        // If we already have a copy of the inventory then add it so the server won't re-send something we already have.
+        // We expect this case to crop up in the case of failed inventory mutations, but it might happen otherwise as well.
+        if (obj->mInventorySerialNum && obj->mInventory)
+            url += "&inventory_serial=" + std::to_string(obj->mInventorySerialNum);
+
+        obj->mInvRequestState = INVENTORY_XFER;
+        LLSD result = httpAdapter->getAndSuspend(httpRequest, url);
+
+        LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+        LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+
+        // Object may have gone away while we were suspended, double-check that it still exists
+        obj = gObjectList.findObject(task_inv);
+        if (!obj)
+        {
+            LL_WARNS() << "Object " << task_inv << " went away while fetching inventory, dropping result" << LL_ENDL;
+            return;
+        }
+
+        bool potentially_stale = false;
+        if (status)
+        {
+            // Dealing with inventory serials is kind of funky. They're monotonically increasing and 16 bits,
+            // so we expect them to overflow, but we can use inv serial < expected serial as a signal that we may
+            // have mutated the task inventory since we kicked off the request, and those mutations may have not
+            // been taken into account yet. Of course, those mutations may have actually failed which would result
+            // in the inv serial never increasing.
+            //
+            // When we detect this case, set the expected inv serial to the inventory serial we actually received
+            // and kick off a re-request after a slight delay.
+            S16 serial = (S16)result["inventory_serial"].asInteger();
+            potentially_stale = serial < obj->mExpectedInventorySerialNum;
+            LL_INFOS() << "Inventory loaded for " << task_inv << LL_ENDL;
+            obj->mInventorySerialNum = serial;
+            obj->mExpectedInventorySerialNum = serial;
+            obj->loadTaskInvLLSD(result);
+        }
+        else if (status.getType() == 304)
+        {
+            LL_INFOS() << "Inventory wasn't changed on server!" << LL_ENDL;
+            obj->mInvRequestState = INVENTORY_REQUEST_STOPPED;
+            // Even though it wasn't necessary to send a response, we still may have mutated
+            // the inventory since we kicked off the request, check for that case.
+            potentially_stale = obj->mInventorySerialNum < obj->mExpectedInventorySerialNum;
+            // Set this to what we already have so that we don't re-request a second time.
+            obj->mExpectedInventorySerialNum = obj->mInventorySerialNum;
+        }
+        else
+        {
+            // Not sure that there's anything sensible we can do to recover here, retrying in a loop would be bad.
+            LL_WARNS() << "Error status while requesting task inventory: " << status.toString() << LL_ENDL;
+            obj->mInvRequestState = INVENTORY_REQUEST_STOPPED;
+        }
+
+        if (potentially_stale)
+        {
+            // Stale? I guess we can use what we got for now, but we'll have to re-request
+            LL_WARNS() << "Stale inv_serial? Re-requesting." << LL_ENDL;
+            obj->fetchInventoryDelayed(INVENTORY_UPDATE_WAIT_TIME_OUTDATED);
+        }
+    }
+}
+
 LLControlAvatar *LLViewerObject::getControlAvatar()
 {
     return getRootEdit()->mControlAvatar.get();
@@ -3322,6 +3405,20 @@ void LLViewerObject::processTaskInv(LLMessageSystem* msg, void** user_data)
     S16 serial = 0;
     msg->getS16Fast(_PREHASH_InventoryData, _PREHASH_Serial, serial);
 
+    if (object->mRegionp && !object->mRegionp->getCapability("RequestTaskInventory").empty())
+    {
+        // It seems that simulator may ask us to re-download the task inventory if an update to the inventory
+        // happened out-of-band while we had the object selected (like if a script is saved.)
+        //
+        // If we're meant to use the HTTP capability, ignore the contents of the UDP message and fetch the
+        // inventory via the CAP so that we don't flow down the UDP inventory request path unconditionally here.
+        // We shouldn't need to wait, as any updates should already be ready to fetch by this point.
+        LL_INFOS() << "Handling unsolicited ReplyTaskInventory for " << task_id << LL_ENDL;
+        object->mExpectedInventorySerialNum = serial;
+        object->fetchInventoryFromServer();
+        return;
+    }
+
     if (serial == object->mInventorySerialNum
         && serial < object->mExpectedInventorySerialNum)
     {
@@ -3529,6 +3626,47 @@ BOOL LLViewerObject::loadTaskInvFile(const std::string& filename)
     return TRUE;
 }
 
+void LLViewerObject::loadTaskInvLLSD(const LLSD& inv_result)
+{
+    if (inv_result.has("contents"))
+    {
+        if(mInventory)
+        {
+            mInventory->clear(); // will deref and delete it
+        }
+        else
+        {
+            mInventory = new LLInventoryObject::object_list_t;
+        }
+
+        // Synthesize the "Contents" category, the viewer expects it, but it isn't sent.
+        LLPointer<LLInventoryObject> inv = new LLInventoryObject(mID, LLUUID::null, LLAssetType::AT_CATEGORY, "Contents");
+        mInventory->push_front(inv);
+
+        const LLSD& inventory = inv_result["contents"];
+        for (const auto& inv_entry : llsd::inArray(inventory))
+        {
+            if (inv_entry.has("item_id"))
+            {
+                LLPointer<LLViewerInventoryItem> inv = new LLViewerInventoryItem;
+                inv->unpackMessage(inv_entry);
+                mInventory->push_front(inv);
+            }
+            else
+            {
+                LL_WARNS_ONCE() << "Unknown inventory entry while reading from inventory file. Entry: '"
+                                << inv_entry << "'" << LL_ENDL;
+            }
+        }
+    }
+    else
+    {
+        LL_WARNS() << "unable to load task inventory: " << inv_result << LL_ENDL;
+        return;
+    }
+    doInventoryCallback();
+}
+
 void LLViewerObject::doInventoryCallback()
 {
     for (callback_list_t::iterator iter = mInventoryCallbacks.begin();
diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h
index c5176503eb964855db62f477709fa628616f8b2b..4e002560a28677ea2842fe63b368d00068b48a75 100644
--- a/indra/newview/llviewerobject.h
+++ b/indra/newview/llviewerobject.h
@@ -730,6 +730,7 @@ class LLViewerObject
     // forms task inventory request after some time passed, marks request as pending
     void fetchInventoryDelayed(const F64 &time_seconds);
     static void fetchInventoryDelayedCoro(const LLUUID task_inv, const F64 time_seconds);
+    static void fetchInventoryFromCapCoro(const LLUUID task_inv);
 
 public:
     //
@@ -861,6 +862,7 @@ class LLViewerObject
 
     static void processTaskInvFile(void** user_data, S32 error_code, LLExtStat ext_status);
     BOOL loadTaskInvFile(const std::string& filename);
+    void loadTaskInvLLSD(const LLSD &inv_result);
     void doInventoryCallback();
 
     BOOL isOnMap();
diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp
index 54f0ea3d5d8c4cdc7a90de3ea52d67ff40659348..4a8acf74cb7fd3dd08d513e2697b570d2eaaec7e 100755
--- a/indra/newview/llviewerregion.cpp
+++ b/indra/newview/llviewerregion.cpp
@@ -3400,6 +3400,7 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames)
         capabilityNames.append("FetchInventory2");
         capabilityNames.append("FetchInventoryDescendents2");
         capabilityNames.append("IncrementCOFVersion");
+        capabilityNames.append("RequestTaskInventory");
         AISAPI::getCapNames(capabilityNames);
     }