Какие у меня сегодня задачи: как ИИ-чат научился видеть доску

Productivity

Человек смотрел на свою канбан-доску в Notez, открыл ИИ-чат и набрал пять слов: какие у меня сегодня задачи.

Ответом был список. Не тот.

Ничего не упало, ошибки не было. Ассистент поискал, нашёл несколько заметок и уверенно их перечислил, но ни одна из них не была карточкой с доски в нескольких сантиметрах левее. Для ИИ-продукта это худший вид бага: он выглядит в точности как ответ.

Три вещи, которых у ассистента не было

Когда ответ неверный, хочется обвинить промпт: дописать строчку, чтобы модель старалась лучше, выкатить и надеяться. Я начал с этого и ничего не добился, потому что дело было не в формулировках. Не хватало трёх деталей, а никакая настройка промпта не создаст деталь, которой нет.

Он не видел экран. В запросе к модели был вопрос и больше ничего: ни режима просмотра, ни доски, ни фильтра по тегу, который сужал список, ни заметки в боковой панели. Человек рядом с вами может взглянуть на монитор. Модель отвечала с закрытыми глазами и даже не могла об этом сказать.

Он не знал, что такое задача. В Notez намеренно нет отдельной сущности для задач. Нет типа «Задача», нет поля срока, нет колонки с чекбоксами, которую приложение считало бы особенной. Есть доска, и её карточки — это то, что люди имеют в виду, когда говорят «мои задачи». Модели об этом никто не сказал. На вопрос о задачах она сделала единственное, что могла: поискала по ключевому слову задача.

У него не было инструмента, который читает доску. Из-за этого две другие проблемы нельзя было решить. Даже модель, которая точно поняла бы, о чём вы, и точно знала бы, на какую доску вы смотрите, не смогла бы эту доску получить. Был инструмент, который выдаёт список заметок, и инструмент, который фильтрует их по тегу, а доска — ни то ни другое: это набор колонок, и в каждой упорядоченный набор карточек. На вопрос о доске функция списка не ответит.

Как модель узнаёт, что у вас на экране

Исправление начинается в браузере. Теперь к каждому сообщению прилагается короткое описание экрана, на котором его набрали: режим просмотра, какая доска открыта, находитесь ли вы в корзине, текст поиска и фильтры по тегам, открытая заметка и выбранные карточки.

Это описание попадает в промпт обычными предложениями, которые модель может процитировать. Без эмбеддингов, без скрытого состояния, без догадок о том, что вы, вероятно, имели в виду. Если ассистент знает, что вы на доске, значит, ему это сообщила сама доска.

Над двумя деталями пришлось думать дольше, чем я ожидал.

Первая: заметка может быть открыта поверх списка. Notez открывает заметки в боковой панели, поэтому страница со списком под ней остаётся смонтированной и продолжает сообщать о своём экране. Оба компонента хотят описать контекст, а смотрите вы только на один. Побеждает панель, потому что она сверху.

Вторая: люди должны это видеть. Под разговором теперь есть ряд чипов — Доска, Эта заметка, Выбрано: 3, — и они точно показывают, что видит ассистент. Ассистенту с невидимым контекстом можно доверять, только проверив его методом проб и ошибок. С чипами достаточно прочитать.

Что такое задача: определение для модели

Теперь у модели есть определение, которого раньше не было:

Отдельной сущности для задач или дел нет. Когда пользователь спрашивает о задачах, делах, над чем работать или что в работе, он имеет в виду карточки на доске, на которую смотрит, или доску рабочего пространства, если на экране доски нет. Колонки, название которых означает завершение (Done, Completed, Archived и похожие), к текущим задачам не относятся.

Последнее предложение значит больше, чем все остальные вместе. Без него на вопрос «какие у меня сегодня задачи» приходит всё, что есть на доске, включая колонку с тем, что вы закончили на прошлой неделе. Формально ответ полный, на практике бесполезный.

То же определение получают MCP-клиенты. Если Notez подключён к Claude Code или Cursor, ваш ассистент там читает тот же самый текст.

Инструмент, который читает доску

get_board возвращает доску с карточками, сгруппированными по колонкам, и число карточек в каждой колонке. Он работает и с доской рабочего пространства, и с собственной доской любой заметки. Вопрос «что сейчас в работе» — это один вызов, а не поиск и потом пять запросов заметок.

Ещё я объединил два инструмента в один. filter_notes больше нет, он вошёл в list_notes, и тот теперь принимает колонки доски, значения свойств и диапазоны дат создания и изменения в дополнение к прежним фильтрам. Все фильтры объединяются через И, поэтому вопрос вроде «заметки с тегом „работа“, изменённые на этой неделе, не в „Готово“» — это один вызов, а не цикл «загрузить и отсеять». И у каждой заметки в результате теперь есть теги, колонка, значения свойств и даты, так что ассистенту не нужно открывать заметку ради одного поля.

Несколько ёмких инструментов лучше множества узких. Каждый лишний инструмент — ещё один шанс, что модель выберет не тот.

Баг, который я написал, пока чинил баг

С фильтрами по датам кто-то должен решить, что значит «сегодня», и честный ответ — зависит от того, где вы находитесь. Поэтому браузер отправляет свой часовой пояс, и сервер считает диапазон в нём.

Первая версия этого кода ошибалась так, что заметить это было бы трудно. Библиотека, которой я собирал дату с учётом часового пояса, сохраняет смещение при сериализации, а колонки с временем у заметок имеют тип timestamp without time zone. Postgres молча отбросил бы смещение и сдвинул каждый диапазон на столько часов, на сколько ваш пояс отличается от UTC. Все пользователи не в UTC получали бы чуть неверные ответы на вопрос «что я создал сегодня», и нигде не появилось бы ни одного сообщения об ошибке.

Тест на это сначала упал и только потом прошёл, поэтому я ему и верю. Потом я проверил ещё раз на настоящей базе: заметка, созданная в 22:00 UTC, то есть в час ночи по местному времени, считается сегодняшней, как и должна.

MCP-клиенты получают UTC: там нет браузера, у которого можно спросить пояс. Это ограничение не спрятано, а записано в описании инструмента, так что ассистент знает, что получает.

Два интерфейса, один набор инструментов

В Notez ассистент добирается до заметок двумя путями: через чат внутри приложения и через MCP-сервер, к которому подключается ваш редактор. У них 29 общих инструментов, и у MCP-сервера есть ещё три своих: два для загрузки файлов, которым нужна командная оболочка, и один, который показывает ваши рабочие пространства при подключении через вход в аккаунт. Общие инструменты совпадают полностью: одинаковые названия, аргументы и обязательные поля. За этим следит тест, который роняет сборку, если два списка разойдутся.

Тест появился потому, что однажды они уже разошлись. Шесть инструментов месяцами были только в чате, и никто этого не заметил: их ни с чем не сравнивали. Соответствие, о котором надо помнить, рано или поздно теряется.

Что он отвечает теперь

Тот же вопрос на той же доске:

К выполнению: Исправить баг со входом, Написать заметки к релизу В работе: Ревью PR 42

Колонку Готово я не включил, потому что там всё завершено: Спланировать роадмап на Q4.

Один вызов инструмента. Правильный список. И фраза о том, что он намеренно не включил в ответ. Для ассистента, который месяцами уверенно отвечал не на тот вопрос, это, пожалуй, самое важное.

Читайте также