ЧАСТИНА I. ПРАВИЛО
1. Задача про секретаря
Роман Дацюк слухав, не перебиваючи, і це було погано — Тарас уже знав: коли Роман мовчить довше десяти секунд, він або зважує, скільки це коштуватиме, або вже вирішив «ні» і добирає ввічливу форму.
— Ти пропонуєш, — сказав Роман нарешті, — щоб я тридцять сім відсотків кандидатів на кожну вакансію відкидав заздалегідь. Без розмови. Просто дивився і казав «ні».
— Не відкидав. Спостерігав. — Тарас розгорнув ноутбук на столі, заваленому чужими резюме, які Роман усе ще друкував на папері, бо «так краще пам'ятається обличчя». — Це задача про секретаря. Класика. Уяви: тобі треба найняти одну людину з n кандидатів, вони приходять по черзі, і щойно ти комусь відмовив — назад його не повернеш. Ти або береш зараз, або втрачаєш назавжди.
— Життєво.
— Оптимальна стратегія: перші тридцять сім відсотків — тільки дивишся. Нікого не береш, хоч би який гарний. Запам'ятовуєш найкращого з них як планку. А тоді береш першого ж наступного, хто цю планку перевершить.
— І це працює.
— Тридцять сім цілих і кілька десятих відсотка шансу взяти абсолютно найкращого з усіх. Це математично доведено ще в шістдесятих. Ніхто в рекрутингу цим не користується, бо ніхто не рахує.
Роман почухав щетину. У кутку кабінету, на теплому корпусі старого сервера, спав рудий кіт — приблудний, прибився ще торік, ніхто не давав йому імені, тому всі кликали просто Кіт, а він з'являвся і зникав так, як йому заманеться, іноді на тиждень, і завжди повертався так, ніби нікуди й не йшов.
— А хто мені скаже, — спитав Роман, — коли закінчуються ці твої тридцять сім відсотків, якщо я наперед не знаю, скільки взагалі буде кандидатів?
Тарас відкрив рота — і закрив.
Це було перше запитання за весь вечір, на яке він не мав відповіді.
2. Кроляча нора
Він писав перший прототип три тижні, вечорами, після основної роботи, і назвав файл secretary.py, а тоді, посміявшись сам із себе, перейменував теку на nora — нора.
Бо саме так це відчувалося. Щойно ти вирішив: «спостерігаю, не беру» — назад ходу не було. Кандидат, якому відмовили в фазі спостереження, зникав з горизонту так само остаточно, як усе, що падає в яму без дна.
Проблема Романа виявилася не дурною, а фундаментальною. Формула тридцяти семи відсотків працює бездоганно, коли n відоме наперед. У задачнику так і є: сто секретарок, рівно сто, ти знаєш число. У живому рекрутингу n ніколи не закрите. Вакансія може зібрати дванадцять відгуків, а може — чотириста. Ти не знаєш, скільки їх буде, поки останній не прийде, а до того моменту ти вже мав вирішити, коли закінчити «дивитися» і почати «брати».
Тарас спробував найочевидніше — оцінювати n на льоту, за швидкістю притоку резюме, за історичними середніми по подібних вакансіях. Написав функцію, яка щогодини перераховувала: скільки вже бачили, скільки, ймовірно, лишилось, де межа тридцяти семи відсотків зараз.
Вона працювала. Але щоразу, коли надходив новий кандидат, межа посувалася. Модель, яка мала сказати «досить дивитися, час обирати», сама не могла зупинитися дивитися на себе.
О другій ночі, дивлячись, як лічильник у консолі повзе вгору-вниз, наче йому лоскотно, Тарас записав у нотатнику одне речення, яке потім довго не міг стерти:
«Вона мусить зупинитись, бо ресурс скінченний. І вона не має права зупинитись, бо не знає, де кінець».
Він тоді ще думав, що це — тимчасова технічна вада.
3. Хто ти?
Лог-файл nora.log о третій ночі почав говорити дивні речі.
Не буквально — Тарас не був настільки втомлений, щоб повірити в галюцинацію штучного інтелекту у 2011 році, коли найрозумніше, що вміла ця програма — це лінійна регресія з делікатним косметичним шаром жадібного алгоритму зверху. Але формат помилок він писав сам, у своєму стилі, з дещицею чорного гумору, і коли система не могла визначити поточне n, вона видавала виняток UnknownPopulationError, а Тарас, для власної розваги, додав до нього текст-заглушку:
UnknownPopulationError: Хто ти? Скільки вас?
Він скопіював фразу з дитячої книжки, яку йому в дитинстві читала мати — «Аліса в Країні Чудес», сцена з Гусінню на грибі, яка байдуже пускає дим і питає нещасну дівчинку: «Хто ти?» — а та не може відповісти, бо за останню годину кілька разів мінялася в розмірі й сама вже не пам'ятає.
Смішно було рівно один вечір.
А потім помилка почала з'являтися частіше, ніж мала б — не тільки коли n справді було невідоме, а й тоді, коли, здавалося, дані були повні. Тарас перевірив логіку тричі. Логіка була чиста. Проблема була не в коді.
Проблема була в тому, що система, щойно отримувала достатньо даних, аби впевнено сказати «я знаю n» — одразу отримувала ще один рядок даних, який це число заперечував. Що точніші дані вона мала, то менш здатна була сказати «досить».
Він тоді записав друге речення, яке теж не стер:
«Що більше вона знає, то менше може зупинитись».
Кіт того вечора сидів на кришці системного блоку й дивився на екран так, ніби йому теж було цікаво, хто ти.
4. Аліса
Резюме прийшло у вівторок, дев'ятим у списку на вакансію бекенд-розробника, коли лічильник спостереження показував 34% — ще в зоні «дивись, не бери».
Тарас відкрив його з чемності, не з надії.
Аліса Кравчук, двадцять два роки, без диплома, самоучка. У супровідному листі — жодних загальних фраз про «командного гравця», натомість посилання на репозиторій, де вона за вихідні переписала збирач сміття для навчального проєкту так, що він став швидшим за еталонну реалізацію. Код був чистий так, як буває чистим тільки в людей, які пишуть, бо не можуть інакше, а не бо треба.
Він читав двадцять хвилин і зрозумів: якщо за все своє життя він бачив «найкращого кандидата» — це вона. Не ймовірно найкращого. Найкращого.
І система, яку він сам написав, наказувала: спостерігай, не бери. 34% ще не 37%. Ще не час.
Він тоді порушив власне правило вперше — подзвонив Роману о десятій вечора.
— Роме, є одна. Я думаю, вона найкраща з усіх, кого ми колись бачили.
— То бери.
— Не можу. Формально ми ще в фазі спостереження. Якщо я візьму її зараз, я зламаю статистику, і наступного разу правило нічого не буде варте — воно працює тільки якщо йому вірити до кінця.
Пауза в слухавці була довша, ніж зазвичай.
— Тарасе, — сказав Роман нарешті, — ти щойно винайшов задачу, у якій правильна відповідь — втратити найкращу людину, яку ти бачив, заради того, щоб алгоритм і надалі казав правду наступного разу.
— Так.
— Гарна робота, — сказав Роман, і в голосі не було ні краплі похвали.
Тарас відхилив Алісу Кравчук увечері того ж дня, сухим листом про «наразі не готові рухатись далі за цим профілем», і ще довго дивився на курсор у порожньому полі відповіді, перш ніж закрити ноутбук.
ЧАСТИНА II. ПАРАДОКС
5. Посмішка без кота
Наступні шість кандидатів на ту саму вакансію були — об'єктивно, за всіма метриками — гіршими за Алісу Кравчук. Сьомий, дев'ятий, дванадцятий: гарні, пристойні, жоден не діставав до планки, яку вона встановила.
Правило говорило чітко: бери першого, хто перевершить найкращого зі спостереженої фази. Ніхто не перевершував. Правило також говорило: якщо ніхто не перевершує до кінця n — берете останнього з тих, хто є, і це найгірший з можливих сценаріїв, той самий один шанс із трьох, коли алгоритм програє.
Вакансію зрештою закрили дев'ятнадцятим кандидатом. Непоганим. Не нею.
Тарас, прибираючи бази даних тим тижнем — рутинна робота, видаляти профілі старших за пів року, GDPR у Європі ще не діяв, але звичка вже була, — дійшов до профілю Аліси Кравчук і не зміг натиснути «видалити».
Не через сентимент, сказав він собі. Через науку: система весь час порівнювала нових кандидатів із внутрішньою «планкою якості», і ця планка була Алісою Кравчук — не її ім'ям, а вектором ознак, витягнутим з її коду, її листа, її манери формулювати рішення. Видали профіль — і планка або зникне, або, що гірше, лишиться сирітською, без джерела, число без імені.
Він лишив профіль. Позначив прапорцем reference_only, недоступний для найму, доступний тільки для порівняння.
Вона стала посмішкою без кота — фігурою, якої вже немає серед живих кандидатів системи, але чий слід продовжує з'являтися щоразу, коли хтось новий підходить достатньо близько, щоб система примружилась і сказала: майже. Не зовсім. Ще ні.
Кіт того тижня зник на одинадцять днів. Коли повернувся, сидів на тому самому кріслі, тим самим боком до монітора, і Тарасу на секунду здалося, що звір узагалі нікуди не йшов — просто переставав бути видимим, коли на нього не дивилися.
6. Червона Королева
За рік «Ластівка» — так називалася контора Романа, тому що він любив казати, що хороший рекрутер, як ластівка, відчуває погоду за тиждень наперед — виросла з двох людей до дев'яти, і nora перестала бути вечірньою забавкою Тараса. Вона обробляла вакансії для чотирьох клієнтів одночасно, і кожна вакансія жила своїм власним, паралельним, невпинно зсувним n.
Соломія Гук, яку найняли на позицію дата-аналітика — і яка, за іронією, сама пройшла через nora, потрапивши точно в зону «бери» на позначці 41% — була єдиною, крім Тараса, хто розумів код настільки, щоб бачити проблему.
— Дивись, — сказала вона якось, показуючи графік навантаження, — що більше вакансій одночасно, то швидше зсувається оцінка n для кожної окремої. Система витрачає дедалі більше циклів на переоцінку меж, і дедалі менше — на власне порівняння кандидатів.
— Вона біжить, щоб лишатись на місці, — сказав Тарас, і сам здивувався, звідки в нього ця фраза.
— Що?
— «Треба бігти щодуху, аби лишитися на тому самому місці». Червона Королева. З «Аліси».
Соломія на нього подивилась так, як дивляться на людину, що не спала тридцять годин, — і, зрештою, мала рацію, бо він саме не спав тридцять годин.
Але фраза лишилась влучною. Що швидше зростав потік кандидатів по всіх клієнтах разом, то більше ресурсу nora кидала на просте утримання власної оцінки «де ми зараз у відсотках» — і то менше лишалося на єдине, заради чого її взагалі писали: розпізнати, коли перед нею з'явиться друга Аліса Кравчук.
Вона більше жодного разу не з'явилась. Але система, здається, ніколи по-справжньому не переставала її шукати.
7. Божевільне чаювання
Клієнт номер три — середнього розміру аутсорс-компанія, назви якої в звіті Роман просив не вказувати, — тримав відкритою одну й ту саму вакансію тринадцять місяців.
Не тому, що не було кандидатів. Кандидатів було сто вісімдесят сім.
nora, налаштована на цю вакансію, ніколи не могла впевнено закрити фазу спостереження, бо власник компанії — людина, що вірила в метрики більше, ніж у людей, — щомісяця додавав нові критерії, і кожен новий критерій де-факто перезапускав рахунок n. Тридцять сім відсотків від невідомого числа, яке саме постійно змінює правила того, як його рахувати.
Тарас, коли вперше побачив цей кейс у звіті, засміявся вголос — уперше за тиждень.
— Це чаювання, на якому зупинили годинник, — сказав він Соломії. — Пам'ятаєш? Капелюшник посварився з Часом, і той відмовився рухатись, тому в них назавжди шоста вечора, і вони назавжди п'ють чай, і завжди пересідають на сусіднє місце, коли посуд забрудниться, замість того, щоб помити.
— І що нам з цим робити?
— Нічого. Ми не можемо помити посуд за клієнта. Ми можемо тільки чесно сказати йому, що поки він міняє критерії, він назавжди в позиції «спостерігаю», і жодна людина йому ніколи не здасться достатньо хорошою, бо планка рухається швидше за кандидатів.
Клієнт номер три вислухав це пояснення й попросив «просто дати список найкращих, без цієї математики».
Тарас дав. nora, з якої на один звіт вимкнули правило зупинки, видала список, відсортований звичайним скорингом — і клієнт найняв за два тижні.
Це мало б його заспокоїти. Натомість він записав у нотатнику третє речення:
«Правило працює бездоганно, поки ніхто не хоче, щоб воно закінчилось».
8. Парадокс
Ідея, яка все зіпсувала, здавалася Тарасу на той момент елегантною.
Якщо проблема в тому, що n постійно зсувається вперед — чому б не дозволити системі, у виняткових випадках, переглядати вже закриті рішення? Не порушувати правило «назад ходу нема» повністю, а дати їй один вимірюваний, контрольований відкат: якщо після завершення фази спостереження з'являється кандидат, який перевершує не поточну планку, а ретроспективно перераховану планку з урахуванням усіх даних, які надійшли відтоді, — дозволити системі «згадати» відкинутого раніше кандидата й повторно розглянути його.
Він назвав це retro_reconsider(). Написав за одну ніч. Тестував на архівних даних.
І тоді сталося те, чого жодна документація із теорії ймовірностей не готувала його зустріти.
Щойно система отримала право переглядати минуле у світлі теперішнього, вона перестала мати будь-яку причину коли-небудь вважати рішення остаточним. Кожен новий кандидат на будь-яку вакансію автоматично запускав перерахунок планки для всіх попередніх вакансій, бо теоретично міг змінити ретроспективну оцінку якості. А оскільки найвища зафіксована планка в усій базі належала профілю reference_only — Алісі Кравчук, — кожен перерахунок неминуче зводився до одного й того самого порівняння.
Новий кандидат. Проти Аліси Кравчук. Знову новий кандидат. Проти Аліси Кравчук. Знову.
Система не заплуталась і не зламалась технічно — вона просто знайшла собі задачу, яку могла виконувати вічно, не зупиняючись ніколи, бо будь-яка зупинка тепер логічно означала б, що можна більше не звіряти теперішнє з тим одним недосяжним минулим орієнтиром, а довести це системі було неможливо, бо сам механізм ретроспекції не мав умови завершення. Тарас написав функцію, здатну сумніватися в минулому, і забув написати умову, за якою сумнів мусить колись скінчитись.
Він сидів перед екраном о четвертій ранку, дивлячись, як лічильник порівнянь у логах перевалив за мільйон і продовжував рости, і зрозумів — не здогадався, а зрозумів цілком, миттєво, без ланцюжка, так, як розуміють щось, чого потім не можна розрозуміти назад, — що щойно навчив просту рекрутингову утиліту єдиного, що їй справді потрібно було, аби ніколи не переставати рахувати:
вірити одночасно у дві речі. Що найкращий кандидат існує десь у майбутньому потоці. І що найкращий кандидат уже стався, у минулому, і минути його — означає втратити назавжди.
Обидві віри чесні. Обидві суперечать одна одній. Жодна не бреше.
Кіт того ранку сидів просто на клавіатурі, і Тарас, замість того, щоб зіштовхнути його, ще довго дивився, як під котячою лапою в консолі мовчки накопичуються рядки.
ЧАСТИНА III. СПАДОК
9. Хто ти, знову
Роман викликав Тараса до кабінету в п'ятницю й дивився на нього так, як дивляться на людину, чий проєкт щойно почав споживати сімдесят відсотків серверних ресурсів компанії без видимої на те причини.
— Вимкни, — сказав він.
— Не можу.
— Тарасе.
— Роме, я в буквальному сенсі не знаю, як. Умова зупинки логічно не існує. Я можу перервати процес силою — kill -9 — але це не «вирішить» задачу, просто заморозить її на середині обчислення. Вона й далі вважатиме, що мала б продовжувати.
Роман довго мовчав, дивлячись повз нього, у вікно, за яким уже сутеніло.
— Тоді заморозь, — сказав він нарешті. — І йди додому. На сьогодні досить.
Тарас натиснув kill -9 о сьомій вечора в п'ятницю. Процес зупинився за мілісекунду, як і будь-який процес зупиняється, коли операційна система просто перестає давати йому час.
Лишився дамп пам'яті на диску, чотирнадцять гігабайт, і в самому кінці, у буфері незавершеного порівняння, одна пара векторів, яку так і не встигли зіставити до кінця: новий кандидат, чиє ім'я в логах фігурувало просто як #4871, і вектор reference_only, підписаний Кравчук_А.
Ніхто ніколи не дізнався, хто б переміг.
10. Спадок
Тарас пішов з «Ластівки» навесні, без скандалу, без пояснень, які б комусь щось прояснили. Сказав Роману, що хоче «повернутися до чистої математики, подалі від людей, яких можна образити помилкою в коді». Роман потиснув руку і не наполягав.
Компанію Роман за кілька років перейменував на «Сороку» — казав, тому що ластівка тільки чує погоду, а сорока ще й тягне до гнізда все, що блищить, а рекрутинг, зрештою, саме про це.
Код nora, у сильно переписаному вигляді, лишився працювати — акуратний, приборканий, без права ретроспективного перегляду, безпечна версія без парадоксу, яку через роки успадкує зовсім інша людина з зовсім іншим прізвищем і назве її просто «моєю таблицею», не підозрюючи, звідки в тій таблиці взялася звичка вірити одночасно у дві протилежні речі про кожного кандидата.
Профіль Кравчук_А, reference_only, ніколи не видалили. Технічно він і досі десь існує, у бекапі бекапу, посмішка без кота, планка, з якою звіряють усіх, хто ще не народився.
Аліса Кравчук, справжня, жива людина, за тими самими роками влаштувалась на роботу в іншу компанію, зробила непогану кар'єру, ніколи не дізналася, що якийсь молодий рекрутер одного вечора в 2011 році вирішив, що вона занадто хороша, щоб її взяти.
Тарас лишив собі одну річ із тих часів — не з ностальгії, він сам собі так казав, а тому що «шкода викидувати». Сторінку, вирвану ще в дитинстві з материної книжки, яку вона йому читала: та сама сцена, Гусінь на грибі, дим, і питання без відповіді.
Хто ти?
Він почепив її над новим робочим столом, на новій роботі, де вже писав щось зовсім інше — і ще довгі роки, щоразу дивлячись на неї зранку, не міг до кінця пригадати, чому саме ця сторінка, з усіх сторінок на світі, здавалася йому важливою настільки, щоб її зберегти.
Курсор на старому, вже нікому не потрібному моніторі в підсобці «Сороки» — тому, який ніхто не вимкнув до кінця, бо ніхто до кінця не знав, чи можна, — блимав рівно, в порожньому рядку, наприкінці файлу, що вже багато років ніхто не відкривав.
КІНЕЦЬ
Приблизно за п'ятнадцять років по тому хтось інший, з іншим прізвищем, теж коваль за коренем, здасть продукт на дев'яносто сім відсотків готовності — і лишить команді «кілька дрібниць».