Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions .github/workflows/daily-research-monitor.prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ You have access to ONLY these paths:
| Path | Purpose | Access |
|------|---------|--------|
| `/tmp/research-monitor/feeds/` | Pre-downloaded feed entries | READ |
| `/tmp/research-monitor/output.json` | Your output file | WRITE |
| `/tmp/research-monitor/title.txt` | Output: single-line title | WRITE |
| `/tmp/research-monitor/body.md` | Output: full Markdown report | WRITE |
| `/tmp/research-monitor/skip.json` | Output: skip instruction | WRITE |
| `skills/research-filter/SKILL.md` | Filtering methodology | READ |
| `skills/research-digest/SKILL.md` | Digest formatting methodology | READ |
| `skills/research-digest/templates/` | Digest templates | READ |
Expand Down Expand Up @@ -80,23 +82,20 @@ The skill returns a finished Markdown string.

### Step 3 — Write output

Wrap the Markdown from Step 2 into JSON and write it to `/tmp/research-monitor/output.json`:
Write the Markdown digest from Step 2 to `/tmp/research-monitor/body.md`
and a single-line title to `/tmp/research-monitor/title.txt`:

```json
{
"title": "Daily AI Research Digest — YYYY-MM-DD",
"body": "Full markdown digest here"
}
```
- **`/tmp/research-monitor/title.txt`** — a concise title like `Daily AI Research Digest — YYYY-MM-DD`
- **`/tmp/research-monitor/body.md`** — full Markdown report content

- Write EXACTLY ONE JSON object. The `body` field contains the full Markdown report.
- Do NOT repeat the title as a heading inside `body`.
Rules:
- Do NOT repeat the title as a heading inside `body.md`.
- **Always write output**, even when no entries survive filtering. If nothing
passes, set `body` to a short report: total entries scanned, how many excluded
passes, write a short report: total entries scanned, how many excluded
at each phase, a one-line summary of what dominated today's feeds, and
optionally the closest near-miss.
- If truly nothing was fetched (empty feeds directory), write:
`{"skip": true, "reason": "No feed entries available"}`
- If truly nothing was fetched (empty feeds directory), write
`/tmp/research-monitor/skip.json` with content `{"skip": true, "reason": "No feed entries available"}`

## Guardrails

Expand Down
115 changes: 52 additions & 63 deletions .github/workflows/daily-research-monitor.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
# Daily RSS Monitor — standalone Copilot CLI workflow
# No gh-aw dependency. Runs Copilot CLI directly as a coding agent.
# Daily RSS Monitor — standalone opencode workflow
# Runs opencode directly as a coding agent.
#
# Required secrets:
# COPILOT_GITHUB_TOKEN — Classic PAT with public_repo + copilot scopes
# (created per gh-aw requirements)
# MISTRAL_API_KEY — Mistral AI API key (https://console.mistral.ai/api-keys)
#
name: "Daily Research Monitor"

on:
schedule:
# NOTE: ArXiv only updates during weekdays, so we can skip weekends.
# - cron: "0 14 * * 1-5" # Weekdays at 6 AM PST (UTC-8)
workflow_dispatch:
inputs:
model:
description: "Copilot model to use"
description: "opencode model to use (provider/model-id format)"
type: string
default: "claude-sonnet-4.6"
default: "mistral/mistral-small-latest"

permissions:
contents: read
Expand All @@ -28,7 +24,7 @@ concurrency:
jobs:
monitor:
runs-on: ubuntu-latest
timeout-minutes: 25
timeout-minutes: 35
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
Expand All @@ -55,9 +51,10 @@ jobs:
--output /tmp/research-monitor/feeds \
--cutoff 24

# ── 2.75. Install Copilot CLI ────────────────────────────────
- name: Install Copilot CLI
run: curl -fsSL https://gh.io/copilot-install | sudo bash
# ── 2.75. Install opencode ──────────────────────────────────
# npm package: opencode-ai (provides the `opencode` binary)
- name: Install opencode
run: npm install -g opencode-ai

# ── 3. Build prompt ──────────────────────────────────────────
# Strips YAML frontmatter from the agent instructions file
Expand All @@ -69,28 +66,27 @@ jobs:
# System preamble — gives the agent runtime context
cat > /tmp/research-monitor/prompt.txt << 'PREAMBLE'
<system>
You are running as a standalone Copilot CLI agent inside a GitHub Actions workflow.
You are running as a standalone opencode agent inside a GitHub Actions workflow.

## Available tools
- **bash**: You can run any shell command (curl, grep, sed, awk, jq, etc.)
- **file tools**: You can read and write files in the workspace.

## Publishing output
You do NOT have access to the GitHub API or `gh` CLI.
Instead, write your output to a JSON file at `/tmp/research-monitor/output.json`.
Instead, write your output using the file structure below.

The JSON file must have this structure:
```json
{
"title": "Your concise title (without [ai] prefix — it will be added automatically)",
"body": "Your full report in Markdown format"
}
```
Write these files:

1. `/tmp/research-monitor/title.txt` — your concise title (plain text, single line, no markdown)
2. `/tmp/research-monitor/body.md` — your full Markdown report (do NOT repeat the title as a heading)

If there is nothing interesting to report, instead write:
`/tmp/research-monitor/skip.json` with content `{"skip": true, "reason": "No relevant content found"}`

