k3amt89h13lptc8s4hbp3llh8e2j2hdb · 488 rows
| id | issue_id | event_type | actor | old_value | new_value | comment | created_at |
|---|---|---|---|---|---|---|---|
| 019f73e8-6c27-7090-b949-807aa368c32e | ah-6ei | closed | Eugene Blikh | Verified live on project 2 / agent-1. Flushed out the real gap: (1) live config lacked design/review task_types+designer/validator roles+skills_dir; (2) NO CI deploy lane existed (.build.yml) and the agent1-deploy key was lost. Built the CI lane (.build.yml, commit 38dd035, build #226 SUCCESS: builds 3 cmds + ships bin/prompts/skills to /opt/agent-hub + restarts), regenerated+registered the deploy key (builds secret cc6bd011), wired designer/validator into the live config. Then ran the flow: task 2 type:design -> designer -> In Review with ## Design (ae7db66); relabel type:review+Ready -> validator -> ## Review changes-requested on the SAME file/branch (374b583, agent/task-2). Validator even caught a real arithmetic bug in the design's edge-case table. | NULL | 2026-07-18T09:27:19Z | |
| 019f73e8-6f29-7a21-8d89-d6c6ecdd6af9 | ah-943 | created | Eugene Blikh | NULL | 2026-07-18T09:27:20Z | ||
| 019f73eb-2c60-7b2b-bbf9-bf0ec606ee9a | ah-bkr | created | Eugene Blikh | NULL | 2026-07-18T09:30:20Z | ||
| 019f73eb-2ea6-7991-8823-ac8492ca4fab | ah-tz0 | created | Eugene Blikh | NULL | 2026-07-18T09:30:20Z | ||
| 019f73eb-3109-774b-b077-31774b346f29 | ah-4el | created | Eugene Blikh | NULL | 2026-07-18T09:30:21Z | ||
| 019f73eb-336f-792f-b2ff-2893e3883189 | ah-07g | created | Eugene Blikh | NULL | 2026-07-18T09:30:21Z | ||
| 019f73f2-e8c0-7de2-a29d-cf94bf14802a | ah-bkr | claimed | Eugene Blikh | {"id":"ah-bkr","title":"Vikunja comments render literal markdown fences — post HTML instead","description":"The In Review notification comment is built as markdown (internal/reconcile/comments.go successComment: triple-backtick fences around the diff stat, backtick spans). Vikunja stores/renders task comments as HTML (TipTap rich-text), so the markdown shows LITERALLY in the UI (screenshot: raw fences and + diffstat shown verbatim). internal/vikunja Comment() sends the string as-is to PUT /tasks/{id}/comments. Fix: render markdown to HTML (or emit HTML) before posting. Affects every daemon comment.","status":"open","priority":2,"issue_type":"bug","owner":"bigbes@gmail.com","created_at":"2026-07-18T06:30:20Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T06:30:20Z"} | {"assignee":"Eugene Blikh","status":"in_progress"} | NULL | 2026-07-18T09:38:46Z |
| 019f73f2-ea09-7eb5-a4f5-2d758685b21c | ah-tz0 | claimed | Eugene Blikh | {"id":"ah-tz0","title":"Surface agent closing summary / review verdict in the In Review comment","description":"domain.RunSummary carries only DiffStat + Commits, so the comment shows diffstat+commits+link but not what was done. Designer and validator both END with a one-line summary/verdict; that line is lost (only in docs/tasks/slug.md). Capture the agent final summary (from exit.json or last event) into RunSummary and render it in successComment. Prereq for verdict-driven automation.","status":"open","priority":3,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-18T06:30:21Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T06:30:21Z"} | {"assignee":"Eugene Blikh","status":"in_progress"} | NULL | 2026-07-18T09:38:47Z |
| 019f73f4-13b9-7e67-8c7d-c07cea00686a | ah-943 | claimed | Eugene Blikh | {"id":"ah-943","title":"Refresh phoebe-lab agent-hub provisioning template + bootstrap for designer/validator","description":"The live /etc/agent-hub/agenthub.yaml on agent-1 was hand-wired for project 2, repo agent-demo, deepseek model, AND now designer/validator roles + design/review task_types + skills_dir=/opt/agent-hub/skills. The phoebe-lab/agent-hub/config/agenthub.yaml.example (first-boot seed) is stale vs this: still project_id 1, repo demo, coder-only, no skills_dir/task_types. bootstrap.sh also only pre-creates /opt/agent-hub/{bin,prompts} (skills/ gets created by the CI tar overlay, which works but isn't declared). Update the example + bootstrap so a fresh box provisions the full role set. Also: keys/agent1-deploy.pub was regenerated this session (new ed25519, matching builds.sr.ht secret cc6bd011) and is modified-uncommitted in the phoebe-lab repo — commit it or a redeploy/bootstrap reinstalls the OLD dead key.","status":"open","priority":3,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-18T06:27:20Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T06:27:20Z"} | {"assignee":"Eugene Blikh","status":"in_progress"} | NULL | 2026-07-18T09:40:03Z |
| 019f73f5-42a8-79a8-93fe-729d9069431d | ah-943 | closed | Eugene Blikh | Provisioning template (config/agenthub.yaml.example) now scaffolds skills_dir + designer/validator roles + design/review task_types; bootstrap.sh pre-creates /opt/agent-hub/skills. Committed phoebe-lab 34bcec3 (local; deploy via labng). Pubkey rotation committed earlier (abab805). | NULL | 2026-07-18T09:41:21Z | |
| 019f73f5-f38e-7bb4-91d4-89df1389eeb2 | ah-07g | updated | Eugene Blikh | {"id":"ah-07g","title":"Investigate unbounded .task/events.jsonl (202MB for one design run)","description":"task-2/.task/events.jsonl was 202 MB for a single ~2min run producing a 100-line doc. pi event stream persisted verbatim, no truncation/rotation, under work_root. work_root fills fast. Investigate pi verbosity, whether events.jsonl is needed post-run, add rotation/cap or prune-on-Done.","status":"open","priority":3,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-18T06:30:22Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T06:30:22Z"} | {"notes":"INVESTIGATION (2026-07-18): events.jsonl is pi's raw event stream, persisted verbatim by pi in \u003cworktree\u003e/.task/. 210MB for task-2's ~2min run; lines up to ~100KB each (full message/context per event). Worktrees are NOT pruned on terminal state (task-1 and task-2 both still on disk under work_root). Two levers: (a) pi-side — reduce event verbosity / don't persist full context per event (pi is external, needs a flag or upstream change); (b) daemon-side — prune the worktree or at least .task/events.jsonl when a task reaches Done (NOT In Review, where zellij-attach/review still want it). Retention policy is a user call: keep-for-debug vs disk. No code changed yet — needs a retention-policy decision."} | NULL | 2026-07-18T09:42:06Z |
| 019f73f7-dca4-7059-8725-f625eb84b6b9 | ah-bkr | closed | Eugene Blikh | markdownToHTML converts outbound comments to HTML at the vikunja Comment() boundary (dep-free, escaping + placeholder-safe). Builders still emit markdown (comments_test green). Verified: 89 subtests pass; live Vikunja round-trip confirmed the API stores <p>/<pre><code>/<ul>/<a> intact (markdown showed literally before). Merged to master (f6098a9). | NULL | 2026-07-18T09:44:11Z | |
| 019f73f9-9709-7e63-a77b-bdabe403a5d8 | ah-tz0 | closed | Eugene Blikh | RunSummary gains a Summary field read from .task/summary.md (runner.readSummaryFile, trim+2000-rune tail-truncate, missing=empty). successComment renders it as a blockquote below the header. designer/validator/coder prompts now write their closing summary/verdict there. Verified: unit tests green in isolation AND combined with ah-bkr on master; full-module build OK. Merged (rebased onto master). NOTE: follow-up needed — the blockquote markdown isn't yet handled by ah-bkr's HTML converter. | NULL | 2026-07-18T09:46:04Z | |
| 019f73fb-8754-700f-bab0-e19243081740 | ah-0iz | created | Eugene Blikh | NULL | 2026-07-18T09:48:11Z | ||
| 019f73fb-88fe-73a9-b111-47216600e38a | ah-0iz | closed | Eugene Blikh | Added <blockquote> handling to markdownToHTML (mirrors the bullet-run logic; bare > = empty line). Unit tests + live Vikunja round-trip confirm blockquote+strong render. Committed on master. | NULL | 2026-07-18T09:48:12Z | |
| 019f7401-a9ef-7ae1-b259-5fa7af5a19ec | ah-a6f | created | Eugene Blikh | NULL | 2026-07-18T09:54:53Z | ||
| 019f7401-ab94-7a35-b6c2-eb65859d4b02 | ah-a6f | closed | Eugene Blikh | Updated 5 e2e comment assertions (branch/worktree/outcome) from markdown backticks to <code> HTML. Full e2e suite passes (10.95s). Root cause: ah-bkr subagent scoped to its own package; build-tagged e2e wasn't exercised until the integration sweep. | NULL | 2026-07-18T09:54:54Z | |
| 019f7402-35c5-7995-865f-81dc2d724d25 | ah-4el | updated | Eugene Blikh | {"id":"ah-4el","title":"Review-verdict-driven transition: changes-requested back to Ready for coder","description":"Every successful run lands In Review; no verdict-aware transition, so a validator changes-requested just sits for a human to drag back. Intentional for now (ah-gs7 human-driven handoff) but user asked for it. Build opt-in loop: parse validator verdict (needs machine-readable verdict), on changes-requested move card back to Ready under coder/fix role with the review as context. Ties into Stage 4 (ah-0ge).","status":"open","priority":3,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-18T06:30:21Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T06:30:21Z"} | {"notes":"DESIGN PROPOSAL (held for user sign-off — changes the core reconcile state machine; not live-verifiable while pushes/deploy are blocked).\n\nNow unblocked by ah-tz0 (.task/summary.md exists). Proposed:\n1. MACHINE-READABLE VERDICT: don't parse free-text. Have the validator write a structured line the daemon can key on — e.g. a first line 'Verdict: pass' | 'Verdict: changes-requested' in .task/summary.md (update prompts/validator.md + validate skill), and add a domain field Verdict (parsed in runner.Summary, empty for non-validator roles).\n2. OPT-IN CONFIG: task_types.review gains 'on_changes_requested: \u003cbucket|role\u003e' (default: none = current behavior, lands In Review for a human). When set e.g. to role 'coder', a changes-requested validator run moves the card to Ready under that role instead of In Review, carrying the review as context (the ## Review is already on the branch).\n3. LOOP GUARD: reuse the delegation generation cap (max_generation) or add a review_rounds cap so design-\u003ereview-\u003ecoder-\u003ereview can't cycle forever. A 'pass' verdict always lands In Review (human ships it).\n4. STATE MACHINE: this is a new transition in internal/reconcile (finalize path) + internal/domain/transitions.go. Cover with reconcile_test.go (injected clock/fakes) AND an e2e flow before shipping.\n\nOpen questions for user: (a) auto-loop to 'coder' or a dedicated 'fixer' role? (b) opt-in per task_type (proposed) vs global? (c) cap value / mechanism. HOLDING until answered."} | NULL | 2026-07-18T09:55:29Z |
| 019f7563-ebe5-7991-97e3-417042380f48 | ah-4el | claimed | Eugene Blikh | {"id":"ah-4el","title":"Review-verdict-driven transition: changes-requested back to Ready for coder","description":"Every successful run lands In Review; no verdict-aware transition, so a validator changes-requested just sits for a human to drag back. Intentional for now (ah-gs7 human-driven handoff) but user asked for it. Build opt-in loop: parse validator verdict (needs machine-readable verdict), on changes-requested move card back to Ready under coder/fix role with the review as context. Ties into Stage 4 (ah-0ge).","notes":"DESIGN PROPOSAL (held for user sign-off — changes the core reconcile state machine; not live-verifiable while pushes/deploy are blocked).\n\nNow unblocked by ah-tz0 (.task/summary.md exists). Proposed:\n1. MACHINE-READABLE VERDICT: don't parse free-text. Have the validator write a structured line the daemon can key on — e.g. a first line 'Verdict: pass' | 'Verdict: changes-requested' in .task/summary.md (update prompts/validator.md + validate skill), and add a domain field Verdict (parsed in runner.Summary, empty for non-validator roles).\n2. OPT-IN CONFIG: task_types.review gains 'on_changes_requested: \u003cbucket|role\u003e' (default: none = current behavior, lands In Review for a human). When set e.g. to role 'coder', a changes-requested validator run moves the card to Ready under that role instead of In Review, carrying the review as context (the ## Review is already on the branch).\n3. LOOP GUARD: reuse the delegation generation cap (max_generation) or add a review_rounds cap so design-\u003ereview-\u003ecoder-\u003ereview can't cycle forever. A 'pass' verdict always lands In Review (human ships it).\n4. STATE MACHINE: this is a new transition in internal/reconcile (finalize path) + internal/domain/transitions.go. Cover with reconcile_test.go (injected clock/fakes) AND an e2e flow before shipping.\n\nOpen questions for user: (a) auto-loop to 'coder' or a dedicated 'fixer' role? (b) opt-in per task_type (proposed) vs global? (c) cap value / mechanism. HOLDING until answered.","status":"open","priority":3,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-18T06:30:21Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T06:55:30Z"} | {"assignee":"Eugene Blikh","status":"in_progress"} | NULL | 2026-07-18T16:21:50Z |
| 019f7563-ed27-79df-a64a-603a85e435d3 | ah-4el | updated | Eugene Blikh | {"id":"ah-4el","title":"Review-verdict-driven transition: changes-requested back to Ready for coder","description":"Every successful run lands In Review; no verdict-aware transition, so a validator changes-requested just sits for a human to drag back. Intentional for now (ah-gs7 human-driven handoff) but user asked for it. Build opt-in loop: parse validator verdict (needs machine-readable verdict), on changes-requested move card back to Ready under coder/fix role with the review as context. Ties into Stage 4 (ah-0ge).","notes":"DESIGN PROPOSAL (held for user sign-off — changes the core reconcile state machine; not live-verifiable while pushes/deploy are blocked).\n\nNow unblocked by ah-tz0 (.task/summary.md exists). Proposed:\n1. MACHINE-READABLE VERDICT: don't parse free-text. Have the validator write a structured line the daemon can key on — e.g. a first line 'Verdict: pass' | 'Verdict: changes-requested' in .task/summary.md (update prompts/validator.md + validate skill), and add a domain field Verdict (parsed in runner.Summary, empty for non-validator roles).\n2. OPT-IN CONFIG: task_types.review gains 'on_changes_requested: \u003cbucket|role\u003e' (default: none = current behavior, lands In Review for a human). When set e.g. to role 'coder', a changes-requested validator run moves the card to Ready under that role instead of In Review, carrying the review as context (the ## Review is already on the branch).\n3. LOOP GUARD: reuse the delegation generation cap (max_generation) or add a review_rounds cap so design-\u003ereview-\u003ecoder-\u003ereview can't cycle forever. A 'pass' verdict always lands In Review (human ships it).\n4. STATE MACHINE: this is a new transition in internal/reconcile (finalize path) + internal/domain/transitions.go. Cover with reconcile_test.go (injected clock/fakes) AND an e2e flow before shipping.\n\nOpen questions for user: (a) auto-loop to 'coder' or a dedicated 'fixer' role? (b) opt-in per task_type (proposed) vs global? (c) cap value / mechanism. HOLDING until answered.","status":"in_progress","priority":3,"issue_type":"feature","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-07-18T06:30:21Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T13:21:51Z","started_at":"2026-07-18T13:21:51Z"} | {"design":"DECIDED 2026-07-18 (user): dedicated FIXER role. Generalized routing design:\n- task_types.\u003ct\u003e gains on_success: \u003ctype\u003e and on_changes_requested: \u003ctype\u003e (target is a TASK TYPE name; validated at config load).\n- Routing = swap board label type:\u003cold\u003e -\u003e type:\u003ctarget\u003e (Vikunja label API, lookup by title), move card to Ready, comment 'routed to \u003ctype\u003e (round n/cap)'. Label-swap failure -\u003e fall back In Review + warn comment.\n- Verdict channel: first line of .task/summary.md must be 'Verdict: pass' or 'Verdict: changes-requested' (case-insensitive). Parsed into RunSummary.Verdict. Missing/absent verdict = pass-equivalent (lands In Review as today).\n- changes-requested + on_changes_requested set -\u003e route; pass/none + on_success set -\u003e route; otherwise In Review (current behavior). Failure paths unchanged.\n- Loop guard: per-task auto_routes counter persisted in store, incremented on EVERY automated route (both edges); config routing.max_auto_routes default 4 (design-\u003ereview-\u003efix-\u003ereview = 3). At cap -\u003e In Review + 'routing cap reached' comment. Human manual relabel+Ready still works past cap.\n- New prompts/fixer.md (address ## Review findings on same branch); validator.md updated to mandate the Verdict first line.\n- Intended live pipeline: design --on_success--\u003e review --changes-requested--\u003e fix --on_success--\u003e review --pass--\u003e In Review (human)."} | NULL | 2026-07-18T16:21:50Z |
| 019f7564-0cef-7a68-a953-9969886e28fb | ah-07g | claimed | Eugene Blikh | {"id":"ah-07g","title":"Investigate unbounded .task/events.jsonl (202MB for one design run)","description":"task-2/.task/events.jsonl was 202 MB for a single ~2min run producing a 100-line doc. pi event stream persisted verbatim, no truncation/rotation, under work_root. work_root fills fast. Investigate pi verbosity, whether events.jsonl is needed post-run, add rotation/cap or prune-on-Done.","notes":"INVESTIGATION (2026-07-18): events.jsonl is pi's raw event stream, persisted verbatim by pi in \u003cworktree\u003e/.task/. 210MB for task-2's ~2min run; lines up to ~100KB each (full message/context per event). Worktrees are NOT pruned on terminal state (task-1 and task-2 both still on disk under work_root). Two levers: (a) pi-side — reduce event verbosity / don't persist full context per event (pi is external, needs a flag or upstream change); (b) daemon-side — prune the worktree or at least .task/events.jsonl when a task reaches Done (NOT In Review, where zellij-attach/review still want it). Retention policy is a user call: keep-for-debug vs disk. No code changed yet — needs a retention-policy decision.","status":"open","priority":3,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-18T06:30:22Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T06:42:06Z"} | {"assignee":"Eugene Blikh","status":"in_progress"} | NULL | 2026-07-18T16:21:59Z |
| 019f7564-0e1e-7cfc-b6ba-dd3f965fb5dd | ah-07g | updated | Eugene Blikh | {"id":"ah-07g","title":"Investigate unbounded .task/events.jsonl (202MB for one design run)","description":"task-2/.task/events.jsonl was 202 MB for a single ~2min run producing a 100-line doc. pi event stream persisted verbatim, no truncation/rotation, under work_root. work_root fills fast. Investigate pi verbosity, whether events.jsonl is needed post-run, add rotation/cap or prune-on-Done.","notes":"INVESTIGATION (2026-07-18): events.jsonl is pi's raw event stream, persisted verbatim by pi in \u003cworktree\u003e/.task/. 210MB for task-2's ~2min run; lines up to ~100KB each (full message/context per event). Worktrees are NOT pruned on terminal state (task-1 and task-2 both still on disk under work_root). Two levers: (a) pi-side — reduce event verbosity / don't persist full context per event (pi is external, needs a flag or upstream change); (b) daemon-side — prune the worktree or at least .task/events.jsonl when a task reaches Done (NOT In Review, where zellij-attach/review still want it). Retention policy is a user call: keep-for-debug vs disk. No code changed yet — needs a retention-policy decision.","status":"in_progress","priority":3,"issue_type":"task","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-07-18T06:30:22Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T13:21:59Z","started_at":"2026-07-18T13:21:59Z"} | {"design":"DECIDED 2026-07-18 (user): 'remove worktree, but backup .task and additional docs'. Policy:\n- When a card reaches Done or Cancelled and its worktree still exists: archive FIRST, then 'git worktree remove --force'.\n- Archive = tar.gz containing .task/ (PROMPT.md, summary.md, events.jsonl, *.json) PLUS any files in the worktree that are uncommitted relative to the branch head (untracked + modified) — i.e. everything that would be lost with the worktree. Branch itself is the durable artifact and is NOT touched.\n- Archive location: config archive_dir, default \u003cwork_root\u003e/archive/, file task-\u003cid\u003e.tar.gz (overwrite on re-archive).\n- Failed/In Review/etc: untouched (zellij attach + debugging still want the worktree).\n- events.jsonl compresses ~10-20x (repeated JSON context), so backup satisfies keep-for-debug without the 202MB/run disk cost."} | NULL | 2026-07-18T16:21:59Z |
| 019f7565-cdef-7710-92f2-3ab99bb8eddc | ah-0ge.5 | created | Eugene Blikh | NULL | 2026-07-18T16:23:53Z | ||
| 019f7565-d0f5-7d4a-ba42-2ad0a166c52e | ah-0ge.6 | created | Eugene Blikh | NULL | 2026-07-18T16:23:54Z | ||
| 019f7565-d418-70c7-8b6c-b8293f66b855 | ah-ydx.1 | created | Eugene Blikh | NULL | 2026-07-18T16:23:55Z | ||
| 019f7565-d702-740b-ab21-98bd6a53105c | ah-ydx.2 | created | Eugene Blikh | NULL | 2026-07-18T16:23:56Z | ||
| 019f7565-da2c-7a8f-a0c9-2b75d820286f | ah-ydx.3 | created | Eugene Blikh | NULL | 2026-07-18T16:23:57Z | ||
| 019f7566-ecba-7111-9ca8-3513f0257b5b | ah-ydx.1 | claimed | Eugene Blikh | {"id":"ah-ydx.1","title":"internal/mem0: REST client for live mem0.bigb.es","description":"Typed Go client for the self-hosted Mem0 server (LIVE at mem0.bigb.es, phoebe-lab/mem0 stack): X-API-Key auth, add/search/get-all/delete memories. Encode scoping conventions as helpers: user_id='proj:\u003cslug\u003e' | 'global', run_id='task:\u003cid\u003e'; do NOT rely on agent_id filtering (buggy upstream per SPEC §14.3). Stdlib-only, httptest unit tests; live verification (incl. API-key provisioning via dashboard/register) is a follow-up ops step.","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-18T13:23:56Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T13:23:56Z"} | {"assignee":"Eugene Blikh","status":"in_progress"} | NULL | 2026-07-18T16:25:07Z |
| 019f7566-edf6-7144-9021-9a291b00ea26 | ah-ydx.2 | claimed | Eugene Blikh | {"id":"ah-ydx.2","title":"internal/ctxpack: Context Pack builder","description":"Deterministic prompt-context assembler per SPEC §14.3: ordered sections (task, comment thread, parent chain, memory, git log/diffstat) under a ~40k char budget, per-section truncate-from-tail with stable ordering so identical inputs render identical packs. Pure package + table tests first; wiring into runner PROMPT.md rendering is a separate integration step after wave 1 merges.","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-18T13:23:56Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T13:23:56Z"} | {"assignee":"Eugene Blikh","status":"in_progress"} | NULL | 2026-07-18T16:25:07Z |
| 019f7571-8032-7ef8-8cc4-d147e18e4c7b | ah-0ge.5 | updated | Eugene Blikh | {"id":"ah-0ge.5","title":"AgentsView: pg push from agent-1 + per-run session deep links in comments","description":"Make agent runs viewable at agentsview.bigb.es (user asked 'can I view agent logs?'). Two halves: (1) OPS on agent-1 — install go.kenn.io/agentsview (v0.37.5+) for user agenthub, ~/.agentsview/config.toml with [pg] url to phoebe LAN :5433 (published for exactly this; allow_insecure lab tradeoff), systemd timer or 'agentsview pg service' running one-shot AGENTSVIEW_NO_DAEMON=1 pg push (notebook launchd pattern proven: NO_DAEMON avoids 0.37.x writer-daemon deadlock). Verify pi-session source is parsed. (2) DAEMON — finalize comments include the session deep link built from stored PiSessionID (verify central URL shape first). Daemon half must wait for ah-4el merge (same comments.go).","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-18T13:23:54Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T13:23:54Z"} | {"design":"ARCHITECTURE (2026-07-18, after user pushed back on pg-port exposure): use AgentsView's NATIVE HTTP remote sync (v0.37.4+, present in 0.37.5) instead of direct pg push from agent-1 — no fork, no firewall change (phoebe DOCKER-USER allowlists DB ports to 192.168.88.35 only; agent-1 stays out of it).\n- agent-1: agentsview v0.37.5 (upstream release binary, SHA256-verified) as systemd service, User=agenthub (HOME=/var/lib/agenthub so default pi source .pi/agent/sessions is found), 'serve' bound to LAN, require_auth=true, token via AGENTSVIEW_AUTH_TOKEN EnvironmentFile (root-owned 0600). Archive endpoints are bearer-gated ALWAYS.\n- phoebe host: same binary as one-shot COLLECTOR on a systemd timer: [[remote_hosts]] {host='agent-1', transport='http', url, token} pulls sessions over HTTP; then AGENTSVIEW_NO_DAEMON=1 pg push to localhost:5433 (phoebe-\u003eown docker-proxy is the allowed local path). Notebook launchd flow untouched.\n- Central agentsview.bigb.es pg serve container: unchanged at 0.37.5. Version-pin ALL nodes at 0.37.5 (pg schema coupling); coordinated bump to 0.38.1 later ('speed up full HTTP sync' is perf-only).\n- Daemon half (after ah-4el merges): finalize comment gains https://agentsview.bigb.es/sessions/\u003cPiSessionID\u003e deep link, config-gated base URL. VERIFY id shape matches AgentsView session ids once first push lands.\n- Secrets never enter operator transcript: token minted on agent-1 and piped host-to-host; pg password composed phoebe-locally from the stack .env."} | NULL | 2026-07-18T16:36:40Z |
| 019f7575-9f69-781a-a617-97c38d4d2a57 | ah-ydx.1 | closed | Eugene Blikh | internal/mem0 client merged to master (55b9b43); routes confirmed against mem0 v2.0.11 source; live verification against mem0.bigb.es tracked in ah-ydx.3 | NULL | 2026-07-18T16:41:10Z | |
| 019f7575-a18f-7ba8-b324-5127c59eea4a | ah-ydx.2 | closed | Eugene Blikh | internal/ctxpack merged to master (342410e); format contract pinned by golden tests; runner integration tracked in ah-ydx.3 | NULL | 2026-07-18T16:41:11Z | |
| 019f7582-6b97-727c-a5de-343dd33c8fcd | ah-ydx.3 | updated | Eugene Blikh | {"id":"ah-ydx.3","title":"Memory lifecycle: load-on-claim CONTEXT section, save-on-Done","description":"Wire mem0 into the task loop per SPEC §14.3: on claim, search mem0 (proj scope + global) and render a memory section into the prompt context via ctxpack; on Done/Cancelled terminal scan (shares the ah-07g hook), save run summary/verdict facts back to mem0 (run_id='task:\u003cid\u003e'). Depends on the mem0 client, ctxpack, and the ah-07g terminal-state scan.","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-18T13:23:57Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T13:23:57Z"} | {"notes":"MEM0 LIVE STATE (2026-07-18): server DEGRADED, needs one operator command (classifier-blocked for me — see handoff). History: (1) found deployed server 502s on all embed/LLM calls — STORED runtime config (mem0_app.settings key=config_overrides, created via dashboard/configure at some point) carries UNPREFIXED model names (deepseek-v4-flash, nemotron-embed-1b-v2) that LiteLLM rejects; container env has the CORRECT prefixed names. (2) My repair attempt made it worse: GET /configure REDACTS secrets, and POSTing the round-tripped body back overwrote the stored pgvector password with a redacted value — now ALL memory endpoints 502 (pgvector auth fails). (3) Correct fix: delete the single settings row so the server falls back to env-of-truth (prefixed models + correct creds), restart api. LESSON for lifecycle bead: never round-trip /configure GET-\u003ePOST (redaction!); prefer env-only config, no stored overrides. Client-side contract validated live BEFORE breakage: routes flat, X-API-Key admin works, LIST/DELETE-scope 200 envelope {results}/{message}, bogus key 401."} | NULL | 2026-07-18T16:55:09Z |
| 019f7582-6d79-7933-9f3a-0f4d303caead | ah-0ge.5 | updated | Eugene Blikh | {"id":"ah-0ge.5","title":"AgentsView: pg push from agent-1 + per-run session deep links in comments","description":"Make agent runs viewable at agentsview.bigb.es (user asked 'can I view agent logs?'). Two halves: (1) OPS on agent-1 — install go.kenn.io/agentsview (v0.37.5+) for user agenthub, ~/.agentsview/config.toml with [pg] url to phoebe LAN :5433 (published for exactly this; allow_insecure lab tradeoff), systemd timer or 'agentsview pg service' running one-shot AGENTSVIEW_NO_DAEMON=1 pg push (notebook launchd pattern proven: NO_DAEMON avoids 0.37.x writer-daemon deadlock). Verify pi-session source is parsed. (2) DAEMON — finalize comments include the session deep link built from stored PiSessionID (verify central URL shape first). Daemon half must wait for ah-4el merge (same comments.go).","design":"ARCHITECTURE (2026-07-18, after user pushed back on pg-port exposure): use AgentsView's NATIVE HTTP remote sync (v0.37.4+, present in 0.37.5) instead of direct pg push from agent-1 — no fork, no firewall change (phoebe DOCKER-USER allowlists DB ports to 192.168.88.35 only; agent-1 stays out of it).\n- agent-1: agentsview v0.37.5 (upstream release binary, SHA256-verified) as systemd service, User=agenthub (HOME=/var/lib/agenthub so default pi source .pi/agent/sessions is found), 'serve' bound to LAN, require_auth=true, token via AGENTSVIEW_AUTH_TOKEN EnvironmentFile (root-owned 0600). Archive endpoints are bearer-gated ALWAYS.\n- phoebe host: same binary as one-shot COLLECTOR on a systemd timer: [[remote_hosts]] {host='agent-1', transport='http', url, token} pulls sessions over HTTP; then AGENTSVIEW_NO_DAEMON=1 pg push to localhost:5433 (phoebe-\u003eown docker-proxy is the allowed local path). Notebook launchd flow untouched.\n- Central agentsview.bigb.es pg serve container: unchanged at 0.37.5. Version-pin ALL nodes at 0.37.5 (pg schema coupling); coordinated bump to 0.38.1 later ('speed up full HTTP sync' is perf-only).\n- Daemon half (after ah-4el merges): finalize comment gains https://agentsview.bigb.es/sessions/\u003cPiSessionID\u003e deep link, config-gated base URL. VERIFY id shape matches AgentsView session ids once first push lands.\n- Secrets never enter operator transcript: token minted on agent-1 and piped host-to-host; pg password composed phoebe-locally from the stack .env.","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-18T13:23:54Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T13:36:40Z"} | {"notes":"OPS HALF DONE + LIVE (2026-07-18): agent-1 fleet-node daemon (systemd agentsview.service, v0.37.5, token-gated :8080, smoke 401/200 ok) + phoebe host collector (agentsview-collect.timer 10min: HTTP remote sync pull -\u003e pg push loopback 5433). First run pushed 5 agent-1 pi sessions to central pg; id shape agent-1~pi:\u003cpi-session-uuid\u003e =\u003e deep link https://agentsview.bigb.es/sessions/agent-1~pi:\u003cPiSessionID\u003e. Infra-as-code committed phoebe-lab 9d4b157 (units + agentsview-node.sh + collector/provision.sh + CLAUDE.md docs, incl. zsh MULTIOS token-leak caution; leaked token was rotated + verified rejected). REMAINING (daemon half, after ah-4el merge): agentsview.base_url config + deep link line in claim/success comments from RunSummary.PiSessionID; verify link renders for a pushed session."} | NULL | 2026-07-18T16:55:09Z |
| 019f7584-d969-719e-9a42-534849e815fb | ah-ydx.3 | updated | Eugene Blikh | {"id":"ah-ydx.3","title":"Memory lifecycle: load-on-claim CONTEXT section, save-on-Done","description":"Wire mem0 into the task loop per SPEC §14.3: on claim, search mem0 (proj scope + global) and render a memory section into the prompt context via ctxpack; on Done/Cancelled terminal scan (shares the ah-07g hook), save run summary/verdict facts back to mem0 (run_id='task:\u003cid\u003e'). Depends on the mem0 client, ctxpack, and the ah-07g terminal-state scan.","notes":"MEM0 LIVE STATE (2026-07-18): server DEGRADED, needs one operator command (classifier-blocked for me — see handoff). History: (1) found deployed server 502s on all embed/LLM calls — STORED runtime config (mem0_app.settings key=config_overrides, created via dashboard/configure at some point) carries UNPREFIXED model names (deepseek-v4-flash, nemotron-embed-1b-v2) that LiteLLM rejects; container env has the CORRECT prefixed names. (2) My repair attempt made it worse: GET /configure REDACTS secrets, and POSTing the round-tripped body back overwrote the stored pgvector password with a redacted value — now ALL memory endpoints 502 (pgvector auth fails). (3) Correct fix: delete the single settings row so the server falls back to env-of-truth (prefixed models + correct creds), restart api. LESSON for lifecycle bead: never round-trip /configure GET-\u003ePOST (redaction!); prefer env-only config, no stored overrides. Client-side contract validated live BEFORE breakage: routes flat, X-API-Key admin works, LIST/DELETE-scope 200 envelope {results}/{message}, bogus key 401.","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-18T13:23:57Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T13:55:09Z"} | {"notes":"MEM0 SERVER HEALTHY (2026-07-18, after user-authorized override delete + restart): /configure now reflects env-of-truth (deepseek/deepseek-v4-flash + nvidia/nemotron-embed-1b-v2). FULL live smoke green: ADD infer=false 200/event=ADD; SEARCH 200 with score (embeddings ok); ADD infer=true 200 (LLM extraction ok); LIST/PURGE/EMPTY clean; bogus key 401. Server-side contract fully verified — lifecycle implementation unblocked once ah-07g lands the terminal-state hook. OPERATIONAL RULE: mem0 config is ENV-ONLY; never POST /configure (GET redacts secrets, round-trip corrupts stored creds — happened + fixed today, backup at phoebe:/root/mem0-settings-backup.sql); if a stored override reappears (dashboard onboarding creates one), delete settings row key=config_overrides + restart."} | NULL | 2026-07-18T16:57:48Z |
| 019f7596-f7c4-760a-a938-7b6f30d05040 | ah-4el | closed | Eugene Blikh | Merged to master 658f128: verdict-driven auto-routing (on_success/on_changes_requested per task_type, label swap + Ready, AutoRoutes cap 4, fixer role, e2e pipeline test design->review->fix->review->In Review). Live rollout (agent-1 config + type:fix board label) pending next CI deploy — noted in ah-0ge.5/ah-07g wave. | NULL | 2026-07-18T17:17:35Z | |
| 019f75b3-2929-7144-a047-3fd054ba575a | ah-07g | closed | Eugene Blikh | Merged 3e2eb3c: Done/Cancelled cards get .task/ + uncommitted files archived to archive_dir/task-<id>.tar.gz (stdlib tar+gzip, atomic overwrite, archive-before-remove), then git worktree remove --force; branch kept; worktree_archived/worktree_gone events. Live verify (drag a Done card, check archive) after next CI deploy. | NULL | 2026-07-18T17:48:23Z | |
| 019f75b3-2b9e-742e-b464-75e328373004 | ah-0ge.5 | closed | Eugene Blikh | Both halves done: ops (agent-1 fleet-node daemon + phoebe collector, sessions live in central pg since 2026-07-18) and daemon (a8e7586: config-gated agentsview{base_url,machine}, session deep-link line on success/routed/failure comments, URL pinned agent-1~pi:<id>). Live link render check after next CI deploy + config stage. | NULL | 2026-07-18T17:48:24Z | |
| 019f75c1-169f-7b9c-a3eb-441bc5115cb0 | ah-gxa | created | Eugene Blikh | NULL | 2026-07-18T18:03:36Z | ||
| 019f75c1-90fd-73d8-81c2-6a722e009315 | ah-2lh | created | Eugene Blikh | NULL | 2026-07-18T18:04:07Z | ||
| 019f75c1-d49a-7ae1-a057-bc520be235ee | ah-25e | created | Eugene Blikh | NULL | 2026-07-18T18:04:24Z | ||
| 019f75c2-13bc-7b1f-b19a-c3f9ccc31d01 | ah-ptu | created | Eugene Blikh | NULL | 2026-07-18T18:04:41Z | ||
| 019f75c4-23da-727c-ac89-db89963919f6 | ah-gxa | claimed | Eugene Blikh | {"id":"ah-gxa","title":"internal/yonote: light API client (doc resolve/export, create, comments; bot-token auth)","description":"Why: agenthubd integrates Yonote (Outline-fork docs product, live at bigbes.yonote.ru) in three slices — claim-time artifact export, finalize-time publishing, later Q\u0026A. All need one light client. What: internal/yonote in the internal/mem0 style (New(baseURL, token, opts), ctx-first, typed *APIError, stdlib-only, httptest tests, doc.go pinning contract+quirks), scoped to exactly the integration surface: AuthInfo, DocumentInfo (uuid|urlId|slug-urlId), ExportMarkdown (/api/v2/documents/{uuid}/markdown), Search/List, CreateDocument/UpdateDocument, comments create/list/resolve, pure ParseDocRefs URL helper. Runs under a BOT token (minting runbook in design). wave 2.5 — implement after feat/wave2-archive-links merges.","design":"# internal/yonote — light API client (stdlib-only, mem0 style)\n\nMirror internal/mem0 exactly in shape: `New(baseURL, token string, opts ...Option) *Client`\n(trailing \"/\" trimmed, default `http.Client{Timeout: 30s}`, `WithHTTPClient`), ctx-first methods,\none `do(ctx, method, path, query, in, out)` round-tripper, typed `*APIError`, package doc.go\npinning the contract + quirks, httptest unit tests. No third-party deps.\n\nContract pinned to live bigbes.yonote.ru, x-app-version 1.47.1, verified 2026-07-18.\nYonote = Outline fork; API is RPC-style `POST /api/\u003cresource\u003e.\u003cverb\u003e` (JSON body) PLUS a newer\n`/api/v2/*` REST namespace. Auth on every request: `Authorization: Bearer \u003ctoken\u003e` (tokens are\nJWTs; treat as opaque). Success envelope `{data, status, ok, [pagination, count|total, policies]}`.\nError envelope `{ok:false, error:\"\u003cmachine_code\u003e\", status:\u003cint, SOMETIMES ABSENT\u003e, message:\"\u003chuman\u003e\"}`\n— key off the HTTP status, carry error/message.\n\n## Endpoint table (methods to implement; L = live-verified, S = spec-only)\n\n| Method | HTTP + path | Request | Response essentials |\n|------------------------------------------|--------------------------------------------|----------------------------------------------------|---------------------|\n| AuthInfo(ctx) | POST /api/auth.info | {} | L data.user{id,name,email,isBot,isAdmin,isViewer}, data.team{name,url,subdomain} |\n| DocumentInfo(ctx, id) | POST /api/documents.info | {\"id\"} — uuid OR urlId OR slug-urlId (all L) | L data = Document |\n| ExportMarkdown(ctx, uuid) | GET /api/v2/documents/{uuid}/markdown | path uuid ONLY (urlId → 400 invalid_format, L) | L {\"data\":\"\u003cmarkdown string\u003e\"} — may be \"\" (see quirks) |\n| SearchDocuments(ctx, req{Query,Limit}) | POST /api/documents.search | {\"query\",\"limit\"} | L data[] of {context string, ranking float64, document Document}; top-level count |\n| ListCollections(ctx, limit, offset) | POST /api/collections.list | {\"limit\",\"offset\"} | L data[] Collection{ID,Name,URLID,URL,Permission,Sharing}; pagination{limit,offset,nextPath}; top-level count |\n| ListDocuments(ctx, req) | POST /api/documents.list | {\"collectionId\",\"parentDocumentId\",\"limit\",\"offset\"} (all optional) | L data[] Document; pagination; top-level total |\n| CreateDocument(ctx, req) | POST /api/documents.create | {\"title\"!,\"collectionId\"!,\"text\",\"parentDocumentId\",\"publish\"} | L data = Document (text echoed VERBATIM markdown; publish:false ⇒ publishedAt null even with team autoPublishDocs) |\n| UpdateDocument(ctx, req) | POST /api/documents.update | {\"id\"!(uuid|urlId),\"title\",\"text\",\"append\",\"publish\"} | S (v1 spec; same Document envelope) |\n| CreateComment(ctx, req) | POST /api/comments.create | {\"entityType\":\"document\",\"entityId\"!,\"text\"!,\"parentCommentId\"} | L data.comment{id,entityId,threadId,text,quote,isResolved,createdById,createdAt,updatedAt} — NESTED under data.comment |\n| ListComments(ctx, req) | POST /api/comments.list | {\"entityId\"!,\"isResolved\"(*bool),\"threadId\",\"limit\"≤100,\"offset\"} | L data.comments[] (nested; items also carry threadCommentsCount, attachments[]); pagination; total |\n| ResolveComment(ctx, id, resolved) | POST /api/comments.resolve | {\"id\"!,\"isResolved\"!} | S (v2-preview spec) data.comment |\n| ParseDocRefs(baseURL, text) []string | (pure, no HTTP) | — | ordered dedup'd doc-id tokens from free text |\n\nDeliberately OUT of the client (documented in doc.go): documents.export RPC (now an ASYNC\nfileOperation on live — see quirks), attachments.* (follow-up if ever needed: attachments.list\n{documentId} → items carry redirectUrl/url/contentType/size/name; attachments.redirect {id} → 302\nsigned URL — both spec'd, redirect flow live-verified via fileOperations), events.list +\nwebhookSubscriptions.* (Q\u0026A bead extends the client then), bots CRUD (one-time provisioning, curl\nrunbook below), documents.delete.\n\n## Types\n\nDocument{ID, URLID, Title, URL (site-relative \"/doc/\u003cslug\u003e-\u003curlId\u003e\"), CollectionID string,\nParentDocumentID *string, Text string, Type string (\"document\"|\"database\"|\"row\"|\"whiteboard\"|\"embed\"),\nRevision int, ArchivedAt, DeletedAt, PublishedAt *string (keep RFC3339 strings, lossless),\nCreatedAt, UpdatedAt string, ChildrenCount int}. Collection{ID, URLID, Name, URL, Permission string,\nSharing bool}. Comment{ID, EntityID string, ThreadID *string, Text string, Quote *string,\nIsResolved bool, CreatedByID, CreatedAt, UpdatedAt string}. AuthInfo{User{ID, Name, Email string,\nIsBot, IsAdmin, IsViewer bool}, Team{Name, URL, Subdomain string}}.\n\nParseDocRefs(baseURL, text): regexp `https?://\u003cescaped-host\u003e/doc/([A-Za-z0-9._~-]+)` where host\ncomes from url.Parse(baseURL). Token = full last path segment (slug-urlId form — documents.info\naccepts it whole, no urlId slicing needed); query/#fragment excluded by the charset; works on both\nraw markdown and TipTap HTML (`\u003ca href=\"...\"\u003e`); dedup preserving first occurrence.\n\n## Errors\n\n`*APIError{StatusCode int, Method, Path, Code, Message, Snippet string}` parsed from the error\nenvelope; `(e *APIError) NotFound() bool` (StatusCode==404). Body snippet capped 2KiB; success-body\nread capped 8MiB (mem0 constants). Observed error bodies (assert in tests):\n- 401 authentication_required, message \"Unable to decode JWT token\" (bad token) / \"Authentication or shareId required\" (no token)\n- 404 not_found, \"Resource not found\" (documents.info, unknown uuid AND unknown urlId) / \"Document not found\" (v2 markdown)\n- 400 invalid_format, \"Invalid UUID (id)\" — NOTE: this body has NO \"status\" key\n- 500 internal_server_error (markdown export of a whiteboard doc)\n\n## Quirks for doc.go (all verified live unless noted)\n\n1. Identity: doc URL is `https://\u003cteam\u003e.yonote.ru/doc/\u003cslug\u003e-\u003curlId\u003e`; documents.info resolves\n uuid | bare urlId | slug-urlId; ALL /api/v2/* paths take the uuid only.\n2. Document.Text is NOT reliable markdown: after editor edits it is a PLAINTEXT projection (all\n markup stripped — verified on a real doc: text had no #/**/`` while /markdown had them).\n /v2/.../markdown is canonical BUT returned \"\" for an API-created never-edited draft whose .text\n still held the submitted markdown verbatim, and returns \"\" for database-type docs. Whether the\n draft case is \"draft\" or \"no editor state yet\" is UNVERIFIED — callers use the fallback chain\n (markdown → info.Text → unavailable) which covers both.\n3. documents.export RPC diverges from Outline AND from the v1 docs: live it requires a uuid and\n returns an async fileOperation {state waiting→complete} (poll fileOperations.info, download via\n fileOperations.redirect → 302 signed Yandex-cloud URL). Not wrapped by this client.\n4. type=whiteboard → markdown export 500s; type=database → \"\" (v2 search/accessible spec says\n types document|database|row|whiteboard|embed).\n5. Pagination: {limit, offset, nextPath}; totals are INCONSISTENT — v1 RPC puts count/total at the\n top level, /api/v2/* puts total inside pagination. Don't share one envelope type.\n6. No rate-limit headers at 1.47.1; no documented limits. No client-side retry (callers own it,\n mem0 precedent); surface 429 as *APIError if it ever appears.\n7. Live (1.47.1) is newer than the published docs (1.38.1). The v2-preview OpenAPI spec exists\n ONLY embedded in the docs-page JS (yonote.ru/developers?v=2 → _next/static/chunks/pages/\n developers-*.js, two JSON.parse('...') literals: v2-preview then legacy v1);\n yonote.ru/openapi-3.json serves the OLD v1 spec. Re-extract from the chunk when in doubt.\n8. Team feature flags seen in auth.info: mcpServer:true + mcpSettings (Yonote hosts a native MCP\n endpoint at /api/v2/mcp — the operator's claude.ai integration uses it; irrelevant to this\n client but explains the product's own tool surface).\n\n## Bot-token provisioning runbook (one-time, operator; do BEFORE deploying the integration beads)\n\nThe daemon must authenticate as a BOT, not as bigbes's personal token (attribution + blast\nradius). Bots are Yonote-native (not in Outline). Admin-only. As of 2026-07-18 GET /api/v2/bots\nreturns [] — none exist yet.\n\n1. POST /api/v2/bots {\"name\":\"agent-hub\",\"username\":\"agent-hub\"} → data: bot user (grab id; user has isBot:true)\n2. POST /api/v2/bots/\u003cbotId\u003e/token {\"name\":\"agenthubd\"} → data{id,name,secret,...} — `secret` IS the API token, shown ONCE → store as YONOTE_TOKEN in /etc/agent-hub/env\n3. Grant collections (bots see nothing by default per Bots tag doc \"granted access to collections\n with read or write permissions\"): POST /api/collections.add_user {\"id\":\u003ccollectionId\u003e,\n \"userId\":\u003cbotId\u003e,\"permission\":\"read\"|\"read_write\"} (v1-spec'd, NOT live-verified).\n4. Verify with the minted token: POST /api/auth.info → user.isBot must be true; then\n documents.info on a granted doc, CreateDocument in the write collection.\n UNVERIFIED until then: auth.info under a bot token, bot default visibility, bot create rights.\nRotation: GET /api/v2/bots/{id}/token (list, no secrets), DELETE /api/v2/bots/{id}/token/{tokenId}.\n\nDaemon behavior: on startup call AuthInfo, log identity, WARN when IsBot==false (personal token in\nprod must be visible, not fatal).\n\n## Test plan (httptest, mem0-test style)\n\nPer method: happy path asserting HTTP method, exact path, Bearer header, JSON body; decode into\ntyped result. Error mapping: 401/404 envelopes, 400 body WITHOUT \"status\" key, non-JSON body →\nSnippet only. Nested data.comment / data.comments decode. ExportMarkdown \"\" passthrough (no error).\nDocumentInfo with the three id forms (same handler, three paths asserted). ParseDocRefs table:\nbare URL in markdown, TipTap `\u003ca href=\"https://host/doc/x-abc\"\u003e`, uuid-in-URL, trailing\npunctuation/\")\"/query/fragment, cross-host rejected, http vs https, dedup order, cap-free (cap is\nbead-2 policy).\n","notes":"Live-verified 2026-07-18 against bigbes.yonote.ru x-app-version 1.47.1 (docs page documents 1.38.1 — live is newer). Evidence: (1) auth.info via Bearer OK — the operator token in use today is PERSONAL (user.isBot=false, isAdmin=true); no bots exist yet (GET /api/v2/bots → []); mint one before deploy (runbook in design). (2) documents.info resolved the same doc by uuid, bare urlId, slug-urlId. (3) /api/v2/documents/{uuid}/markdown returned faithful markdown for an editor-edited doc while documents.info .text for the SAME doc was plaintext with all markup stripped — .text is NOT markdown after editor edits. (4) One-probe-doc lifecycle (created→deleted, approved): documents.create with markdown text echoed text VERBATIM and honored publish:false (publishedAt null despite team autoPublishDocs:true); /markdown for that fresh API-created draft returned '' — hence the markdown→.text fallback chain; comments.create → {data:{comment}} nested, text stored verbatim, comments.list → {data:{comments:[…]}}; documents.delete {permanent:true} → {success:true}; info after delete → 404. (5) documents.export RPC live = ASYNC fileOperation (uuid-only, 400 invalid_format on urlId); fileOperations.info reached state complete and fileOperations.redirect 302'd to a signed Yandex-storage URL (Camera-export.md). SIDE EFFECT LEFT BEHIND: one accidental export fileOperation record (id 6b6e2056-9487-4f91-b10b-818d7c1a486c, doc 'Camera', 604B, complete) lingers in the workspace export history — harmless, operator may delete. (6) Error bodies: 401 authentication_required 'Unable to decode JWT token' / 'Authentication or shareId required'; 404 not_found; 400 invalid_format WITHOUT a status key; 500 internal_server_error on whiteboard markdown export; database-type docs export ''. (7) No rate-limit headers observed. (8) v2-preview OpenAPI spec exists ONLY embedded in the docs-page JS (yonote.ru/developers?v=2 → _next chunk pages/developers-*.js, two JSON.parse single-quoted literals: v2-preview 1.38.1 with /v2/bots + /v2/documents, then legacy v1 0.1.0); yonote.ru/openapi-3.json serves the OLD v1 spec. (9) webhookSubscriptions.list responds live (empty) though undocumented in both specs; events.list live-verified. UNVERIFIED (implementer/provisioning): auth.info under a bot token; bot default visibility + collections.add_user grant; bot create rights; documents.update; comments.resolve; whether /markdown '' means draft-only or missing-editor-state (fallback chain covers both).","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-18T15:03:36Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T15:03:36Z"} | {"assignee":"Eugene Blikh","status":"in_progress"} | NULL | 2026-07-18T18:06:56Z |
| 019f75c4-250b-78b2-9074-3ad41aae73fa | ah-2lh | claimed | Eugene Blikh | {"id":"ah-2lh","title":"Claim-time Yonote artifact materialization into .task/artifacts/ + prompt manifest","description":"Why: the operator authors specs/big documents in Yonote and wants task cards to reference them by URL; the claimed agent must see the CURRENT doc content without the repo ever carrying it. What: optional yonote config block (base_url, token via ${YONOTE_TOKEN} from /etc/agent-hub/env, claim_budget); at claim, scan the frontmatter-stripped description body for https://\u003cyonote-host\u003e/doc/\u003cseg\u003e URLs (works on both raw-markdown and Vikunja TipTap-HTML descriptions), resolve via documents.info, export markdown (with .text fallback), write .task/artifacts/\u003cNN\u003e-\u003cslug\u003e.md via StartSpec.Artifacts in prepareTaskDir, append a '## Reference documents' manifest to the prompt, add an artifacts count to the claim comment + an event. Dead references NEVER fail the claim; every attempt re-exports fresh. wave 2.5 — implement after feat/wave2-archive-links merges. Depends on ah-gxa (client).","design":"# Claim-time Yonote artifact materialization\n\nOperator authors specs/big docs in Yonote; a task card references them by URL; at claim the\ndaemon exports each referenced doc to `.task/artifacts/\u003cNN\u003e-\u003cslug\u003e.md` in the worktree and lists\nthem in the prompt. `.task/` is git-excluded (excludeTaskDir) and archived to tar.gz at Done, so\nartifacts never touch the repo.\n\n## Config (internal/config)\n\nNew OPTIONAL top-level block, feature fully inert when absent (telegram/ntfy presence pattern):\n\n yonote:\n base_url: \"https://bigbes.yonote.ru\" # required when present; validateHTTPURL\n token: \"${YONOTE_TOKEN}\" # required when present; BOT token (see client bead runbook); ${VAR} from /etc/agent-hub/env via existing expandEnv\n claim_budget: \"90s\" # optional; TOTAL wall clock for all exports in one claim; default 90s; positive (parseDuration)\n\n`Yonote{BaseURL, Token string; ClaimBudget time.Duration}` + `present()` (trimmed BaseURL or Token\nnon-empty) + validation (both required when present; URL check; strict KnownFields comes free from\nrawConfig). Trim trailing \"/\" off BaseURL at resolve (AgentsView precedent).\nWiring (internal/deps or cmd wiring, wherever mem0/vikunja clients are built): when present →\n`yonote.New(BaseURL, Token)`; call AuthInfo once at startup: log identity, WARN if !IsBot; a\nstartup AuthInfo FAILURE logs an error and continues — Yonote outage must never block board work.\n\n## Reference convention (DECISION)\n\nEvery substring of the card description matching `https?://\u003chost-of-base_url\u003e/doc/\u003cseg\u003e` is a\nreference; `\u003cseg\u003e` = last path segment `[A-Za-z0-9._~-]+` (query/fragment excluded by charset).\nExtraction runs over the frontmatter-STRIPPED body (`res.Body` from spec.Resolve — the same text\nthe prompt template receives). Order of first occurrence; dedup by RESOLVED document id (two URL\nforms of one doc collapse); cap `maxArtifactRefs = 10` (const, not config) — refs past the cap get\nmanifest lines \"skipped: over per-task artifact cap\".\n\nWhy bare-URL matching, not an `artifact:` prefix line:\n(a) descriptions reach the daemon in TWO shapes — raw markdown on daemon-created child cards, and\n TipTap HTML (`\u003cp\u003e…\u003ca href=\"URL\"\u003etext\u003c/a\u003e\u003c/p\u003e`) on operator-edited cards; internal/vikunja\n passes Description verbatim (board.go), nothing normalizes HTML — a line-anchored convention\n breaks under HTML rewrapping, host+path substring matching survives both;\n(b) zero ceremony: paste a link into the card and it just works;\n(c) false positives are benign — a linked Yonote doc IS relevant context by definition.\n`/collection/...` and `/share/...` URLs deliberately NOT matched in v1. Resolution: pass `\u003cseg\u003e`\nverbatim to documents.info (accepts uuid | urlId | slug-urlId — live-verified) → canonical uuid,\ntitle, archivedAt.\n\n## Claim flow (internal/reconcile claim(), after renderPrompt succeeds, before runner.Start)\n\n refs := yonote.ParseDocRefs(cfg.Yonote.BaseURL, res.Body) // nil client or 0 refs → unchanged behavior\n ctx2 := context.WithTimeout(ctx, cfg.Yonote.ClaimBudget) // one budget for ALL refs; reconcile loop is single-goroutine (PublishTimeout precedent)\n serially per ref:\n doc, err := DocumentInfo(ctx2, ref) // err (404 deleted, 401, timeout…) → unavailable(reason), continue\n md, err := ExportMarkdown(ctx2, doc.ID) // MUST use doc.ID (uuid) — urlId 400s\n if err (incl. whiteboard 500) → unavailable(reason)\n if md == \"\" → md = doc.Text // API-created never-edited docs keep markdown in .text; database docs are \"\" both ways\n if md still \"\" → unavailable(\"document exported empty\")\n marker \"(archived)\" when doc.ArchivedAt != nil — still materialized (archived docs stay readable)\n Artifact{FileName: fmt.Sprintf(\"%02d-%s.md\", n, slug(doc.Title)), Content: md,\n Title: doc.Title, SourceURL: cfg.Yonote.BaseURL + doc.URL}\n budget exhausted → remaining refs unavailable(\"artifact budget exhausted\")\n\nslug(): lowercase; non-[a-z0-9] runs → \"-\"; trim \"-\"; cap 60 chars; empty → doc.URLID. The NN-\nordinal prefix makes collisions impossible. Cyrillic titles will slug to \"\" often → URLID fallback\nmatters.\n\nFAILURE SEMANTICS (spec-style): NOTHING in materialization ever fails or bounces the claim. A dead\n/ archived / empty / oversized-budget reference degrades to a manifest warning line + a claim-\ncomment count + a warn log. Rationale: the doc is context, not a precondition; the operator sees\nthe warning immediately (prompt + card comment) and can fix the link and re-run. No triage bounce\n(spec is not malformed), no failed state (nothing ran).\n\n## Prompt manifest\n\nReconciler APPENDS to the rendered prompt (role templates untouched; keeps SPEC §12 rendering\ncontract intact):\n\n ## Reference documents (.task/artifacts/)\n Exported from Yonote at claim time; read them before starting; treat as read-only input.\n 1. \"\u003cTitle\u003e\" — .task/artifacts/01-\u003cslug\u003e.md (source: \u003cabs url\u003e)\n 2. \"\u003cTitle\u003e\" (archived) — .task/artifacts/02-\u003cslug\u003e.md (source: \u003cabs url\u003e)\n 3. \"\u003cTitle or ref\u003e\" — UNAVAILABLE (\u003creason\u003e) (source: \u003cabs url\u003e)\n\n## Ports + runner\n\n- ports.StartSpec gains `Artifacts []Artifact`; `type Artifact struct{ FileName, Content, Title, SourceURL string }`.\n- runner prepareTaskDir: ALWAYS `os.RemoveAll(.task/artifacts)` first (attempt-scoped channel —\n same rationale as clearing summary/tasks/question: attempt N-1 exports must not leak into\n attempt N). Then when len(Artifacts)\u003e0: MkdirAll + writeFileAtomic each. Guard: reject FileName\n containing \"/\" or \"..\" (defense in depth; daemon generates them).\n- RE-CLAIM SEMANTICS (DECISION): every attempt RE-EXPORTS fresh content. Freshness wins over\n snapshot stability because the whole point is \"the current spec\", specs get edited between\n attempts, and each finished attempt's snapshot is already preserved by the Done-archive tar.gz.\n A doc that died between attempts becomes an UNAVAILABLE manifest line (stale file removed by the\n RemoveAll), so the agent never reads outdated content silently.\n\n## Observability\n\n- Claim comment (existing \"attempt N started · …\" line) gains, only when refs were found:\n `· artifacts: N exported[, M unavailable]`.\n- appendEvent \"artifacts\" {\"exported\": N, \"unavailable\": M} (deduped like existing events).\n\n## Interface seam\n\nreconcile depends on a narrow local interface (fake-friendly, matches existing port style):\n\n type yonoteExporter interface {\n DocumentInfo(ctx context.Context, id string) (*yonote.Document, error)\n ExportMarkdown(ctx context.Context, uuid string) (string, error)\n }\n\nnil = feature off. *yonote.Client satisfies it.\n\n## Tests\n\n- config: block absent (inert) / present-partial (errors join) / bad URL / ${VAR} unset problem /\n budget default + non-positive rejection (mirror telegram/ntfy config tests).\n- reconcile claim with fake exporter: 2-ref happy path (manifest text, StartSpec.Artifacts,\n comment suffix, event payload); 404 ref → UNAVAILABLE + claim proceeds; markdown \"\" → Text\n fallback; both empty → unavailable; budget timeout → remaining skipped; dedup two URL forms of\n one doc; TipTap-HTML description with \u003ca href=...\u003e; cap at 10; archived marker; nil exporter →\n byte-identical prompt to today.\n- runner: artifacts written under .task/artifacts/; stale dir removed when Artifacts empty;\n traversal FileName rejected.\n\nwave 2.5 — implement after feat/wave2-archive-links merges. Depends on the internal/yonote client\nbead.\n","notes":"Code-recon evidence: (1) descriptions reach the daemon VERBATIM — internal/vikunja/board.go:110 copies wire Description straight into ports.BoardTask; there is NO html→markdown normalization on read (markdownToHTML in internal/vikunja/markdown.go is write-side, comments only) — so operator-edited cards arrive as TipTap HTML while daemon-created child cards (reconcile.go childDescription) are raw markdown: the URL scanner must handle both, which is why the convention is host+path substring matching, not an 'artifact:' line. (2) Hook point: internal/reconcile/reconcile.go claim(), after renderPrompt success (~line 362-404) and before runner.Start; prompt is plain string concatenation — role templates (SPEC §12) stay untouched. (3) File writing: internal/runner/runner.go prepareTaskDir (~591-633) already clears attempt-scoped channels (summary/tasks/question) — artifacts dir clearing joins that list; writeFileAtomic + excludeTaskDir + Done-archive tar.gz already cover durability/git-exclusion/archival (SPEC §9). (4) API evidence for the fallback chain and failure modes lives in ah-gxa notes: /markdown is uuid-only, returns '' for API-created-never-edited drafts and database docs, 500s for whiteboards, and .text is plaintext after editor edits — hence markdown → .text → UNAVAILABLE. (5) claim runs on the single reconcile goroutine — claim_budget bounds total materialization wall-clock (PublishTimeout precedent, config.go). (6) documents.info of an ARCHIVED doc was NOT live-verifiable (workspace has no archived docs); Outline heritage says it returns the doc with archivedAt set — treated as exportable + '(archived)' marker; implementer verifies by archiving a scratch doc in the UI once. Nested docs: no doc with childrenCount\u003e0 exists in the workspace; /markdown has no children param (single-doc export is spec-consistent) — v1 semantics = SINGLE doc per reference, children never walked (documents.list {parentDocumentId} exists if a future version wants a tree).","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-18T15:04:08Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T15:04:08Z"} | {"assignee":"Eugene Blikh","status":"in_progress"} | NULL | 2026-07-18T18:06:56Z |
| 019f75c4-2645-733c-93ff-b573473c0544 | ah-25e | claimed | Eugene Blikh | {"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"} | {"assignee":"Eugene Blikh","status":"in_progress"} | NULL | 2026-07-18T18:06:56Z |
| 019f75f3-e70e-76a7-86e2-16953fca1473 | ah-25e | updated | Eugene Blikh | {"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"} | {"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."} | NULL | 2026-07-18T18:59:06Z |
| 019f75f3-ea08-74a7-8e20-cde907d25363 | ah-gxa | closed | Eugene Blikh | 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. | NULL | 2026-07-18T18:59:07Z | |
| 019f75f3-eaef-73c8-897f-5b9c8cbe15f6 | ah-2lh | closed | Eugene Blikh | 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. | NULL | 2026-07-18T18:59:07Z | |
| 019f75f3-ebc5-7e50-a9c7-77bbcec08ecc | ah-25e | closed | Eugene Blikh | 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. | NULL | 2026-07-18T18:59:07Z | |
| 019f75f4-6587-743d-bf04-0f5e232a4219 | ah-ydx.3 | claimed | Eugene Blikh | {"id":"ah-ydx.3","title":"Memory lifecycle: load-on-claim CONTEXT section, save-on-Done","description":"Wire mem0 into the task loop per SPEC §14.3: on claim, search mem0 (proj scope + global) and render a memory section into the prompt context via ctxpack; on Done/Cancelled terminal scan (shares the ah-07g hook), save run summary/verdict facts back to mem0 (run_id='task:\u003cid\u003e'). Depends on the mem0 client, ctxpack, and the ah-07g terminal-state scan.","notes":"MEM0 SERVER HEALTHY (2026-07-18, after user-authorized override delete + restart): /configure now reflects env-of-truth (deepseek/deepseek-v4-flash + nvidia/nemotron-embed-1b-v2). FULL live smoke green: ADD infer=false 200/event=ADD; SEARCH 200 with score (embeddings ok); ADD infer=true 200 (LLM extraction ok); LIST/PURGE/EMPTY clean; bogus key 401. Server-side contract fully verified — lifecycle implementation unblocked once ah-07g lands the terminal-state hook. OPERATIONAL RULE: mem0 config is ENV-ONLY; never POST /configure (GET redacts secrets, round-trip corrupts stored creds — happened + fixed today, backup at phoebe:/root/mem0-settings-backup.sql); if a stored override reappears (dashboard onboarding creates one), delete settings row key=config_overrides + restart.","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-18T13:23:57Z","created_by":"Eugene Blikh","updated_at":"2026-07-18T13:57:49Z"} | {"assignee":"Eugene Blikh","status":"in_progress"} | NULL | 2026-07-18T18:59:38Z |