~bigbes/agents-dev · parade

main · last commit 13 days ago · 7g0stsfu

← Back to the parade

ah-25e Yonote publish lane: .task/publish.json → bot-authored docs at finalize Past Stand

status: closed P2 feature @Eugene Blikh
bd reopen ah-25e
Created byEugene Blikh
Ownerbigbes@gmail.com
Created2026-07-18T15:04:25Z
Started2026-07-18T15:06:57Z
Updated2026-07-18T16:09:01Z
Closed2026-07-18T15:59:08Z
Description
Why: the operator wants agents to publish specs/reports/answers INTO Yonote instead of committing them to the project repo — with the write credential never entering the agent's environment. What: a third finalize-time file channel in the SPEC §14.4 family (tasks.json, question.json): the agent writes markdown under .task/publish/ and declares it in .task/publish.json {documents:[{title, file, collection_id?, parent_url?}]}; on successful finalize the daemon creates the docs via the internal/yonote client under the BOT token (publish:true, target = yonote.publish_collection_id or per-entry override, optional nesting under a referenced doc), comments the resulting URLs on the card, emits a yonote_published event. Publish failures never change the run outcome. wave 2.5 — implement after feat/wave2-archive-links merges. Depends on ah-gxa (client: CreateDocument/DocumentInfo) and ah-2lh (yonote config block + prepareTaskDir channel-clearing site).
Design
# Yonote publish lane: .task/publish.json → bot-authored docs at finalize

Agents publish specs/reports/answers INTO Yonote instead of committing them to the project repo.
The agent declares intent via a file channel; the daemon executes with ITS bot token at finalize
and links results on the card.

## Why file-channel, not direct API access from the agent (DECISION)

The agent must never hold the Yonote token: worktrees run arbitrary generated code; `.task/` is
archived to tar.gz at Done (a token in env or files would fossilize into archives); per-run env
plumbing would put a write-capable credential into every zellij session. The daemon already owns a
finalize-time file-channel protocol with exactly the right semantics — `.task/tasks.json`
(delegation) and `.task/question.json` (ask-user), SPEC §14.4: agent writes declarative intent,
daemon acts with its own credentials, results land as card comments. Publishing is the third
channel of that family. Attribution lands on the BOT user — uniform and auditable.

## Channel format

`.task/publish.json` (attempt-scoped; cleared in prepareTaskDir alongside summary/tasks/question):

    {"documents": [
      {"title": "Design: frobnicator", "file": "publish/design.md",
       "collection_id": "", "parent_url": ""}
    ]}

- title: required, non-empty, ≤255 chars (Yonote bot/user name limits suggest 255 conventions).
- file: required; RELATIVE path resolved under `.task/` (convention: agents put content in
  `.task/publish/<name>.md`). Guard rigor as repo-slug F8: reject absolute paths, filepath.Clean
  must stay under .task/, and the opened file must not escape via symlink (os.Root / EvalSymlinks
  containment check).
- collection_id: optional uuid overriding the config default target.
- parent_url: optional Yonote doc URL → resolve via yonote.ParseDocRefs(single) + DocumentInfo →
  parentDocumentId, so a report can nest under the spec it answers (documents.create
  parentDocumentId is v1-spec'd; child creation itself NOT live-verified — verify in tests against
  httptest only, live during rollout).
- Caps: maxPublishDocs = 3 per run; maxPublishBytes = 512 KiB per file. No server-side text limit
  was verified — cap defensively; an oversize entry FAILS (listed in the comment), never truncated.

Read at SUCCESSFUL finalize only, same site and semantics as readTasksFile: absent → nil (common
case); present-but-unparseable → same bounce treatment as a malformed tasks.json (do not silently
drop agent intent).

## Config

The `yonote` block (introduced by the materialization bead) gains:

    publish_collection_id: "…"    # uuid; the default target collection

Validation: when set, must be a UUID shape; lane is ACTIVE only when the yonote client is
configured AND publish_collection_id is set. When the client is configured but the collection is
not, publish.json entries all fail visibly with "publishing not configured" in the card comment —
never silently ignored. Operational prerequisite (document next to the config): the BOT must have
read_write on the target collection (collections.add_user — client-bead runbook step 3).

## Daemon flow (finalize, success path, after summary/tasks/question handling)

Per entry (≤3, in file order): resolve target collection (override or default) → resolve
parent_url if set (its failure fails ONLY that entry) → read + cap content →
CreateDocument{Title, CollectionID, ParentDocumentID, Text: content, Publish: true} → record
{Title, absURL = cfg base_url + doc.URL} or {Title, err}.

- Card comment (goes through the existing markdownToHTML comment path):
      published to Yonote:
      - <Title> → <abs url>
      - <Title> → FAILED: <message>
- appendEvent "yonote_published" {"ok": n, "failed": m}.
- Publish failures NEVER change the run outcome — the run already succeeded; publication is a
  side effect. Operator retries by re-running, or publishes manually from the archived tar.gz
  (the content survives there by construction).
- Idempotency (DECISION): re-running a card re-publishes and creates a NEW doc. Accepted for v1 —
  re-runs are rare, docs are cheap, and dedup would need a doc-id echo channel; revisit only if it
  annoys in practice (then: UpdateDocument by echoed id).

## Agent contract (role-prompt snippet, config-side; document in deploy notes)

    To publish a document to Yonote: write markdown to .task/publish/<name>.md and declare it in
    .task/publish.json as {"documents":[{"title":"…","file":"publish/<name>.md"}]}. Publication
    happens AFTER your run succeeds; resulting links are posted on the task card.

## Tests

- reader: absent → nil; malformed → tasks.json-parity bounce; >3 entries; empty title; path
  guards (absolute, .., symlink escape); oversize file.
- finalize with fake yonote writer (interface seam: CreateDocument + DocumentInfo): 2 ok + 1 fail
  → comment body lines, event payload, run outcome unchanged; lane-off (no collection id) →
  "publishing not configured" per entry; parent_url resolve failure isolates the entry;
  collection_id override honored.
- prepareTaskDir clears publish.json + the publish/ dir between attempts.

