Skip to content

Commit 8989f21

Browse files
Improve workflow reliability, issue dedup, and update AI model (#102)
- Fix cache persistence gaps: add cache/save steps to daily-screener and performance-report workflows (data was being lost between runs) - Add failure alert issues to all 5 agent workflows: on failure, creates or comments on an existing agent-failure issue with run link - Add issues:write permission to trading-agent workflow for failure alerts - Improve portfolio review issue deduplication: add title-similarity matching (keyword overlap) on top of existing category-based dedup to prevent repeated themes like "add crypto" across different categories - Add stale issue cleanup to watchlist review agent (auto-close >7 days) - Update default AI model from claude-sonnet-4-20250514 to claude-sonnet-4-6-20250620 across all workflows and config https://claude.ai/code/session_016RNRhxDH2k1GVjnaRmakmZ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 02b4c6f commit 8989f21

8 files changed

Lines changed: 269 additions & 19 deletions

File tree

.github/workflows/daily-screener.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,39 @@ jobs:
6060
GH_TOKEN: ${{ github.token }}
6161
run: python -m financial_agent.screener_main
6262

63+
- name: Save persistent data
64+
if: always()
65+
uses: actions/cache/save@v5
66+
with:
67+
path: .data
68+
key: agent-data-${{ github.run_id }}
69+
6370
- name: Post summary
6471
if: always()
6572
run: |
6673
echo "## Daily Screener" >> $GITHUB_STEP_SUMMARY
6774
echo "- **Time:** $(date -u '+%Y-%m-%d %H:%M UTC')" >> $GITHUB_STEP_SUMMARY
6875
echo "- **Status:** ${{ job.status }}" >> $GITHUB_STEP_SUMMARY
76+
77+
- name: Create failure issue
78+
if: failure()
79+
env:
80+
GH_TOKEN: ${{ github.token }}
81+
run: |
82+
EXISTING=$(gh issue list --label "agent-failure" --state open --limit 1 --json number --jq '.[0].number // empty')
83+
if [ -n "$EXISTING" ]; then
84+
gh issue comment "$EXISTING" --body "Daily Screener failed at $(date -u '+%Y-%m-%d %H:%M UTC'). Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
85+
else
86+
gh label create agent-failure --color D93F0B --force 2>/dev/null || true
87+
gh issue create \
88+
--title "Daily Screener workflow failure" \
89+
--label "agent-failure" \
90+
--body "## Daily Screener Failed
91+
92+
**Time:** $(date -u '+%Y-%m-%d %H:%M UTC')
93+
**Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
94+
95+
Check the [workflow logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.
96+
97+
_This issue was automatically created by the CI failure alert._"
98+
fi

.github/workflows/performance-report.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,39 @@ jobs:
5858
GH_TOKEN: ${{ github.token }}
5959
run: python -m financial_agent.performance_main
6060

61+
- name: Save persistent data
62+
if: always()
63+
uses: actions/cache/save@v5
64+
with:
65+
path: .data
66+
key: agent-data-${{ github.run_id }}
67+
6168
- name: Post summary
6269
if: always()
6370
run: |
6471
echo "## Performance Report" >> $GITHUB_STEP_SUMMARY
6572
echo "- **Time:** $(date -u '+%Y-%m-%d %H:%M UTC')" >> $GITHUB_STEP_SUMMARY
6673
echo "- **Status:** ${{ job.status }}" >> $GITHUB_STEP_SUMMARY
74+
75+
- name: Create failure issue
76+
if: failure()
77+
env:
78+
GH_TOKEN: ${{ github.token }}
79+
run: |
80+
EXISTING=$(gh issue list --label "agent-failure" --state open --limit 1 --json number --jq '.[0].number // empty')
81+
if [ -n "$EXISTING" ]; then
82+
gh issue comment "$EXISTING" --body "Performance Report failed at $(date -u '+%Y-%m-%d %H:%M UTC'). Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
83+
else
84+
gh label create agent-failure --color D93F0B --force 2>/dev/null || true
85+
gh issue create \
86+
--title "Performance Report workflow failure" \
87+
--label "agent-failure" \
88+
--body "## Performance Report Failed
89+
90+
**Time:** $(date -u '+%Y-%m-%d %H:%M UTC')
91+
**Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
92+
93+
Check the [workflow logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.
94+
95+
_This issue was automatically created by the CI failure alert._"
96+
fi

.github/workflows/portfolio-review.yml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ jobs:
5757

5858
# Configuration (reuses trading variables)
5959
ALPACA_BASE_URL: ${{ vars.ALPACA_BASE_URL || 'https://paper-api.alpaca.markets' }}
60-
ANTHROPIC_MODEL: ${{ vars.ANTHROPIC_MODEL || 'claude-sonnet-4-20250514' }}
60+
ANTHROPIC_MODEL: ${{ vars.ANTHROPIC_MODEL || 'claude-sonnet-4-6-20250620' }}
6161
TRADING_MAX_POSITION_PCT: ${{ vars.TRADING_MAX_POSITION_PCT || '0.10' }}
6262
TRADING_MAX_DAILY_TRADES: ${{ vars.TRADING_MAX_DAILY_TRADES || '10' }}
6363
TRADING_STOP_LOSS_PCT: ${{ vars.TRADING_STOP_LOSS_PCT || '0.05' }}
@@ -84,3 +84,26 @@ jobs:
8484
echo "## Portfolio Review" >> $GITHUB_STEP_SUMMARY
8585
echo "- **Time:** $(date -u '+%Y-%m-%d %H:%M UTC')" >> $GITHUB_STEP_SUMMARY
8686
echo "- **Status:** ${{ job.status }}" >> $GITHUB_STEP_SUMMARY
87+
88+
- name: Create failure issue
89+
if: failure()
90+
env:
91+
GH_TOKEN: ${{ github.token }}
92+
run: |
93+
EXISTING=$(gh issue list --label "agent-failure" --state open --limit 1 --json number --jq '.[0].number // empty')
94+
if [ -n "$EXISTING" ]; then
95+
gh issue comment "$EXISTING" --body "Portfolio Review failed at $(date -u '+%Y-%m-%d %H:%M UTC'). Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
96+
else
97+
gh label create agent-failure --color D93F0B --force 2>/dev/null || true
98+
gh issue create \
99+
--title "Portfolio Review workflow failure" \
100+
--label "agent-failure" \
101+
--body "## Portfolio Review Failed
102+
103+
**Time:** $(date -u '+%Y-%m-%d %H:%M UTC')
104+
**Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
105+
106+
Check the [workflow logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.
107+
108+
_This issue was automatically created by the CI failure alert._"
109+
fi

.github/workflows/trading-agent.yml

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ concurrency:
3535

3636
permissions:
3737
contents: read
38+
issues: write
3839

3940
jobs:
4041
trade:
@@ -78,7 +79,7 @@ jobs:
7879

7980
# Configuration (from GitHub Variables)
8081
ALPACA_BASE_URL: ${{ vars.ALPACA_BASE_URL || 'https://paper-api.alpaca.markets' }}
81-
ANTHROPIC_MODEL: ${{ vars.ANTHROPIC_MODEL || 'claude-sonnet-4-20250514' }}
82+
ANTHROPIC_MODEL: ${{ vars.ANTHROPIC_MODEL || 'claude-sonnet-4-6-20250620' }}
8283
TRADING_MAX_POSITION_PCT: ${{ vars.TRADING_MAX_POSITION_PCT || '0.10' }}
8384
TRADING_MAX_DAILY_TRADES: ${{ vars.TRADING_MAX_DAILY_TRADES || '10' }}
8485
TRADING_STOP_LOSS_PCT: ${{ vars.TRADING_STOP_LOSS_PCT || '0.05' }}
@@ -122,3 +123,27 @@ jobs:
122123
echo "- **Dry Run:** ${{ inputs.dry_run || vars.TRADING_DRY_RUN || 'false' }}" >> $GITHUB_STEP_SUMMARY
123124
echo "" >> $GITHUB_STEP_SUMMARY
124125
echo "The agent crashed before completing. Check the logs above for details." >> $GITHUB_STEP_SUMMARY
126+
127+
- name: Create failure issue
128+
if: failure()
129+
env:
130+
GH_TOKEN: ${{ github.token }}
131+
run: |
132+
EXISTING=$(gh issue list --label "agent-failure" --state open --limit 1 --json number --jq '.[0].number // empty')
133+
if [ -n "$EXISTING" ]; then
134+
gh issue comment "$EXISTING" --body "Trading Agent failed again at $(date -u '+%Y-%m-%d %H:%M UTC'). Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
135+
else
136+
gh label create agent-failure --color D93F0B --force 2>/dev/null || true
137+
gh issue create \
138+
--title "Trading Agent workflow failure" \
139+
--label "agent-failure" \
140+
--body "## Trading Agent Failed
141+
142+
**Time:** $(date -u '+%Y-%m-%d %H:%M UTC')
143+
**Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
144+
**Strategy:** ${{ inputs.strategy || vars.TRADING_STRATEGY || 'balanced' }}
145+
146+
Check the [workflow logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.
147+
148+
_This issue was automatically created by the CI failure alert._"
149+
fi

.github/workflows/watchlist-review.yml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ jobs:
5858

5959
# Configuration
6060
ALPACA_BASE_URL: ${{ vars.ALPACA_BASE_URL || 'https://paper-api.alpaca.markets' }}
61-
ANTHROPIC_MODEL: ${{ vars.ANTHROPIC_MODEL || 'claude-sonnet-4-20250514' }}
61+
ANTHROPIC_MODEL: ${{ vars.ANTHROPIC_MODEL || 'claude-sonnet-4-6-20250620' }}
6262
TRADING_WATCHLIST: ${{ vars.TRADING_WATCHLIST || 'AAPL,MSFT,GOOGL,AMZN,NVDA,META,TSLA,JPM,V,JNJ' }}
6363
TRADING_CRYPTO_WATCHLIST: ${{ vars.TRADING_CRYPTO_WATCHLIST || 'BTC/USD,ETH/USD,SOL/USD' }}
6464
TRADING_STOCK_UNIVERSE: ${{ vars.TRADING_STOCK_UNIVERSE || '' }}
@@ -88,3 +88,26 @@ jobs:
8888
echo "## Watchlist Review" >> $GITHUB_STEP_SUMMARY
8989
echo "- **Time:** $(date -u '+%Y-%m-%d %H:%M UTC')" >> $GITHUB_STEP_SUMMARY
9090
echo "- **Status:** ${{ job.status }}" >> $GITHUB_STEP_SUMMARY
91+
92+
- name: Create failure issue
93+
if: failure()
94+
env:
95+
GH_TOKEN: ${{ secrets.GH_PAT }}
96+
run: |
97+
EXISTING=$(gh issue list --label "agent-failure" --state open --limit 1 --json number --jq '.[0].number // empty')
98+
if [ -n "$EXISTING" ]; then
99+
gh issue comment "$EXISTING" --body "Watchlist Review failed at $(date -u '+%Y-%m-%d %H:%M UTC'). Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
100+
else
101+
gh label create agent-failure --color D93F0B --force 2>/dev/null || true
102+
gh issue create \
103+
--title "Watchlist Review workflow failure" \
104+
--label "agent-failure" \
105+
--body "## Watchlist Review Failed
106+
107+
**Time:** $(date -u '+%Y-%m-%d %H:%M UTC')
108+
**Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
109+
110+
Check the [workflow logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.
111+
112+
_This issue was automatically created by the CI failure alert._"
113+
fi

src/financial_agent/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class AIConfig(BaseSettings):
3434

3535
api_key: str = Field(description="Anthropic API key (GitHub Secret: ANTHROPIC_API_KEY)")
3636
model: str = Field(
37-
default="claude-sonnet-4-20250514",
37+
default="claude-sonnet-4-6-20250620",
3838
description="Claude model to use (GitHub Variable: ANTHROPIC_MODEL)",
3939
)
4040
max_tokens: int = Field(

src/financial_agent/review_main.py

Lines changed: 73 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,10 @@ def _run_gh_command(cmd: list[str], timeout: int = 30) -> tuple[bool, str]:
3636
return False, ""
3737

3838

39-
def _get_open_review_categories() -> set[str]:
40-
"""Get the set of categories that already have open portfolio-review issues.
39+
def _get_open_review_issues() -> tuple[set[str], list[str]]:
40+
"""Get categories and titles of open portfolio-review issues for deduplication.
4141
42-
Returns category labels (e.g. 'risk', 'strategy', 'config') that have at least
43-
one open issue, so we can skip creating duplicates.
42+
Returns a tuple of (category_set, title_list) from open portfolio-review issues.
4443
"""
4544
log = structlog.get_logger()
4645
success, output = _run_gh_command(
@@ -55,28 +54,80 @@ def _get_open_review_categories() -> set[str]:
5554
"--limit",
5655
"30",
5756
"--json",
58-
"labels",
57+
"labels,title",
5958
],
6059
)
6160
if not success or not output:
62-
return set()
61+
return set(), []
6362

6463
try:
6564
issues = json.loads(output)
6665
except json.JSONDecodeError:
67-
return set()
66+
return set(), []
6867

6968
# Extract category labels from existing open issues
7069
category_labels = {"risk", "performance", "strategy", "config", "watchlist"}
71-
existing: set[str] = set()
70+
existing_categories: set[str] = set()
71+
existing_titles: list[str] = []
7272
for issue in issues:
73+
existing_titles.append(issue.get("title", "").lower())
7374
for label in issue.get("labels", []):
7475
name = label.get("name", "") if isinstance(label, dict) else str(label)
7576
if name in category_labels:
76-
existing.add(name)
77+
existing_categories.add(name)
7778

78-
log.info("existing_review_categories", categories=sorted(existing), open_issues=len(issues))
79-
return existing
79+
log.info(
80+
"existing_review_issues",
81+
categories=sorted(existing_categories),
82+
open_issues=len(issues),
83+
)
84+
return existing_categories, existing_titles
85+
86+
87+
def _is_duplicate_title(new_title: str, existing_titles: list[str]) -> bool:
88+
"""Check if a new issue title is too similar to an existing open issue.
89+
90+
Uses keyword overlap: if 50%+ of significant words in the new title appear
91+
in an existing title, it's considered a duplicate.
92+
"""
93+
stop_words = {
94+
"a",
95+
"an",
96+
"the",
97+
"to",
98+
"for",
99+
"of",
100+
"in",
101+
"on",
102+
"and",
103+
"or",
104+
"with",
105+
"from",
106+
"by",
107+
"at",
108+
"is",
109+
"it",
110+
"as",
111+
"be",
112+
"this",
113+
"that",
114+
"into",
115+
"current",
116+
"based",
117+
}
118+
new_words = {w for w in new_title.lower().split() if w not in stop_words and len(w) > 2}
119+
if not new_words:
120+
return False
121+
122+
for existing in existing_titles:
123+
existing_words = {w for w in existing.lower().split() if w not in stop_words and len(w) > 2}
124+
if not existing_words:
125+
continue
126+
overlap = new_words & existing_words
127+
if len(overlap) >= len(new_words) * 0.5:
128+
return True
129+
130+
return False
80131

81132

82133
def _close_stale_review_issues() -> int:
@@ -253,8 +304,8 @@ def main() -> None:
253304
# Close stale issues before creating new ones
254305
_close_stale_review_issues()
255306

256-
# Check which categories already have open issues to avoid duplicates
257-
existing_categories = _get_open_review_categories()
307+
# Check which categories/titles already have open issues to avoid duplicates
308+
existing_categories, existing_titles = _get_open_review_issues()
258309

259310
# Ensure labels exist
260311
all_labels: set[str] = {"portfolio-review"}
@@ -278,7 +329,13 @@ def main() -> None:
278329

279330
# Skip if an open issue already covers this category
280331
if category and category in existing_categories:
281-
log.info("issue_skipped_duplicate", title=title, category=category)
332+
log.info("issue_skipped_duplicate_category", title=title, category=category)
333+
skipped += 1
334+
continue
335+
336+
# Skip if a similar title already exists (prevents repeated themes across categories)
337+
if _is_duplicate_title(title, existing_titles):
338+
log.info("issue_skipped_duplicate_title", title=title)
282339
skipped += 1
283340
continue
284341

@@ -315,9 +372,10 @@ def main() -> None:
315372

316373
if _create_github_issue(title, issue_body, labels):
317374
issues_created += 1
318-
# Track newly created category to avoid duplicates within same run
375+
# Track newly created category/title to avoid duplicates within same run
319376
if category:
320377
existing_categories.add(category)
378+
existing_titles.append(title.lower())
321379

322380
log.info(
323381
"review_agent_complete",

0 commit comments

Comments
 (0)