From bb98edea9615457f28bc21e6ac5a16d07356b0b2 Mon Sep 17 00:00:00 2001 From: eric-wang-1990 Date: Wed, 12 Aug 2026 19:22:23 -0700 Subject: [PATCH 1/5] feat(learning): add daily retrospective flow + bump engine to 654a31d0 Turn on the engineer-bot learning loop for this repo, mirroring databricks-sql-python (the reference). Three parts: - engineer-bot-learning.yml (new): daily schedule (17:23 UTC) + workflow_dispatch (string since / window-hours recovery inputs). Own job sharing ./.github/actions/ bot-prelude for Python + pinned engine install, PAT-free. No setup-jfrog step: install-bot-engine self-mints its pip credential via OIDC and never reads PIP_INDEX_URL from env, so a setup-jfrog step would be redundant (the engine install is the only fetch; the learning flow runs no go build) and would leak a token-bearing PIP_INDEX_URL to the job env. actions:read added for Track B (lists engineer-bot author runs + downloads their logs via the App token). - .bot/config.yaml: add the retrospective block (log_path, branch_prefix, pr_label, plus the repo's existing prompts/retrospective_system.md as additive guidance) and close the loop with author.knowledge_log matching retrospective.log_path (.claude/knowledge/learning-log.md), so the author phase reads back what the retrospective learns. - bot-prelude: bump engine-ref d05dcb11 to 654a31d0 (engine main) so the pinned engine has the daily-cron retrospective + per-bot models. SDK/CLI (0.2.102 / 2.1.61) unchanged at that SHA, so this is a SHA-only bump. The schedule registers only once this lands on the default branch; validate before the first cron via a manual workflow_dispatch with a recent since. Co-authored-by: Isaac Signed-off-by: eric-wang-1990 --- .bot/config.yaml | 20 ++++ .github/actions/bot-prelude/action.yml | 2 +- .github/workflows/engineer-bot-learning.yml | 123 ++++++++++++++++++++ 3 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/engineer-bot-learning.yml diff --git a/.bot/config.yaml b/.bot/config.yaml index b6e2a7b0..a55f57e2 100644 --- a/.bot/config.yaml +++ b/.bot/config.yaml @@ -91,6 +91,11 @@ author: issue_url: ISSUE_URL context_files: - issue_body.txt # {{issue_body}} + # Close the loop: the author phase reads this log back so fixes benefit from + # what the retrospective has learned. MUST match `retrospective.log_path` below + # — the retrospective WRITES that path; the author only READS it if the two + # agree. (Set together, they connect learning → application.) + knowledge_log: .claude/knowledge/learning-log.md # Engine orchestration for the author phase. `bug-fix` runs the plan → # author_tests → fix pipeline (write a failing test → fix the code → re-run to @@ -99,3 +104,18 @@ author: # specifics (commands, layout, live e2e requirement) live in # prompts/engineer/system.md, not here. flow: bug-fix + +# Daily learning extraction (retrospective flow). Run by engineer-bot-learning.yml +# via `python -m databricks_bot_engine.engineer_bot.retrospective`: over an adaptive +# look-back window it sweeps every merged PR (diff + review comments, which the +# engine gathers ITSELF per PR) plus engineer-bot author runs, and if the model +# finds a durable, reusable learning, opens/updates a single rolling PR appending a +# dated section to log_path. Human-gated — never commits the canonical log directly. +# Omitting this block makes the retrospective a no-op. The system prompt is the +# engine's built-in base; prompts/retrospective_system.md (present in this repo) is +# appended as additive guidance. +retrospective: + system_prompt: prompts/retrospective_system.md + log_path: .claude/knowledge/learning-log.md # MUST match author.knowledge_log above + branch_prefix: ai/learning-pr- + pr_label: engineer-bot-learning diff --git a/.github/actions/bot-prelude/action.yml b/.github/actions/bot-prelude/action.yml index abe2941a..7cfd32ee 100644 --- a/.github/actions/bot-prelude/action.yml +++ b/.github/actions/bot-prelude/action.yml @@ -30,7 +30,7 @@ inputs: # value to move every bot to a new engine commit; never @main. description: 'Engine commit SHA (full 40-char) to install.' required: false - default: 'd05dcb113332401b4aee8d6aa05c7107399ad44f' + default: '654a31d0d26d2b77bcb58e84f1e4abe018f9fd5a' engine-repo: description: 'owner/name of the engine repo.' required: false diff --git a/.github/workflows/engineer-bot-learning.yml b/.github/workflows/engineer-bot-learning.yml new file mode 100644 index 00000000..1ad80117 --- /dev/null +++ b/.github/workflows/engineer-bot-learning.yml @@ -0,0 +1,123 @@ +# Engineer Bot — learning (retrospective) extraction — DAILY CRON. +# +# Over an adaptive look-back window the engine gathers merged PRs (diff + review +# comments) AND recent engineer-bot author-run console logs ITSELF via the GitHub +# API — no in-workflow context gathering, no per-PR trigger — and if the model +# finds durable, reusable learnings, opens ONE ROLLING PR on a stable branch +# (`ai/learning-pr`), appending a dated section per day until a human merges it. +# Human-gated by design: it NEVER commits the canonical log directly. +# +# Own job (NOT `uses: databricks/databricks-bot-engine/...`): an external repo +# can't resolve the internal engine's reusable workflows ("not found"). It shares +# the SAME prelude the other bots use — ./.github/actions/bot-prelude (tokens + +# Node + pinned engine install) — so the engine pin stays single-sourced in +# bot-prelude's `engine-ref` default (no second SHA to drift). +# +# Opt-in is purely via the `retrospective:` block in .bot/config.yaml + this +# workflow; absent that block the engine phase is a clean no-op. +name: Engineer Bot — Learning + +on: + schedule: + # 17:23 UTC daily — off-peak, off-:00 minute (GitHub delays/drops on-the-hour crons). + - cron: "23 17 * * *" + workflow_dispatch: + inputs: + since: + description: 'ISO lower bound to shorten the window and recover a wedged flow. Empty = adaptive cursor.' + type: string + default: '' + window-hours: + # STRING, not number: a `type: number` workflow_dispatch input fails the + # whole run at startup ("workflow file issue") when combined with the + # `schedule` trigger. argparse coerces it to int downstream. + description: 'Fallback look-back window (hours) used only when there is no prior successful run.' + type: string + default: '24' + +permissions: + contents: write # push the learning branch / open the learning PR + pull-requests: write + actions: read # Track B lists engineer-bot author runs + logs via the App token; + # the engineer-bot App installation must ALSO carry actions:read + # (a missing scope surfaces as a 403 that fails the whole run — + # list_author_runs raises, no escape hatch). + id-token: write # JFrog OIDC exchange for the engine/SDK/CLI install + +concurrency: + # One learning run at a time; a queued run waits rather than racing the rolling + # PR's branch. Not keyed on a PR number (this is a cron, no PR event). + group: engineer-bot-learning-cron + cancel-in-progress: false + +jobs: + learning: + environment: azure-prod # DATABRICKS_HOST / DATABRICKS_TOKEN live here + runs-on: + group: databricks-protected-runner-group + labels: linux-ubuntu-latest + timeout-minutes: 20 + steps: + # Checkout the default branch (the learning PR is cut from it) FIRST, so the + # local `./` composites below resolve. persist-credentials:false — the + # retrospective sets its own authenticated push remote (see the run step), + # so no token is left in .git/config. + - name: Checkout default branch (learning PR is cut from it) + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + persist-credentials: false + + # No separate `setup-jfrog` step. install-bot-engine (via bot-prelude) does + # its own keyless OIDC→JFrog mint and passes the credential through job-local + # files + per-command --index-url flags; it never reads PIP_INDEX_URL / + # JFROG_ACCESS_TOKEN from the environment. A preceding setup-jfrog would be + # redundant (the engine install is the only fetch — the learning flow runs no + # `go build`) AND harmful: setup-jfrog exports a token-bearing PIP_INDEX_URL + # to $GITHUB_ENV, exposing it to every later step including the model run. + # Mirrors the read-only sibling reviewer-bot.yml. + - name: Setup Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: '3.11' + + # Shared prelude: mint the engineer-bot token (opens the learning PR) + the + # engine-scoped token, set up Node, install the pinned engine (PAT-free). The + # engine pin comes from bot-prelude's `engine-ref` default — the SINGLE source + # of truth for every bot; there is no second SHA in this file to drift. + - name: Bot prelude (tokens + Node + engine install) + id: prelude + uses: ./.github/actions/bot-prelude + with: + app-id: ${{ secrets.ENGINEER_BOT_APP_ID }} + private-key: ${{ secrets.ENGINEER_BOT_APP_PRIVATE_KEY }} + + # NOTE: no git-identity step — the engine's retrospective configures the git + # user AND DCO sign-off itself from .bot/config.yaml `bot_login_prefix`. + # NOTE: no context-gather step — the daily-cron engine enumerates merged PRs + # + author runs itself over the adaptive window. + - name: Extract learnings + open rolling PR + env: + GH_TOKEN: ${{ steps.prelude.outputs.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + # Only the `/serving-endpoints/` prefix matters: + # sdk_agent.translate_endpoint rewrites this to `.../serving-endpoints/anthropic` + # and discards the model path segment. The effective model comes from + # .bot/config.yaml `retrospective.model` (or the engine default). + MODEL_ENDPOINT: https://${{ secrets.DATABRICKS_HOST }}/serving-endpoints/anthropic/invocations + DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_TOKEN }} + RUNNER_TEMP: ${{ runner.temp }} + SINCE: ${{ inputs.since }} + WINDOW_HOURS: ${{ inputs.window-hours }} + # The retrospective pushes the learning branch with a plain `git push + # origin`, and the checkout ran persist-credentials:false — so set an + # authenticated push remote from the minted App token first, mirroring + # engineer-bot.yml's publish step. --since / --window-hours are passed only + # when provided via workflow_dispatch (the schedule trigger leaves them + # empty → the adaptive cursor drives the window). + run: | + git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + args=(--repo-dir "$GITHUB_WORKSPACE") + [ -n "$SINCE" ] && args+=(--since "$SINCE") + [ -n "$WINDOW_HOURS" ] && args+=(--window-hours "$WINDOW_HOURS") + python -m databricks_bot_engine.engineer_bot.retrospective "${args[@]}" From 3358ac0112b3194bc510f093faaeadea724f1335 Mon Sep 17 00:00:00 2001 From: eric-wang-1990 Date: Wed, 12 Aug 2026 20:15:37 -0700 Subject: [PATCH 2/5] fix(learning): drop retrospective.system_prompt (no prompt file in this repo) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The retrospective block pointed system_prompt at prompts/retrospective_system.md, which does not exist in this repo (only databricks-sql-kernel ships one; the sql-python reference correctly omits the key). The engine treats a set-but-missing system_prompt as a HARD ERROR, so the daily cron would fail every run. Drop the key so the flow uses the engine's built-in base prompt (engineer_prompts.RETRO_SYSTEM_PROMPT) — matching databricks-sql-python and the odbc learning PR. Also corrects the config comment, which wrongly claimed the file was "present in this repo." Caught by peco-review-bot on the sibling PRs. Co-authored-by: Isaac Signed-off-by: eric-wang-1990 --- .bot/config.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.bot/config.yaml b/.bot/config.yaml index a55f57e2..6a7f2134 100644 --- a/.bot/config.yaml +++ b/.bot/config.yaml @@ -111,11 +111,14 @@ flow: bug-fix # engine gathers ITSELF per PR) plus engineer-bot author runs, and if the model # finds a durable, reusable learning, opens/updates a single rolling PR appending a # dated section to log_path. Human-gated — never commits the canonical log directly. -# Omitting this block makes the retrospective a no-op. The system prompt is the -# engine's built-in base; prompts/retrospective_system.md (present in this repo) is -# appended as additive guidance. +# Omitting this block makes the retrospective a no-op. +# +# `system_prompt` is intentionally OMITTED: this repo ships no +# prompts/retrospective_system.md, so the engine's built-in base prompt is used. +# (A set-but-missing system_prompt file would be a hard error; an UNSET key uses +# the base. Add the key later only if you write a repo-specific additive prompt.) +# The learning log is created on first write — no seed file needed. retrospective: - system_prompt: prompts/retrospective_system.md log_path: .claude/knowledge/learning-log.md # MUST match author.knowledge_log above branch_prefix: ai/learning-pr- pr_label: engineer-bot-learning From 093f38b160ec478385e89a210ea7086072a3481b Mon Sep 17 00:00:00 2001 From: eric-wang-1990 Date: Wed, 12 Aug 2026 20:36:19 -0700 Subject: [PATCH 3/5] fix(build): use $(CURDIR) not $(pwd) for golangci-lint GOBIN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `bin/golangci-lint` set `GOBIN=$(pwd)/bin`, but $(pwd) is a Make expression, not shell — Make expands the undefined `pwd` variable to EMPTY, so GOBIN was blank and `go install` fell back to the default bin dir (GOPATH/bin, which resolves to /bin on the protected runner) → `open /bin/golangci-lint: permission denied`, failing `make tools`. This only surfaced now because `make tools` runs on the protected runner solely via the engineer-bot build prelude, and engineer-bot was triggered on this repo for the first time (the `engineer-bot` label was just applied). The bug itself predates this PR (Makefile line unchanged since 2026-07-10). Use $(CURDIR) — Make's built-in absolute cwd — so golangci-lint installs into ./bin as intended. Verified via `make -n`: GOBIN now expands to the repo path. Co-authored-by: Isaac Signed-off-by: eric-wang-1990 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6744c9f9..39985464 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ help: ## Show this help. all: gen fmt lint test coverage ## format and test everything bin/golangci-lint: go.mod go.sum - GOBIN=$(pwd)/bin go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 + GOBIN=$(CURDIR)/bin go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 bin/gotestsum: go.mod go.sum @mkdir -p bin/ From 1f189b690b31a46fa2928d2feaa2eba9ff9d4aaf Mon Sep 17 00:00:00 2001 From: "peco-engineer-bot[bot]" Date: Thu, 13 Aug 2026 03:48:52 +0000 Subject: [PATCH 4/5] ai: apply changes for #442 (2 review threads) Addresses: - #3771947885 at .bot/config.yaml:99 - #3771952784 at .bot/config.yaml:122 Signed-off-by: peco-engineer-bot[bot] --- .claude/knowledge/learning-log.md | 12 ++++++++++++ .gitignore | 8 +++++++- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 .claude/knowledge/learning-log.md diff --git a/.claude/knowledge/learning-log.md b/.claude/knowledge/learning-log.md new file mode 100644 index 00000000..988c85f4 --- /dev/null +++ b/.claude/knowledge/learning-log.md @@ -0,0 +1,12 @@ +# Engineer-bot learning log + +This file is the canonical, human-gated knowledge log shared by two consumers +configured in `.bot/config.yaml`: + +- `retrospective.log_path` — the retrospective flow APPENDS a dated section here + when it extracts a durable, reusable learning (via a human-gated rolling PR). +- `author.knowledge_log` — the author phase READS this file so fixes benefit from + what has been learned. Seeded here so the read path is never a missing file. + +No learnings have been recorded yet. Dated sections are appended below by the +retrospective flow. diff --git a/.gitignore b/.gitignore index 970720b6..9061e4ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,12 @@ .vscode .idea -.claude +# Ignore everything under .claude EXCEPT the engineer-bot learning log, which the +# retrospective flow must commit into its rolling PR and the author phase reads +# back (see .bot/config.yaml: author.knowledge_log / retrospective.log_path). +.claude/* +!.claude/knowledge/ +.claude/knowledge/* +!.claude/knowledge/learning-log.md # Binaries for programs and plugins *.exe From 2fb3bb093dc43acef478f70c1f5767bbf31c0270 Mon Sep 17 00:00:00 2001 From: eric-wang-1990 Date: Wed, 12 Aug 2026 22:38:41 -0700 Subject: [PATCH 5/5] fix(learning): correct MODEL_ENDPOINT to the concrete serving-endpoint form MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The learning workflow set MODEL_ENDPOINT to `.../serving-endpoints/anthropic/invocations`. That is NOT translated the way the old comment claimed: sdk_agent.translate_endpoint has an early-return for URLs already containing `/serving-endpoints/anthropic`, which keeps the trailing `/invocations` — so the CLI appends `/v1/messages` and calls `.../serving-endpoints/anthropic/invocations/v1/messages`, which the gateway rejects with `400 Unsupported native API path`. Proof: databricks-sql-python's learning cron (same endpoint form) has failed every scheduled run with exactly this 400. The reviewer-bot flagged it on the sibling PRs (Low), and it is the real cause. Use the concrete `.../serving-endpoints/databricks-claude-opus-4-8/invocations` form that reviewer-bot.yml / engineer-bot.yml already use successfully: translate_endpoint strips `/invocations` to the `.../serving-endpoints/ anthropic` base the CLI needs. Comment corrected to explain the trap. Co-authored-by: Isaac Signed-off-by: eric-wang-1990 --- .github/workflows/engineer-bot-learning.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/engineer-bot-learning.yml b/.github/workflows/engineer-bot-learning.yml index 1ad80117..11c809ee 100644 --- a/.github/workflows/engineer-bot-learning.yml +++ b/.github/workflows/engineer-bot-learning.yml @@ -100,11 +100,15 @@ jobs: env: GH_TOKEN: ${{ steps.prelude.outputs.token }} GITHUB_REPOSITORY: ${{ github.repository }} - # Only the `/serving-endpoints/` prefix matters: - # sdk_agent.translate_endpoint rewrites this to `.../serving-endpoints/anthropic` - # and discards the model path segment. The effective model comes from - # .bot/config.yaml `retrospective.model` (or the engine default). - MODEL_ENDPOINT: https://${{ secrets.DATABRICKS_HOST }}/serving-endpoints/anthropic/invocations + # Use the concrete `.../serving-endpoints//invocations` form (same + # as reviewer-bot.yml / engineer-bot.yml). sdk_agent.translate_endpoint + # strips it to the `.../serving-endpoints/anthropic` base the CLI needs. + # Do NOT use `.../serving-endpoints/anthropic/invocations` here: that hits + # translate_endpoint's already-v2 early-return, which keeps the trailing + # `invocations`, so the CLI appends `/v1/messages` → + # `.../anthropic/invocations/v1/messages` → HTTP 400 (unsupported path). + # The effective model is set by the engine default (no retrospective.model). + MODEL_ENDPOINT: https://${{ secrets.DATABRICKS_HOST }}/serving-endpoints/databricks-claude-opus-4-8/invocations DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_TOKEN }} RUNNER_TEMP: ${{ runner.temp }} SINCE: ${{ inputs.since }}