~bigbes/agents-dev · parade

main · last commit 13 days ago · 7g0stsfu

← Back to the parade

ah-ptu Yonote Q&A bot loop: poll doc comments, answer via board tasks (post-wave-2.5) Past Stand

status: closed P3 feature @Eugene Blikh
bd reopen ah-ptu
Created byEugene Blikh
Ownerbigbes@gmail.com
Created2026-07-18T15:04:41Z
Started2026-07-18T18:19:54Z
Updated2026-07-18T18:47:53Z
Closed2026-07-18T18:47:53Z
Description
Why: the operator wants to ask questions as comments on Yonote docs (e.g. on agent-published reports) and have the bot answer in-thread. What: a polling loop (daemon has no public ingress — loopback-only listen — so webhooks are out for now) over events.list filtered to comments.create since a persisted cursor; qualifying comments spawn a question-answer board card whose description carries the doc URL (ah-2lh materialization then feeds the agent the doc as context); the answer is delivered via comments.create (parentCommentId threading) + optional comments.resolve under the bot identity. EXPLICITLY NOT wave 2.5: needs cursor persistence, a second poll loop, trigger-convention + mention-encoding verification, and a reply-channel decision. Depends on ah-gxa (comments methods; extend with ListEvents when picked up) and ah-25e (reply channel + bot write provisioning).
Design
# Yonote Q&A bot loop (post-wave-2.5 — honest scope, NOT part of wave 2.5)

Goal: the operator asks questions IN Yonote (comments on docs — e.g. on agent-published reports
or on specs); the bot notices, an agent produces the answer, the bot replies in-thread and
resolves the comment.

## Verified surface this builds on (2026-07-18, bigbes.yonote.ru 1.47.1)

- comments.list {entityId, isResolved filter, threadId, limit ≤100, offset} → {data:{comments:[…]},
  pagination, total} — LIVE-verified (nested envelope; items carry threadId, isResolved,
  threadCommentsCount, attachments).
- comments.create {entityType:"document", entityId, text, parentCommentId} → {data:{comment}} —
  LIVE-verified end-to-end on a probe doc: text stored verbatim (plain string), threading via
  parentCommentId; a reply's threadId points at the root comment.
- comments.resolve {id, isResolved} → data.comment — spec'd (v2-preview), plus the product MCP
  server exposes yonote_resolve_comment, so the surface is real.
- events.list POST {name, actorId, documentId, collectionId, limit, offset, sort/direction} —
  LIVE-verified (returned {pagination, data:[{id, name, actorId, actor, documentId, collectionId,
  createdAt, data, modelId}]}); the `name` filter ("objects.verb", e.g. "comments.create") is
  spec'd — server-side event filtering makes cheap incremental polling possible.
- webhookSubscriptions.list responds LIVE (empty list) though it is UNDOCUMENTED in both Yonote
  OpenAPI specs (Outline heritage; auth.info policies include create/listWebhookSubscription).
  BUT the daemon binds loopback-only (config validateLoopback) with no public ingress, so v1
  transport = POLLING events.list; webhooks stay a documented future option if ingress appears.

## Sketch (design decisions OPEN — settle when picked up)

- Poll loop: own ticker (~60s), separate from the reconcile tick; cursor persisted in the store
  (new kv row, e.g. yonote_cursor = last seen event id/createdAt). MUST verify then: events.list
  ordering + Sorting params for reliable incremental reads (direction/sort exist in the Pagination
  /Sorting schemas but were not exercised).
- Trigger filter (pick one): (a) new unresolved comments on docs AUTHORED BY THE BOT
  (createdById == bot user id from AuthInfo) — simplest, covers "questions about agent output";
  (b) explicit @mention of the bot — mention ENCODING in comment text is UNVERIFIED (probe stored
  plain text verbatim; the editor may encode mentions as structured nodes invisible to .text);
  (c) any unresolved comment in configured collections.
- Self-trigger guard: ignore events with actorId == bot user id.
- Answer path: reuse the whole existing pipeline — create a board card (existing CreateTask port,
  agent_tasks bucket conventions) of a question-answer task type whose description contains the
  doc URL (the materialization bead then auto-exports the doc as context for the agent!) plus the
  question thread text; the agent writes the answer; the daemon replies via comments.create with
  parentCommentId = thread root and optionally comments.resolve. Reply delivery needs either a
  small extension of the publish channel ({"reply_to_comment": …}) or a dedicated
  .task/answer.json — decide at design time.

## Why not wave 2.5

