vacancy-radar — хранилище откликов на вакансии, которое целиком лежит у вас на диске: обычные markdown-файлы, гибридный поиск (по словам и по смыслу) и MCP-сервер, через который языковая модель читает и пишет в него напрямую.
Я веду учёт своих откликов больше десяти лет — и с каждым разом всё менее криво. Это история о том, как привычка превратилась в небольшой проект с открытым исходным кодом.
История
2012: откликаться на всё подряд
Сразу после университета я откликался на каждую вакансию, которая казалась интересной. В 2012-м интересных позиций в больших компаниях было немного, зато вокруг хватало мест, где можно было применить свои навыки. Казалось, что если тебе нужна работа — ты её найдёшь. Просто это будет не работа мечты.
Повторный отклик
Позже я стал откликаться только туда, где мог получить что-то новое для себя. Во время одного из поисков по конкретному стеку рекрутер ответил не по шаблону. Обычный ответ звучал примерно так:
«К сожалению, сейчас у нас нет позиции для вас, мы свяжемся с вами позже».
А этот написал:
«Вы уже откликались на эту позицию».
Вот тогда до меня и дошло. Без учёта того, что уже отправлено, я откликался в одни и те же компании по нескольку раз — а рекрутер, который видит пачку откликов от одного человека, скорее всего просто идёт дальше.
Память сайта вместо собственной
Почти всё, что я отправлял в те годы, шло через один сайт с вакансиями, так что я мог хотя бы посмотреть историю откликов по каждому своему резюме. Это и была моя система учёта: площадка помнила всё за меня.
2019: таблица
Работало, пока интересные вакансии не начали появляться на других сайтах. Одна и та же компания могла висеть сразу на нескольких площадках, а иногда хотелось откликнуться напрямую через сайт компании. Так в 2019-м я завёл документ .ods (то же самое, что Excel, только в LibreOffice) с четырьмя колонками: название компании, ссылка на вакансию, дата отклика и свободный комментарий в колонке «Стадия» — про то, что там сейчас происходит.
Цветовая разметка позволяла понять состояние каждой строки с одного взгляда:
- Белый — только откликнулся, новостей нет (цвет по умолчанию)
- Жёлтый — жду ответ
- Фиолетовый — не интересны
- Синий — требуются мои действия
- Зелёный — вроде бы всё хорошо, но нужны согласования
- Красный — отказ
- Тёмно-оливковый — оффер, а после уточнений отказ
2024: подключение языковых моделей
В 2024-м я начал использовать языковые модели, чтобы разбирать описания вакансий и писать сопроводительные письма. Они же взяли на себя заполнение 100500 полей в самодельных анкетах, которые некоторые компании обязательно требуют заполнить. Это был первый шаг к тому, чтобы встроить ИИ в поиск работы — и момент, когда таблица начала выглядеть слабым звеном.
Где таблица перестала справляться
Диффы, которые невозможно читать (2026)
В 2026-м захотелось встроить ИИ в процесс плотнее, и я добавил скилл для Claude Code, который читает и обновляет таблицу напрямую. Тогда же я перевёл файл из .ods в .fods: плоский XML заметно лучше диффится и сжимается в git, чем зазипованный бинарник .ods, а работа в LibreOffice при этом никак не меняется.
Размен оказался не бесплатным. Один отклик всё равно может превратиться в дифф на 800 строк, потому что LibreOffice при открытии и сохранении переписывает описания стилей по всему документу, даже если визуально ничего не изменилось.
Некуда складывать подробности
Через какое-то время стало ясно, что хранить хочется куда больше: переписку, тестовые задания, кто и что говорил на созвонах. В свободный комментарий в колонке «Стадия» это не помещается.
А когда такие подробности где-то лежат, по ним можно заранее прикидывать, стоит ли вообще откликаться: где я не дотягиваю по стеку, а где в самом описании что-то не сходится. Через независимых рекрутеров и кадровые агентства я не устроился ни разу — такие отклики хотелось бы отмечать сразу, а не замечать подвох каждый раз заново.
Привязка к формату
И не хотелось застревать. Что бы я ни выбрал дальше, нужен был понятный путь уйти на что-то другое, если появится вариант получше.
Чего хотелось вместо этого
- Читаемые файлы — данные должны быть понятны человеку в любой момент, а не заперты в закрытой БД
- Поиск — возможность найти что-то конкретное по всему архиву: например, рекрутера по имени, даже если человек сменил компанию
- Простота — не больше механики, чем требует задача
- Не скучно — мне должно хотеться этим пользоваться, а значит это не должно быть просто ещё одной БД
- Без вендор-лока — менять технологии и поставщиков языковых моделей когда захочу
Как это делалось
Одно разделение я знал заранее: код отдельно, данные отдельно. Командная строка, будущий MCP-сервер, логика поиска и ранжирования — всё это должно работать с любым хранилищем, а само хранилище должно оставаться просто файлами.
Первая итерация: RAG, LangChain и веб-интерфейс
Я не понимал, как именно хочу работать с этими данными, поэтому начал не с задачи, а с формы: загружаем вакансии, посередине ставим ChromaDB, сверху — командная строка над файлами, а над ней веб-интерфейс. Эта итерация лежит в ветке langchain-rag.
По ходу дела я переехал на LangChain, чтобы проще менять модели и алгоритмы. В планах был A2UI (Agent-to-User Interface), а собирать большую часть всего этого должен был spec-kit.
Чему научила первая итерация
Становилось сложнее. Потом ещё сложнее. spec-kit гонял меня по кругу написания спецификаций там, где надо было просто взять и поменять код. И где-то там всплыл настоящий вывод: отдельный интерфейс мне вообще не нужен. Он у меня уже был — тот самый клиент с языковой моделью, в котором я и так сидел.
Вторая итерация: убрать интерфейс
Устройство схлопнулось в одну строчку:
Языковая модель → MCP → Файлы (источник правды) / БД
Клиент общается по Model Context Protocol, MCP-сервер читает и пишет обычный markdown, а БД за этими файлами работает поисковым индексом, который обновляется на каждую запись. Описывать заранее стало нечего, так что вместо spec-kit я взял openspec и перестал спотыкаться о многошаговые спецификации.
Как попробовать
Сначала я проверил всё на себе: натравил одного агента на свою старую таблицу — годы компаний и заметок — и за пару часов получил из неё готовое хранилище.
Полная настройка описана в README репозитория, а в общих чертах так:
- Установка — нужен только uv: uv tool install vacancy-radar. В PATH появятся vacancy-radar и vacancy-radar-mcp; при первом поиске догрузится небольшая модель эмбеддингов.
- Создать хранилище — vacancy-radar init ~/job-search
- Подключить MCP-клиент — указать Claude Desktop или Claude Code на vacancy-radar-mcp --vault ~/job-search
- Разговаривать — перезапустить клиент и попросить записать отклик или показать, где всё затихло. Либо напрямую из командной строки: vacancy-radar list --status applied
Всё, что он пишет, ложится в хранилище обычным markdown: читаемо, ищется и лежит у вас.
Это окончательный вариант?
Нет. Что именно поменяется — пока не знаю, но расти это будет из уже написанного кода.
Направление, к которому я всё время возвращаюсь, — превратить типовые сценарии (записать отклик, растормошить зависшую вакансию, отметить подозрительное описание) в переиспользуемые скиллы вместо того, чтобы каждый раз объяснять их заново. Пока что я в основном пробую разные модели и формулировки запросов, чтобы понять, что реально работает, прежде чем это закреплять.
Если есть идея или нашли ошибку — не стесняйтесь завести задачу на GitHub.
И напоследок — про фотографию за заголовком. Одиннадцать монтажников-высотников обедают на балке в 260 метрах над Манхэттеном, в самый тяжёлый год Великой депрессии, когда без работы был примерно каждый четвёртый американец. У этих работа была. Кому-то всё равно нужно было ходить по стали на такой высоте, и умели это именно они. Так что если поиск кажется безнадёжным: бывало и хуже, и людей всё равно нанимали. Сложность в том, чтобы найти ту самую балку, на которую пойдут немногие, — и помнить, где ты уже искал.
«Обед на вершине небоскрёба», 20 сентября 1932 года, 69-й этаж здания RCA в Рокфеллер-центре. Автор снимка не установлен — в тот день на площадке работали Чарльз Эббетс, Том Келли и Уильям Лефтвич. Public domain, Wikimedia Commons.