~bigbes/agents-dev · events

9pjd88lk734le69tg7kt0fo79lq56l21 · 411 rows

idissue_idevent_typeactorold_valuenew_valuecommentcreated_at
019f7cdf-2e5c-74fd-bf26-105aacf83db2ah-efelabel_removedEugene BlikhNULLNULLRemoved label: milestone:v0.1.02026-07-20T03:13:49Z
019f7cdf-2f63-79f6-8bb2-265a57ced2eeah-efelabel_addedEugene BlikhNULLNULLAdded label: milestone:v0.2.02026-07-20T03:13:49Z
019f7cdf-6fba-75e7-965f-79478aa1b878ah-1cxlabel_removedEugene BlikhNULLNULLRemoved label: milestone:v0.1.02026-07-20T03:14:05Z
019f7cdf-70c3-7ecd-8e47-c24bfda0df97ah-efelabel_removedEugene BlikhNULLNULLRemoved label: milestone:v0.2.02026-07-20T03:14:06Z
019f7cea-e517-7557-9476-bacfa9af34c6ah-wd4createdEugene BlikhNULL2026-07-20T03:26:36Z
019f7cf1-3477-79bc-be78-cf15a6ec2977ah-wd4updatedEugene Blikh{"id":"ah-wd4","title":"DECISION: move the task board from Vikunja to beads+Dolt (full swap vs hybrid mirror)","description":"Three independent Opus researchers audited this on 2026-07-20 (board contract / beads capabilities / human workflow). Consensus: writing the adapter is the EASY part; the cost is infrastructure and the human write path.\n\nFEASIBILITY (good news): ports.Board is only 6 methods (Snapshot, MoveToBucket, Comment, Comments, CreateTask, SwapLabel), 22 call sites all in internal/reconcile, and everything else (attempts, runs, lineage, Q\u0026A links) lives in the SQLite store. domain/ is Vikunja-free; bucket names are domain constants. Adapter est. a few hundred lines, minus the 238-line markdown-\u003eHTML converter which a plain-text board does not need. All 9 canonical buckets ARE expressible via beads custom statuses with active/wip/done/frozen categories, wired into the ready_issues view at SQL level. bd ready --claim is a real compare-and-swap (ErrAlreadyClaimed) — stronger than what we have today. Comments live in their own table, NOT mixed with the events audit log, so the ask-user answer detector is safe from machine-generated lines.\n\nBLOCKERS (the real cost):\n1. NO Go library (all packages are internal/), no MCP, no HTTP daemon. The only interfaces are fork/exec of bd --json (~250ms warm) or raw MySQL to a dolt sql-server.\n2. Embedded mode is single-process and BLOCKS UNBOUNDEDLY — measured bd count waiting 43s behind a 40s external DB hold, no timeout knob. Upstream design doc calls multi-process embedded 'unsupported'. Migrating to dolt sql-server mode is MANDATORY (backup + bd init --server + restore; different data dir; a new server process to supervise).\n3. Human write path. Vikunja is the human INPUT surface, not just storage; the viewer is read-only. The killer interaction is ask-user: a card parked in Question (or a live agent polling /api/tool/answer against a 30m timeout) waits on a human comment. Today that is typed from any device in \u003c=20s; under beads it is laptop-only bd comment behind a ~5min auto-push debounce (~17% of a run timeout per exchange). Lowering the interval does not fix the failure CLASS: a local write that reports success and is invisible to the daemon.\n4. Every write is a Dolt commit — a comment per state change plus per-heartbeat progress = write amplification into a version-controlled DAG that auto-pushes. bd batch help names this; bd compact/gc/flatten are the cleanup treadmill.\n5. int64 task IDs are load-bearing (branch task-\u003cid\u003e, zellij session, archive filename, tool-token binding, HTTP API, notification URL). Beads ids are strings (ah-1cx.1). Recommended fix: repo-wide int64-\u003estring (mechanical, compiler-verified, ~10 files) over a synthetic mapping table that can drift.\n6. Snapshot must NEVER be partial: a card missing from a snapshot is treated as vanished and the daemon KILLS the live run and cancels the record. Any adapter must enumerate transactionally or prove completeness (the Vikunja adapter refuses a truncated bucket rather than dropping tasks). Watch bd list default limits.\n7. Same-field concurrent updates are last-writer-wins with no optimistic locking (upstream open question #3); only --claim has CAS. Contradicts the SPEC principle that human intent wins and the daemon aligns.\n8. No change notification, by product charter ('Beads does not need sub-second sync'). Poll the events table by created_at. NOTE: this is NOT a real blocker for us — the Vikunja webhook is explicitly only an acceleration of the 20s poll, and agent-completion latency rides the separate run-exit poke.\n\nWHAT IMPROVES: Task Spec escapes the rich-text editor (plainTextFromHTML exists ONLY to undo Vikunja HTML mangling of YAML frontmatter — becomes deletable); first-class deferred/--defer beats an unmapped Someday column; dependency-aware bd ready for free; agent and human share one tracker beside the code; full history/diff/branching. Attachments are a non-issue (zero code references).\n\nOPTIONS:\nA) HYBRID MIRROR (low risk): keep Vikunja as the board, add a one-way exporter into beads for reporting/milestones. No reconcile changes at all. Note the milestone viewer at dolt.srht.bigb.es/~bigbes/agents-dev/view/milestones already delivers most of this value today.\nB) FULL SWAP, gated on prerequisites: migrate beads to dolt sql-server mode; daemon speaks MySQL directly (or bd --json --sandbox with pushes on its own timer); map ready-\u003estatus open (REQUIRED: the CAS claim hardcodes status='open'); use metadata JSON for daemon-private state; events.created_at as poll cursor; batch comment writes + scheduled compaction; int64-\u003estring ids; AND build a human write path — 3 POST endpoints on the existing httpapi (comment / status / label) reusing the proven bearer-token pattern, plus a small form in the viewer. Because the daemon runs on agent-1 next to the authoritative working set, writes through it have ZERO sync latency and take the laptop out of the write path entirely.\n\nRECOMMENDATION: do not swap while the viewer is read-only. Either stay on Vikunja, or commit to option B including the write path — the write path is what makes it viable, not the adapter. Implementation beads to be filed once this decision is made.","notes":"Researcher evidence is summarized here rather than linked; the three reports were transcript-only. Key measured facts to re-verify before acting: bd 1.1.0 embedded blocking (43s observed), bd has no non-internal Go packages, claim.go hardcodes status='open' in the CAS UPDATE, auto-push debounce default 5m / timeout 30s.","status":"open","priority":3,"issue_type":"decision","owner":"bigbes@gmail.com","created_at":"2026-07-20T00:26:37Z","created_by":"Eugene Blikh","updated_at":"2026-07-20T00:26:37Z"}{"notes":"CORRECTION 2026-07-20 (verified against the v1.1.0 source zip from proxy.golang.org): blocker #1 'NO Go library' is WRONG. github.com/steveyegge/beads has a root package beads.go documented as 'a minimal public API for extending bd with custom orchestration', MIT licensed. It re-exports the internal layer via type aliases (Storage, Transaction, RemoteStore, SyncStore, Issue, Comment, Event, IssueFilter, WorkFilter, status/type constants) and exposes Open(ctx, dbPath), OpenFromConfig(ctx, beadsDir), FindBeadsDir, FindDatabasePath.\n\nThe Storage interface covers the Board port almost 1:1: SearchIssues/GetReadyWork -\u003e Snapshot; UpdateIssue(id, {status}) -\u003e MoveToBucket; AddIssueComment/GetIssueComments (typed, ordered) -\u003e Comment/Comments; CreateIssue -\u003e CreateTask; AddLabel+RemoveLabel -\u003e SwapLabel.\n\nThree risks in the description are downgraded by this API:\n- Write amplification: RunInTransaction(ctx, commitMsg, fn) batches many writes into ONE Dolt commit, rolls back on error or panic. Also makes SwapLabel ATOMIC — better than the current Vikunja adapter, which documents a deliberate non-atomic add-then-remove.\n- Change notification: GetAllEventsSince(ctx, since time.Time) is a typed poll cursor; no hand-rolled SQL over the events table needed.\n- Embedded-vs-server: OpenFromConfig respects dolt_mode in metadata.json, so switching is configuration, not code, and the daemon holds the connection instead of fork/exec-ing a ~250ms CLI. RemoteStore (via type assertion) exposes Push/Pull so the daemon controls push timing itself rather than inheriting the 5-min auto-push debounce.\n\nSTILL TO VERIFY before relying on it: which call enumerates ALL issues for Snapshot (there is no plain ListIssues — likely SearchIssues with an empty query + IssueFilter) and whether IssueFilter applies a default limit. A silently truncated snapshot makes the daemon treat missing cards as vanished and KILL live runs, so this needs an explicit completeness guarantee.\n\nUNCHANGED: the human write path is still the real blocker, and the recommendation stands — do not swap while the viewer is read-only."}NULL2026-07-20T03:33:30Z
019f7e7f-0ba7-75a1-bef6-9198f4bf61adah-wd4updatedEugene Blikh{"id":"ah-wd4","title":"DECISION: move the task board from Vikunja to beads+Dolt (full swap vs hybrid mirror)","description":"Three independent Opus researchers audited this on 2026-07-20 (board contract / beads capabilities / human workflow). Consensus: writing the adapter is the EASY part; the cost is infrastructure and the human write path.\n\nFEASIBILITY (good news): ports.Board is only 6 methods (Snapshot, MoveToBucket, Comment, Comments, CreateTask, SwapLabel), 22 call sites all in internal/reconcile, and everything else (attempts, runs, lineage, Q\u0026A links) lives in the SQLite store. domain/ is Vikunja-free; bucket names are domain constants. Adapter est. a few hundred lines, minus the 238-line markdown-\u003eHTML converter which a plain-text board does not need. All 9 canonical buckets ARE expressible via beads custom statuses with active/wip/done/frozen categories, wired into the ready_issues view at SQL level. bd ready --claim is a real compare-and-swap (ErrAlreadyClaimed) — stronger than what we have today. Comments live in their own table, NOT mixed with the events audit log, so the ask-user answer detector is safe from machine-generated lines.\n\nBLOCKERS (the real cost):\n1. NO Go library (all packages are internal/), no MCP, no HTTP daemon. The only interfaces are fork/exec of bd --json (~250ms warm) or raw MySQL to a dolt sql-server.\n2. Embedded mode is single-process and BLOCKS UNBOUNDEDLY — measured bd count waiting 43s behind a 40s external DB hold, no timeout knob. Upstream design doc calls multi-process embedded 'unsupported'. Migrating to dolt sql-server mode is MANDATORY (backup + bd init --server + restore; different data dir; a new server process to supervise).\n3. Human write path. Vikunja is the human INPUT surface, not just storage; the viewer is read-only. The killer interaction is ask-user: a card parked in Question (or a live agent polling /api/tool/answer against a 30m timeout) waits on a human comment. Today that is typed from any device in \u003c=20s; under beads it is laptop-only bd comment behind a ~5min auto-push debounce (~17% of a run timeout per exchange). Lowering the interval does not fix the failure CLASS: a local write that reports success and is invisible to the daemon.\n4. Every write is a Dolt commit — a comment per state change plus per-heartbeat progress = write amplification into a version-controlled DAG that auto-pushes. bd batch help names this; bd compact/gc/flatten are the cleanup treadmill.\n5. int64 task IDs are load-bearing (branch task-\u003cid\u003e, zellij session, archive filename, tool-token binding, HTTP API, notification URL). Beads ids are strings (ah-1cx.1). Recommended fix: repo-wide int64-\u003estring (mechanical, compiler-verified, ~10 files) over a synthetic mapping table that can drift.\n6. Snapshot must NEVER be partial: a card missing from a snapshot is treated as vanished and the daemon KILLS the live run and cancels the record. Any adapter must enumerate transactionally or prove completeness (the Vikunja adapter refuses a truncated bucket rather than dropping tasks). Watch bd list default limits.\n7. Same-field concurrent updates are last-writer-wins with no optimistic locking (upstream open question #3); only --claim has CAS. Contradicts the SPEC principle that human intent wins and the daemon aligns.\n8. No change notification, by product charter ('Beads does not need sub-second sync'). Poll the events table by created_at. NOTE: this is NOT a real blocker for us — the Vikunja webhook is explicitly only an acceleration of the 20s poll, and agent-completion latency rides the separate run-exit poke.\n\nWHAT IMPROVES: Task Spec escapes the rich-text editor (plainTextFromHTML exists ONLY to undo Vikunja HTML mangling of YAML frontmatter — becomes deletable); first-class deferred/--defer beats an unmapped Someday column; dependency-aware bd ready for free; agent and human share one tracker beside the code; full history/diff/branching. Attachments are a non-issue (zero code references).\n\nOPTIONS:\nA) HYBRID MIRROR (low risk): keep Vikunja as the board, add a one-way exporter into beads for reporting/milestones. No reconcile changes at all. Note the milestone viewer at dolt.srht.bigb.es/~bigbes/agents-dev/view/milestones already delivers most of this value today.\nB) FULL SWAP, gated on prerequisites: migrate beads to dolt sql-server mode; daemon speaks MySQL directly (or bd --json --sandbox with pushes on its own timer); map ready-\u003estatus open (REQUIRED: the CAS claim hardcodes status='open'); use metadata JSON for daemon-private state; events.created_at as poll cursor; batch comment writes + scheduled compaction; int64-\u003estring ids; AND build a human write path — 3 POST endpoints on the existing httpapi (comment / status / label) reusing the proven bearer-token pattern, plus a small form in the viewer. Because the daemon runs on agent-1 next to the authoritative working set, writes through it have ZERO sync latency and take the laptop out of the write path entirely.\n\nRECOMMENDATION: do not swap while the viewer is read-only. Either stay on Vikunja, or commit to option B including the write path — the write path is what makes it viable, not the adapter. Implementation beads to be filed once this decision is made.","notes":"CORRECTION 2026-07-20 (verified against the v1.1.0 source zip from proxy.golang.org): blocker #1 'NO Go library' is WRONG. github.com/steveyegge/beads has a root package beads.go documented as 'a minimal public API for extending bd with custom orchestration', MIT licensed. It re-exports the internal layer via type aliases (Storage, Transaction, RemoteStore, SyncStore, Issue, Comment, Event, IssueFilter, WorkFilter, status/type constants) and exposes Open(ctx, dbPath), OpenFromConfig(ctx, beadsDir), FindBeadsDir, FindDatabasePath.\n\nThe Storage interface covers the Board port almost 1:1: SearchIssues/GetReadyWork -\u003e Snapshot; UpdateIssue(id, {status}) -\u003e MoveToBucket; AddIssueComment/GetIssueComments (typed, ordered) -\u003e Comment/Comments; CreateIssue -\u003e CreateTask; AddLabel+RemoveLabel -\u003e SwapLabel.\n\nThree risks in the description are downgraded by this API:\n- Write amplification: RunInTransaction(ctx, commitMsg, fn) batches many writes into ONE Dolt commit, rolls back on error or panic. Also makes SwapLabel ATOMIC — better than the current Vikunja adapter, which documents a deliberate non-atomic add-then-remove.\n- Change notification: GetAllEventsSince(ctx, since time.Time) is a typed poll cursor; no hand-rolled SQL over the events table needed.\n- Embedded-vs-server: OpenFromConfig respects dolt_mode in metadata.json, so switching is configuration, not code, and the daemon holds the connection instead of fork/exec-ing a ~250ms CLI. RemoteStore (via type assertion) exposes Push/Pull so the daemon controls push timing itself rather than inheriting the 5-min auto-push debounce.\n\nSTILL TO VERIFY before relying on it: which call enumerates ALL issues for Snapshot (there is no plain ListIssues — likely SearchIssues with an empty query + IssueFilter) and whether IssueFilter applies a default limit. A silently truncated snapshot makes the daemon treat missing cards as vanished and KILL live runs, so this needs an explicit completeness guarantee.\n\nUNCHANGED: the human write path is still the real blocker, and the recommendation stands — do not swap while the viewer is read-only.","status":"open","priority":3,"issue_type":"decision","owner":"bigbes@gmail.com","created_at":"2026-07-20T00:26:37Z","created_by":"Eugene Blikh","updated_at":"2026-07-20T00:33:30Z"}{"design":"HANDOFF DESIGN — human write path for a beads-backed board. Written 2026-07-20 for a worker with\nno prior conversation context. Read this whole section before touching code.\n\n== WHY THIS EXISTS ==\nVikunja is not merely storage for agenthubd: it is the surface through which a HUMAN expresses\nintent, which the daemon then observes. SPEC.md:36 and :41-43 state the contract — the board is\ndesired state owned by the human, the SQLite store plus runtime is actual state owned by the\ndaemon, and \"the daemon never fights a human drag: human intent wins\". Replacing the board with\nbeads+Dolt while the only web UI is READ-ONLY removes the human's write surface. That is the sole\nremaining blocker to the migration; everything else is tractable work (see the description).\n\n== THE COMPLETE HUMAN INTENT VOCABULARY (do not add verbs beyond these without re-deriving) ==\nEvery human gesture the daemon can observe reduces to four writes. Evidence is by interaction:\n\n1. COMMENT — the only latency-critical write. Two cases:\n a. Card parked in Question: reconcile.go:1151 handleQuestion -\u003e Comments() -\u003e tools.go:263-278\n detectAnswer. It finds the LAST comment containing marker \"agent-hub:awaiting-answer\"\n (reconcile.go:1105); if ANY comment follows it, that trailing comment IS the answer.\n b. Mid-run: tools.go:210-238 ToolAskUser parks the card while the agent stays LIVE polling\n GET /api/tool/answer (tools.go:245-252). The run is burning against cfg.Timeout (default 30m,\n enforced in check()). Delivery latency here is on the critical path of a running agent.\n CONSEQUENCE: any write path slower than ~1 min materially degrades (b). A 5-minute\n auto-push debounce consumes ~17% of a default run budget per exchange.\n\n2. STATUS CHANGE — highest-frequency gesture; five human actions share this one operation:\n trigger work (drag to Ready -\u003e reconcile.go:234-235 handleReady + domain.CanClaim);\n promote an agent-created task out of Triage (created by tools.go:344-350 into\n cfg.AgentTasks.TargetBucket, default triage; Triage is a PARKED bucket, reconcile.go:238-244 —\n never a claim source); cancel/kill (reconcile.go:254-256 handleTerminal:1525-1560 -\u003e runner.Kill,\n outcome killed); route from In Review; park out of the way (unmapped bucket, reconcile.go:228-232).\n\n3. CREATE TASK — title + description + initial status. The description carries the Task Spec YAML\n frontmatter (role/model/skills/timeout), parsed by internal/spec/frontmatter.go:36-58.\n\n4. EDIT DESCRIPTION / LABELS — easy to under-rate. When the daemon REJECTS a Task Spec it bounces\n the card to Triage (reconcile.go:380) with a comment that literally instructs: \"Fix the Task\n Spec in the description, then drag the card back to Ready\" (comments.go:51-59). Without an edit\n path a rejected card is unrecoverable from any device that lacks the bd CLI. Labels are the same\n operation class: the type:\u003cname\u003e label selects the task-type preset (spec.go:194-217) and is read\n by verdict routing (routing.go:23-42). Exactly one type:* label is legal.\n\nNOT needed: assignees, priorities, due dates, attachments, ordering, reactions. The daemon reads\nnone of them (grep -rni attachment internal/ returns zero hits). BoardTask carries only\n{ID, Title, Description, Bucket, Labels, UpdatedAt} and UpdatedAt has zero readers.\n\n== TWO DELIVERY SHAPES — evaluate SHAPE A FIRST, it may be nearly free ==\n\nSHAPE A: one shared Dolt sql-server; no new code.\n Run dolt sql-server on agent-1 beside the daemon; point every bd client at it over the network.\n bd supports this explicitly: 'bd dolt set host \u003cip\u003e [--update-config]', plus port/user/database,\n BEADS_DOLT_SERVER_MODE=1, bd init --server (see bd dolt --help; docs/DOLT.md in the beads source\n says server mode \"connects to a running dolt sql-server for multi-client access ... enables\n concurrent agents\"). With ONE database there is no push, no pull, no debounce, no divergence and\n no merge conflicts. Solves every desk interaction at ~zero engineering cost.\n DOES NOT solve: any device without bd + network access to the server (i.e. phone).\n Costs: a supervised sql-server process; network exposure of the DB port; migration from embedded\n to server mode is backup + 'bd init --server' + restore with a DIFFERENT data dir\n (.beads/dolt/ vs .beads/embeddeddolt/) — not a flag flip.\n\nSHAPE B: HTTP write endpoints on the daemon's existing httpapi.\n POST /api/v1/board/:id/comment {\"text\": \"...\"} -\u003e interaction 1 (DO FIRST)\n POST /api/v1/board/:id/status {\"status\": \"open\"} -\u003e interaction 2 (DO SECOND)\n POST /api/v1/board {\"title\",\"description\",\"status\"} -\u003e interaction 3\n PATCH /api/v1/board/:id {\"description\",\"labels\"} -\u003e interaction 4\n Why the daemon and not the viewer: the daemon runs ON agent-1 next to the authoritative Dolt\n working set, so a write through it has ZERO sync latency — it mutates the DB the reconciler reads\n and pushes on the daemon's own schedule. This takes the laptop out of the write path, which is\n what eliminates the failure CLASS (a local write that reports success and is invisible to the\n daemon). Lowering the auto-push interval only narrows the window; it does not remove the class.\n MINIMUM VIABLE SLICE = comment + status. Those two cover the blocker and the highest-frequency\n gesture. Create/edit can lag because filing new work is a desk activity anyway.\n\n== NON-OBVIOUS COSTS OF SHAPE B (largest hidden cost; read before estimating) ==\n- The daemon is LOOPBACK-ONLY today: config.example.yaml line 1, listen: \"127.0.0.1:9100\".\n A human-facing write API means binding off-loopback, which drags in TLS and a real auth story.\n- Auth machinery to REUSE, not reinvent: internal/reconcile/tools.go:60-137 mints per-task 256-bit\n bearer tokens with a constant-time compare; internal/httpapi/httpapi.go:311-353 does HMAC-SHA256\n verification for the Vikunja webhook. What is genuinely NEW is an OPERATOR token with a different\n lifetime and scope than a per-run token. Do not reuse per-task tokens for humans.\n- TWO WRITE PATHS CAN DIVERGE: if the laptop keeps writing a LOCAL Dolt DB while the HTTP API writes\n agent-1's, the merge problem returns. Shape A avoids this by construction. If shipping B alone,\n point the laptop's bd at agent-1 as well, or consciously accept Dolt merges.\n- Viewer integration: wire the existing read-only viewer's issue rows to POST at these endpoints\n (https://dolt.srht.bigb.es/~bigbes/agents-dev/view/milestones). It already renders id/title/\n priority/type/status and milestone progress; it is a sourcehut-style page with Log in/Register\n in the nav, so an auth context may already exist there.\n\n== INVARIANTS ANY IMPLEMENTATION MUST NOT BREAK ==\n1. NEVER return a partial board from Snapshot. A card missing from a snapshot is treated as VANISHED:\n reconcile.go:1659 handleVanished KILLS the live run and marks the record cancelled. The Vikunja\n adapter refuses a truncated bucket rather than dropping tasks (vikunja/board.go:95-99). See the\n open spike on SearchIssues/IssueFilter default limits — this is the gating unknown.\n2. Do NOT let machine-generated audit lines into the COMMENT stream. detectAnswer takes the LAST\n comment unconditionally, so an injected \"status changed to X\" line would be consumed as the\n human's answer. Beads keeps comments in their own table separate from the events audit log, so\n this is currently safe — preserve that separation.\n3. Move-then-comment, never comment-then-move (reconcile.go:376-379, :413-414, :1371-1373, :1494-1496).\n Only a successful move earns a comment, so a persistently failing move cannot spam one comment\n per tick.\n4. Persist-before-move; never assume a write landed and never re-read to confirm. Every failed move\n converges on a later tick (heal branch reconcile.go:286-299, alignCardToRecord:1436). This\n tolerance is what makes a non-transactional board safe.\n5. 'ready' MUST map to beads status 'open'. The atomic claim CAS hardcodes it:\n internal/storage/issueops/claim.go:47-58 UPDATE ... WHERE id=? AND status='open'. A custom\n 'ready:active' status would appear in bd ready but would NOT be claimable.\n\n== BEADS PUBLIC API (use it; do NOT import internal/ and do NOT shell out to the CLI) ==\nRoot package github.com/steveyegge/beads (MIT). Verified against the v1.1.0 source zip.\n Open(ctx, dbPath) / OpenFromConfig(ctx, beadsDir) -- the latter respects dolt_mode in\n metadata.json, so embedded-vs-server is CONFIGURATION not code.\n Storage interface maps ~1:1 to ports.Board:\n SearchIssues / GetReadyWork -\u003e Snapshot (SEE SPIKE: default limit unverified)\n UpdateIssue(id, {\"status\": ...}) -\u003e MoveToBucket\n AddIssueComment / GetIssueComments (typed, ordered) -\u003e Comment / Comments\n CreateIssue -\u003e CreateTask\n AddLabel + RemoveLabel -\u003e SwapLabel\n RunInTransaction(ctx, commitMsg, fn) batches many writes into ONE Dolt commit with rollback on\n error or panic — use it to (a) kill write amplification and (b) make SwapLabel ATOMIC, which is\n strictly better than the current Vikunja adapter's documented non-atomic add-then-remove.\n GetAllEventsSince(ctx, since time.Time) is a typed change-feed cursor — no hand-rolled SQL.\n RemoteStore (via type assertion) exposes Push/Pull so the daemon controls push timing itself.\nEscape hatches if the public API ever falls short, in order of preference: direct SQL (bd's own docs\nrecommend this for extensions); vendor the MIT-licensed code; a shim module declared under\ngithub.com/steveyegge/beads/\u003cx\u003e plus a replace directive (Go's internal rule is a path-prefix check\non the IMPORTING package path, so this legally compiles). internal/ carries no compat guarantees."}NULL2026-07-20T10:48:03Z
019f7e7f-5c6c-7bfe-bdf4-90566080bcc6ah-wd4.1createdEugene BlikhNULL2026-07-20T10:48:23Z
019fcf18-22d8-7dda-83c2-527acdc041f0ah-tqcclaimedEugene Blikh{"id":"ah-tqc","title":"Runner: an errored-final-turn pi run (exit 0, zero tokens) finalizes as success","description":"Live incident 2026-07-19 (task 4, Q\u0026A smoke): pi --mode json made exactly one model call, the litellm proxy 403'd it (key not allowed for model 'coder'), pi recorded stopReason:error with zero usage and EXITED 0 in ~0.87s. The daemon trusts the exit code: outcome=success, card advanced to in_review, Q\u0026A delivery then correctly reported 'no answer.json' — a broken model config masquerades as a successful run. Fix direction: Status/finalize (or ahub-run) should inspect the tail of events.jsonl — a run whose final assistant turn has stopReason:error (or whose agent_end follows zero completed tool/text turns) should finalize as outcome=error regardless of exit code. Second finding to fold in: /api/v1/status shows cost_usd=0 for ALL runs including real multi-minute ones (tasks 1-2), so the usage.cost.total accumulation from events.jsonl appears broken on pi 0.73.x — re-verify the event shape and fix the cost parse.","status":"open","priority":2,"issue_type":"bug","owner":"bigbes@gmail.com","created_at":"2026-07-19T05:38:11Z","created_by":"Eugene Blikh","updated_at":"2026-07-19T05:38:11Z","labels":["milestone:v0.1.0"]}{"assignee":"Eugene Blikh","status":"in_progress"}NULL2026-08-05T02:24:53Z
019fcf1e-9c53-7036-9f0d-4a4bfa72b31aah-tqcupdatedEugene Blikh{"id":"ah-tqc","title":"Runner: an errored-final-turn pi run (exit 0, zero tokens) finalizes as success","description":"Live incident 2026-07-19 (task 4, Q\u0026A smoke): pi --mode json made exactly one model call, the litellm proxy 403'd it (key not allowed for model 'coder'), pi recorded stopReason:error with zero usage and EXITED 0 in ~0.87s. The daemon trusts the exit code: outcome=success, card advanced to in_review, Q\u0026A delivery then correctly reported 'no answer.json' — a broken model config masquerades as a successful run. Fix direction: Status/finalize (or ahub-run) should inspect the tail of events.jsonl — a run whose final assistant turn has stopReason:error (or whose agent_end follows zero completed tool/text turns) should finalize as outcome=error regardless of exit code. Second finding to fold in: /api/v1/status shows cost_usd=0 for ALL runs including real multi-minute ones (tasks 1-2), so the usage.cost.total accumulation from events.jsonl appears broken on pi 0.73.x — re-verify the event shape and fix the cost parse.","status":"in_progress","priority":2,"issue_type":"bug","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-07-19T05:38:11Z","created_by":"Eugene Blikh","updated_at":"2026-08-04T23:24:53Z","started_at":"2026-08-04T23:24:53Z","labels":["milestone:v0.1.0"]}{"notes":"FINDING 1 (errored-final-turn ⇒ false success) FIXED in 665e805. Root cause confirmed as filed: applyExit mapped exit.json's code straight to the §9 outcome, and pi exits 0 even when its only assistant turn was rejected by the provider (stopReason 'error', zero usage). Fix: ParseEvents now also records the LAST assistant message_end's stopReason/errorMessage plus an assistant-message count, exposed as EventStreamInfo.Errored(); applyExit takes the parsed stream and maps exit 0 + Errored() to domain.OutcomeError while still recording the true exit code on the run row. The stream is POSITIVE evidence only — an absent, unreadable, or assistant-turn-less events.jsonl leaves the exit code's verdict alone, so a genuine success can never be flipped by a missing file. Both Status completion branches were factored into (*PiZellij).finalizeFromExit so the ah-2ef re-read and the ah-wka grace-clear can no longer drift apart between them; it WARNs with the provider's rejection text whenever it overrules a zero exit. SPEC §9 'Status resolution' updated to match. Re-verified against a REAL pi 0.82.1 capture that the pinned message_end shape (message.usage.cost.total, message.stopReason, message.errorMessage) is unchanged since the 0.70.2 fixtures.\n\nFINDING 2 (cost_usd=0 on every live run) STILL OPEN. Ruled out: the parser. A real pi 0.82.1 run against the direct deepseek provider produces message.usage.cost.total exactly where ParseEvents reads it, and the summation is correct (see ah-1cx.4). Leading hypothesis: agent-1 runs pi through a CUSTOM 'litellm' provider (models are named litellm/\u003cmodel\u003e), and pi prices a response from its own per-provider model registry — a custom OpenAI-compatible provider has no pricing metadata, so every cost field comes back 0. That would make cost_usd=0 pi's behaviour, not our bug, and the fix would be to source cost from LiteLLM instead. NEEDS LIVE EVIDENCE from agent-1 (an events.jsonl from a real run: is message.usage.cost.total literally 0, or is the usage block shaped differently under the litellm provider?) — the ssh probe is blocked by the local permission classifier, so this needs an operator '!' handoff."}NULL2026-08-05T02:31:57Z
019fcf1e-f613-790c-8f99-35fe20082165ah-1cx.8createdEugene BlikhNULL2026-08-05T02:32:20Z