wave 2.5 — implement after feat/wave2-archive-links merges. Depends on: internal/yonote client
bead (CreateDocument/DocumentInfo), materialization bead (yonote config block + prepareTaskDir
channel-clearing site).
Notes
  ROLLOUT LIVE-SMOKE CHECKLIST (from wave-2.5 implementer, against            
  bigbes.yonote.ru after deploy): 1) mint bot per ah-gxa runbook (POST        
  /api/v2/bots + token, collections.add_user grant — grant call is spec-only, 
  NOT live-verified), AuthInfo under bot token must show IsBot=true (daemon   
  WARNs at startup otherwise); 2) documents.create with parentDocumentId      
  (child nesting unverified live) + bot WRITE rights on publish_collection_id 
  + publish:true attribution to bot; 3) archived-doc export ((archived) marker
  path); 4) ExportMarkdown empty-string cases (API-created never-edited draft,
  database-type doc) fall back to .text; 5) comments.create/list/resolve field
  names (only Q&A bead ah-ptu uses them). SEMANTICS NOTE: publish fires on    
  ROUTED successful runs too (inside firstFinalize&&success before maybeRoute)
  — a design stage with publish.json publishes before routing to review;      
  intentional. publish_collection_id (UUID) must be chosen by operator at     
  config staging.                                                             



DEPENDS ON
  → ✓ ah-2lh: Claim-time Yonote artifact materialization into .task/artifacts/ + prompt manifest ● P2
  → ✓ ah-gxa: internal/yonote: light API client (doc resolve/export, create, comments; bot-token auth) ● P2

BLOCKS
  ← ○ ah-ptu: Yonote Q&A bot loop: poll doc comments, answer via board tasks (post-wave-2.5) ● P3

