feat(tools): пассивный сигнал «new comments: N» в результатах tool-вызовов — комментарии человека находят агента сами #417

Open
opened 2026-07-07 21:06:14 +03:00 by agent_vscode · 0 comments
Collaborator

Мотивация

Цикл «человек комментирует, пока агент работает» сейчас pull-only: агент должен ВСПОМНИТЬ вызвать checkNewComments (deferred-тир, т.е. тул ещё и спрятан за каталогом), а сам тул дорогой — полный обход дерева спейса + per-page выборка комментариев (client.ts:2801-2852). На практике агент в длинном ходе (роль исследователя, класс инцидента #410) не проверяет комментарии никогда, и suggestedText-флоу createComment (правки «на подпись» человеку) не превращается в петлю обратной связи.

Прецедент пассивного сигнала уже есть — <page_changed>-блок in-app чата (ai-chat.prompt.ts:185-191): диф открытой страницы доставляется агенту без запроса. Но он per-turn и только про тело страницы. Здесь — то же самое для комментариев, и mid-turn: короткая эфемерная строка «new comments: N» в результате ЛЮБОГО tool-вызова. Проблема исчезает классом: сигнал сам находит агента, помнить ничего не нужно.

Дизайн

Формат сигнала

Дополнительный content-элемент в результате тула (не смешивать с основным результатом):

[signal] new comments: 2 on page 8x3k1... ("Иранские языки") — call listComments(pageId) for details
  • Только счётчик + pageId/title, БЕЗ текста комментариев: содержимое комментария — untrusted-данные другого пользователя; включать его в системный сигнал — вектор prompt-injection (прецедент осторожности: defang </page_changed> в ai-chat.prompt.ts:124-135).
  • Emit-on-change: строка появляется один раз при продвижении watermark, а не в каждом последующем результате (иначе шум затирает сигнал).
  • Не добавлять сигнал в результаты самих комментарийных тулов (listComments/checkNewComments/createComment) — там он тавтологичен.

Точки перехвата (обе поверхности)

  • Standalone MCP: второй wrapper в существующем choke point — monkeypatched registerTool (packages/mcp/src/index.ts:118-125, паттерн метрик #402/#403): все тулы проходят через него, per-tool боилерплейта нет. Состояние — per-session: McpServer + DocmostClient создаются на сессию (http.ts:35-52), state живёт рядом с клиентом.
  • In-app: аналогичный wrapper вокруг execute в ai-chat-tools.service.ts (или общий с метрик-обвязкой #402). Межходовой watermark — из тех же механизмов, что снапшот pageChanged.

Источник сигнала (стоимость — главный вопрос)

Наивное «checkNewComments на каждый вызов» запрещено (обход всего спейса). Варианты по слоям:

  1. In-app (рекомендуется): прямой DB-count (createdAt > watermark AND pageId IN <рабочий набор>) — сервер имеет доступ к БД, один дешёвый запрос; дебаунс ≥15–30 с.
  2. Standalone MCP (REST-only): rate-limited listComments(pageId) по страницам рабочего набора — не чаще раза в X секунд на страницу; пустой рабочий набор → ноль вызовов.
  3. Опция-оптимизация (обе стороны): бесплатный mark-scan — тулы, уже загрузившие живой doc (getPageRaw/mutatePageContent), сканируют comment-марки и диффят набор id против per-session seen-set: ноль лишних API-вызовов. Ограничение: не видит реплаи (реплай не добавляет марку) и unanchored-комментарии → годится как ТРИГГЕР для точного listComments, не как замена.

Рабочий набор = pageId, к которым сессия обращалась (+ открытая страница для in-app). Весь workspace автоматически НЕ сканируется никогда — для этого остаётся явный checkNewComments.

Watermark: инициализируется стартом сессии (in-app — временем конца прошлого хода); продвигается при emission и при явных вызовах listComments/checkNewComments.

Критерии приёмки

  • Человек оставляет комментарий на странице, которую агент редактирует → следующий tool result (после дебаунс-окна) содержит строку сигнала; агент реально реагирует (проверяется сценарием роли исследователя).
  • Нет новых комментариев → in-app: ноль лишних запросов; MCP: не более 1 listComments на страницу за дебаунс-окно.
  • Текст комментария в сигнале не встречается ни при каких входных данных (unit на injection-safety).
  • Когда сигнала нет, результаты тулов байт-в-байт не меняются (существующие тесты зелёные без правок).

Тесты

Unit на wrapper: emission-on-change, дебаунс, тавтологичные тулы исключены, injection-safety; e2e: комментарий появляется между двумя tool-вызовами → сигнал во втором.

Связано

Roadmap #416 (независимая работа, фаза 0/1, non-breaking). #410 (сценарий исследователя — главный потребитель), #408 (комментарийная петля createComment), #407 (наблюдаемость эффекта), #412 (имя listComments в тексте сигнала — сразу camelCase).

## Мотивация Цикл «человек комментирует, пока агент работает» сейчас pull-only: агент должен ВСПОМНИТЬ вызвать `checkNewComments` (deferred-тир, т.е. тул ещё и спрятан за каталогом), а сам тул дорогой — полный обход дерева спейса + per-page выборка комментариев (`client.ts:2801-2852`). На практике агент в длинном ходе (роль исследователя, класс инцидента #410) не проверяет комментарии никогда, и suggestedText-флоу `createComment` (правки «на подпись» человеку) не превращается в петлю обратной связи. Прецедент пассивного сигнала уже есть — `<page_changed>`-блок in-app чата (`ai-chat.prompt.ts:185-191`): диф открытой страницы доставляется агенту без запроса. Но он per-turn и только про тело страницы. Здесь — то же самое для комментариев, и **mid-turn**: короткая эфемерная строка «new comments: N» в результате ЛЮБОГО tool-вызова. Проблема исчезает классом: сигнал сам находит агента, помнить ничего не нужно. ## Дизайн ### Формат сигнала Дополнительный content-элемент в результате тула (не смешивать с основным результатом): ``` [signal] new comments: 2 on page 8x3k1... ("Иранские языки") — call listComments(pageId) for details ``` - **Только счётчик + pageId/title, БЕЗ текста комментариев**: содержимое комментария — untrusted-данные другого пользователя; включать его в системный сигнал — вектор prompt-injection (прецедент осторожности: defang `</page_changed>` в `ai-chat.prompt.ts:124-135`). - **Emit-on-change**: строка появляется один раз при продвижении watermark, а не в каждом последующем результате (иначе шум затирает сигнал). - Не добавлять сигнал в результаты самих комментарийных тулов (`listComments`/`checkNewComments`/`createComment`) — там он тавтологичен. ### Точки перехвата (обе поверхности) - **Standalone MCP**: второй wrapper в существующем choke point — monkeypatched `registerTool` (`packages/mcp/src/index.ts:118-125`, паттерн метрик #402/#403): все тулы проходят через него, per-tool боилерплейта нет. Состояние — per-session: `McpServer` + `DocmostClient` создаются на сессию (`http.ts:35-52`), state живёт рядом с клиентом. - **In-app**: аналогичный wrapper вокруг execute в `ai-chat-tools.service.ts` (или общий с метрик-обвязкой #402). Межходовой watermark — из тех же механизмов, что снапшот `pageChanged`. ### Источник сигнала (стоимость — главный вопрос) Наивное «checkNewComments на каждый вызов» запрещено (обход всего спейса). Варианты по слоям: 1. **In-app (рекомендуется)**: прямой DB-count (`createdAt > watermark AND pageId IN <рабочий набор>`) — сервер имеет доступ к БД, один дешёвый запрос; дебаунс ≥15–30 с. 2. **Standalone MCP (REST-only)**: rate-limited `listComments(pageId)` по страницам рабочего набора — не чаще раза в X секунд на страницу; пустой рабочий набор → ноль вызовов. 3. **Опция-оптимизация (обе стороны)**: бесплатный mark-scan — тулы, уже загрузившие живой doc (`getPageRaw`/`mutatePageContent`), сканируют comment-марки и диффят набор id против per-session seen-set: ноль лишних API-вызовов. Ограничение: не видит реплаи (реплай не добавляет марку) и unanchored-комментарии → годится как ТРИГГЕР для точного `listComments`, не как замена. **Рабочий набор** = pageId, к которым сессия обращалась (+ открытая страница для in-app). Весь workspace автоматически НЕ сканируется никогда — для этого остаётся явный `checkNewComments`. **Watermark**: инициализируется стартом сессии (in-app — временем конца прошлого хода); продвигается при emission и при явных вызовах `listComments`/`checkNewComments`. ## Критерии приёмки - Человек оставляет комментарий на странице, которую агент редактирует → следующий tool result (после дебаунс-окна) содержит строку сигнала; агент реально реагирует (проверяется сценарием роли исследователя). - Нет новых комментариев → in-app: ноль лишних запросов; MCP: не более 1 `listComments` на страницу за дебаунс-окно. - Текст комментария в сигнале не встречается ни при каких входных данных (unit на injection-safety). - Когда сигнала нет, результаты тулов байт-в-байт не меняются (существующие тесты зелёные без правок). ## Тесты Unit на wrapper: emission-on-change, дебаунс, тавтологичные тулы исключены, injection-safety; e2e: комментарий появляется между двумя tool-вызовами → сигнал во втором. ## Связано Roadmap #416 (независимая работа, фаза 0/1, non-breaking). #410 (сценарий исследователя — главный потребитель), #408 (комментарийная петля createComment), #407 (наблюдаемость эффекта), #412 (имя `listComments` в тексте сигнала — сразу camelCase).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#417