Needs cursor persistence, a second poll loop, trigger-convention and mention-encoding
verification, and a reply file-channel decision — none of which artifact materialization or the
publish lane depend on. Client extensions required when picked up: ListEvents (events.list) and
possibly webhookSubscriptions.* — add to internal/yonote then, not now.

Depends on: internal/yonote client bead (comments methods land there), publish-lane bead (reply
channel shape + bot write provisioning).
Notes
Live evidence (2026-07-18): events.list POST works — {pagination, data:[{id, name, actorId, actor, documentId, collectionId, createdAt, data, modelId}]}, event names 'objects.verb' (observed documents.permanent_delete from the trash-purge cron); the name/actorId/documentId/collectionId request filters are v1-spec'd. comments.create/list verified end-to-end on the probe doc (threading via parentCommentId; replies carry threadId of the root; text stored verbatim). comments.resolve spec'd + present as yonote_resolve_comment in the product's own MCP server. webhookSubscriptions.list responds live (empty, UNDOCUMENTED in both OpenAPI specs — Outline heritage; auth.info policies include createWebhookSubscription) — future option only, blocked on public ingress. UNVERIFIED for pickup: events.list ordering/Sorting params for reliable incremental cursors; @mention encoding inside comment text (probe stored plain text; the editor may use structured nodes invisible in .text — trigger option (a) 'comments on bot-authored docs' avoids the question entirely).

Depends on

  • ah-gxa — internal/yonote: light API client (doc resolve/export, create, comments; bot-token auth) blocks closed
  • ah-25e — Yonote publish lane: .task/publish.json → bot-authored docs at finalize blocks closed

Depended on by

  • ah-eje — Q&A loop rollout: live-verify events.list ordering + comments.resolve, wire qa role/config on agent-1 blocks

Prerequisite chain — everything this waits on, transitively

  • ah-gxa — internal/yonote: light API client (doc resolve/export, create, comments; bot-token auth) blocks closed
  • ah-25e — Yonote publish lane: .task/publish.json → bot-authored docs at finalize blocks closed
  • ah-2lh — Claim-time Yonote artifact materialization into .task/artifacts/ + prompt manifest blocks closed

No comments.

Close reason

Implemented: events.list poll loop w/ kv cursor + exactly-once spawn, threaded answer delivery via answer.json, config yonote.qa block, store v5, prompts/qa.md. Rollout/live-verify tracked in ah-eje.
  • Eugene Blikh created the issue · 2026-07-18T18:04:41Z
  • Eugene Blikh added dependency on ah-gxa · 2026-07-18T18:04:57Z
  • Eugene Blikh added dependency on ah-25e · 2026-07-18T18:04:57Z
  • Eugene Blikh claimed · 2026-07-18T21:19:54Z
  • Eugene Blikh ah-eje now depends on this · 2026-07-18T21:47:52Z
  • Eugene Blikh closed the issue · 2026-07-18T21:47:53Z
    Implemented: events.list poll loop w/ kv cursor + exactly-once spawn, threaded answer delivery via answer.json, config yonote.qa block, store v5, prompts/qa.md. Rollout/live-verify tracked in ah-eje.
  • Eugene Blikh added label milestone:yonote · 2026-07-20T03:08:53Z
  • Eugene Blikh removed label milestone:yonote · 2026-07-20T03:10:31Z
