diff --git a/.github/workflows/daily-research-monitor.prompt.md b/.github/workflows/daily-research-monitor.prompt.md index c445d48..8720114 100644 --- a/.github/workflows/daily-research-monitor.prompt.md +++ b/.github/workflows/daily-research-monitor.prompt.md @@ -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 | @@ -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 diff --git a/.github/workflows/daily-research-monitor.yml b/.github/workflows/daily-research-monitor.yml index 377fdf2..e6df26e 100644 --- a/.github/workflows/daily-research-monitor.yml +++ b/.github/workflows/daily-research-monitor.yml @@ -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 @@ -28,7 +24,7 @@ concurrency: jobs: monitor: runs-on: ubuntu-latest - timeout-minutes: 25 + timeout-minutes: 35 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: @@ -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 @@ -69,7 +66,7 @@ jobs: # System preamble — gives the agent runtime context cat > /tmp/research-monitor/prompt.txt << 'PREAMBLE' - 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.) @@ -77,20 +74,19 @@ jobs: ## 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 @@ -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 @@ -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 \ @@ -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"