From 5293ca54310e626aaff4fb86d4a87da382b77e60 Mon Sep 17 00:00:00 2001 From: seemeroland Date: Tue, 23 Jun 2026 21:13:40 +0000 Subject: [PATCH] Add GitHub PR comment trigger workflow for self-hosted k8s workers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds consumer-workflows/gh-pr-comment.yml — a GitHub Actions workflow template that enables the @oz-agent comment trigger pattern for teams running self-hosted Kubernetes workers. Unlike oz-agent-action's respond-to-comment.yml (which runs oz on GitHub-hosted runners using Warp cloud agents), this workflow calls the Warp REST API directly with config.worker_host targeting the customer's self-hosted worker ID. The Oz agent then runs inside the customer's k8s cluster, checks out the PR branch, makes the requested changes, commits, and replies to the original comment. Key properties: - No oz CLI installation required on the GitHub runner (curl only) - Works on GitHub-hosted runners OR self-hosted runners (any Linux) - Worker routing via config.worker_host in the Warp API request - GitHub credentials injected into task pods via kubernetesBackend.podTemplate Also adds a 'GitHub PR Integration' section to README.md documenting: - How the trigger flow works end-to-end - K8s worker GitHub credential setup (GITHUB_TOKEN or SSH key) - Comparison table vs. oz-agent-action approach - Customization guidance for prompts and skills Resolves REMOTE-2003. Co-Authored-By: Oz --- README.md | 126 ++++++++++++ consumer-workflows/gh-pr-comment.yml | 276 +++++++++++++++++++++++++++ 2 files changed, 402 insertions(+) create mode 100644 consumer-workflows/gh-pr-comment.yml diff --git a/README.md b/README.md index ab42058..49867f4 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,132 @@ go build -o oz-agent-worker ./oz-agent-worker --api-key "wk-abc123" --worker-id "my-worker" ``` +## GitHub PR Integration + +Self-hosted Kubernetes workers can be triggered from GitHub PR comments using an `@oz-agent` mention pattern, equivalent to the `respond-to-comment.yml` workflow in [`oz-agent-action`](https://github.com/warpdotdev/oz-agent-action) but routing work to your own k8s cluster. + +### How it works + +The trigger workflow runs on standard GitHub-hosted runners. It does not install the `oz` CLI; instead it calls the Warp REST API directly with `config.worker_host` set to your worker ID. Warp routes the task to whichever connected worker matches that ID — your k8s deployment. The Oz agent then runs inside your cluster, checks out the PR branch, makes the requested changes, and replies to the comment. + +``` +Developer comments "@oz-agent fix the null check" + └─► GitHub Actions workflow fires (GitHub-hosted runner) + └─► POST https://app.warp.dev/api/v1/agent/runs + { config: { worker_host: "your-k8s-worker-id" } } + └─► Task routed to your oz-agent-worker in k8s + └─► Oz checks out PR branch, implements fix, + commits, pushes, replies to comment +``` + +### Setup + +**1. Prepare your k8s worker for GitHub access** + +The Oz agent running in your cluster needs credentials to clone your repository and push commits. Store them as a Kubernetes Secret and inject them via `pod_template`: + +```yaml +# Create a secret with your GitHub credentials +kubectl create secret generic oz-github-creds \ + --from-literal=GITHUB_TOKEN="ghp_..." \ + --namespace agents +``` + +In your Helm values, inject the secret into task containers: + +```yaml +kubernetesBackend: + podTemplate: + containers: + - name: task + env: + - name: GITHUB_TOKEN + valueFrom: + secretKeyRef: + name: oz-github-creds + key: GITHUB_TOKEN +``` + +For SSH-based cloning, mount your deploy key instead: + +```yaml +kubernetesBackend: + podTemplate: + volumes: + - name: ssh-key + secret: + secretName: oz-github-deploy-key + defaultMode: 0400 + containers: + - name: task + volumeMounts: + - name: ssh-key + mountPath: /root/.ssh + readOnly: true +``` + +**2. Add the GitHub Actions workflow to your repository** + +Copy `consumer-workflows/gh-pr-comment.yml` from this repository into `.github/workflows/` in the repository where you want the `@oz-agent` trigger. + +**3. Configure secrets and variables** + +In your GitHub repository settings, add: + +| Type | Name | Value | +|------|------|-------| +| Secret | `WARP_API_KEY` | Your Warp service account API key (team-scoped) | +| Variable | `OZ_WORKER_ID` | The `worker_id` from your `oz-agent-worker` config (e.g. `my-k8s-worker`) | + +**4. Use it** + +Comment on any PR or inline review comment: + +``` +@oz-agent refactor the error handling in auth.go to use the new ErrorResponse type +``` + +Oz will acknowledge with 👀, run in your k8s cluster, push any commits, and reply with a summary. + +### Comparison: self-hosted vs. cloud runner approach + +| | `gh-pr-comment.yml` (this repo) | `oz-agent-action` `respond-to-comment.yml` | +|---|---|---| +| Oz runs on | Your k8s cluster | Warp-hosted cloud agents | +| GitHub runner | GitHub-hosted (ubuntu-latest) | GitHub-hosted (ubuntu-latest) | +| oz CLI needed on runner | No (curl only) | Yes (installed by the action) | +| Output capture | Async — Oz posts its own comment | Synchronous — workflow posts reply | +| Trigger | `@oz-agent` comment | `@oz-agent` comment | +| Self-hosted runner support | Yes (change `runs-on`) | Requires Linux (apt-installable) | + +### Running on self-hosted GitHub Actions runners + +If you also run self-hosted GitHub Actions runners (e.g., in the same k8s cluster), change `runs-on` in the workflow to your runner label: + +```yaml +jobs: + trigger: + runs-on: [self-hosted, linux, my-org-runners] +``` + +The workflow only needs `curl`, `jq`, and the `gh` CLI available on the runner. No `oz` CLI is needed. + +### Customizing the prompt + +The consumer workflow passes a structured prompt to Oz. Modify the `PROMPT` variable in the workflow to add team-specific instructions, coding standards, or technology-specific context: + +```yaml +PROMPT="... + +## Team conventions +- All Go errors must be wrapped with fmt.Errorf and include context +- Run 'go test ./...' before committing + +${PROMPT}" +``` + +Alternatively, set a `skill` in the API payload to provide reusable base instructions that can be maintained separately from the workflow file. + ## Environment Variables for Task Containers Use `-e` / `--env` to pass environment variables into task containers: diff --git a/consumer-workflows/gh-pr-comment.yml b/consumer-workflows/gh-pr-comment.yml new file mode 100644 index 0000000..80f7733 --- /dev/null +++ b/consumer-workflows/gh-pr-comment.yml @@ -0,0 +1,276 @@ +# ====================================================================================== +# Workflow: Trigger Oz Agent on PR Comment (Self-Hosted Kubernetes Workers) +# ====================================================================================== +# Usage: +# - Comment "@oz-agent " on any PR or inline review comment. +# - Oz will implement the requested changes on the PR branch and reply with a summary. +# +# Setup: +# 1. Copy this file into .github/workflows/ in your repository. +# 2. Set the following secrets/variables in your repository or organization: +# - WARP_API_KEY (secret): A Warp service account API key scoped to your team. +# - OZ_WORKER_ID (variable): The worker_id value of your self-hosted Oz worker +# (the value you passed as --worker-id or worker_id in your worker config). +# +# How it works: +# This workflow runs on GitHub-hosted runners. When triggered, it calls the Warp +# REST API directly to create an Oz run targeted at your self-hosted k8s worker. +# The Oz agent runs inside your k8s cluster, checks out the PR branch, implements +# the requested changes, commits them, and replies to the original comment. +# +# No oz CLI installation is required on the GitHub runner. Only curl is needed. +# +# Prerequisites on the Oz worker side: +# - The oz-agent-worker must be running in your k8s cluster with the Helm chart +# or equivalent deployment, configured with the kubernetes backend. +# - The worker must have access to your GitHub repository (e.g., a mounted SSH key +# or a GitHub App credential in a Kubernetes Secret injected via pod_template). +# - The GITHUB_TOKEN variable below must be passed to task containers so the Oz +# agent can push commits and post GitHub comments. Inject it as a Kubernetes +# Secret and reference it in your Helm values under kubernetesBackend.podTemplate. +# +# Expected output: +# - Oz checks out the PR branch, implements the requested change, commits it, and +# pushes it back to the PR branch. +# - Oz posts a GitHub comment on the PR with a summary of what it did and a link +# to the Warp run. +# +# Customization: +# - Adjust the trigger phrase by changing "@oz-agent" in the `if:` condition. +# - Pass additional context in the prompt (e.g., coding standards, team conventions). +# - Set a `skill` in the API payload to provide reusable base instructions. +# - Set `model_id` in the API config block to override the default LLM. +# +# When to use vs. oz-agent-action respond-to-comment.yml: +# - Use THIS workflow when your Oz workloads must run on your self-hosted k8s workers. +# - Use oz-agent-action respond-to-comment.yml when you are happy with Warp-hosted +# (cloud) agent execution and want synchronous output captured in the workflow. +# ====================================================================================== + +name: Oz Agent PR Comment (Self-Hosted) + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + +jobs: + trigger: + runs-on: ubuntu-latest + # Only fire when the comment contains the trigger phrase, is on a PR, and was + # not posted by the Actions bot itself (to prevent loops). + if: | + contains(github.event.comment.body, '@oz-agent') && + ( + github.event_name == 'pull_request_review_comment' || + (github.event_name == 'issue_comment' && github.event.issue.pull_request) + ) && + github.actor != 'github-actions[bot]' + permissions: + contents: read + pull-requests: write + issues: write + steps: + # ── Step 1: Acknowledge the comment ──────────────────────────────────────── + # React with 👀 so the commenter knows the request was received. + - name: Acknowledge comment + env: + GH_TOKEN: ${{ github.token }} + run: | + COMMENT_ID="${{ github.event.comment.id }}" + + if [ "${{ github.event_name }}" = "pull_request_review_comment" ]; then + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/${{ github.repository }}/pulls/comments/$COMMENT_ID/reactions" \ + -f content="eyes" || true + else + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/${{ github.repository }}/issues/comments/$COMMENT_ID/reactions" \ + -f content="eyes" || true + fi + + # ── Step 2: Resolve PR number and branch ──────────────────────────────────── + - name: Resolve PR context + id: pr + env: + GH_TOKEN: ${{ github.token }} + run: | + if [ "${{ github.event_name }}" = "pull_request_review_comment" ]; then + PR_NUMBER="${{ github.event.pull_request.number }}" + else + PR_NUMBER="${{ github.event.issue.number }}" + fi + + PR_JSON=$(gh api "/repos/${{ github.repository }}/pulls/$PR_NUMBER") + PR_TITLE=$(echo "$PR_JSON" | jq -r '.title') + PR_BODY=$(echo "$PR_JSON" | jq -r '.body // ""') + PR_BRANCH=$(echo "$PR_JSON" | jq -r '.head.ref') + PR_REPO=$(echo "$PR_JSON" | jq -r '.head.repo.clone_url') + CHANGED_FILES=$(gh api "/repos/${{ github.repository }}/pulls/$PR_NUMBER/files" \ + | jq -r '[.[].filename] | join(", ")') + + echo "number=$PR_NUMBER" >> "$GITHUB_OUTPUT" + echo "title=$PR_TITLE" >> "$GITHUB_OUTPUT" + echo "branch=$PR_BRANCH" >> "$GITHUB_OUTPUT" + echo "repo=$PR_REPO" >> "$GITHUB_OUTPUT" + echo "files=$CHANGED_FILES" >> "$GITHUB_OUTPUT" + # Store multi-line body in env file to avoid quoting issues + echo "PR_BODY<> "$GITHUB_ENV" + echo "$PR_BODY" >> "$GITHUB_ENV" + echo "EOF" >> "$GITHUB_ENV" + + # ── Step 3: Submit the Oz run to your self-hosted k8s worker ──────────────── + # Calls the Warp REST API with config.worker_host set to your worker ID. + # The Oz agent will run inside your k8s cluster and push changes back to GitHub. + - name: Trigger Oz agent on self-hosted worker + id: oz_run + env: + WARP_API_KEY: ${{ secrets.WARP_API_KEY }} + GH_TOKEN: ${{ github.token }} + run: | + COMMENT_BODY="${{ github.event.comment.body }}" + COMMENT_AUTHOR="${{ github.event.comment.user.login }}" + COMMENT_URL="${{ github.event.comment.html_url }}" + PR_NUMBER="${{ steps.pr.outputs.number }}" + PR_TITLE="${{ steps.pr.outputs.title }}" + PR_BRANCH="${{ steps.pr.outputs.branch }}" + PR_REPO="${{ steps.pr.outputs.repo }}" + CHANGED_FILES="${{ steps.pr.outputs.files }}" + REPO="${{ github.repository }}" + + # Build diff context for inline review comments + DIFF_CONTEXT="" + if [ "${{ github.event_name }}" = "pull_request_review_comment" ]; then + FILE_PATH="${{ github.event.comment.path }}" + LINE_NUMBER="${{ github.event.comment.line }}" + DIFF_HUNK=$(echo '${{ toJSON(github.event.comment.diff_hunk) }}' | jq -r '.') + DIFF_CONTEXT=" + The comment is on file '${FILE_PATH}' at line ${LINE_NUMBER}. + Diff context around the comment: + \`\`\`diff + ${DIFF_HUNK} + \`\`\`" + fi + + # Construct the prompt for the Oz agent. + # The agent will run in your k8s environment with access to your repo. + PROMPT="You are an expert software engineer responding to a GitHub PR comment. + + ## Your task + A developer has tagged you in a PR comment with a request. Implement it. + + ## PR context + - Repository: ${REPO} + - PR: #${PR_NUMBER} — ${PR_TITLE} + - Branch: ${PR_BRANCH} + - Changed files: ${CHANGED_FILES} + + ## The request + @${COMMENT_AUTHOR} wrote: \"${COMMENT_BODY}\" + ${DIFF_CONTEXT} + + ## Instructions + 1. Clone or check out the branch '${PR_BRANCH}' from '${PR_REPO}' if it is not already available. + 2. Read the relevant files to understand the full context. + 3. Implement the requested change, following existing code style and conventions. + 4. If the request is a question rather than a change, answer it in your reply comment. + 5. Commit any changes with a clear commit message and push to branch '${PR_BRANCH}'. + 6. Post a GitHub comment on PR #${PR_NUMBER} in the repository '${REPO}' summarizing what you did (or your answer). Tag @${COMMENT_AUTHOR} in the reply. You can use the \`gh\` CLI for this: + gh pr comment ${PR_NUMBER} --repo ${REPO} --body \"@${COMMENT_AUTHOR}: \" + 7. Do not output code diffs in your final comment — only summarize the change or answer the question. + + Original comment URL for reference: ${COMMENT_URL}" + + # Call the Warp REST API to create a run on the self-hosted k8s worker. + # OZ_WORKER_ID must match the worker_id configured in your oz-agent-worker deployment. + RESPONSE=$(curl -s -w "\n%{http_code}" \ + -X POST "https://app.warp.dev/api/v1/agent/runs" \ + -H "Authorization: Bearer $WARP_API_KEY" \ + -H "Content-Type: application/json" \ + -d "$(jq -n \ + --arg prompt "$PROMPT" \ + --arg worker_id "${{ vars.OZ_WORKER_ID }}" \ + --arg run_name "PR #${PR_NUMBER} — oz-agent comment" \ + '{ + prompt: $prompt, + config: { + worker_host: $worker_id, + name: $run_name + } + }' + )") + + HTTP_CODE=$(echo "$RESPONSE" | tail -1) + BODY=$(echo "$RESPONSE" | head -n -1) + + if [ "$HTTP_CODE" != "200" ]; then + echo "::error::Failed to create Oz run (HTTP $HTTP_CODE): $BODY" + exit 1 + fi + + RUN_ID=$(echo "$BODY" | jq -r '.run_id') + echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT" + echo "Oz run created: $RUN_ID" + + # ── Step 4: Post a link to the run ───────────────────────────────────────── + # Lets the commenter track the run while Oz works. + - name: Post run link + if: success() && steps.oz_run.outputs.run_id != '' + env: + GH_TOKEN: ${{ github.token }} + run: | + RUN_ID="${{ steps.oz_run.outputs.run_id }}" + COMMENT_AUTHOR="${{ github.event.comment.user.login }}" + PR_NUMBER="${{ steps.pr.outputs.number }}" + RUN_URL="https://app.warp.dev/run/${RUN_ID}" + + REPLY="@${COMMENT_AUTHOR}: I've started working on your request on your self-hosted Oz worker. You can follow the run here: ${RUN_URL} + + I'll push any changes directly to this PR branch and leave a summary comment when done." + + if [ "${{ github.event_name }}" = "pull_request_review_comment" ]; then + COMMENT_ID="${{ github.event.comment.id }}" + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/${{ github.repository }}/pulls/comments/$COMMENT_ID/replies" \ + -f body="$REPLY" || true + else + gh pr comment "$PR_NUMBER" \ + --repo "${{ github.repository }}" \ + --body "$REPLY" || true + fi + + # ── Step 5: Report failure ────────────────────────────────────────────────── + - name: Report failure + if: failure() + env: + GH_TOKEN: ${{ github.token }} + run: | + COMMENT_AUTHOR="${{ github.event.comment.user.login }}" + PR_NUMBER="${{ steps.pr.outputs.number }}" + WORKFLOW_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + + REPLY="@${COMMENT_AUTHOR}: ⚠️ I encountered an error while trying to start your request. Please check the [workflow logs]($WORKFLOW_URL) for details." + + if [ "${{ github.event_name }}" = "pull_request_review_comment" ]; then + COMMENT_ID="${{ github.event.comment.id }}" + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/${{ github.repository }}/pulls/comments/$COMMENT_ID/replies" \ + -f body="$REPLY" || true + else + gh pr comment "$PR_NUMBER" \ + --repo "${{ github.repository }}" \ + --body "$REPLY" || true + fi