Rules:
- Write EXACTLY ONE JSON object to `/tmp/research-monitor/output.json`.
- The `body` field should contain the full Markdown report. Do NOT repeat the title as a heading.
- If there is nothing interesting to report, write: `{"skip": true, "reason": "No relevant content found"}`
- Always publish output. If no entries survive filtering, write a report explaining what was scanned and why nothing qualified.
- Do NOT write `output.json`. Write the separate files as described above.
- Do NOT attempt to create discussions, issues, or PRs yourself.

## Workspace
Expand All @@ -112,45 +108,45 @@ jobs:
# /tmp/research-monitor/feeds/, filters content, and writes output.json.
# It has NO network access to RSS feeds (pre-downloaded).
#
- name: Run Copilot CLI agent
timeout-minutes: 20
- name: Run opencode agent
timeout-minutes: 30
env:
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
run: |
copilot \
--add-dir "$GITHUB_WORKSPACE" \
--add-dir /tmp/research-monitor \
--allow-all-tools \
--allow-all-paths \
--model "${{ inputs.model || 'claude-sonnet-4.6' }}" \
--prompt "$(cat /tmp/research-monitor/prompt.txt)" \
npx opencode run \
--model "${{ inputs.model || 'mistral/mistral-small-latest' }}" \
--auto \
--dir "$GITHUB_WORKSPACE" \
"$(cat /tmp/research-monitor/prompt.txt)" \
2>&1 | tee /tmp/research-monitor/agent-output.log

# ── 5. Write output to job summary ──────────────────────────
- name: Write output to job summary
if: success()
run: |
OUTPUT_FILE="/tmp/research-monitor/output.json"
if [ ! -f "$OUTPUT_FILE" ]; then
echo "⚠️ No output.json produced" >> "$GITHUB_STEP_SUMMARY"
SKIP_FILE="/tmp/research-monitor/skip.json"
TITLE_FILE="/tmp/research-monitor/title.txt"
BODY_FILE="/tmp/research-monitor/body.md"

if [ -f "$SKIP_FILE" ]; then
echo "⏭️ Skipped: $(jq -r '.reason // "No reason"' "$SKIP_FILE")" >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
if jq -e '.skip // false' "$OUTPUT_FILE" > /dev/null 2>&1; then
echo "⏭️ Skipped: $(jq -r '.reason // "No reason"' "$OUTPUT_FILE")" >> "$GITHUB_STEP_SUMMARY"

if [ ! -f "$TITLE_FILE" ] || [ ! -f "$BODY_FILE" ]; then
echo "⚠️ Agent did not produce output files" >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
TITLE=$(jq -r '.title // empty' "$OUTPUT_FILE")
BODY=$(jq -r '.body // empty' "$OUTPUT_FILE")

TITLE=$(cat "$TITLE_FILE")
{
echo "## 📢 [ai] $TITLE"
echo ""
echo "$BODY"
} >> "$GITHUB_STEP_SUMMARY"
cat "$BODY_FILE" >> "$GITHUB_STEP_SUMMARY"

# ── 6. Publish output as GitHub Discussion ───────────────────
# Reads the agent's JSON output and creates a discussion.
# Reads the agent's title/body files and creates a discussion.
# Requires DISCUSSIONS_WRITE_TOKEN secret. If not set, skips.
#
- name: Create discussion from agent output
Expand All @@ -163,29 +159,22 @@ jobs:
exit 0
fi

OUTPUT_FILE="/tmp/research-monitor/output.json"
SKIP_FILE="/tmp/research-monitor/skip.json"
TITLE_FILE="/tmp/research-monitor/title.txt"
BODY_FILE="/tmp/research-monitor/body.md"

if [ ! -f "$OUTPUT_FILE" ]; then
echo "⚠️ No output.json found — agent produced no output"
# Check if agent decided to skip
if [ -f "$SKIP_FILE" ]; then
echo "⏭️ Agent skipped: $(jq -r '.reason // "No reason given"' "$SKIP_FILE")"
exit 0
fi

# Check if agent decided to skip
if jq -e '.skip // false' "$OUTPUT_FILE" > /dev/null 2>&1; then
REASON=$(jq -r '.reason // "No reason given"' "$OUTPUT_FILE")
echo "⏭️ Agent skipped: $REASON"
if [ ! -f "$TITLE_FILE" ] || [ ! -f "$BODY_FILE" ]; then
echo "⚠️ Agent produced no output"
exit 0
fi

# Validate required fields
TITLE=$(jq -r '.title // empty' "$OUTPUT_FILE")
BODY=$(jq -r '.body // empty' "$OUTPUT_FILE")

if [ -z "$TITLE" ] || [ -z "$BODY" ]; then
echo "❌ output.json missing title or body"
jq . "$OUTPUT_FILE"
exit 1
fi
TITLE=$(cat "$TITLE_FILE")

echo "📢 Creating discussion: [ai] $TITLE"
gh api graphql \
Expand All @@ -201,7 +190,7 @@ jobs:
-f repoId="$(gh api repos/$GITHUB_REPOSITORY --jq .node_id)" \
-f catId="$(gh api graphql -f query='query($owner:String!,$repo:String!){ repository(owner:$owner,name:$repo){ discussionCategory(slug:"general"){ id } } }' -f owner="${GITHUB_REPOSITORY%%/*}" -f repo="${GITHUB_REPOSITORY##*/}" --jq '.data.repository.discussionCategory.id')" \
-f title="[ai] $TITLE" \
-f body="$BODY"
-f body="$(cat "$BODY_FILE")"

echo "✅ Discussion created"

Expand Down
Loading