ROLLOUT FACTS (2026-07-18, live-verified): bot agenthub id=34fbc9a3-ec75-4722-a01f-805db3f3ff1b (v2 create needs username field — used bigbes@gmail.com); token agent-1-daemon installed as YONOTE_TOKEN in /etc/agent-hub/env; auth.info isBot=true CONFIRMED; collections.add_user CONFIRMED; Agents collection 8656642e-0297-4d69-b6fb-4246517a015b = publish_collection_id, bot read_write. CAVEAT: bot sees ALL team-default-readable collections (not grant-scoped) — operator may tighten per-collection perms in UI. Config staging after CI deploy: yonote{base_url: https://bigbes.yonote.ru, token: ${YONOTE_TOKEN}, publish_collection_id: 8656642e-0297-4d69-b6fb-4246517a015b}.

Depends on

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

Depended on by

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

Prerequisite chain — everything this waits on, transitively

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

Unblocks — everything waiting on this, transitively

  • ah-ptu — Yonote Q&A bot loop: poll doc comments, answer via board tasks (post-wave-2.5) blocks closed
  • ah-eje — Q&A loop rollout: live-verify events.list ordering + comments.resolve, wire qa role/config on agent-1 blocks closed

No comments.

Close reason

Merged to master f9a7b4b (3 commits 6c9e259/a25b692/f9a7b4b): yonote client + claim-time artifact materialization + publish lane. Live smoke at rollout per ah-25e notes.
  • Eugene Blikh created the issue · 2026-07-18T18:04:24Z
  • Eugene Blikh added dependency on ah-2lh · 2026-07-18T18:04:56Z
  • Eugene Blikh added dependency on ah-gxa · 2026-07-18T18:04:56Z
  • Eugene Blikh ah-ptu now depends on this · 2026-07-18T18:04:57Z
  • Eugene Blikh claimed · 2026-07-18T18:06:56Z
  • Eugene Blikh updated notes to ROLLOUT LIVE-SMOKE CHECKLIST (from wave-2.5 implementer, against bigbes.yonote.ru after deploy): 1) mint bot per ah-gxa runbook (POST /api/v2/bots + token, collections.add_user grant — grant call is spec-only, NOT live-verified), AuthInfo under bot token must show IsBot=true (daemon WARNs at startup otherwise); 2) documents.create with parentDocumentId (child nesting unverified live) + bot WRITE rights on publish_collection_id + publish:true attribution to bot; 3) archived-doc export ((archived) marker path); 4) ExportMarkdown empty-string cases (API-created never-edited draft, database-type doc) fall back to .text; 5) comments.create/list/resolve field names (only Q&A bead ah-ptu uses them). SEMANTICS NOTE: publish fires on ROUTED successful runs too (inside firstFinalize&&success before maybeRoute) — a design stage with publish.json publishes before routing to review; intentional. publish_collection_id (UUID) must be chosen by operator at config staging. · 2026-07-18T18:59:06Z
  • Eugene Blikh closed the issue · 2026-07-18T18:59:07Z
    Merged to master f9a7b4b (3 commits 6c9e259/a25b692/f9a7b4b): yonote client + claim-time artifact materialization + publish lane. Live smoke at rollout per ah-25e notes.
  • Eugene Blikh updated notes to ROLLOUT LIVE-SMOKE CHECKLIST (from wave-2.5 implementer, against bigbes.yonote.ru after deploy): 1) mint bot per ah-gxa runbook (POST /api/v2/bots + token, collections.add_user grant — grant call is spec-only, NOT live-verified), AuthInfo under bot token must show IsBot=true (daemon WARNs at startup otherwise); 2) documents.create with parentDocumentId (child nesting unverified live) + bot WRITE rights on publish_collection_id + publish:true attribution to bot; 3) archived-doc export ((archived) marker path); 4) ExportMarkdown empty-string cases (API-created never-edited draft, database-type doc) fall back to .text; 5) comments.create/list/resolve field names (only Q&A bead ah-ptu uses them). SEMANTICS NOTE: publish fires on ROUTED successful runs too (inside firstFinalize&&success before maybeRoute) — a design stage with publish.json publishes before routing to review; intentional. publish_collection_id (UUID) must be chosen by operator at config staging. DEPENDS ON → ✓ ah-2lh: Claim-time Yonote artifact materialization into .task/artifacts/ + prompt manifest ● P2 → ✓ ah-gxa: internal/yonote: light API client (doc resolve/export, create, comments; bot-token auth) ● P2 BLOCKS ← ○ ah-ptu: Yonote Q&A bot loop: poll doc comments, answer via board tasks (post-wave-2.5) ● P3 ROLLOUT FACTS (2026-07-18, live-verified): bot agenthub id=34fbc9a3-ec75-4722-a01f-805db3f3ff1b (v2 create needs username field — used bigbes@gmail.com); token agent-1-daemon installed as YONOTE_TOKEN in /etc/agent-hub/env; auth.info isBot=true CONFIRMED; collections.add_user CONFIRMED; Agents collection 8656642e-0297-4d69-b6fb-4246517a015b = publish_collection_id, bot read_write. CAVEAT: bot sees ALL team-default-readable collections (not grant-scoped) — operator may tighten per-collection perms in UI. Config staging after CI deploy: yonote{base_url: https://bigbes.yonote.ru, token: ${YONOTE_TOKEN}, publish_collection_id: 8656642e-0297-4d69-b6fb-4246517a015b}. · 2026-07-18T19:09:01Z
  • Eugene Blikh added label milestone:yonote · 2026-07-20T03:08:53Z
  • Eugene Blikh removed label milestone:yonote · 2026-07-20T03:10:30Z
Stored rows — what this pane was built from, as read
issues 1 row
id ah-25e
content_hash 81a248a73cc547b68a28816e4f3c67ea4f5ca77bf4c95f40233a17b35b901880
title Yonote publish lane: .task/publish.json → bot-authored docs at finalize
description Why: the operator wants agents to publish specs/reports/answers INTO Yonote instead of committing them to the project repo — with the write credential never entering the agent's environment. What: a third finalize-time file channel in the SPEC §14.4 family (tasks.json, question.json): the agent writes markdown under .task/publish/ and declares it in .task/publish.json {documents:[{title, file, collection_id?, parent_url?}]}; on successful finalize the daemon creates the docs via the internal/yonote client under the BOT token (publish:true, target = yonote.publish_collection_id or per-entry override, optional nesting under a referenced doc), comments the resulting URLs on the card, emits a yonote_published event. Publish failures never change the run outcome. wave 2.5 — implement after feat/wave2-archive-links merges. Depends on ah-gxa (client: CreateDocument/DocumentInfo) and ah-2lh (yonote config block + prepareTaskDir channel-clearing site).
design # Yonote publish lane: .task/publish.json → bot-authored docs at finalize Agents publish specs/reports/answers INTO Yonote instead of committing them to the project repo. The agent declares intent via a file channel; the daemon executes with ITS bot token at finalize and links results on the card. ## Why file-channel, not direct API access from the agent (DECISION) The agent must never hold the Yonote token: worktrees run arbitrary generated code; `.task/` is archived to tar.gz at Done (a token in env or files would fossilize into archives); per-run env plumbing would put a write-capable credential into every zellij session. The daemon already owns a finalize-time file-channel protocol with exactly the right semantics — `.task/tasks.json` (delegation) and `.task/question.json` (ask-user), SPEC §14.4: agent writes declarative intent, daemon acts with its own credentials, results land as card comments. Publishing is the third channel of that family. Attribution lands on the BOT user — uniform and auditable. ## Channel format `.task/publish.json` (attempt-scoped; cleared in prepareTaskDir alongside summary/tasks/question): {"documents": [ {"title": "Design: frobnicator", "file": "publish/design.md", "collection_id": "", "parent_url": ""} ]} - title: required, non-empty, ≤255 chars (Yonote bot/user name limits suggest 255 conventions). - file: required; RELATIVE path resolved under `.task/` (convention: agents put content in `.task/publish/<name>.md`). Guard rigor as repo-slug F8: reject absolute paths, filepath.Clean must stay under .task/, and the opened file must not escape via symlink (os.Root / EvalSymlinks containment check). - collection_id: optional uuid overriding the config default target. - parent_url: optional Yonote doc URL → resolve via yonote.ParseDocRefs(single) + DocumentInfo → parentDocumentId, so a report can nest under the spec it answers (documents.create parentDocumentId is v1-spec'd; child creation itself NOT live-verified — verify in tests against httptest only, live during rollout). - Caps: maxPublishDocs = 3 per run; maxPublishBytes = 512 KiB per file. No server-side text limit was verified — cap defensively; an oversize entry FAILS (listed in the comment), never truncated. Read at SUCCESSFUL finalize only, same site and semantics as readTasksFile: absent → nil (common case); present-but-unparseable → same bounce treatment as a malformed tasks.json (do not silently drop agent intent). ## Config The `yonote` block (introduced by the materialization bead) gains: publish_collection_id: "…" # uuid; the default target collection Validation: when set, must be a UUID shape; lane is ACTIVE only when the yonote client is configured AND publish_collection_id is set. When the client is configured but the collection is not, publish.json entries all fail visibly with "publishing not configured" in the card comment — never silently ignored. Operational prerequisite (document next to the config): the BOT must have read_write on the target collection (collections.add_user — client-bead runbook step 3). ## Daemon flow (finalize, success path, after summary/tasks/question handling) Per entry (≤3, in file order): resolve target collection (override or default) → resolve parent_url if set (its failure fails ONLY that entry) → read + cap content → CreateDocument{Title, CollectionID, ParentDocumentID, Text: content, Publish: true} → record {Title, absURL = cfg base_url + doc.URL} or {Title, err}. - Card comment (goes through the existing markdownToHTML comment path): published to Yonote: - <Title> → <abs url> - <Title> → FAILED: <message> - appendEvent "yonote_published" {"ok": n, "failed": m}. - Publish failures NEVER change the run outcome — the run already succeeded; publication is a side effect. Operator retries by re-running, or publishes manually from the archived tar.gz (the content survives there by construction). - Idempotency (DECISION): re-running a card re-publishes and creates a NEW doc. Accepted for v1 — re-runs are rare, docs are cheap, and dedup would need a doc-id echo channel; revisit only if it annoys in practice (then: UpdateDocument by echoed id). ## Agent contract (role-prompt snippet, config-side; document in deploy notes) To publish a document to Yonote: write markdown to .task/publish/<name>.md and declare it in .task/publish.json as {"documents":[{"title":"…","file":"publish/<name>.md"}]}. Publication happens AFTER your run succeeds; resulting links are posted on the task card. ## Tests - reader: absent → nil; malformed → tasks.json-parity bounce; >3 entries; empty title; path guards (absolute, .., symlink escape); oversize file. - finalize with fake yonote writer (interface seam: CreateDocument + DocumentInfo): 2 ok + 1 fail → comment body lines, event payload, run outcome unchanged; lane-off (no collection id) → "publishing not configured" per entry; parent_url resolve failure isolates the entry; collection_id override honored. - prepareTaskDir clears publish.json + the publish/ dir between attempts. wave 2.5 — implement after feat/wave2-archive-links merges. Depends on: internal/yonote client bead (CreateDocument/DocumentInfo), materialization bead (yonote config block + prepareTaskDir channel-clearing site).
acceptance_criteria
notes ROLLOUT LIVE-SMOKE CHECKLIST (from wave-2.5 implementer, against bigbes.yonote.ru after deploy): 1) mint bot per ah-gxa runbook (POST /api/v2/bots + token, collections.add_user grant — grant call is spec-only, NOT live-verified), AuthInfo under bot token must show IsBot=true (daemon WARNs at startup otherwise); 2) documents.create with parentDocumentId (child nesting unverified live) + bot WRITE rights on publish_collection_id + publish:true attribution to bot; 3) archived-doc export ((archived) marker path); 4) ExportMarkdown empty-string cases (API-created never-edited draft, database-type doc) fall back to .text; 5) comments.create/list/resolve field names (only Q&A bead ah-ptu uses them). SEMANTICS NOTE: publish fires on ROUTED successful runs too (inside firstFinalize&&success before maybeRoute) — a design stage with publish.json publishes before routing to review; intentional. publish_collection_id (UUID) must be chosen by operator at config staging. DEPENDS ON → ✓ ah-2lh: Claim-time Yonote artifact materialization into .task/artifacts/ + prompt manifest ● P2 → ✓ ah-gxa: internal/yonote: light API client (doc resolve/export, create, comments; bot-token auth) ● P2 BLOCKS ← ○ ah-ptu: Yonote Q&A bot loop: poll doc comments, answer via board tasks (post-wave-2.5) ● P3 ROLLOUT FACTS (2026-07-18, live-verified): bot agenthub id=34fbc9a3-ec75-4722-a01f-805db3f3ff1b (v2 create needs username field — used bigbes@gmail.com); token agent-1-daemon installed as YONOTE_TOKEN in /etc/agent-hub/env; auth.info isBot=true CONFIRMED; collections.add_user CONFIRMED; Agents collection 8656642e-0297-4d69-b6fb-4246517a015b = publish_collection_id, bot read_write. CAVEAT: bot sees ALL team-default-readable collections (not grant-scoped) — operator may tighten per-collection perms in UI. Config staging after CI deploy: yonote{base_url: https://bigbes.yonote.ru, token: ${YONOTE_TOKEN}, publish_collection_id: 8656642e-0297-4d69-b6fb-4246517a015b}.
status closed
priority 2
issue_type feature
assignee Eugene Blikh
estimated_minutes NULL
created_at 2026-07-18T15:04:25Z
created_by Eugene Blikh
owner bigbes@gmail.com
updated_at 2026-07-18T16:09:01Z
closed_at 2026-07-18T15:59:08Z
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 Merged to master f9a7b4b (3 commits 6c9e259/a25b692/f9a7b4b): yonote client + claim-time artifact materialization + publish lane. Live smoke at rollout per ah-25e notes.
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-18T15:06:57Z
is_blocked 0
dependencies 3 rows
id 56546f03-0823-5534-84f8-a1d919c5ce0b
issue_id ah-25e
type blocks
created_at 2026-07-18T18:04:56Z
created_by Eugene Blikh
metadata �{}
thread_id
depends_on_issue_id ah-2lh
depends_on_wisp_id NULL
depends_on_external NULL
id 5b6045e4-6bd4-5f00-bb39-d36a8c263ace
issue_id ah-25e
type blocks
created_at 2026-07-18T18:04:56Z
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 7 rows
id 019f75c1-d49a-7ae1-a057-bc520be235ee
issue_id ah-25e
event_type created
actor Eugene Blikh
old_value
new_value
comment NULL
created_at 2026-07-18T18:04:24Z
id 019f75c4-2645-733c-93ff-b573473c0544
issue_id ah-25e
event_type claimed
actor Eugene Blikh
old_value {"id":"ah-25e","title":"Yonote publish lane: .task/publish.json → bot-authored docs at finalize","description":"Why: the operator wants agents to publish specs/reports/answers INTO Yonote instead of committing them to the project repo — with the write credential never entering the agent's environment. What: a third finalize-time file channel in the SPEC §14.4 family (tasks.json, question.json): the agent writes markdown under .task/publish/ and declares it in .task/publish.json {documents:[{title, file, collection_id?, parent_url?}]}; on successful finalize the daemon creates the docs via the internal/yonote client under the BOT token (publish:true, target = yonote.publish_collection_id or per-entry override, optional nesting under a referenced doc), comments the resulting URLs on the card, emits a yonote_published event. Publish failures never change the run outcome. wave 2.5 — implement after feat/wave2-archive-links merges. Depends on ah-gxa (client: CreateDocument/DocumentInfo) and ah-2lh (yonote config block + prepareTaskDir channel-clearing site).","design":"# Yonote publish lane: .task/publish.json → bot-authored docs at finalize\n\nAgents publish specs/reports/answers INTO Yonote instead of committing them to the project repo.\nThe agent declares intent via a file channel; the daemon executes with ITS bot token at finalize\nand links results on the card.\n\n## Why file-channel, not direct API access from the agent (DECISION)\n\nThe agent must never hold the Yonote token: worktrees run arbitrary generated code; `.task/` is\narchived to tar.gz at Done (a token in env or files would fossilize into archives); per-run env\nplumbing would put a write-capable credential into every zellij session. The daemon already owns a\nfinalize-time file-channel protocol with exactly the right semantics — `.task/tasks.json`\n(delegation) and `.task/question.json` (ask-user), SPEC §14.4: agent writes declarative intent,\ndaemon acts with its own credentials, results land as card comments. Publishing is the third\nchannel of that family. Attribution lands on the BOT user — uniform and auditable.\n\n## Channel format\n\n`.task/publish.json` (attempt-scoped; cleared in prepareTaskDir alongside summary/tasks/question):\n\n {\"documents\": [\n {\"title\": \"Design: frobnicator\", \"file\": \"publish/design.md\",\n \"collection_id\": \"\", \"parent_url\": \"\"}\n ]}\n\n- title: required, non-empty, ≤255 chars (Yonote bot/user name limits suggest 255 conventions).\n- file: required; RELATIVE path resolved under `.task/` (convention: agents put content in\n `.task/publish/\u003cname\u003e.md`). Guard rigor as repo-slug F8: reject absolute paths, filepath.Clean\n must stay under .task/, and the opened file must not escape via symlink (os.Root / EvalSymlinks\n containment check).\n- collection_id: optional uuid overriding the config default target.\n- parent_url: optional Yonote doc URL → resolve via yonote.ParseDocRefs(single) + DocumentInfo →\n parentDocumentId, so a report can nest under the spec it answers (documents.create\n parentDocumentId is v1-spec'd; child creation itself NOT live-verified — verify in tests against\n httptest only, live during rollout).\n- Caps: maxPublishDocs = 3 per run; maxPublishBytes = 512 KiB per file. No server-side text limit\n was verified — cap defensively; an oversize entry FAILS (listed in the comment), never truncated.\n\nRead at SUCCESSFUL finalize only, same site and semantics as readTasksFile: absent → nil (common\ncase); present-but-unparseable → same bounce treatment as a malformed tasks.json (do not silently\ndrop agent intent).\n\n## Config\n\nThe `yonote` block (introduced by the materialization bead) gains:\n\n publish_collection_id: \"…\" # uuid; the default target collection\n\nValidation: when set, must be a UUID shape; lane is ACTIVE only when the yonote client is\nconfigured AND publish_collection_id is set. When the client is configured but the collection is\nnot, publish.json entries all fail visibly with \"publishing not configured\" in the card comment —\nnever silently ignored. Operational prerequisite (document next to the config): the BOT must have\nread_write on the target collection (collections.add_user — client-bead runbook step 3).\n\n## Daemon flow (finalize, success path, after summary/tasks/question handling)\n\nPer entry (≤3, in file order): resolve target collection (override or default) → resolve\nparent_url if set (its failure fails ONLY that entry) → read + cap content →\nCreateDocument{Title, CollectionID, ParentDocumentID, Text: content, Publish: true} → record\n{Title, absURL = cfg base_url + doc.URL} or {Title, err}.\n\n- Card comment (goes through the existing markdownToHTML comment path):\n published to Yonote:\n - \u003cTitle\u003e → \u003cabs url\u003e\n - \u003cTitle\u003e → FAILED: \u003cmessage\u003e\n- appendEvent \"yonote_published\" {\"ok\": n, \"failed\": m}.\n- Publish failures NEVER change the run outcome — the run already succeeded; publication is a\n side effect. Operator retries by re-running, or publishes manually from the archived tar.gz\n (the content survives there by construction).\n- Idempotency (DECISION): re-running a card re-publishes and creates a NEW doc. Accepted for v1 —\n re-runs are rare, docs are cheap, and dedup would need a doc-id echo channel; revisit only if it\n annoys in practice (then: UpdateDocument by echoed id).\n\n## Agent contract (role-prompt snippet, config-side; document in deploy notes)\n\n To publish a document to Yonote: write markdown to .task/publish/\u003cname\u003e.md and declare it in\n .task/publish.json as {\"documents\":[{\"title\":\"…\",\"file\":\"publish/\u003cname\u003e.md\"}]}. Publication\n happens AFTER your run succeeds; resulting links are posted on the task card.\n\n## Tests\n\n- reader: absent → nil; malformed → tasks.json-parity bounce; \u003e3 entries; empty title; path\n guards (absolute, .., symlink escape); oversize file.\n- finalize with fake yonote writer (interface seam: CreateDocument + DocumentInfo): 2 ok + 1 fail\n → comment body lines, event payload, run outcome unchanged; lane-off (no collection id) →\n \"publishing not configured\" per entry; parent_url resolve failure isolates the entry;\n collection_id override honored.\n- prepareTaskDir clears publish.json + the publish/ dir between attempts.\n\nwave 2.5 — implement after feat/wave2-archive-links merges. Depends on: internal/yonote client\nbead (CreateDocument/DocumentInfo), materialization bead (yonote config block + prepareTaskDir\nchannel-clearing site).\n","notes":"Decision record: direct agent→Yonote API access was REJECTED — worktrees run arbitrary generated code, .task/ fossilizes into Done-archive tar.gz (a token in env/files would be archived), and the existing file-channel protocol (runner/taskfiles.go readTasksFile/readQuestionFile, attempt-scoped clearing in prepareTaskDir) already gives declarative-intent + daemon-credential semantics with card-comment observability. API evidence (ah-gxa notes, one-probe lifecycle): documents.create stores submitted markdown VERBATIM (create response echoed text byte-identical) and returns the site-relative url for the card comment (abs = base_url + url); publish:false honored (draft), so publish:true is explicit; comments.create/list verified for the future reply path. NOT live-verified: parentDocumentId child creation (v1-spec'd; exercise via httptest now, live during rollout), server-side max document text size (none documented — hence the defensive 512 KiB per-file cap), bot write rights on a granted collection (provisioning-time check, ah-gxa runbook step 4).","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-18T15:04:25Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T15:04:25Z"}
new_value {"assignee":"Eugene Blikh","status":"in_progress"}
comment NULL
created_at 2026-07-18T18:06:56Z
id 019f75f3-e70e-76a7-86e2-16953fca1473
issue_id ah-25e
event_type updated
actor Eugene Blikh
old_value {"id":"ah-25e","title":"Yonote publish lane: .task/publish.json → bot-authored docs at finalize","description":"Why: the operator wants agents to publish specs/reports/answers INTO Yonote instead of committing them to the project repo — with the write credential never entering the agent's environment. What: a third finalize-time file channel in the SPEC §14.4 family (tasks.json, question.json): the agent writes markdown under .task/publish/ and declares it in .task/publish.json {documents:[{title, file, collection_id?, parent_url?}]}; on successful finalize the daemon creates the docs via the internal/yonote client under the BOT token (publish:true, target = yonote.publish_collection_id or per-entry override, optional nesting under a referenced doc), comments the resulting URLs on the card, emits a yonote_published event. Publish failures never change the run outcome. wave 2.5 — implement after feat/wave2-archive-links merges. Depends on ah-gxa (client: CreateDocument/DocumentInfo) and ah-2lh (yonote config block + prepareTaskDir channel-clearing site).","design":"# Yonote publish lane: .task/publish.json → bot-authored docs at finalize\n\nAgents publish specs/reports/answers INTO Yonote instead of committing them to the project repo.\nThe agent declares intent via a file channel; the daemon executes with ITS bot token at finalize\nand links results on the card.\n\n## Why file-channel, not direct API access from the agent (DECISION)\n\nThe agent must never hold the Yonote token: worktrees run arbitrary generated code; `.task/` is\narchived to tar.gz at Done (a token in env or files would fossilize into archives); per-run env\nplumbing would put a write-capable credential into every zellij session. The daemon already owns a\nfinalize-time file-channel protocol with exactly the right semantics — `.task/tasks.json`\n(delegation) and `.task/question.json` (ask-user), SPEC §14.4: agent writes declarative intent,\ndaemon acts with its own credentials, results land as card comments. Publishing is the third\nchannel of that family. Attribution lands on the BOT user — uniform and auditable.\n\n## Channel format\n\n`.task/publish.json` (attempt-scoped; cleared in prepareTaskDir alongside summary/tasks/question):\n\n {\"documents\": [\n {\"title\": \"Design: frobnicator\", \"file\": \"publish/design.md\",\n \"collection_id\": \"\", \"parent_url\": \"\"}\n ]}\n\n- title: required, non-empty, ≤255 chars (Yonote bot/user name limits suggest 255 conventions).\n- file: required; RELATIVE path resolved under `.task/` (convention: agents put content in\n `.task/publish/\u003cname\u003e.md`). Guard rigor as repo-slug F8: reject absolute paths, filepath.Clean\n must stay under .task/, and the opened file must not escape via symlink (os.Root / EvalSymlinks\n containment check).\n- collection_id: optional uuid overriding the config default target.\n- parent_url: optional Yonote doc URL → resolve via yonote.ParseDocRefs(single) + DocumentInfo →\n parentDocumentId, so a report can nest under the spec it answers (documents.create\n parentDocumentId is v1-spec'd; child creation itself NOT live-verified — verify in tests against\n httptest only, live during rollout).\n- Caps: maxPublishDocs = 3 per run; maxPublishBytes = 512 KiB per file. No server-side text limit\n was verified — cap defensively; an oversize entry FAILS (listed in the comment), never truncated.\n\nRead at SUCCESSFUL finalize only, same site and semantics as readTasksFile: absent → nil (common\ncase); present-but-unparseable → same bounce treatment as a malformed tasks.json (do not silently\ndrop agent intent).\n\n## Config\n\nThe `yonote` block (introduced by the materialization bead) gains:\n\n publish_collection_id: \"…\" # uuid; the default target collection\n\nValidation: when set, must be a UUID shape; lane is ACTIVE only when the yonote client is\nconfigured AND publish_collection_id is set. When the client is configured but the collection is\nnot, publish.json entries all fail visibly with \"publishing not configured\" in the card comment —\nnever silently ignored. Operational prerequisite (document next to the config): the BOT must have\nread_write on the target collection (collections.add_user — client-bead runbook step 3).\n\n## Daemon flow (finalize, success path, after summary/tasks/question handling)\n\nPer entry (≤3, in file order): resolve target collection (override or default) → resolve\nparent_url if set (its failure fails ONLY that entry) → read + cap content →\nCreateDocument{Title, CollectionID, ParentDocumentID, Text: content, Publish: true} → record\n{Title, absURL = cfg base_url + doc.URL} or {Title, err}.\n\n- Card comment (goes through the existing markdownToHTML comment path):\n published to Yonote:\n - \u003cTitle\u003e → \u003cabs url\u003e\n - \u003cTitle\u003e → FAILED: \u003cmessage\u003e\n- appendEvent \"yonote_published\" {\"ok\": n, \"failed\": m}.\n- Publish failures NEVER change the run outcome — the run already succeeded; publication is a\n side effect. Operator retries by re-running, or publishes manually from the archived tar.gz\n (the content survives there by construction).\n- Idempotency (DECISION): re-running a card re-publishes and creates a NEW doc. Accepted for v1 —\n re-runs are rare, docs are cheap, and dedup would need a doc-id echo channel; revisit only if it\n annoys in practice (then: UpdateDocument by echoed id).\n\n## Agent contract (role-prompt snippet, config-side; document in deploy notes)\n\n To publish a document to Yonote: write markdown to .task/publish/\u003cname\u003e.md and declare it in\n .task/publish.json as {\"documents\":[{\"title\":\"…\",\"file\":\"publish/\u003cname\u003e.md\"}]}. Publication\n happens AFTER your run succeeds; resulting links are posted on the task card.\n\n## Tests\n\n- reader: absent → nil; malformed → tasks.json-parity bounce; \u003e3 entries; empty title; path\n guards (absolute, .., symlink escape); oversize file.\n- finalize with fake yonote writer (interface seam: CreateDocument + DocumentInfo): 2 ok + 1 fail\n → comment body lines, event payload, run outcome unchanged; lane-off (no collection id) →\n \"publishing not configured\" per entry; parent_url resolve failure isolates the entry;\n collection_id override honored.\n- prepareTaskDir clears publish.json + the publish/ dir between attempts.\n\nwave 2.5 — implement after feat/wave2-archive-links merges. Depends on: internal/yonote client\nbead (CreateDocument/DocumentInfo), materialization bead (yonote config block + prepareTaskDir\nchannel-clearing site).\n","notes":"Decision record: direct agent→Yonote API access was REJECTED — worktrees run arbitrary generated code, .task/ fossilizes into Done-archive tar.gz (a token in env/files would be archived), and the existing file-channel protocol (runner/taskfiles.go readTasksFile/readQuestionFile, attempt-scoped clearing in prepareTaskDir) already gives declarative-intent + daemon-credential semantics with card-comment observability. API evidence (ah-gxa notes, one-probe lifecycle): documents.create stores submitted markdown VERBATIM (create response echoed text byte-identical) and returns the site-relative url for the card comment (abs = base_url + url); publish:false honored (draft), so publish:true is explicit; comments.create/list verified for the future reply path. NOT live-verified: parentDocumentId child creation (v1-spec'd; exercise via httptest now, live during rollout), server-side max document text size (none documented — hence the defensive 512 KiB per-file cap), bot write rights on a granted collection (provisioning-time check, ah-gxa runbook step 4).","status":"in_progress","priority":2,"issue_type":"feature","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-07-18T15:04:25Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T15:06:57Z","started_at":"2026-07-18T15:06:57Z"}
new_value {"notes":"ROLLOUT LIVE-SMOKE CHECKLIST (from wave-2.5 implementer, against bigbes.yonote.ru after deploy): 1) mint bot per ah-gxa runbook (POST /api/v2/bots + token, collections.add_user grant — grant call is spec-only, NOT live-verified), AuthInfo under bot token must show IsBot=true (daemon WARNs at startup otherwise); 2) documents.create with parentDocumentId (child nesting unverified live) + bot WRITE rights on publish_collection_id + publish:true attribution to bot; 3) archived-doc export ((archived) marker path); 4) ExportMarkdown empty-string cases (API-created never-edited draft, database-type doc) fall back to .text; 5) comments.create/list/resolve field names (only Q\u0026A bead ah-ptu uses them). SEMANTICS NOTE: publish fires on ROUTED successful runs too (inside firstFinalize\u0026\u0026success before maybeRoute) — a design stage with publish.json publishes before routing to review; intentional. publish_collection_id (UUID) must be chosen by operator at config staging."}
comment NULL
created_at 2026-07-18T18:59:06Z
id 019f75f3-ebc5-7e50-a9c7-77bbcec08ecc
issue_id ah-25e
event_type closed
actor Eugene Blikh
old_value
new_value Merged to master f9a7b4b (3 commits 6c9e259/a25b692/f9a7b4b): yonote client + claim-time artifact materialization + publish lane. Live smoke at rollout per ah-25e notes.
comment NULL
created_at 2026-07-18T18:59:07Z
id 019f75fc-fa59-73c0-9b78-8f13c2fb7d69
issue_id ah-25e
event_type updated
actor Eugene Blikh
old_value {"id":"ah-25e","title":"Yonote publish lane: .task/publish.json → bot-authored docs at finalize","description":"Why: the operator wants agents to publish specs/reports/answers INTO Yonote instead of committing them to the project repo — with the write credential never entering the agent's environment. What: a third finalize-time file channel in the SPEC §14.4 family (tasks.json, question.json): the agent writes markdown under .task/publish/ and declares it in .task/publish.json {documents:[{title, file, collection_id?, parent_url?}]}; on successful finalize the daemon creates the docs via the internal/yonote client under the BOT token (publish:true, target = yonote.publish_collection_id or per-entry override, optional nesting under a referenced doc), comments the resulting URLs on the card, emits a yonote_published event. Publish failures never change the run outcome. wave 2.5 — implement after feat/wave2-archive-links merges. Depends on ah-gxa (client: CreateDocument/DocumentInfo) and ah-2lh (yonote config block + prepareTaskDir channel-clearing site).","design":"# Yonote publish lane: .task/publish.json → bot-authored docs at finalize\n\nAgents publish specs/reports/answers INTO Yonote instead of committing them to the project repo.\nThe agent declares intent via a file channel; the daemon executes with ITS bot token at finalize\nand links results on the card.\n\n## Why file-channel, not direct API access from the agent (DECISION)\n\nThe agent must never hold the Yonote token: worktrees run arbitrary generated code; `.task/` is\narchived to tar.gz at Done (a token in env or files would fossilize into archives); per-run env\nplumbing would put a write-capable credential into every zellij session. The daemon already owns a\nfinalize-time file-channel protocol with exactly the right semantics — `.task/tasks.json`\n(delegation) and `.task/question.json` (ask-user), SPEC §14.4: agent writes declarative intent,\ndaemon acts with its own credentials, results land as card comments. Publishing is the third\nchannel of that family. Attribution lands on the BOT user — uniform and auditable.\n\n## Channel format\n\n`.task/publish.json` (attempt-scoped; cleared in prepareTaskDir alongside summary/tasks/question):\n\n {\"documents\": [\n {\"title\": \"Design: frobnicator\", \"file\": \"publish/design.md\",\n \"collection_id\": \"\", \"parent_url\": \"\"}\n ]}\n\n- title: required, non-empty, ≤255 chars (Yonote bot/user name limits suggest 255 conventions).\n- file: required; RELATIVE path resolved under `.task/` (convention: agents put content in\n `.task/publish/\u003cname\u003e.md`). Guard rigor as repo-slug F8: reject absolute paths, filepath.Clean\n must stay under .task/, and the opened file must not escape via symlink (os.Root / EvalSymlinks\n containment check).\n- collection_id: optional uuid overriding the config default target.\n- parent_url: optional Yonote doc URL → resolve via yonote.ParseDocRefs(single) + DocumentInfo →\n parentDocumentId, so a report can nest under the spec it answers (documents.create\n parentDocumentId is v1-spec'd; child creation itself NOT live-verified — verify in tests against\n httptest only, live during rollout).\n- Caps: maxPublishDocs = 3 per run; maxPublishBytes = 512 KiB per file. No server-side text limit\n was verified — cap defensively; an oversize entry FAILS (listed in the comment), never truncated.\n\nRead at SUCCESSFUL finalize only, same site and semantics as readTasksFile: absent → nil (common\ncase); present-but-unparseable → same bounce treatment as a malformed tasks.json (do not silently\ndrop agent intent).\n\n## Config\n\nThe `yonote` block (introduced by the materialization bead) gains:\n\n publish_collection_id: \"…\" # uuid; the default target collection\n\nValidation: when set, must be a UUID shape; lane is ACTIVE only when the yonote client is\nconfigured AND publish_collection_id is set. When the client is configured but the collection is\nnot, publish.json entries all fail visibly with \"publishing not configured\" in the card comment —\nnever silently ignored. Operational prerequisite (document next to the config): the BOT must have\nread_write on the target collection (collections.add_user — client-bead runbook step 3).\n\n## Daemon flow (finalize, success path, after summary/tasks/question handling)\n\nPer entry (≤3, in file order): resolve target collection (override or default) → resolve\nparent_url if set (its failure fails ONLY that entry) → read + cap content →\nCreateDocument{Title, CollectionID, ParentDocumentID, Text: content, Publish: true} → record\n{Title, absURL = cfg base_url + doc.URL} or {Title, err}.\n\n- Card comment (goes through the existing markdownToHTML comment path):\n published to Yonote:\n - \u003cTitle\u003e → \u003cabs url\u003e\n - \u003cTitle\u003e → FAILED: \u003cmessage\u003e\n- appendEvent \"yonote_published\" {\"ok\": n, \"failed\": m}.\n- Publish failures NEVER change the run outcome — the run already succeeded; publication is a\n side effect. Operator retries by re-running, or publishes manually from the archived tar.gz\n (the content survives there by construction).\n- Idempotency (DECISION): re-running a card re-publishes and creates a NEW doc. Accepted for v1 —\n re-runs are rare, docs are cheap, and dedup would need a doc-id echo channel; revisit only if it\n annoys in practice (then: UpdateDocument by echoed id).\n\n## Agent contract (role-prompt snippet, config-side; document in deploy notes)\n\n To publish a document to Yonote: write markdown to .task/publish/\u003cname\u003e.md and declare it in\n .task/publish.json as {\"documents\":[{\"title\":\"…\",\"file\":\"publish/\u003cname\u003e.md\"}]}. Publication\n happens AFTER your run succeeds; resulting links are posted on the task card.\n\n## Tests\n\n- reader: absent → nil; malformed → tasks.json-parity bounce; \u003e3 entries; empty title; path\n guards (absolute, .., symlink escape); oversize file.\n- finalize with fake yonote writer (interface seam: CreateDocument + DocumentInfo): 2 ok + 1 fail\n → comment body lines, event payload, run outcome unchanged; lane-off (no collection id) →\n \"publishing not configured\" per entry; parent_url resolve failure isolates the entry;\n collection_id override honored.\n- prepareTaskDir clears publish.json + the publish/ dir between attempts.\n\nwave 2.5 — implement after feat/wave2-archive-links merges. Depends on: internal/yonote client\nbead (CreateDocument/DocumentInfo), materialization bead (yonote config block + prepareTaskDir\nchannel-clearing site).\n","notes":"ROLLOUT LIVE-SMOKE CHECKLIST (from wave-2.5 implementer, against bigbes.yonote.ru after deploy): 1) mint bot per ah-gxa runbook (POST /api/v2/bots + token, collections.add_user grant — grant call is spec-only, NOT live-verified), AuthInfo under bot token must show IsBot=true (daemon WARNs at startup otherwise); 2) documents.create with parentDocumentId (child nesting unverified live) + bot WRITE rights on publish_collection_id + publish:true attribution to bot; 3) archived-doc export ((archived) marker path); 4) ExportMarkdown empty-string cases (API-created never-edited draft, database-type doc) fall back to .text; 5) comments.create/list/resolve field names (only Q\u0026A bead ah-ptu uses them). SEMANTICS NOTE: publish fires on ROUTED successful runs too (inside firstFinalize\u0026\u0026success before maybeRoute) — a design stage with publish.json publishes before routing to review; intentional. publish_collection_id (UUID) must be chosen by operator at config staging.","status":"closed","priority":2,"issue_type":"feature","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-07-18T15:04:25Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T15:59:08Z","started_at":"2026-07-18T15:06:57Z","closed_at":"2026-07-18T15:59:08Z","close_reason":"Merged to master f9a7b4b (3 commits 6c9e259/a25b692/f9a7b4b): yonote client + claim-time artifact materialization + publish lane. Live smoke at rollout per ah-25e notes."}
new_value {"notes":"\n ROLLOUT LIVE-SMOKE CHECKLIST (from wave-2.5 implementer, against \n bigbes.yonote.ru after deploy): 1) mint bot per ah-gxa runbook (POST \n /api/v2/bots + token, collections.add_user grant — grant call is spec-only, \n NOT live-verified), AuthInfo under bot token must show IsBot=true (daemon \n WARNs at startup otherwise); 2) documents.create with parentDocumentId \n (child nesting unverified live) + bot WRITE rights on publish_collection_id \n + publish:true attribution to bot; 3) archived-doc export ((archived) marker\n path); 4) ExportMarkdown empty-string cases (API-created never-edited draft,\n database-type doc) fall back to .text; 5) comments.create/list/resolve field\n names (only Q\u0026A bead ah-ptu uses them). SEMANTICS NOTE: publish fires on \n ROUTED successful runs too (inside firstFinalize\u0026\u0026success before maybeRoute)\n — a design stage with publish.json publishes before routing to review; \n intentional. publish_collection_id (UUID) must be chosen by operator at \n config staging. \n\n\n\nDEPENDS ON\n → ✓ ah-2lh: Claim-time Yonote artifact materialization into .task/artifacts/ + prompt manifest ● P2\n → ✓ ah-gxa: internal/yonote: light API client (doc resolve/export, create, comments; bot-token auth) ● P2\n\nBLOCKS\n ← ○ ah-ptu: Yonote Q\u0026A bot loop: poll doc comments, answer via board tasks (post-wave-2.5) ● P3\n\nROLLOUT FACTS (2026-07-18, live-verified): bot agenthub id=34fbc9a3-ec75-4722-a01f-805db3f3ff1b (v2 create needs username field — used bigbes@gmail.com); token agent-1-daemon installed as YONOTE_TOKEN in /etc/agent-hub/env; auth.info isBot=true CONFIRMED; collections.add_user CONFIRMED; Agents collection 8656642e-0297-4d69-b6fb-4246517a015b = publish_collection_id, bot read_write. CAVEAT: bot sees ALL team-default-readable collections (not grant-scoped) — operator may tighten per-collection perms in UI. Config staging after CI deploy: yonote{base_url: https://bigbes.yonote.ru, token: ${YONOTE_TOKEN}, publish_collection_id: 8656642e-0297-4d69-b6fb-4246517a015b}."}
comment NULL
created_at 2026-07-18T19:09:01Z
id 019f7cda-aa8b-79b0-aee3-fd66aec0213f
issue_id ah-25e
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-2604-77f3-a4c4-2eb4d27129b2
issue_id ah-25e
event_type label_removed
actor Eugene Blikh
old_value NULL
new_value NULL
comment Removed label: milestone:yonote
created_at 2026-07-20T03:10:30Z