~bigbes/agents-dev · events

1pqt6hhc48ed3hjbvsmhk706q03sbjap · 449 rows

idissue_idevent_typeactorold_valuenew_valuecommentcreated_at
019f595b-cdd0-786c-b6a6-c176256d528aah-nyl.15status_changedEugene Blikh{"id":"ah-nyl.15","title":"fix: adopt-guard livelock + neither-state timeout arm (final verification findings)","description":"Final composed-semantics verification (@ 825238d) returned FIX-FIRST with two findings, both pre-analyzed with exact fixes. Scope: internal/reconcile/ ONLY.\n\nV1 HIGH (reconcile.go:524): adoptOrFail's finished-row branch guard `run.State == RunStateFinished \u0026\u0026 !st.Running` fires on ANY non-Running status including hard Completed for the NEXT attempt (exit.json{K+1} present but the K+1 row was never persisted — crash before CreateRun or persist failure whose compensating kill raced a fast-exiting pi). Result (empirically proven by the verifier): terminal record -\u003e alignCardToRecord silently bounces ready-\u003efailed on every human re-drag, K+1's real result never persisted/commented, Start never issued, stale exit.json never cleaned — livelock against the human. FIX (verifier-tested in scratch against the full suite): change the guard to `!runtimeEvidence(st)` so hard Completed evidence falls through to the adopt branch (which persists the K+1 row from the evidence and finalizes with its true outcome). Add regression test: finished row K + probe returns hard Completed{attempt K+1, exit 0} -\u003e adopts+finalizes K+1 (row created, in_review, one comment), then K+2 claimable; also the terminal-record variant (card in ready, terminal rec, completed evidence -\u003e adopt path, NOT silent bounce).\n\nV2 MEDIUM (reconcile.go:409-421): check() switch has no arm for the neither-state (st.Running==false \u0026\u0026 st.Completed==false — live session, meta gone/mismatched; reachable when the worktree is rm -rf'd mid-run, MetaAttempt=0). Today: no finalize, no timeout, forever — run wedged in in_progress past any deadline (verifier proved 31min past a 30m timeout, zero kills). FIX: add a third arm — when neither-state persists and now \u003e run.StartedAt + cfg.Timeout: runner.Kill + finalize with OutcomeTimeout (same path as the Running-timeout arm); before the deadline, log at warn (observation degraded) and do nothing. Regression test: running row, status neither (MetaAttempt 0), fake clock past deadline -\u003e Kill called, finalized timeout, card failed; before deadline -\u003e no action.\n\nV3 INFO (reconcile_test.go:36): statusCrashed fixture sets MetaAttempt 0, but the real runner reports the surviving meta's attempt on crash inference (0 only when the worktree is gone). Update the fixture/helpers so crashed statuses carry a realistic MetaAttempt (parameterize; keep a worktree-gone variant with 0) — hygiene so future MetaAttempt consumers are tested against real shapes.\n\nConstraints: internal/reconcile/ only; testify+culpa; go build/vet/test -race -count=1 ./internal/reconcile/... (never ./...); commit 'reconcile: adopt completed evidence, timeout the neither-state' staging only internal/reconcile; hooks bypassed (git -c core.hooksPath=/tmp/nohooks commit); no push; no .beads/.\n","status":"open","priority":0,"issue_type":"bug","owner":"bigbes@gmail.com","created_at":"2026-07-13T02:43:36Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T02:43:36Z"}{"status":"in_progress"}NULL2026-07-13T05:43:36Z
019f596b-c8c6-7c73-9212-282cd251efc3ah-nyl.15closedEugene Blikhlanded 30292cb: adopt-guard via runtimeEvidence + same-tick finalize of hard completed evidence, neither-state timeout arm w/ shared timeoutKill, fixture hygiene; negative controls confirmedNULL2026-07-13T06:01:03Z
019f596c-9a51-7952-a9d8-aba6b099642cah-nyl.6status_changedEugene Blikh{"id":"ah-nyl.6","title":"wiring: httpapi + agenthubd + ahub CLIs","description":"Wire the daemon together per docs/SPEC.md SS3, SS11 (read SPEC fully). All sibling packages now exist — full-tree builds are allowed and expected in THIS task.\n\nDeliverables:\n- internal/httpapi: loopback server per SS11 — GET /healthz; POST /internal/v1/run-exit {task_id,attempt,exit_code} validated -\u003e calls a RunExitHook (func injected by main; it pokes the reconciler); GET /api/v1/status -\u003e JSON {tasks:[TaskRecord+latest Run], generated_at}. stdlib net/http + 1.22 mux patterns; graceful shutdown; tests via httptest.\n- cmd/agenthubd: flags --config (default ./agenthub.yaml) --log-level; load config, open store, construct vikunja client, runner, no-op notifier (slog), reconciler; start httpapi + reconciler.Run; SIGINT/SIGTERM -\u003e graceful stop (context cancel, http shutdown, store close). slog JSON to stderr.\n- cmd/ahub: subcommands (stdlib flag, no cobra): `status` (GET /api/v1/status from --addr default 127.0.0.1:9100, human-readable table + --json raw), `validate-config` (load config, print OK or the collected errors, exit code accordingly), `version` (var set via -ldflags, default \"dev\").\n- justfile: verify `just build` produces bin/agenthubd bin/ahub bin/ahub-run (adjust if needed).\n- Smoke check you must run and make pass: `go build ./... \u0026\u0026 go vet ./... \u0026\u0026 go test ./...` (whole tree), then `bin/agenthubd --config config.example.yaml` with a fake VIKUNJA_TOKEN env — it must start, log the bucket-resolution failure gracefully (retry next tick, not crash-loop-exit), and /healthz must answer. Note in the bead comment if SPEC/behavior forced any deviation.\n\nConstraints: you own cmd/agenthubd, cmd/ahub, internal/httpapi, plus minimal glue edits elsewhere ONLY if a sibling package has an integration bug you must fix to link (document any such fix in its own commit '\u003cpkg\u003e: fix ...'). No .beads/ changes. Commits: 'httpapi: ...', 'cmd: ...'. Do NOT push.\n","notes":"CONVENTION UPDATE (see SPEC §13, commit 5ce35c5+): tests use testify (require/assert); errors via auxilia culpa; agenthubd wires scribe handlers (TintHandler for ahub CLI, JSON or Multi for the daemon); steward MAY be used for daemon lifecycle wiring if it stays simple. testify + auxilia already in go.mod.\nREVIEW INPUT (data-layer review finding 4): agenthubd startup must PREFLIGHT the board — construct the vikunja client and resolve the configured bucket titles once at startup; a missing title / no-kanban-view error at that point is FATAL with a clear message (SPEC §8 'fatal config error'), while the same error later at runtime stays retryable inside the loop. Wire this into cmd/agenthubd.","status":"open","priority":1,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-12T23:35:55Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T00:41:23Z"}{"status":"in_progress"}NULL2026-07-13T06:01:57Z
019f597b-6e19-7278-a328-d93c488d0d8bah-nyl.6closedEugene Blikhlanded e116576+ae38c10+254c0f8: httpapi 3 endpoints, agenthubd wiring w/ fatal preflight + graceful shutdown, ahub CLI; scribe JSON/Tint handlers; steward skipped (justified); full happy-path smoke against fake vikunja doneNULL2026-07-13T06:18:09Z
019f597b-9422-7572-9a5a-bfd0d24aefd0ah-nyl.7status_changedEugene Blikh{"id":"ah-nyl.7","title":"e2e: harness with fake vikunja + stub pi","description":"Build the end-to-end harness per docs/SPEC.md SS1/SS14 stage-1 acceptance: prove the full loop without live services. Build tag e2e, directory e2e/, run via `just e2e`.\n\nScenario (single test, subtests per phase): temp dir with (a) a real git repo as the target repo (one commit on master), (b) fake Vikunja: httptest server implementing the subset internal/vikunja uses (views, kanban tasks, buckets, move, comment) over in-memory state you can mutate from the test; (c) stub `pi` script on PATH that reads .task/PROMPT.md, makes a commit in the cwd repo ('stub: change'), emits 2-3 plausible JSONL lines to stdout, exits 0; (d) real zellij is NOT used: stub `zellij` script that for `run` executes the wrapped command directly (background), for attach --create-background no-ops, for list-sessions prints active names from a state file — i.e. simulate sessions with files. (e) real ahub-run and agenthubd binaries built by the test (go build into t.TempDir()).\n\nFlow: start agenthubd with a generated config (short poll_interval ~200ms) -\u003e put a task in fake-Vikunja Ready bucket -\u003e wait (poll with deadline, no fixed sleeps) for: card moved to In Progress with claim comment; then card in In Review with a comment containing the diff stat; store db has task in_review + run finished/success (inspect via ahub status --json against the daemon). Negative subtest: stub pi exits 1 -\u003e card lands in Failed with diagnostic comment. Timeout subtest optional (only if cheap with the fake clock — the daemon uses real time; skip if it needs sleeps \u003e2s and note why).\n\nConstraints: everything under e2e/ (+ justfile tweak if needed). Full-tree build allowed. If you find integration bugs in other packages, fix them in separate commits '\u003cpkg\u003e: fix ...' with a test where feasible. No .beads/ changes. Commit 'e2e: ...'. Do NOT push.\n","notes":"CONVENTION UPDATE (see SPEC §13): tests use testify (require/assert); errors via culpa. Deps already in go.mod.","status":"open","priority":2,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-12T23:35:56Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T00:09:35Z"}{"status":"in_progress"}NULL2026-07-13T06:18:18Z
019f598a-4241-7cc9-a4b1-e10b96c3b220ah-nyl.16createdEugene BlikhNULL2026-07-13T06:34:20Z
019f598a-44a9-7bf6-95e5-e8e958b2b338ah-nyl.16status_changedEugene Blikh{"id":"ah-nyl.16","title":"fix: wiring review findings 1-7 (flag swallowing, agenthubd tests, polish)","description":"Hostile review of the wiring commits (e116576/ae38c10/254c0f8) returned FIX-FIRST. Fix ALL seven. Line refs @ HEAD.\n\nW1 BLOCKER (cmd/agenthubd/main.go:70, cmd/ahub/main.go:105,200): stdlib flag.Parse stops at the first positional and nothing checks fs.NArg() -\u003e `agenthubd serve --config ./missing.yaml` silently starts with ./agenthub.yaml defaults (exit 0, WRONG config); `ahub status 127.0.0.1:9188` silently queries the default :9100 (wrong daemon, exit 0). FIX: after each fs.Parse (three call sites), reject fs.NArg() \u003e 0 with a usage error to stderr and exit 2. Tests for all three sites.\n\nW2 BLOCKER-adjacent (cmd/agenthubd): zero tests despite run() being injection-shaped. ADD minimum: bad flag -\u003e exit 2; positional arg -\u003e exit 2 (W1); missing/broken config -\u003e exit 1; invalid --log-level -\u003e exit 2; connection-refused preflight -\u003e exit 1 AND stderr contains the operator-friendly preflight message. Use the injected args/stderr seams; httptest fake vikunja where needed (views endpoint refusing/absent). Keep each test \u003c2s.\n\nW3 LOW (cmd/ahub/main.go:219-230 + internal/config/config.go:422-427): single-problem validate-config prints the path twice and skips the bullet format (culpa.Join(1) returns the bare error so the multi-unwrap loop never fires); also the existing two-problem test passes via Contains even if the multi-unwrap loop is deleted. FIX in ahub only (do not change config's wrap): fallback branch prints the problem without the duplicated prefix; pin the EXACT multi-line output format for the \u003e=2 case and the exact single-line format for the ==1 case with require.Equal.\n\nW4 LOW (internal/httpapi/httpapi.go:116-119): wire http.Server.ErrorLog = slog.NewLogLogger(\u003chandler\u003e, slog.LevelError) so per-connection panics/header noise join the structured stream instead of log.Default() raw text. Test optional (constructor wiring assertion is enough).\n\nW5 INFO (cmd/agenthubd/main.go:184): the explicit stop() next to defer stop() is load-bearing — it unregisters signal handling so a second SIGTERM during the drain hard-kills (desired escape hatch). Add the pinning comment so a cleanup doesn't remove it.\n\nW6 INFO (internal/httpapi run-exit): trailing garbage after the JSON object is accepted (Decode reads one value). Add a dec.More() strictness check -\u003e 400 on trailing content; adjust/add test.\n\nW7 INFO (cmd/agenthubd/main.go:117): SIGTERM during the preflight window logs the scary \"startup preflight failed ... check vikunja.url\" message and exits 1. Branch on errors.Is(err, context.Canceled) -\u003e quiet \"shutdown requested during startup\" log, still exit 1 (or 0 — pick 1 for \"did not reach ready\", document in the message). Test if cheap via the run() seam.\n\nConstraints: scope = cmd/agenthubd/, cmd/ahub/, internal/httpapi/. A sibling agent is writing e2e/ concurrently — do NOT touch e2e/, internal/\u003canything else\u003e, docs/, .beads/, go.mod. Conventions: testify, culpa, slog. Build/vet/test -race ONLY your three packages; never ./... . Commits: 'cmd: reject positional args, add agenthubd run() tests' + 'httpapi: strict run-exit decode, slog ErrorLog' (split as sensible); hooks bypassed (git -c core.hooksPath=/tmp/nohooks commit); retry on index.lock; no push.\n","status":"open","priority":0,"issue_type":"bug","owner":"bigbes@gmail.com","created_at":"2026-07-13T03:34:21Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T03:34:21Z"}{"status":"in_progress"}NULL2026-07-13T06:34:21Z
019f598f-8a09-7704-979f-9c6324aad248ah-nyl.7closedEugene Blikhlanded 892cbda: hermetic full-loop harness (fake vikunja w/ per-response pagination + preflight support, argv-faithful zellij/pi stubs, real binaries); happy/failure/timeout paths green 4x no flakes, just e2e 3.3s; zero integration bugs foundNULL2026-07-13T06:40:07Z
019f5996-076d-7a93-a3ed-3705e586a7c8ah-nyl.16closedEugene Blikhlanded c07e697+97133f9: NArg guards x3 w/ tests, agenthubd run() suite (9 tests), exact validate-config formats, slog ErrorLog, strict decode, stop() comment, calm preflight shutdown (found NotifyContext cause never unwraps to Canceled on go1.26)NULL2026-07-13T06:47:12Z
019f59a4-40d1-7d9c-bd68-c657f7a56e41ah-nylclosedEugene BlikhStage 1 complete: all 15 child beads closed; e2e acceptance green (happy/failure/timeout); 3 review rounds + 2 empirical validations converged to SHIPNULL2026-07-13T07:02:44Z
019f59e6-461e-7860-834a-a6fc38027e10ah-xuc.1createdEugene BlikhNULL2026-07-13T08:14:51Z
019f59e6-8639-7603-80dc-7aee95e670a6ah-xuc.2createdEugene BlikhNULL2026-07-13T08:15:07Z
019f59e6-902a-7566-9107-548982eff4b3ah-xuc.3createdEugene BlikhNULL2026-07-13T08:15:10Z
019f59e6-bc60-7c63-827d-9105a660bc1eah-xuc.4createdEugene BlikhNULL2026-07-13T08:15:21Z
019f59e6-cf39-7dde-9525-56771a93f590ah-xuc.5createdEugene BlikhNULL2026-07-13T08:15:26Z
019f59e6-fe49-70fa-9e6e-f96846f174fbah-xuc.6createdEugene BlikhNULL2026-07-13T08:15:38Z
019f59e7-11cc-7d71-a6ba-40ba924b8b16ah-xuc.7createdEugene BlikhNULL2026-07-13T08:15:43Z
019f59e7-50cc-78f1-b52d-8e918c02e62aah-xuc.8createdEugene BlikhNULL2026-07-13T08:15:59Z
019f59e7-6c40-7cb2-92fa-a802d0ab18d0ah-xuc.9createdEugene BlikhNULL2026-07-13T08:16:06Z
019f59e7-9843-7a48-9f86-42ee8b5ebbedah-xuc.10createdEugene BlikhNULL2026-07-13T08:16:17Z
019f59e7-b0fa-7b42-87c0-1df483fca5c1ah-xuc.11createdEugene BlikhNULL2026-07-13T08:16:24Z
019f59e7-dcf7-7524-8d16-fc30d1cf5915ah-xuc.12createdEugene BlikhNULL2026-07-13T08:16:35Z
019f59e8-8043-7459-b96e-7d73392d0d2fah-xucupdatedEugene Blikh{"id":"ah-xuc","title":"Stage 2: webhooks, Task Spec, full buckets, Telegram, srht push, watchdog","description":"Per SPEC SS14.2: Vikunja webhook receiver (HMAC) as reconciler poke; Task Spec YAML frontmatter (role/model/skills/limits) + label type defaults + validation -\u003e Triage bounce; buckets Triage/Blocked/Question; Telegram notifier (honor HTTPS_PROXY; TG blocked from lab network); push agent/task-N branch to git.srht.bigb.es for review + link in In Review comment; watchdog on stale events.jsonl; pi --skill materialization from /srv/skills.","status":"open","priority":3,"issue_type":"epic","owner":"bigbes@gmail.com","created_at":"2026-07-12T23:36:28Z","created_by":"Eugene Blikh","updated_at":"2026-07-12T23:36:28Z"}{"design":"Stage 2 design decisions (coordinator, 2026-07-13):\n1. Webhook: POST /webhooks/vikunja on the SAME loopback mux; exposure to Vikunja is infra (ah-k23, tunnel/reverse-proxy). Hex HMAC-SHA256 of the raw body in X-Vikunja-Signature, constant-time compare; secret vikunja.webhook_secret; unset secret = route not registered. Any verified event = Poke(); payload untrusted beyond event_name logging. Poll loop remains the source of truth (Vikunja delivers webhooks once, no retries).\n2. Buckets: triage/blocked/question are PARKED buckets — mapped and title-validated, never claim sources, never kill triggers, detached runtime supervision only. Triage is the bounce target for spec validation failures (not Failed — the human fixes and re-drags). Blocked/Question earn real semantics in Stage 4. domain.CanonicalBuckets() = States() + the three; buckets are a superset of states; config requires all nine keys.\n3. Task Spec: YAML frontmatter (--- delimited) at the head of the description. Fields: role, model, skills, timeout. Merge precedence: config defaults \u003c type:\u003cname\u003e label preset (config task_types) \u003c frontmatter. Multiple type:* labels or an unknown type = validation error. Unknown frontmatter fields = warnings appended to the claim comment, never a bounce. Prompt renders over the frontmatter-stripped body.\n4. Skills: names are safe slugs; resolved to \u003cskills_dir\u003e/\u003cname\u003e; SKILL.md must exist with a non-empty frontmatter description (pi refuses skills without one). pi argv keeps --no-skills --no-extensions and adds explicit --skill \u003cabs\u003e per skill — deterministic set.\n5. Per-run timeout: effective value (spec/type override or config default) persisted on the run row (migration v2, runs.timeout_seconds, 0 = config default) so restarts enforce the right deadline.\n6. Watchdog: stalled = Running and now - max(events.jsonl mtime, StartedAt) \u003e stall_timeout (default 10m, 0 disables, else \u003e= 1m). Kill + finalize OutcomeStalled/exit -1. Neither-state observation stays deadline-only.\n7. Telegram: optional config block {token, chat_id, api_base}; default-transport proxy semantics honor HTTPS_PROXY; notify is best-effort after move+comment on finalize (success/failed/timeout/stalled) and spec bounce; token redacted from errors/logs. Unconfigured = existing slog no-op.\n8. Publish: Runner.Publish(ctx, taskID) -\u003e PublishInfo{RemoteURL, Branch, WebURL}; plain git push of agent/task-N to per-repo review_remote (never force); zero info + nil err = not configured; failure degrades to a push-failed line in the In Review comment; re-push on refinalize is idempotent. review_url template with {branch} builds the human link.\n9. vikunja.web_url (default: url minus /api/v1) builds human task links for comments and notifications.\n10. Scope cuts: NO worker pool / max_parallel in Stage 2 (research mentions it; epic and SPEC roadmap do not). No repo selection via spec (multi-repo is Stage 5). No new external deps.\nInfra prerequisites tracked in ah-k23: three new board columns (Triage/Blocked/Question), Vikunja webhook target+secret, srht repos + SSH key for the daemon user, TG bot token + proxy path."}NULL2026-07-13T08:17:17Z
019f59ea-3da2-7111-a3a4-0c69038932b3ah-xuc.1updatedEugene Blikh{"id":"ah-xuc.1","title":"Stage 2 foundation: domain buckets/outcome, ports contracts, config surface","description":"Every Stage 2 feature extends the shared contracts; land them first so later waves stay disjoint (SPEC section 13 isolation rule). No new external deps (crypto/hmac is stdlib, yaml.v3 already present) — go.mod stays untouched for the whole epic.\n\ndomain: OutcomeStalled (\"stalled\"); bucket-name constants for triage/blocked/question plus CanonicalBuckets() (States() plus the three — canonical buckets are a superset of states from now on); Run.Timeout time.Duration (0 = use config default).\n\nports: BoardTask.Labels []string (label titles); StartSpec.Skills []string (absolute skill dirs); Runner gains Publish(ctx, taskID) (PublishInfo, error) with PublishInfo{RemoteURL, Branch, WebURL} — zero-value info with nil error means publish not configured (skipped); error only on a real push failure. Document contracts in comments in the existing style.\n\nconfig: vikunja.webhook_secret (optional); vikunja.web_url (optional, default = url with trailing /api/v1 stripped) for human task links; buckets now require all nine canonical keys (validate over domain.CanonicalBuckets()); skills_dir (optional path, tilde-expanded); task_types map[label-name]{role, model, skills, timeout — all optional} with validation (role exists, skill names are safe slugs — reuse validSlug, timeout parses positive, any skills referenced require skills_dir set); telegram optional block {token, chat_id, api_base default https://api.telegram.org} — token and chat_id required when the block is present; stall_timeout duration (default 10m, explicit \"0\" disables, otherwise at least 1m); repos[*].review_remote (optional git URL) and repos[*].review_url (optional template, must contain {branch} when set). Update config.example.yaml with commented examples of every new key.","acceptance_criteria":"go test ./internal/domain/... ./internal/config/... green; go vet clean on touched packages; table tests for nine-bucket validation, task_types, telegram block, stall_timeout, review_url template; config.example.yaml stays parseable; commits follow the area convention; only own packages staged","status":"open","priority":2,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-13T05:14:51Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T05:14:51Z"}{"notes":"Coordinator refinements: (a) do NOT add a method to the ports.Runner interface — define a SEPARATE port Publisher { Publish(ctx, taskID) (PublishInfo, error) } plus the PublishInfo struct; the runner implements it in ah-xuc.5; this keeps every package (cmd, reconcile fakes) compiling between waves. (b) The nine-bucket requirement invalidates config fixtures outside internal/config: update cmd/agenthubd test fixtures AND the e2e harness fixtures (harness config + fake vikunja board columns Triage/Blocked/Question) in THIS bead — wave 1 runs solo so touching them is safe; goal: go test ./... and go test -tags e2e ./e2e/... stay green at every wave boundary. (c) vikunja.web_url default: vikunja.url with a trailing /api/v1 stripped. (d) stall_timeout: absent = 10m default; explicit \"0\" = disabled; the current parseDuration rejects nonpositive values, so handle the explicit zero separately. (e) review_url set without review_remote is a config error; review_remote alone is fine."}NULL2026-07-13T08:19:11Z
019f59ea-3fb2-7910-897b-b256d315b2a3ah-xuc.5updatedEugene Blikh{"id":"ah-xuc.5","title":"runner: explicit --skill arguments and Publish to the review remote","description":"Two runner extensions, both behind existing seams. All argv construction stays in commands.go per SPEC section 9.\n\nSkills: piArgv gains the resolved skill dirs — one --skill \u003cabsolute-path\u003e per entry, appended while keeping --no-skills and --no-extensions (pi 0.70.2 loads explicit --skill paths even under --no-skills, giving a deterministic per-run set). Start threads StartSpec.Skills through.\n\nPublish(ctx, taskID): resolve the task worktree, branch, and repo the same way Summary does today. When the repo has no review_remote, return a zero PublishInfo and nil error (publish not configured). Otherwise git -C \u003cworktree\u003e push \u003creview_remote\u003e \u003cbranch\u003e with the branch pushed to the same name — plain push, never force (SPEC: agent branches are never force-pushed). On success fill PublishInfo{RemoteURL, Branch, WebURL} where WebURL is the repo review_url with {branch} substituted (empty when review_url unset). Push failures return a wrapped error carrying a stderr prefix.","acceptance_criteria":"go test ./internal/runner/... green, go vet clean; argv table tests for skills present/absent and push; PATH-shim git stub verifies push argv, success, and failure propagation; a Start test asserts --skill flags reach the pi argv","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-13T05:15:26Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T05:15:26Z"}{"notes":"Coordinator refinement: implement ports.Publisher (separate port defined in ah-xuc.1) as a method on the existing runner type — the ports.Runner interface itself does not change."}NULL2026-07-13T08:19:11Z
019f59ea-4114-7f0f-9c0f-7c69d020c13aah-xuc.9updatedEugene Blikh{"id":"ah-xuc.9","title":"reconcile: publish review branch, watchdog on stale events, Telegram notifications","description":"Finalize-path integration of Stage 2. Starts only after the claim-path bead is committed (same package).\n\nPublish: in finalize, on the success path with moveCard set, call runner.Publish before posting the In Review comment. Zero PublishInfo = not configured, plain comment. Success adds a review link line (WebURL, falling back to RemoteURL plus branch) and appends event published. Failure degrades: the comment still posts with a push-failed line carrying the error, event publish_failed (deduped). A refinalize repeats the push — pushing an up-to-date branch is idempotent and fine.\n\nWatchdog: in the check() running arm, when cfg.StallTimeout \u003e 0 and now minus max(st.LastEvent, run.StartedAt) exceeds StallTimeout, the run is stalled: kill and finalize with OutcomeStalled, exit -1 (share the timeoutKill shape). The neither-state arm stays deadline-only — no events are observable there. failureComment renders the stalled outcome distinctly (agent went silent, not merely slow).\n\nNotifications: Notify best-effort — log on error, never gate the flow, guard the nil notifier — after a successful move plus comment: finalize with moveCard (success and failure including timeout and stalled) and the spec bounce. Text: outcome emoji, task id and title, outcome word, cost when known, and the human task link built from vikunja web_url.\n\nTests with fakes: the In Review comment contains the review link; publish failure still posts the comment; the stall kill fires just past the boundary and not before; a run with events flowing but past run.Timeout still dies by deadline; notify recorded after move and comment in that order; nil notifier safe; a notify failure does not fail finalize.","acceptance_criteria":"go test ./internal/reconcile/... green, go vet clean; ordering asserted move then comment then notify; stalled and timeout outcomes distinguishable in comments, events, and notifications","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-13T05:16:07Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T05:16:07Z"}{"notes":"Coordinator refinement: publishing arrives via a new optional reconcile Deps field of type ports.Publisher (nil-guarded like Notifier); finalize publishes only when the field is non-nil. cmd wires the runner into it in ah-xuc.10."}NULL2026-07-13T08:19:12Z
019f59ea-4279-72e2-a7a4-20b39d8f862bah-xuc.10updatedEugene Blikh{"id":"ah-xuc.10","title":"cmd/agenthubd: wire webhook secret and Telegram notifier","description":"Wiring only. agenthubd run(): construct internal/telegram when the config block is present, otherwise keep the existing slog no-op notifier; pass the notifier into reconcile.Deps; pass the webhook secret and a hook invoking Reconciler.Poke into httpapi.Deps. internal/deps updated if that is the wiring point. Startup board preflight: with nine required buckets the existing adapter title lookup already fails fatal with the found-titles list when the Triage/Blocked/Question columns are missing on the board — verify that failure stays readable at startup. ahub needs no changes (validate-config picks up the new keys through the config package).","acceptance_criteria":"go build ./cmd/... green; go test ./cmd/... green including a run() smoke asserting the webhook route responds when the secret is set and is absent when unset, and that the telegram notifier is selected when configured; go vet clean","status":"open","priority":2,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-13T05:16:18Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T05:16:18Z"}{"notes":"Coordinator refinement: also wire the runner as ports.Publisher into reconcile.Deps."}NULL2026-07-13T08:19:12Z
019f59ea-43f2-7392-af22-238c6c9ce41fah-xuc.1claimedEugene Blikh{"id":"ah-xuc.1","title":"Stage 2 foundation: domain buckets/outcome, ports contracts, config surface","description":"Every Stage 2 feature extends the shared contracts; land them first so later waves stay disjoint (SPEC section 13 isolation rule). No new external deps (crypto/hmac is stdlib, yaml.v3 already present) — go.mod stays untouched for the whole epic.\n\ndomain: OutcomeStalled (\"stalled\"); bucket-name constants for triage/blocked/question plus CanonicalBuckets() (States() plus the three — canonical buckets are a superset of states from now on); Run.Timeout time.Duration (0 = use config default).\n\nports: BoardTask.Labels []string (label titles); StartSpec.Skills []string (absolute skill dirs); Runner gains Publish(ctx, taskID) (PublishInfo, error) with PublishInfo{RemoteURL, Branch, WebURL} — zero-value info with nil error means publish not configured (skipped); error only on a real push failure. Document contracts in comments in the existing style.\n\nconfig: vikunja.webhook_secret (optional); vikunja.web_url (optional, default = url with trailing /api/v1 stripped) for human task links; buckets now require all nine canonical keys (validate over domain.CanonicalBuckets()); skills_dir (optional path, tilde-expanded); task_types map[label-name]{role, model, skills, timeout — all optional} with validation (role exists, skill names are safe slugs — reuse validSlug, timeout parses positive, any skills referenced require skills_dir set); telegram optional block {token, chat_id, api_base default https://api.telegram.org} — token and chat_id required when the block is present; stall_timeout duration (default 10m, explicit \"0\" disables, otherwise at least 1m); repos[*].review_remote (optional git URL) and repos[*].review_url (optional template, must contain {branch} when set). Update config.example.yaml with commented examples of every new key.","acceptance_criteria":"go test ./internal/domain/... ./internal/config/... green; go vet clean on touched packages; table tests for nine-bucket validation, task_types, telegram block, stall_timeout, review_url template; config.example.yaml stays parseable; commits follow the area convention; only own packages staged","notes":"Coordinator refinements: (a) do NOT add a method to the ports.Runner interface — define a SEPARATE port Publisher { Publish(ctx, taskID) (PublishInfo, error) } plus the PublishInfo struct; the runner implements it in ah-xuc.5; this keeps every package (cmd, reconcile fakes) compiling between waves. (b) The nine-bucket requirement invalidates config fixtures outside internal/config: update cmd/agenthubd test fixtures AND the e2e harness fixtures (harness config + fake vikunja board columns Triage/Blocked/Question) in THIS bead — wave 1 runs solo so touching them is safe; goal: go test ./... and go test -tags e2e ./e2e/... stay green at every wave boundary. (c) vikunja.web_url default: vikunja.url with a trailing /api/v1 stripped. (d) stall_timeout: absent = 10m default; explicit \"0\" = disabled; the current parseDuration rejects nonpositive values, so handle the explicit zero separately. (e) review_url set without review_remote is a config error; review_remote alone is fine.","status":"open","priority":2,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-13T05:14:51Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T05:19:11Z"}{"assignee":"Eugene Blikh","status":"in_progress"}NULL2026-07-13T08:19:12Z
019f59ea-4560-77fe-be93-8b7b7663999cah-xuc.1status_changedEugene Blikh{"id":"ah-xuc.1","title":"Stage 2 foundation: domain buckets/outcome, ports contracts, config surface","description":"Every Stage 2 feature extends the shared contracts; land them first so later waves stay disjoint (SPEC section 13 isolation rule). No new external deps (crypto/hmac is stdlib, yaml.v3 already present) — go.mod stays untouched for the whole epic.\n\ndomain: OutcomeStalled (\"stalled\"); bucket-name constants for triage/blocked/question plus CanonicalBuckets() (States() plus the three — canonical buckets are a superset of states from now on); Run.Timeout time.Duration (0 = use config default).\n\nports: BoardTask.Labels []string (label titles); StartSpec.Skills []string (absolute skill dirs); Runner gains Publish(ctx, taskID) (PublishInfo, error) with PublishInfo{RemoteURL, Branch, WebURL} — zero-value info with nil error means publish not configured (skipped); error only on a real push failure. Document contracts in comments in the existing style.\n\nconfig: vikunja.webhook_secret (optional); vikunja.web_url (optional, default = url with trailing /api/v1 stripped) for human task links; buckets now require all nine canonical keys (validate over domain.CanonicalBuckets()); skills_dir (optional path, tilde-expanded); task_types map[label-name]{role, model, skills, timeout — all optional} with validation (role exists, skill names are safe slugs — reuse validSlug, timeout parses positive, any skills referenced require skills_dir set); telegram optional block {token, chat_id, api_base default https://api.telegram.org} — token and chat_id required when the block is present; stall_timeout duration (default 10m, explicit \"0\" disables, otherwise at least 1m); repos[*].review_remote (optional git URL) and repos[*].review_url (optional template, must contain {branch} when set). Update config.example.yaml with commented examples of every new key.","acceptance_criteria":"go test ./internal/domain/... ./internal/config/... green; go vet clean on touched packages; table tests for nine-bucket validation, task_types, telegram block, stall_timeout, review_url template; config.example.yaml stays parseable; commits follow the area convention; only own packages staged","notes":"Coordinator refinements: (a) do NOT add a method to the ports.Runner interface — define a SEPARATE port Publisher { Publish(ctx, taskID) (PublishInfo, error) } plus the PublishInfo struct; the runner implements it in ah-xuc.5; this keeps every package (cmd, reconcile fakes) compiling between waves. (b) The nine-bucket requirement invalidates config fixtures outside internal/config: update cmd/agenthubd test fixtures AND the e2e harness fixtures (harness config + fake vikunja board columns Triage/Blocked/Question) in THIS bead — wave 1 runs solo so touching them is safe; goal: go test ./... and go test -tags e2e ./e2e/... stay green at every wave boundary. (c) vikunja.web_url default: vikunja.url with a trailing /api/v1 stripped. (d) stall_timeout: absent = 10m default; explicit \"0\" = disabled; the current parseDuration rejects nonpositive values, so handle the explicit zero separately. (e) review_url set without review_remote is a config error; review_remote alone is fine.","status":"in_progress","priority":2,"issue_type":"task","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-07-13T05:14:51Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T05:19:13Z","started_at":"2026-07-13T05:19:13Z"}{"status":"in_progress"}NULL2026-07-13T08:19:13Z
019f59fe-4220-7271-929f-b4b825b88931ah-xuc.8updatedEugene Blikh{"id":"ah-xuc.8","title":"reconcile: spec-driven claim, Triage bounce, parked buckets, per-run timeout","description":"Claim-path integration of Stage 2. Same-package constraint: this bead owns the internal/reconcile edits for the claim path; the finalize-path bead must not start until this one is committed.\n\niterate(): canonical buckets triage/blocked/question become parked buckets — superviseRuntime only (detached supervision persists runtime truth; no claim, no kill, no card moves, no comments). They are never claim sources and never terminal targets. Blocked and Question get real semantics in Stage 4; Stage 2 only reserves and parks them.\n\nclaim(): resolve the Task Spec via internal/spec from BoardTask.Labels plus Description. A validation failure bounces: MoveToBucket(triage) first, then one diagnostic comment listing the problems verbatim plus a hint to fix the spec and drag back to Ready, then event spec_rejected — move-then-comment discipline so a persistent move failure cannot spam comments; appendEvent dedup applies. No record is persisted and the card never goes to Failed for spec problems. Warnings (unknown fields) never block: append them to the claim comment.\n\nSuccess path: the spec role name resolves through cfg.Roles; model override applies; the prompt renders over the frontmatter-stripped Body; resolved SkillPaths go into StartSpec.Skills; the effective timeout (spec override or config default) goes into StartSpec.Timeout and is persisted as Run.Timeout.\n\ncheck(): deadline enforcement uses run.Timeout when positive, else cfg.Timeout — in both the running arm and the degraded neither-state arm.\n\nExtend fakes_test.go (fake runner captures Skills and Timeout; board tasks carry Labels) and cover: each bounce class end to end, merge precedence reaching StartSpec, parked buckets neither claim nor kill while detached supervision still finalizes a finished runtime, per-run timeout enforced at the right boundary with the fake clock — both shorter and longer than the config default.","acceptance_criteria":"go test ./internal/reconcile/... green, go vet clean; bounce tests assert move-before-comment ordering and zero Failed transitions; parked-bucket tests assert no board writes; timeout tests cover run.Timeout smaller and larger than the config default","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-13T05:16:00Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T05:16:00Z"}{"notes":"\nAdditional: pre-existing gofmt drift in internal/reconcile/reconcile_test.go (comment alignment around lines 983-984 and 1345, present since before Stage 2) — run gofmt -w on the files you touch and fold the fix into this bead's commit."}NULL2026-07-13T08:41:03Z
019f5a01-510c-7023-85d5-c13bbe0b3b86ah-xuc.1closedEugene Blikh4 commits landed (3c3e558..7b158c8); hostile review merge-ready with zero findings; empirical validation 8/9 (only pre-existing gofmt debt, pinned to ah-xuc.8)NULL2026-07-13T08:44:23Z
019f5a01-533d-7850-a7e2-b8893f559e5dah-xuc.2claimedEugene Blikh{"id":"ah-xuc.2","title":"internal/spec: Task Spec frontmatter parse, type-label defaults, validation","description":"New pure package internal/spec (imports domain, config, yaml only — no board/store/runner I/O). Custom fields do not exist in Vikunja, so the machine-readable part of a task lives as YAML frontmatter at the head of the description; labels give per-type defaults.\n\nParse: frontmatter delimited by --- lines at the very start of the description (tolerate CRLF; no frontmatter = empty spec). Known fields: role, model, skills (list of names), timeout (duration string). Unknown fields are collected as warnings, never errors. Returns spec + body (description with frontmatter stripped) + warnings.\n\nResolve(cfg, labels, description): merge precedence config defaults \u003c type:\u003cname\u003e label preset (cfg.TaskTypes) \u003c frontmatter. More than one type:* label is a validation error; a type:* label naming an unknown task type is a validation error. Validation (bounce-class) errors: broken YAML, unknown role, skill name not a safe slug, skills requested while skills_dir unset, missing \u003cskills_dir\u003e/\u003cname\u003e/SKILL.md, SKILL.md frontmatter with an empty description, timeout unparseable or nonpositive. Output: Resolved{RoleName, Model, SkillPaths (absolute), Timeout, Body, Warnings}. Filesystem checks go through a small injected func so most tests need no real skills tree; add one real-FS test with t.TempDir() fixtures.\n\nValidation errors must be a typed, human-readable list — the reconciler posts them verbatim in the Triage bounce comment — distinct from internal errors.","acceptance_criteria":"go test ./internal/spec/... green, go vet clean; table tests cover merge precedence for every field and each bounce class; frontmatter stripping keeps the body exact after the closing delimiter","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-13T05:15:08Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T05:15:08Z"}{"assignee":"Eugene Blikh","status":"in_progress"}NULL2026-07-13T08:44:24Z
019f5a01-546d-7d69-99b8-9135a5558585ah-xuc.3claimedEugene Blikh{"id":"ah-xuc.3","title":"vikunja: fetch task labels into BoardTask.Labels","description":"Stage 2 type defaults key off Vikunja labels and the adapter currently drops them. wireTask gains the labels array (verify the exact field shape against the live docs.json or the go-vikunja v2.3.0 source, as was done for result-count semantics in commit 7802095). Snapshot copies label titles into ports.BoardTask.Labels ([]string, empty-safe, order as returned). Extend the httptest fixtures with tasks carrying zero, one, and several labels, including one with a type: prefix. No behavior change for existing methods.","acceptance_criteria":"go test ./internal/vikunja/... green, go vet clean; fixtures cover labeled and label-less tasks","status":"open","priority":2,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-13T05:15:10Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T05:15:10Z"}{"assignee":"Eugene Blikh","status":"in_progress"}NULL2026-07-13T08:44:24Z
019f5a01-55bd-7d9c-90b6-8f8b7a6fd6e3ah-xuc.4claimedEugene Blikh{"id":"ah-xuc.4","title":"store: migration v2 — per-run timeout column","description":"The effective timeout can differ per run once Task Spec overrides land, and a daemon restart must keep enforcing the right deadline, so it is persisted on the run row.\n\nSchema user_version 2: runs gains timeout_seconds INTEGER NOT NULL DEFAULT 0 (0 = config default at enforcement time). Fresh databases create straight at v2; existing v1 files get ALTER TABLE on open. Follow the existing hand-rolled migrator (PRAGMA user_version gate, BEGIN IMMEDIATE serialization, first-boot busy retry — keep every one of those properties). CreateRun/UpdateRun/LatestRun round-trip domain.Run.Timeout, stored as integer seconds.","acceptance_criteria":"go test ./internal/store/... green, go vet clean; tests: fresh create lands at user_version 2; a v1 database upgrades in place preserving rows; timeout round-trips; existing first-boot race tests keep passing","status":"open","priority":2,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-13T05:15:22Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T05:15:22Z"}{"assignee":"Eugene Blikh","status":"in_progress"}NULL2026-07-13T08:44:24Z
019f5a01-570b-791f-ac92-3e53ec34587fah-xuc.5claimedEugene Blikh{"id":"ah-xuc.5","title":"runner: explicit --skill arguments and Publish to the review remote","description":"Two runner extensions, both behind existing seams. All argv construction stays in commands.go per SPEC section 9.\n\nSkills: piArgv gains the resolved skill dirs — one --skill \u003cabsolute-path\u003e per entry, appended while keeping --no-skills and --no-extensions (pi 0.70.2 loads explicit --skill paths even under --no-skills, giving a deterministic per-run set). Start threads StartSpec.Skills through.\n\nPublish(ctx, taskID): resolve the task worktree, branch, and repo the same way Summary does today. When the repo has no review_remote, return a zero PublishInfo and nil error (publish not configured). Otherwise git -C \u003cworktree\u003e push \u003creview_remote\u003e \u003cbranch\u003e with the branch pushed to the same name — plain push, never force (SPEC: agent branches are never force-pushed). On success fill PublishInfo{RemoteURL, Branch, WebURL} where WebURL is the repo review_url with {branch} substituted (empty when review_url unset). Push failures return a wrapped error carrying a stderr prefix.","acceptance_criteria":"go test ./internal/runner/... green, go vet clean; argv table tests for skills present/absent and push; PATH-shim git stub verifies push argv, success, and failure propagation; a Start test asserts --skill flags reach the pi argv","notes":"Coordinator refinement: implement ports.Publisher (separate port defined in ah-xuc.1) as a method on the existing runner type — the ports.Runner interface itself does not change.","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-13T05:15:26Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T05:19:12Z"}{"assignee":"Eugene Blikh","status":"in_progress"}NULL2026-07-13T08:44:25Z
019f5a01-583e-788b-b679-f6a08fc60237ah-xuc.6claimedEugene Blikh{"id":"ah-xuc.6","title":"telegram: ports.Notifier implementation (Bot API through HTTPS_PROXY)","description":"New package internal/telegram implementing ports.Notifier. POST {api_base}/bot{token}/sendMessage with JSON {chat_id, text, disable_web_page_preview: true}. http.Client with a 10s timeout whose transport keeps ProxyFromEnvironment semantics so HTTPS_PROXY is honored (Telegram is blocked from the lab network; egress goes through the proxy) — do not build a bare Transport without the Proxy field. Non-2xx responses and ok:false bodies become errors carrying a short body prefix; the bot token must never appear in logs or error strings — redact the URL when wrapping errors. No retries: the reconciler treats Notify as best-effort. api_base comes from config telegram.api_base so tests and e2e can point it at httptest.","acceptance_criteria":"go test ./internal/telegram/... green, go vet clean; httptest covers success, HTTP error, ok:false, and token redaction in returned errors; a test asserts proxy resolution from the environment is active","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-13T05:15:38Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T05:15:38Z"}{"assignee":"Eugene Blikh","status":"in_progress"}NULL2026-07-13T08:44:25Z
019f5a01-5987-77cc-a14e-5139a3efbd20ah-xuc.7claimedEugene Blikh{"id":"ah-xuc.7","title":"httpapi: Vikunja webhook receiver with HMAC verification","description":"POST /webhooks/vikunja as a reconciler poke. Design principle 1: a webhook only triggers an immediate iteration; polling stays the source of truth because Vikunja delivers webhooks once, without retries.\n\nDeps gain WebhookSecret string and a Webhook hook (non-blocking; cmd wires it to Reconciler.Poke). Empty secret = feature disabled: the route is not registered at all. Verification: X-Vikunja-Signature carries hex HMAC-SHA256 over the raw request body; compute over the exact bytes read and compare with hmac.Equal; missing or wrong signature = 401 with a terse body that echoes nothing back; cap the body at 256 KiB. After verification decode {event_name} best-effort for the log line only — the payload is otherwise untrusted and unused. Respond 200 fast, call the hook once per verified delivery. Exposing the loopback listener to Vikunja is infra (ah-k23), not this bead.","acceptance_criteria":"go test ./internal/httpapi/... green, go vet clean; tests: a valid computed signature pokes the hook and returns 200; tampered body, wrong secret, and missing header return 401 without invoking the hook; oversized body rejected; unset secret leaves the route absent and existing routes unaffected","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-13T05:15:43Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T05:15:43Z"}{"assignee":"Eugene Blikh","status":"in_progress"}NULL2026-07-13T08:44:25Z
019f5a24-767f-74e9-bf1f-934005014f4dah-xuc.2closedEugene Blikhd714741 + eb5a4be (review-gap pinning); hostile adversarial review merge-ready (yaml alias bombs bounded, path traversal blocked, error taxonomy sound)NULL2026-07-13T09:22:46Z
019f5a24-79d1-7626-85c2-b11c3a395b00ah-xuc.3closedEugene Blikh1e9fc7e; wire shape pinned against upstream go-vikunja v2.3.0; review cleanNULL2026-07-13T09:22:47Z
019f5a24-7cf2-7c4c-83c2-a2e967f76383ah-xuc.4closedEugene Blikhc04f7d7; step-list migrator preserves all race properties (verified under -race); review cleanNULL2026-07-13T09:22:48Z
019f5a24-7fb2-7c6b-ad13-948bf772aa29ah-xuc.5closedEugene Blikh28f75aa + eae0845; refspec refs/heads/X:refs/heads/X pinned, no-force verified, findWorktree semantics shared with Summary; review cleanNULL2026-07-13T09:22:49Z
019f5a24-81dc-7173-8178-88767a97e031ah-xuc.6closedEugene Blikhe15fe4c; token-leak vectors traced to stdlib source and closed; proxy semantics verified empirically; review cleanNULL2026-07-13T09:22:49Z
019f5a24-8416-7c7b-92f8-00b2136410d2ah-xuc.7closedEugene Blikh61400ae; HMAC discipline verified (full-read-before-verify, uniform 401, constant-time compare); review cleanNULL2026-07-13T09:22:50Z
019f5a25-3e77-79a0-9c9b-7f4c714863d5ah-xuc.8claimedEugene Blikh{"id":"ah-xuc.8","title":"reconcile: spec-driven claim, Triage bounce, parked buckets, per-run timeout","description":"Claim-path integration of Stage 2. Same-package constraint: this bead owns the internal/reconcile edits for the claim path; the finalize-path bead must not start until this one is committed.\n\niterate(): canonical buckets triage/blocked/question become parked buckets — superviseRuntime only (detached supervision persists runtime truth; no claim, no kill, no card moves, no comments). They are never claim sources and never terminal targets. Blocked and Question get real semantics in Stage 4; Stage 2 only reserves and parks them.\n\nclaim(): resolve the Task Spec via internal/spec from BoardTask.Labels plus Description. A validation failure bounces: MoveToBucket(triage) first, then one diagnostic comment listing the problems verbatim plus a hint to fix the spec and drag back to Ready, then event spec_rejected — move-then-comment discipline so a persistent move failure cannot spam comments; appendEvent dedup applies. No record is persisted and the card never goes to Failed for spec problems. Warnings (unknown fields) never block: append them to the claim comment.\n\nSuccess path: the spec role name resolves through cfg.Roles; model override applies; the prompt renders over the frontmatter-stripped Body; resolved SkillPaths go into StartSpec.Skills; the effective timeout (spec override or config default) goes into StartSpec.Timeout and is persisted as Run.Timeout.\n\ncheck(): deadline enforcement uses run.Timeout when positive, else cfg.Timeout — in both the running arm and the degraded neither-state arm.\n\nExtend fakes_test.go (fake runner captures Skills and Timeout; board tasks carry Labels) and cover: each bounce class end to end, merge precedence reaching StartSpec, parked buckets neither claim nor kill while detached supervision still finalizes a finished runtime, per-run timeout enforced at the right boundary with the fake clock — both shorter and longer than the config default.","acceptance_criteria":"go test ./internal/reconcile/... green, go vet clean; bounce tests assert move-before-comment ordering and zero Failed transitions; parked-bucket tests assert no board writes; timeout tests cover run.Timeout smaller and larger than the config default","notes":"\nAdditional: pre-existing gofmt drift in internal/reconcile/reconcile_test.go (comment alignment around lines 983-984 and 1345, present since before Stage 2) — run gofmt -w on the files you touch and fold the fix into this bead's commit.","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-13T05:16:00Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T05:41:03Z"}{"assignee":"Eugene Blikh","status":"in_progress"}NULL2026-07-13T09:23:38Z
019f5a44-0c63-7e3c-805b-1be979864ba7ah-xuc.8closedEugene Blikh9823250 + 06_pin commit; review merge-ready (all SPEC section-10 invariants traced clean); validator 10/10 incl. e2e byte-identical for spec-less tasks; adopt-over-bounce coverage gap closedNULL2026-07-13T09:57:16Z
019f5a44-70df-70e2-8e61-bd0f63c34013ah-xuc.9claimedEugene Blikh{"id":"ah-xuc.9","title":"reconcile: publish review branch, watchdog on stale events, Telegram notifications","description":"Finalize-path integration of Stage 2. Starts only after the claim-path bead is committed (same package).\n\nPublish: in finalize, on the success path with moveCard set, call runner.Publish before posting the In Review comment. Zero PublishInfo = not configured, plain comment. Success adds a review link line (WebURL, falling back to RemoteURL plus branch) and appends event published. Failure degrades: the comment still posts with a push-failed line carrying the error, event publish_failed (deduped). A refinalize repeats the push — pushing an up-to-date branch is idempotent and fine.\n\nWatchdog: in the check() running arm, when cfg.StallTimeout \u003e 0 and now minus max(st.LastEvent, run.StartedAt) exceeds StallTimeout, the run is stalled: kill and finalize with OutcomeStalled, exit -1 (share the timeoutKill shape). The neither-state arm stays deadline-only — no events are observable there. failureComment renders the stalled outcome distinctly (agent went silent, not merely slow).\n\nNotifications: Notify best-effort — log on error, never gate the flow, guard the nil notifier — after a successful move plus comment: finalize with moveCard (success and failure including timeout and stalled) and the spec bounce. Text: outcome emoji, task id and title, outcome word, cost when known, and the human task link built from vikunja web_url.\n\nTests with fakes: the In Review comment contains the review link; publish failure still posts the comment; the stall kill fires just past the boundary and not before; a run with events flowing but past run.Timeout still dies by deadline; notify recorded after move and comment in that order; nil notifier safe; a notify failure does not fail finalize.","acceptance_criteria":"go test ./internal/reconcile/... green, go vet clean; ordering asserted move then comment then notify; stalled and timeout outcomes distinguishable in comments, events, and notifications","notes":"Coordinator refinement: publishing arrives via a new optional reconcile Deps field of type ports.Publisher (nil-guarded like Notifier); finalize publishes only when the field is non-nil. cmd wires the runner into it in ah-xuc.10.","status":"open","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-13T05:16:07Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T05:19:12Z"}{"assignee":"Eugene Blikh","status":"in_progress"}NULL2026-07-13T09:57:42Z
019f5a66-e2bb-700b-b967-5640052f29ffah-xuc.9closedEugene Blikhd5d1efc + 64a7216 (review fix: bounded publish ctx via publish_timeout knob, zero-delta recorder covers publish/notify); reviewer merge-ready after fix, validator 10/10 with all 24 subtests by nameNULL2026-07-13T10:35:19Z
019f5a66-e579-77c8-8ab7-2ad404997ef9ah-xuc.10claimedEugene Blikh{"id":"ah-xuc.10","title":"cmd/agenthubd: wire webhook secret and Telegram notifier","description":"Wiring only. agenthubd run(): construct internal/telegram when the config block is present, otherwise keep the existing slog no-op notifier; pass the notifier into reconcile.Deps; pass the webhook secret and a hook invoking Reconciler.Poke into httpapi.Deps. internal/deps updated if that is the wiring point. Startup board preflight: with nine required buckets the existing adapter title lookup already fails fatal with the found-titles list when the Triage/Blocked/Question columns are missing on the board — verify that failure stays readable at startup. ahub needs no changes (validate-config picks up the new keys through the config package).","acceptance_criteria":"go build ./cmd/... green; go test ./cmd/... green including a run() smoke asserting the webhook route responds when the secret is set and is absent when unset, and that the telegram notifier is selected when configured; go vet clean","notes":"Coordinator refinement: also wire the runner as ports.Publisher into reconcile.Deps.","status":"open","priority":2,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-13T05:16:18Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T05:19:13Z"}{"assignee":"Eugene Blikh","status":"in_progress"}NULL2026-07-13T10:35:20Z
019f5a77-ea50-7f13-a0b5-e3448f3c3739ah-xuc.10closedEugene Blikh95560cc; reviewer merge-ready (single wiring path, nil-safe, pointer-identity tested); validator all-pass incl. first live daemon runs — readable fatal preflight, telegram deferred past preflight with no I/ONULL2026-07-13T10:53:56Z
019f5a77-ec60-723a-8ec1-0e38dce8d631ah-xuc.11claimedEugene Blikh{"id":"ah-xuc.11","title":"e2e: Stage 2 flows — webhook poke, Triage bounce, skills, publish, watchdog, Telegram","description":"Extend the e2e harness (build tag e2e, fake vikunja plus stub pi/zellij, stub-honesty rules from commit b3ee57d). Fake vikunja gains labels on tasks and the nine-bucket board.\n\n- webhook poke: long poll_interval, POST a correctly signed payload to /webhooks/vikunja, assert the claim happens promptly (poke, not poll); a bad signature does nothing.\n- Triage bounce: a Ready task with broken frontmatter lands in Triage with the diagnostic comment and no record or run row.\n- skills: a task whose spec names skills (fixture SKILL.md dirs under a temp skills_dir) — the stub pi records argv; assert the --skill absolute paths and the retained --no-skills.\n- publish: repo review_remote points at a local bare repository; after a successful run the In Review comment carries the review link and the bare repo has the agent/task-N ref at the expected commit.\n- watchdog: stub pi hangs without touching events.jsonl; with a short stall_timeout the run is killed and the card lands in Failed with a stalled comment.\n- telegram: telegram.api_base points at a fake httptest Bot API; assert sendMessage calls for the in_review and stalled paths, and that the token appears only in the request path, never in daemon logs.","acceptance_criteria":"go test -tags e2e ./e2e/... green and hermetic (loopback only); each flow asserted through externally observable surfaces — board moves, comments, bare repo refs, fake TG requests — not daemon internals","status":"open","priority":2,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-13T05:16:24Z","created_by":"Eugene Blikh","updated_at":"2026-07-13T05:16:24Z"}{"assignee":"Eugene Blikh","status":"in_progress"}NULL2026-07-13T10:53:56Z