1# status maintenance via letta-backed agent 2# 3# two-phase workflow: 4# 1. workflow_dispatch: runs letta-backed script to archive, generate audio, open PR 5# 2. on PR merge: uploads audio to plyr.fm 6# 7# the letta agent maintains persistent memory across runs, remembering: 8# - previous status updates and their content 9# - architectural decisions and patterns 10# - recurring themes in development 11# 12# required secrets: 13# LETTA_API_KEY - letta cloud API key (for persistent memory) 14# ANTHROPIC_API_KEY - anthropic API key (for claude) 15# GOOGLE_API_KEY - gemini TTS (for audio generation) 16# PLYR_BOT_TOKEN - plyr.fm developer token (for audio upload) 17 18name: status maintenance 19 20on: 21 # TODO: restore schedule after testing 22 # schedule: 23 # - cron: "0 9 * * 1" # every monday 9am UTC 24 workflow_dispatch: 25 inputs: 26 skip_audio: 27 description: "skip audio generation" 28 type: boolean 29 default: false 30 dry_run: 31 description: "dry run (no file changes)" 32 type: boolean 33 default: false 34 pull_request: 35 types: [closed] 36 branches: [main] 37 38jobs: 39 # phase 1: run letta-backed maintenance script + open PR 40 maintain: 41 if: github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' 42 runs-on: ubuntu-latest 43 permissions: 44 contents: write 45 pull-requests: write 46 47 steps: 48 - uses: actions/checkout@v4 49 with: 50 fetch-depth: 0 51 52 - uses: astral-sh/setup-uv@v4 53 54 - name: Run letta-backed status maintenance 55 id: maintenance 56 run: | 57 ARGS="" 58 if [ "${{ inputs.skip_audio }}" = "true" ]; then 59 ARGS="$ARGS --skip-audio" 60 fi 61 if [ "${{ inputs.dry_run }}" = "true" ]; then 62 ARGS="$ARGS --dry-run" 63 fi 64 65 uv run scripts/status_maintenance.py $ARGS 66 67 # check if files were modified 68 if [ -f .modified_files ]; then 69 echo "files_modified=true" >> $GITHUB_OUTPUT 70 else 71 echo "files_modified=false" >> $GITHUB_OUTPUT 72 fi 73 env: 74 LETTA_API_KEY: ${{ secrets.LETTA_API_KEY }} 75 ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} 76 GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} 77 78 - name: Create PR 79 if: steps.maintenance.outputs.files_modified == 'true' && inputs.dry_run != true 80 run: | 81 # generate unique branch name 82 BRANCH="status-maintenance-$(date +%Y%m%d-%H%M%S)" 83 84 # configure git 85 git config user.name "github-actions[bot]" 86 git config user.email "github-actions[bot]@users.noreply.github.com" 87 88 # create branch and commit 89 git checkout -b "$BRANCH" 90 git add .status_history/ STATUS.md update.wav 2>/dev/null || true 91 git add -A # catch any other changes 92 93 # check if there are changes to commit 94 if git diff --cached --quiet; then 95 echo "No changes to commit" 96 exit 0 97 fi 98 99 git commit -m "chore: status maintenance (letta-backed) 100 101 🤖 Generated with letta-backed status agent 102 " 103 104 git push -u origin "$BRANCH" 105 106 # create PR 107 gh pr create \ 108 --title "chore: status maintenance - $(date +'%B %d, %Y')" \ 109 --body "## automated status maintenance 110 111 this PR was generated by the letta-backed status maintenance agent. 112 113 ### what's included 114 - STATUS.md updates (if any changes were detected) 115 - archived content moved to .status_history/ (if needed) 116 - update.wav podcast audio (if audio generation was enabled) 117 118 ### letta memory 119 the agent maintains persistent memory across runs, improving context 120 and consistency over time. 121 122 --- 123 🤖 generated by status-maintenance workflow" \ 124 --label "ai-generated" || gh label create "ai-generated" --description "Generated by AI" && gh pr create \ 125 --title "chore: status maintenance - $(date +'%B %d, %Y')" \ 126 --body "automated status maintenance PR" \ 127 --label "ai-generated" 128 env: 129 GH_TOKEN: ${{ github.token }} 130 131 # phase 2: upload audio after PR merge 132 upload-audio: 133 if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'status-maintenance-') 134 runs-on: ubuntu-latest 135 136 steps: 137 - uses: actions/checkout@v4 138 139 - uses: astral-sh/setup-uv@v4 140 141 - name: Upload audio to plyr.fm 142 run: | 143 if [ ! -f update.wav ]; then 144 echo "No update.wav found, skipping upload" 145 exit 0 146 fi 147 148 # check existing tracks to determine episode number 149 EXISTING=$(uv run --with plyrfm -- plyrfm my-tracks --limit 50 2>/dev/null || echo "") 150 TODAY=$(date +'%B %d, %Y') 151 YEAR=$(date +%Y) 152 153 # count how many "plyr.fm update - {date}" tracks exist for today 154 TODAY_COUNT=$(echo "$EXISTING" | grep -c "plyr.fm update - $TODAY" || echo "0") 155 156 if [ "$TODAY_COUNT" -gt 0 ]; then 157 # already have one today, add episode number 158 EPISODE=$((TODAY_COUNT + 1)) 159 TITLE="plyr.fm update - $TODAY (#$EPISODE)" 160 else 161 TITLE="plyr.fm update - $TODAY" 162 fi 163 164 echo "Uploading as: $TITLE" 165 uv run --with plyrfm -- plyrfm upload update.wav "$TITLE" --album "$YEAR" -t '["ai"]' 166 env: 167 PLYR_TOKEN: ${{ secrets.PLYR_BOT_TOKEN }}