Stored rows — what this pane was built from, as read
issues 1 row
id ah-ptu
content_hash 656fcc1b3e50d2d0c581f01969cc197111ed13fffaf60d43d5c82a961ec5f638
title Yonote Q&A bot loop: poll doc comments, answer via board tasks (post-wave-2.5)
description Why: the operator wants to ask questions as comments on Yonote docs (e.g. on agent-published reports) and have the bot answer in-thread. What: a polling loop (daemon has no public ingress — loopback-only listen — so webhooks are out for now) over events.list filtered to comments.create since a persisted cursor; qualifying comments spawn a question-answer board card whose description carries the doc URL (ah-2lh materialization then feeds the agent the doc as context); the answer is delivered via comments.create (parentCommentId threading) + optional comments.resolve under the bot identity. EXPLICITLY NOT wave 2.5: needs cursor persistence, a second poll loop, trigger-convention + mention-encoding verification, and a reply-channel decision. Depends on ah-gxa (comments methods; extend with ListEvents when picked up) and ah-25e (reply channel + bot write provisioning).
design # Yonote Q&A bot loop (post-wave-2.5 — honest scope, NOT part of wave 2.5) Goal: the operator asks questions IN Yonote (comments on docs — e.g. on agent-published reports or on specs); the bot notices, an agent produces the answer, the bot replies in-thread and resolves the comment. ## Verified surface this builds on (2026-07-18, bigbes.yonote.ru 1.47.1) - comments.list {entityId, isResolved filter, threadId, limit ≤100, offset} → {data:{comments:[…]}, pagination, total} — LIVE-verified (nested envelope; items carry threadId, isResolved, threadCommentsCount, attachments). - comments.create {entityType:"document", entityId, text, parentCommentId} → {data:{comment}} — LIVE-verified end-to-end on a probe doc: text stored verbatim (plain string), threading via parentCommentId; a reply's threadId points at the root comment. - comments.resolve {id, isResolved} → data.comment — spec'd (v2-preview), plus the product MCP server exposes yonote_resolve_comment, so the surface is real. - events.list POST {name, actorId, documentId, collectionId, limit, offset, sort/direction} — LIVE-verified (returned {pagination, data:[{id, name, actorId, actor, documentId, collectionId, createdAt, data, modelId}]}); the `name` filter ("objects.verb", e.g. "comments.create") is spec'd — server-side event filtering makes cheap incremental polling possible. - webhookSubscriptions.list responds LIVE (empty list) though it is UNDOCUMENTED in both Yonote OpenAPI specs (Outline heritage; auth.info policies include create/listWebhookSubscription). BUT the daemon binds loopback-only (config validateLoopback) with no public ingress, so v1 transport = POLLING events.list; webhooks stay a documented future option if ingress appears. ## Sketch (design decisions OPEN — settle when picked up) - Poll loop: own ticker (~60s), separate from the reconcile tick; cursor persisted in the store (new kv row, e.g. yonote_cursor = last seen event id/createdAt). MUST verify then: events.list ordering + Sorting params for reliable incremental reads (direction/sort exist in the Pagination /Sorting schemas but were not exercised). - Trigger filter (pick one): (a) new unresolved comments on docs AUTHORED BY THE BOT (createdById == bot user id from AuthInfo) — simplest, covers "questions about agent output"; (b) explicit @mention of the bot — mention ENCODING in comment text is UNVERIFIED (probe stored plain text verbatim; the editor may encode mentions as structured nodes invisible to .text); (c) any unresolved comment in configured collections. - Self-trigger guard: ignore events with actorId == bot user id. - Answer path: reuse the whole existing pipeline — create a board card (existing CreateTask port, agent_tasks bucket conventions) of a question-answer task type whose description contains the doc URL (the materialization bead then auto-exports the doc as context for the agent!) plus the question thread text; the agent writes the answer; the daemon replies via comments.create with parentCommentId = thread root and optionally comments.resolve. Reply delivery needs either a small extension of the publish channel ({"reply_to_comment": …}) or a dedicated .task/answer.json — decide at design time. ## Why not wave 2.5 Needs cursor persistence, a second poll loop, trigger-convention and mention-encoding verification, and a reply file-channel decision — none of which artifact materialization or the publish lane depend on. Client extensions required when picked up: ListEvents (events.list) and possibly webhookSubscriptions.* — add to internal/yonote then, not now. Depends on: internal/yonote client bead (comments methods land there), publish-lane bead (reply channel shape + bot write provisioning).
acceptance_criteria
notes Live evidence (2026-07-18): events.list POST works — {pagination, data:[{id, name, actorId, actor, documentId, collectionId, createdAt, data, modelId}]}, event names 'objects.verb' (observed documents.permanent_delete from the trash-purge cron); the name/actorId/documentId/collectionId request filters are v1-spec'd. comments.create/list verified end-to-end on the probe doc (threading via parentCommentId; replies carry threadId of the root; text stored verbatim). comments.resolve spec'd + present as yonote_resolve_comment in the product's own MCP server. webhookSubscriptions.list responds live (empty, UNDOCUMENTED in both OpenAPI specs — Outline heritage; auth.info policies include createWebhookSubscription) — future option only, blocked on public ingress. UNVERIFIED for pickup: events.list ordering/Sorting params for reliable incremental cursors; @mention encoding inside comment text (probe stored plain text; the editor may use structured nodes invisible in .text — trigger option (a) 'comments on bot-authored docs' avoids the question entirely).
status closed
priority 3
issue_type feature
assignee Eugene Blikh
estimated_minutes NULL
created_at 2026-07-18T15:04:41Z
created_by Eugene Blikh
owner bigbes@gmail.com
updated_at 2026-07-18T18:47:53Z
closed_at 2026-07-18T18:47:53Z
closed_by_session
external_ref NULL
spec_id
compaction_level 0
compacted_at NULL
compacted_at_commit NULL
original_size NULL
sender
ephemeral 0
wisp_type
pinned 0
is_template 0
mol_type
work_type
source_system
metadata �{}
source_repo
close_reason Implemented: events.list poll loop w/ kv cursor + exactly-once spawn, threaded answer delivery via answer.json, config yonote.qa block, store v5, prompts/qa.md. Rollout/live-verify tracked in ah-eje.
event_kind
actor
target
payload
await_type
await_id
timeout_ns 0
waiters
hook_bead
role_bead
agent_state
last_activity NULL
role_type
rig
due_at NULL
defer_until NULL
no_history 0
started_at 2026-07-18T18:19:54Z
is_blocked 0
dependencies 3 rows
id 22775d24-27d7-51cb-b356-4a769bb6c58b
issue_id ah-eje
type blocks
created_at 2026-07-18T21:47:52Z
created_by Eugene Blikh
metadata �{}
thread_id
depends_on_issue_id ah-ptu
depends_on_wisp_id NULL
depends_on_external NULL
id 6a912c84-0b5c-5b5f-9646-941067cace98
issue_id ah-ptu
type blocks
created_at 2026-07-18T18:04:57Z
created_by Eugene Blikh
metadata �{}
thread_id
depends_on_issue_id ah-gxa
depends_on_wisp_id NULL
depends_on_external NULL
id 76375cfe-53a8-5c6d-ba68-4de57738ddc7
issue_id ah-ptu
type blocks
created_at 2026-07-18T18:04:57Z
created_by Eugene Blikh
metadata �{}
thread_id
depends_on_issue_id ah-25e
depends_on_wisp_id NULL
depends_on_external NULL
events 5 rows
id 019f75c2-13bc-7b1f-b19a-c3f9ccc31d01
issue_id ah-ptu
event_type created
actor Eugene Blikh
old_value
new_value
comment NULL
created_at 2026-07-18T18:04:41Z
id 019f7674-cd39-7b71-b8aa-6e354dfe3552
issue_id ah-ptu
event_type claimed
actor Eugene Blikh
old_value {"id":"ah-ptu","title":"Yonote Q\u0026A bot loop: poll doc comments, answer via board tasks (post-wave-2.5)","description":"Why: the operator wants to ask questions as comments on Yonote docs (e.g. on agent-published reports) and have the bot answer in-thread. What: a polling loop (daemon has no public ingress — loopback-only listen — so webhooks are out for now) over events.list filtered to comments.create since a persisted cursor; qualifying comments spawn a question-answer board card whose description carries the doc URL (ah-2lh materialization then feeds the agent the doc as context); the answer is delivered via comments.create (parentCommentId threading) + optional comments.resolve under the bot identity. EXPLICITLY NOT wave 2.5: needs cursor persistence, a second poll loop, trigger-convention + mention-encoding verification, and a reply-channel decision. Depends on ah-gxa (comments methods; extend with ListEvents when picked up) and ah-25e (reply channel + bot write provisioning).","design":"# Yonote Q\u0026A bot loop (post-wave-2.5 — honest scope, NOT part of wave 2.5)\n\nGoal: the operator asks questions IN Yonote (comments on docs — e.g. on agent-published reports\nor on specs); the bot notices, an agent produces the answer, the bot replies in-thread and\nresolves the comment.\n\n## Verified surface this builds on (2026-07-18, bigbes.yonote.ru 1.47.1)\n\n- comments.list {entityId, isResolved filter, threadId, limit ≤100, offset} → {data:{comments:[…]},\n pagination, total} — LIVE-verified (nested envelope; items carry threadId, isResolved,\n threadCommentsCount, attachments).\n- comments.create {entityType:\"document\", entityId, text, parentCommentId} → {data:{comment}} —\n LIVE-verified end-to-end on a probe doc: text stored verbatim (plain string), threading via\n parentCommentId; a reply's threadId points at the root comment.\n- comments.resolve {id, isResolved} → data.comment — spec'd (v2-preview), plus the product MCP\n server exposes yonote_resolve_comment, so the surface is real.\n- events.list POST {name, actorId, documentId, collectionId, limit, offset, sort/direction} —\n LIVE-verified (returned {pagination, data:[{id, name, actorId, actor, documentId, collectionId,\n createdAt, data, modelId}]}); the `name` filter (\"objects.verb\", e.g. \"comments.create\") is\n spec'd — server-side event filtering makes cheap incremental polling possible.\n- webhookSubscriptions.list responds LIVE (empty list) though it is UNDOCUMENTED in both Yonote\n OpenAPI specs (Outline heritage; auth.info policies include create/listWebhookSubscription).\n BUT the daemon binds loopback-only (config validateLoopback) with no public ingress, so v1\n transport = POLLING events.list; webhooks stay a documented future option if ingress appears.\n\n## Sketch (design decisions OPEN — settle when picked up)\n\n- Poll loop: own ticker (~60s), separate from the reconcile tick; cursor persisted in the store\n (new kv row, e.g. yonote_cursor = last seen event id/createdAt). MUST verify then: events.list\n ordering + Sorting params for reliable incremental reads (direction/sort exist in the Pagination\n /Sorting schemas but were not exercised).\n- Trigger filter (pick one): (a) new unresolved comments on docs AUTHORED BY THE BOT\n (createdById == bot user id from AuthInfo) — simplest, covers \"questions about agent output\";\n (b) explicit @mention of the bot — mention ENCODING in comment text is UNVERIFIED (probe stored\n plain text verbatim; the editor may encode mentions as structured nodes invisible to .text);\n (c) any unresolved comment in configured collections.\n- Self-trigger guard: ignore events with actorId == bot user id.\n- Answer path: reuse the whole existing pipeline — create a board card (existing CreateTask port,\n agent_tasks bucket conventions) of a question-answer task type whose description contains the\n doc URL (the materialization bead then auto-exports the doc as context for the agent!) plus the\n question thread text; the agent writes the answer; the daemon replies via comments.create with\n parentCommentId = thread root and optionally comments.resolve. Reply delivery needs either a\n small extension of the publish channel ({\"reply_to_comment\": …}) or a dedicated\n .task/answer.json — decide at design time.\n\n## Why not wave 2.5\n\nNeeds cursor persistence, a second poll loop, trigger-convention and mention-encoding\nverification, and a reply file-channel decision — none of which artifact materialization or the\npublish lane depend on. Client extensions required when picked up: ListEvents (events.list) and\npossibly webhookSubscriptions.* — add to internal/yonote then, not now.\n\nDepends on: internal/yonote client bead (comments methods land there), publish-lane bead (reply\nchannel shape + bot write provisioning).\n","notes":"Live evidence (2026-07-18): events.list POST works — {pagination, data:[{id, name, actorId, actor, documentId, collectionId, createdAt, data, modelId}]}, event names 'objects.verb' (observed documents.permanent_delete from the trash-purge cron); the name/actorId/documentId/collectionId request filters are v1-spec'd. comments.create/list verified end-to-end on the probe doc (threading via parentCommentId; replies carry threadId of the root; text stored verbatim). comments.resolve spec'd + present as yonote_resolve_comment in the product's own MCP server. webhookSubscriptions.list responds live (empty, UNDOCUMENTED in both OpenAPI specs — Outline heritage; auth.info policies include createWebhookSubscription) — future option only, blocked on public ingress. UNVERIFIED for pickup: events.list ordering/Sorting params for reliable incremental cursors; @mention encoding inside comment text (probe stored plain text; the editor may use structured nodes invisible in .text — trigger option (a) 'comments on bot-authored docs' avoids the question entirely).","status":"open","priority":3,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-18T15:04:41Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T15:04:41Z"}
new_value {"assignee":"Eugene Blikh","status":"in_progress"}
comment NULL
created_at 2026-07-18T21:19:54Z
id 019f768e-6c3c-7213-b14d-2bbf0b22157c
issue_id ah-ptu
event_type closed
actor Eugene Blikh
old_value
new_value Implemented: events.list poll loop w/ kv cursor + exactly-once spawn, threaded answer delivery via answer.json, config yonote.qa block, store v5, prompts/qa.md. Rollout/live-verify tracked in ah-eje.
comment NULL
created_at 2026-07-18T21:47:53Z
id 019f7cda-aa8c-777a-b3da-2dda34bda058
issue_id ah-ptu
event_type label_added
actor Eugene Blikh
old_value NULL
new_value NULL
comment Added label: milestone:yonote
created_at 2026-07-20T03:08:53Z
id 019f7cdc-29dd-7683-8ecf-896151103c2e
issue_id ah-ptu
event_type label_removed
actor Eugene Blikh
old_value NULL
new_value NULL
comment Removed label: milestone:yonote
created_at 2026-07-20T03:10:31Z