diff --git a/src/js/findSlave.js b/src/js/findSlave.js
new file mode 100644
index 0000000000000000000000000000000000000000..0e5b447910136478d2e386764ad3ec676c1fd9c9
--- /dev/null
+++ b/src/js/findSlave.js
@@ -0,0 +1,59 @@
+App.FindSlave = {};
+
+/**
+ * Fragment searching: See if every needle can found somewhere in the field of haystacks
+ * @param {string[]} haystacks
+ * @param {RegExp[]} needles
+ * @returns {boolean}
+ */
+App.FindSlave._fragmentSearch = function(haystacks, needles) {
+    const hs = haystacks.join(" ");
+    return needles.every((needle) => { return needle.test(hs); });
+};
+
+/**
+ * Get slave indices which match a predicate
+ * @param {function} predicate
+ * @returns {number[]}
+ */
+App.FindSlave._slaveIndices = function(predicate) {
+    return V.slaves.reduce((acc, slave, ind) => {
+        if (predicate(createReadonlyProxy(slave))) {
+            acc.push(ind);
+        }
+        return acc;
+    }, []);
+};
+
+/**
+ * Generate a slave list as the result of fragment searching all the name-type fields
+ * @param {string} query
+ * @returns {DocumentFragment}
+ */
+App.FindSlave.searchByName = function(query) {
+    const needles = query.split(" ").map((needle) => { return new RegExp(needle, "i"); });
+    const indices = this._slaveIndices((slave) => { return this._fragmentSearch([slave.slaveName, slave.slaveSurname, slave.birthName, slave.birthSurname], needles); });
+    return App.UI.SlaveList.render.listDOM(indices, [], App.UI.SlaveList.SlaveInteract.stdInteract);
+};
+
+/**
+ * Generate a slave list as the result of fragment searching profession and origin
+ * @param {string} query
+ * @returns {DocumentFragment}
+ */
+App.FindSlave.searchByBackground = function(query) {
+    const needles = query.split(" ").map((needle) => { return new RegExp(needle, "i"); });
+    const indices = this._slaveIndices((slave) => { return this._fragmentSearch([slave.career, slave.origin], needles); });
+    return App.UI.SlaveList.render.listDOM(indices, [], App.UI.SlaveList.SlaveInteract.stdInteract);
+};
+
+/**
+ * Generate a slave list as the result of evaluating an expression
+ * @param {string} query
+ * @returns {DocumentFragment}
+ */
+App.FindSlave.searchByExpression = function(query) {
+    const pred = new Function("slave", "return (" + query + ");");
+    const indices = runWithReadonlyProxy(() => { return this._slaveIndices(pred); });
+    return App.UI.SlaveList.render.listDOM(indices, [], App.UI.SlaveList.SlaveInteract.stdInteract);
+};
diff --git a/src/uncategorized/findSlave.tw b/src/uncategorized/findSlave.tw
new file mode 100644
index 0000000000000000000000000000000000000000..991d56cb61dea865aa93a15d36472e3396e5e589
--- /dev/null
+++ b/src/uncategorized/findSlave.tw
@@ -0,0 +1,32 @@
+:: Find Slave [nobr]
+
+<<set $nextButton = "Back to Main", $nextLink = "Main", $showEncyclopedia = 0>>
+
+After spending a minute trying to remember some details about one of your slaves, you sit down behind your desk and tell $assistantName that you need to locate a particular slave's records.<br><br>
+
+"Certainly, <<= properMaster()>>.  What can you tell me about them?"<br><br>
+
+"They're called something like:
+<<textbox "_nameSearch" "" autofocus>>
+<<link "Locate">>
+    <<script>>$('#slaveList').empty().append(App.FindSlave.searchByName(State.temporary.nameSearch));<</script>>
+<</link>>
+<br>//(Enter a fragment of their nickname, name, surname, birth name, or birth surname)//<br><br>
+
+"In the past, they were:
+<<textbox "_backgroundSearch" "">>
+<<link "Locate">>
+    <<script>>$('#slaveList').empty().append(App.FindSlave.searchByBackground(State.temporary.backgroundSearch));<</script>>
+<</link>>
+<br>//(Enter a fragment of their origin or past job, for example, "shelter" or "lawyer")//<br><br>
+
+"Their data should meet this condition:
+<<textbox "_dataSearch" "">>
+<<link "Locate">>
+     <<script>>$('#slaveList').empty().append(App.FindSlave.searchByExpression(State.temporary.dataSearch));<</script>>
+<</link>>
+<br>//(Enter a conditional expression which evaluates to true for the slave you want to find, such as "slave.physicalAge >= 18 && slave.physicalAge < 21")// <br><br>
+
+<span id="slaveList">
+    /* results list gets populated here by jQuery */
+</span>
diff --git a/src/uncategorized/storyCaption.tw b/src/uncategorized/storyCaption.tw
index 2b58dccd294e4df2728f0471ea874f7b7a0438b0..ae1184e974f4ed04861c314c488080a8831e1db4 100644
--- a/src/uncategorized/storyCaption.tw
+++ b/src/uncategorized/storyCaption.tw
@@ -539,6 +539,12 @@
 			<<if ($corpSpecToken > 0) && ($corpSpecTimer == 0)>>@@.yellow;[!]@@<</if>>
 		<</if>>
 
+		<<if _Pass != "Find Slave">>
+			<span id="findSlave"> <br>
+				<<link [[Locate Slave|Find Slave]]>><</link>>
+			</span>
+		<</if>>
+
 		<<if $sideBarOptions.compact === 0 && _Pass === "Main" || $sideBarOptions.compact === 1 && _Pass === "Manage Arcology">> <br>
 			<<if $FSAnnounced>>
 				<span id="FSButton"> <br>