Skip to content

Commit bd5439a

Browse files
authored
Fix memory path resolution and redesign feedback learning for fork PRs (#35)
1 parent b2ad694 commit bd5439a

File tree

5 files changed

+148
-216
lines changed

5 files changed

+148
-216
lines changed

.github/workflows/review-pr.yml

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,14 @@
99
# pull_request_target:
1010
# types: [ready_for_review, opened]
1111
#
12-
# permissions:
13-
# contents: read
14-
# pull-requests: write
15-
# issues: write
16-
#
1712
# jobs:
1813
# review:
1914
# uses: docker/cagent-action/.github/workflows/review-pr.yml@latest
2015
# secrets: inherit
16+
# permissions:
17+
# contents: read
18+
# pull-requests: write
19+
# issues: write
2120

2221
name: PR Review
2322

@@ -231,37 +230,53 @@ jobs:
231230
mistral-api-key: ${{ secrets.MISTRAL_API_KEY }}
232231

233232
# ==========================================================================
234-
# LEARN FROM FEEDBACK
235-
# Processes replies to agent review comments for continuous improvement
233+
# CAPTURE FEEDBACK
234+
# Saves feedback data as an artifact for lazy processing. This job
235+
# intentionally avoids using secrets so it works for fork PRs in public
236+
# repos. The actual AI processing happens during the next review run,
237+
# which has full secret access via pull_request_target or issue_comment.
236238
# ==========================================================================
237-
learn-from-feedback:
239+
capture-feedback:
238240
if: github.event_name == 'pull_request_review_comment' && github.event.comment.in_reply_to_id
239241
runs-on: ubuntu-latest
240-
env:
241-
HAS_APP_SECRETS: ${{ secrets.CAGENT_REVIEWER_APP_ID != '' }}
242242

243243
steps:
244-
- name: Checkout repository
245-
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
244+
- name: Check if reply is to agent comment
245+
id: check
246+
shell: bash
247+
env:
248+
GH_TOKEN: ${{ github.token }}
249+
PARENT_ID: ${{ github.event.comment.in_reply_to_id }}
250+
run: |
251+
if [ -z "$PARENT_ID" ]; then
252+
echo "is_agent=false" >> $GITHUB_OUTPUT
253+
echo "⏭️ Not a reply comment, skipping"
254+
exit 0
255+
fi
246256
247-
# Generate GitHub App token for custom app identity (optional - falls back to github.token)
248-
- name: Generate GitHub App token
249-
if: env.HAS_APP_SECRETS == 'true'
250-
id: app-token
251-
continue-on-error: true # Don't fail workflow if token generation fails
252-
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2
253-
with:
254-
app_id: ${{ secrets.CAGENT_REVIEWER_APP_ID }}
255-
private_key: ${{ secrets.CAGENT_REVIEWER_APP_PRIVATE_KEY }}
257+
parent=$(gh api repos/${{ github.repository }}/pulls/comments/$PARENT_ID 2>/dev/null || echo "{}")
258+
if echo "$parent" | jq -r '.body // ""' | grep -q "<!-- cagent-review -->"; then
259+
echo "is_agent=true" >> $GITHUB_OUTPUT
260+
echo "✅ Reply is to an agent review comment"
261+
else
262+
echo "is_agent=false" >> $GITHUB_OUTPUT
263+
echo "⏭️ Not a reply to agent comment, skipping"
264+
fi
265+
266+
- name: Save feedback data
267+
if: steps.check.outputs.is_agent == 'true'
268+
shell: bash
269+
env:
270+
COMMENT_JSON: ${{ toJSON(github.event.comment) }}
271+
run: |
272+
mkdir -p feedback
273+
echo "$COMMENT_JSON" > feedback/feedback.json
274+
echo "📦 Saved feedback data for async processing"
256275
257-
- name: Learn from user feedback
258-
uses: docker/cagent-action/review-pr/learn@latest
276+
- name: Upload feedback artifact
277+
if: steps.check.outputs.is_agent == 'true'
278+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
259279
with:
260-
github-token: ${{ steps.app-token.outputs.token || github.token }}
261-
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
262-
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
263-
google-api-key: ${{ secrets.GOOGLE_API_KEY }}
264-
aws-bearer-token-bedrock: ${{ secrets.AWS_BEARER_TOKEN_BEDROCK }}
265-
xai-api-key: ${{ secrets.XAI_API_KEY }}
266-
nebius-api-key: ${{ secrets.NEBIUS_API_KEY }}
267-
mistral-api-key: ${{ secrets.MISTRAL_API_KEY }}
280+
name: pr-review-feedback
281+
path: feedback/
282+
retention-days: 90

action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,10 @@ runs:
467467
ARGS+=("--hide-tool-calls" "--hide-tool-results")
468468
fi
469469
470+
# Set working directory so relative paths (e.g., memory toolset) resolve
471+
# from the repo root, not from the agent YAML's parent directory
472+
ARGS+=("--working-dir" "$(pwd)")
473+
470474
# Add extra args if provided
471475
# Note: This uses simple word splitting. Quoted arguments with spaces are not supported.
472476
# Using eval would be a security risk with user-provided input.

review-pr/README.md

Lines changed: 7 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,6 @@ name: PR Review
9090
on:
9191
issue_comment:
9292
types: [created]
93-
pull_request_review_comment:
94-
types: [created]
9593
9694
permissions:
9795
contents: read
@@ -111,18 +109,10 @@ jobs:
111109
with:
112110
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
113111
github-token: ${{ secrets.GITHUB_TOKEN }}
114-
115-
learn:
116-
if: github.event_name == 'pull_request_review_comment' && github.event.comment.in_reply_to_id
117-
runs-on: ubuntu-latest
118-
steps:
119-
- uses: actions/checkout@v4
120-
121-
- uses: docker/cagent-action/review-pr/learn@latest
122-
with:
123-
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
124112
```
125113

114+
> **Note:** When using the composite action directly, feedback learning is handled automatically — the review action collects and processes any pending feedback artifacts before each review. To capture feedback, use the reusable workflow which includes the `capture-feedback` job, or add the equivalent artifact upload step to your own workflow.
115+
126116
---
127117

128118
## Adding Language-Specific Guidelines
@@ -247,25 +237,6 @@ PR number and comment ID are auto-detected from `github.event` when not provided
247237

248238
*At least one API key is required.
249239

250-
### `review-pr/learn`
251-
252-
Comment data is read automatically from `github.event.comment`.
253-
254-
| Input | Description | Required |
255-
|-------|-------------|----------|
256-
| `anthropic-api-key` | Anthropic API key | No* |
257-
| `openai-api-key` | OpenAI API key | No* |
258-
| `google-api-key` | Google API key (Gemini) | No* |
259-
| `aws-bearer-token-bedrock` | AWS Bedrock token | No* |
260-
| `xai-api-key` | xAI API key (Grok) | No* |
261-
| `nebius-api-key` | Nebius API key | No* |
262-
| `mistral-api-key` | Mistral API key | No* |
263-
| `github-token` | GitHub token | No |
264-
| `model` | Model override | No |
265-
| `cagent-version` | CAgent version | No |
266-
267-
*At least one API key is required.
268-
269240
---
270241

271242
## Cost
@@ -331,12 +302,12 @@ PR Diff → Drafter (hypotheses) → Verifier (confirm) → Post Comments
331302
### Learning System
332303

333304
When you reply to a review comment:
334-
1. Action checks if it's a reply to an agent comment (via `<!-- cagent-review -->` marker)
335-
2. If yes, processes your feedback
336-
3. Stores learnings in a memory database (cached per-repo)
337-
4. Future reviews avoid the same mistakes
305+
1. The `capture-feedback` job checks if it's a reply to an agent comment (via `<!-- cagent-review -->` marker)
306+
2. If yes, saves the feedback as a GitHub Actions artifact (no secrets required — works for fork PRs)
307+
3. On the next review run, pending feedback artifacts are downloaded and processed into the memory database
308+
4. Future reviews use these learnings to avoid repeating the same mistakes
338309

339-
**Memory persistence:** The memory database is stored in GitHub Actions cache. Each run restores the previous cache, adds new learnings, and saves with a unique key. Old caches are automatically cleaned up (keeping the 5 most recent) to prevent cache proliferation while supporting concurrent reviews.
310+
**Memory persistence:** The memory database is stored in GitHub Actions cache. Each review run restores the previous cache, processes any pending feedback, runs the review, and saves with a unique key. Old caches are automatically cleaned up (keeping the 5 most recent).
340311

341312
---
342313

review-pr/action.yml

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,103 @@ runs:
157157
run: mkdir -p "${{ github.workspace }}/.cache"
158158

159159
- name: Restore reviewer memory
160+
id: restore-memory
160161
uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
161162
with:
162163
path: ${{ github.workspace }}/.cache/pr-review-memory.db
163164
key: pr-review-memory-${{ github.repository }}-${{ github.run_id }}
164165
restore-keys: |
165166
pr-review-memory-${{ github.repository }}-
166167
168+
# ========================================
169+
# PROCESS PENDING FEEDBACK
170+
# Downloads feedback artifacts left by capture-feedback jobs
171+
# and processes them into memory before the review runs.
172+
# ========================================
173+
- name: Collect pending feedback
174+
id: pending-feedback
175+
continue-on-error: true
176+
shell: bash
177+
env:
178+
GH_TOKEN: ${{ steps.resolve-token.outputs.token }}
179+
run: |
180+
ARTIFACTS=$(gh api "repos/${{ github.repository }}/actions/artifacts?name=pr-review-feedback" \
181+
--jq '.artifacts[].id' 2>/dev/null || echo "")
182+
183+
if [ -z "$ARTIFACTS" ]; then
184+
echo "has_feedback=false" >> $GITHUB_OUTPUT
185+
echo "ℹ️ No pending feedback to process"
186+
exit 0
187+
fi
188+
189+
mkdir -p pending_feedback
190+
COMBINED=""
191+
COUNT=0
192+
193+
for AID in $ARTIFACTS; do
194+
# Download artifact zip
195+
gh api "repos/${{ github.repository }}/actions/artifacts/$AID/zip" > "feedback_$AID.zip" 2>/dev/null || continue
196+
unzip -o "feedback_$AID.zip" -d "pending_feedback/" 2>/dev/null || continue
197+
198+
if [ -f pending_feedback/feedback.json ]; then
199+
FB_PATH=$(jq -r '.path // "unknown"' pending_feedback/feedback.json)
200+
FB_LINE=$(jq -r '.line // "?"' pending_feedback/feedback.json)
201+
FB_BODY=$(jq -r '.body // ""' pending_feedback/feedback.json)
202+
203+
COMBINED+="- **File:** ${FB_PATH} (line ${FB_LINE})"$'\n'
204+
COMBINED+=" **Feedback:** ${FB_BODY}"$'\n\n'
205+
((COUNT++)) || true
206+
fi
207+
208+
rm -f pending_feedback/feedback.json
209+
210+
# Delete processed artifact
211+
gh api "repos/${{ github.repository }}/actions/artifacts/$AID" -X DELETE 2>/dev/null || true
212+
done
213+
214+
if [ "$COUNT" -gt 0 ]; then
215+
echo "has_feedback=true" >> $GITHUB_OUTPUT
216+
{
217+
echo "prompt<<FEEDBACK_EOF"
218+
echo "$COMBINED"
219+
echo "FEEDBACK_EOF"
220+
} >> $GITHUB_OUTPUT
221+
echo "✅ Found $COUNT pending feedback item(s) to process"
222+
else
223+
echo "has_feedback=false" >> $GITHUB_OUTPUT
224+
fi
225+
226+
- name: Process pending feedback
227+
if: steps.pending-feedback.outputs.has_feedback == 'true'
228+
continue-on-error: true
229+
uses: docker/cagent-action@latest
230+
with:
231+
agent: ${{ github.action_path }}/agents/pr-review-feedback.yaml
232+
prompt: |
233+
Developers left feedback on previous review comments. Learn from each item:
234+
235+
${{ steps.pending-feedback.outputs.prompt }}
236+
237+
For each item:
238+
1. If they're correcting a false positive, add a memory to avoid this mistake
239+
2. If they're asking for clarification, note what was unclear
240+
3. If they're agreeing and adding context, store the additional insight
241+
242+
Use add_memory to record what you learned from each feedback item.
243+
anthropic-api-key: ${{ inputs.anthropic-api-key }}
244+
openai-api-key: ${{ inputs.openai-api-key }}
245+
google-api-key: ${{ inputs.google-api-key }}
246+
aws-bearer-token-bedrock: ${{ inputs.aws-bearer-token-bedrock }}
247+
xai-api-key: ${{ inputs.xai-api-key }}
248+
nebius-api-key: ${{ inputs.nebius-api-key }}
249+
mistral-api-key: ${{ inputs.mistral-api-key }}
250+
github-token: ${{ steps.resolve-token.outputs.token }}
251+
cagent-version: ${{ inputs.cagent-version }}
252+
extra-args: ${{ inputs.model && format('--model={0}', inputs.model) || '' }}
253+
254+
# ========================================
255+
# BUILD REVIEW CONTEXT
256+
# ========================================
167257
- name: Build review context
168258
id: context
169259
shell: bash
@@ -250,6 +340,7 @@ runs:
250340

251341
- name: Save reviewer memory
252342
if: always()
343+
continue-on-error: true # Don't fail if memory file doesn't exist (first run)
253344
uses: actions/cache/save@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
254345
with:
255346
path: ${{ github.workspace }}/.cache/pr-review-memory.db

0 commit comments

Comments
 (0)