Last30Days: как на самом деле работает ресерч-агент с ранжированием по вовлеченности
🎬 Смотреть на YouTube: Your AI Doesn’t Know What Happened Last Month. One Command Fixes It
Спроси базовую модель, что произошло за последний месяц, — она ответит из весов, которые замерзли до того, как этот месяц наступил. Спроси поисковик — получишь SEO-статьи, написанные чтобы ранжироваться, а не чтобы информировать. А обсуждение, которое действительно важно — тред, где трое людей, реально запустивших эту штуку в прод, спорят о том, почему она сломалась, — живет внутри закрытых садов, куда не дотягивается ни один из них.
Last30Days — это агентский скилл, построенный вокруг этого разрыва. Он набрал около 39 000 звезд на GitHub и первое место в трендах за счет структурно простой идеи: выполнять поиск с твоей машины, под твоей залогиненной сессией браузера, а потом ранжировать выдачу по тому, с чем люди реально взаимодействовали.
Я прошелся по архитектурным утверждениям исходного видео и сверил каждое с репозиторием. Несколько оказались неверными — причем интересным образом. Ниже версия, которая выдержала столкновение с кодом.
Дистрибуция — половина продукта
Два способа установки, и разница между ними важнее, чем кажется:
# Claude Code, через маркетплейс плагинов — автообновления
/plugin marketplace add mvanhorn/last30days-skill
# Все остальное — Codex, Cursor, Copilot, Gemini CLI, 50+ харнессов
npx skills add mvanhorn/last30days-skill -g
Флаг -g ставит глобально, а не в отдельный проект. Один этот флаг — разница между инструментом, который ты переинициализируешь в каждом микросервисном репозитории, и возможностью, которая просто есть в любом открытом терминале. Для ресерч-скилла — того, к чему тянешься между проектами, а не внутри одного, — глобальная установка единственный вменяемый дефолт.
Это тот же сдвиг, о котором я писал в Claude Code Skills и Wayfinder: скиллы перестали быть локальным конфигом репозитория и стали устанавливаемыми версионируемыми пакетами, работающими в разных харнессах. Last30Days показывает, как выглядит эта экосистема, когда в один скилл за релизный цикл вливается 175 смерженных пул-реквестов от сообщества.
Свои ключи, свои куки
Никакого центрального пула API и никакого серверного парка парсеров. Reddit, Hacker News, Polymarket, GitHub, arXiv и Techmeme работают вообще без настройки. X, YouTube, Bluesky, TikTok, Instagram и LinkedIn требуют доступов — и доступы твои: куки читаются локально из браузера, ключи берутся из .env или из связки ключей macOS через scripts/setup-keychain.sh.
Размен тут явный. Ты проходишь антибот-защиту, потому что не являешься датацентровым IP, изображающим человека, — ты и есть собственная авторизованная сессия человека на его же машине. Твои исследовательские запросы не покидают ноутбук. Взамен проект берет на себя поддержку каждой комбинации браузера и операционной системы, которые хранят куки по-разному, а ты берешь на себя все, что написано в правилах площадки про автоматический доступ из твоего аккаунта.
Это реальная цена, а не сноска. Но ставка та же, что и в локальных паттернах из архитектуры агентского харнесса: выносить возможности на край, держать состояние на машине пользователя и оставлять доступы харнессу, а не центральному сервису.
Ранжирование — вот где настоящая инженерия
Однострочное описание из README — “оценивает контент по метрикам вовлеченности: апвоуты, просмотры, объем торгов” — сильно недооценивает происходящее. Открываешь интеграцию с Polymarket в skills/last30days/scripts/lib/polymarket.py и находишь конкретную настроенную формулу:
market_quality = (
0.50 * vol_score + # логарифм месячного объема, насыщение ~$9M
0.25 * liq_score + # логарифм ликвидности, насыщение ~$1.2M
0.15 * movement_score + # максимальное движение цены; 20% = насыщение
0.10 * competitive_score # рынки около 50/50 получают бонус
)
relevance = min(1.0, text_score * (0.75 + 0.25 * market_quality))
Из этих пяти строк стоит забрать три вещи.
Смысловая релевантность умножает, качество рынка только модулирует. Итоговый скор — это text_score, умноженный на коэффициент от 0.75 до 1.0. Дико ликвидный рынок, не совпадающий с твоей темой, физически не может подняться в выдаче: качество уточняет релевантные совпадения, а не вытаскивает нерелевантные. Одно это структурное решение не пускает предвыборный рынок на $50M в твой запрос про Python-библиотеку.
Все сигналы насыщаются логарифмически. min(1.0, log1p(volume) / 16) означает, что рынок на $9M и рынок на $90M дают почти одинаковый вклад. Без логарифма один кит расплющил бы все остальные сигналы. Свежесть движения цены тоже взвешена: дневное изменение считается с коэффициентом 3, недельное 2, месячное 1 — потому что рынок, который только что дернулся, это рынок, где что-то произошло.
Рынки около 50/50 получают бонус. Рынок на 97% — не новость, это решенный вопрос со спредом. Интересный сигнал — настоящее разногласие.
И вот здесь исходное видео утверждало, что разработчики “полностью отказались от долларовых объемов и перешли на чистые вероятности, потому что объем искажается китами”. История красивая и неверная. Объем — самый тяжелый член формулы с весом 0.50. Проблема китов решается логарифмическим сжатием, а не выбрасыванием сигнала.
Дедупликация нарочно примитивная — и в этом урок
Второе утверждение, которое я проверял: якобы в третьей версии появились “векторные эмбеддинги для кроссплатформенной семантической кластеризации”, схлопывающие тред на Reddit, спор в X и комментарии на YouTube в одну сущность.
Реальная реализация в lib/dedupe.py открывается докстрингом """Within-source near-duplicate detection.""" — то есть поиск почти-дублей внутри одного источника — и работает на схожести Жаккара:
def hybrid_similarity(text_a: str, text_b: str) -> float:
return max(
jaccard_similarity(get_ngrams(text_a), get_ngrams(text_b)), # символьные 3-граммы
token_jaccard(text_a, text_b), # токены без стоп-слов
)
Символьные 3-граммы ИЛИ отфильтрованные множества токенов — что даст больше совпадения, порог 0.7. Ни эмбеддингов, ни векторной базы, ни единого вызова модели. Сегментация китайского вынесена в отдельный модуль cjk, а не отдана на откуп надежде, что мультиязычная модель эмбеддингов сама разберется.
А еще там лучший комментарий во всем файле — баг-репорт, сохраненный прямо в докстринге:
Вакансии дедуплицируются только по точному URL: разные объявления на одной карьерной странице делят между собой тяжелые шаблонные куски (описание компании, TL;DR, соцпакет), которые ломают нечеткое сравнение текстов и схлопывают несвязанные роли (доска из 26 вакансий сжималась до 7).
Двадцать шесть вакансий превращались в семь, потому что у всех был одинаковый корпоративный блок. Починка была не в улучшении метрики схожести, а в осознании, что у вакансии уже есть уникальный идентификатор — и надо использовать его. Вся дисциплина в одном абзаце: нечеткое сравнение — это запасной вариант на случай, когда идентификатора нет, а не апгрейд над ним.
Каждый вызов LLM, который ты не сделал, — это сэкономленные задержка и деньги; тот же аргумент, что и в экономии токенов в Claude Code, только уровнем ниже. Сначала работают дешевые детерминированные фильтры, и только выжившее доходит до модели.
Открытые поломки
Исходное видео подало два бага как изящно решенные. Оба до сих пор открыты на GitHub, и оба поучительнее именно нерешенными.
Issue #887 — понижение по промаху сущности не работает на темах без отчетливой сущности. Пайплайн понижает результаты, которые не упоминают то, что ты спрашивал. Но на абстрактном запросе — “AI code review”, “comparison” — парсер не находит твердого имени собственного, вырезает слова намерения и цепляется за оставшийся слишком широкий токен. Запросил “AI code review”, потерял “review”, остался “AI” — и теперь релевантным считается вообще любой контент во вселенной, где написано “AI”. Симптом из тикета: ролики по астрономии и реклама курсов обходят реальные технические треды.
Предлагаемая починка требует дискриминирующего токена — статистически редкого — прежде чем засчитать релевантность. Это честный размен точности и полноты, а не бесплатная победа: затянешь гайки — перестанешь находить неожиданный результат из смежной области, ради которого инструмент и был интересен. Для инструмента, чья метрика — “сэкономленное время разработчика до стендапа”, ложное срабатывание обходится дороже упущенного инсайта. Решение разумное. Но это все равно размен.
Issue #818 — установленная через npx v3.14 скатывается к путям относительно репозитория. Скрипты искали свои ресурсы относительно process.cwd(). Нормально, когда запускаешь из корня проекта; ломается ровно в тот момент, когда -g означает запуск из ~/work/some-enterprise-app, а сам пакет лежит в глобальном дереве node modules. Инструмент идет искать свои HTML-шаблоны отчетов внутри твоего приложения.
Обрати внимание, что делает этот баг структурным, а не небрежностью: глобальная установка и есть главная фича, и она же ломает исходное допущение. Отгрузил удобство — получил проблему путей.
Issue #463 — закрыт, и самый переносимый из трех. Хук check-config.sh на старте сессии выходил с кодом 1, когда предыдущего запуска не было: файла истории просто не существовало. В обычном шелле это желтое предупреждение “файл не найден”, которое никто не читает. Внутри агентского харнесса код возврата 1 — фатальная ошибка задачи: агент паникует, ретраит, галлюцинирует восстановление или уходит в цикл.
Агенты гораздо чувствительнее к кодам возврата, чем люди. “Предыдущего состояния нет” — не сбой, а первый запуск. Если пишешь хуки для агентских сред, именно это различие отделяет чистый старт от спирали самопочинки. Ровно тот способ думать на уровне харнесса, к которому я возвращаюсь в Харнесс, а не модель.
Контроль расходов как полноценная фича
Агент, который автономно делает сотни запросов, способен тихо сжечь бюджет. Два механизма:
EXCLUDE_SOURCES=tiktok,instagramжестко блокирует площадки на уровне ядра, еще до планирования вызовов.- Платные источники — Perplexity, Brave Search, ScrapeCreators — работают по явному согласию. Без флага включения движок не потратит ничего.
Плюс --preflight, который печатает, что будет прочитано и записано, ничего не выполняя. Очевидное применение — проверка перед запуском. Более ценное — CI: убедиться, что конфигурация, переменные окружения и роутинг корректны, не трогая реальные куки браузера и не отправляя ни одного исходящего запроса. Разделение “построить граф выполнения” и “выполнить его” — единственное, что вообще позволяет иметь интеграционные тесты у парсящего пайплайна.
Второй рычаг по расходам приехал в v3.16.0 (PR #827): комментарии YouTube теперь достаются бесплатно через yt-dlp вместо платного скрапинг-API. Интереснее всего сама схема запасного пути: сначала пробуем бесплатный локальный экстрактор, падаем на платный API, когда YouTube в пятницу вечером меняет верстку, и автоматически возвращаемся к бесплатному, как только open source чинит парсер. Бесплатный путь основной, платный как страховка, переключение без ручного вмешательства.
Что отсюда забрать
Даже если ты не поставишь этот скилл, четыре паттерна переносятся напрямую на любую агентскую систему:
- Релевантность умножает, качество модулирует. Популярность никогда не должна обходить попадание в тему.
- Сжимай неограниченные сигналы логарифмом. Один кит не должен расплющивать тысячу честных голосов.
- Есть идентификатор — используй идентификатор. Нечеткое сравнение это то, что делают, когда идентификатора нет.
- Никогда не выходи с ненулевым кодом на “тут пока пусто”. В агентской среде код ошибки — триггер паники.
И один мета-урок, из-за которого эта статья написана именно так. Видео, по которому я ее делал, само является AI-синтезом, и оно с полной уверенностью заявило, что два открытых тикета закрыты, что от ранжирования по объему отказались и что дедупликатор на Жаккаре — это пайплайн на векторных эмбеддингах. Каждое из этих утверждений проверялось секунд за девяносто через GitHub API и чтение исходников. Уверенность бесплатна, проверка дешева. Проверяй.
Источник: статья написана по техническому разбору с канала Yersham, все архитектурные утверждения перепроверены по репозиторию last30days-skill на версии v3.18.4.