Skip to content
Closed
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
111 changes: 111 additions & 0 deletions .github/workflows/migrations.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
name: Migrations

on:
pull_request:
branches: [main]
paths:
- 'packages/server/src/db/**'
push:
branches: [main]
paths:
- 'packages/server/src/db/**'

concurrency:
group: migrations-${{ github.ref }}
cancel-in-progress: true

jobs:
validate:
name: Validate Migrations
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: ./.github/actions/setup
with:
build: 'true'

# Create a minimal wrangler.toml for local D1 operations
- name: Create temporary wrangler config
run: |
cat > wrangler.toml <<'EOF'
name = "relaycast-migration-check"
compatibility_date = "2024-12-01"

[[d1_databases]]
binding = "DB"
database_name = "relaycast"
database_id = "local"
migrations_dir = "packages/server/src/db/migrations"
EOF

- name: Apply migrations to local D1
run: npx wrangler d1 migrations apply DB --local

# Verify no uncommitted migration changes from schema drift
- name: Check for uncommitted migration changes
run: |
cd packages/server
npx drizzle-kit generate
cd ../..
if [ -n "$(git status --porcelain packages/server/src/db/migrations/)" ]; then
echo "::error::Uncommitted migration changes detected. Run 'npx drizzle-kit generate' locally and commit the results."
git diff packages/server/src/db/migrations/
exit 1
fi
echo "No uncommitted migration changes."

migration-check:
name: Incremental Migration Check
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
# Check out base branch and apply its migrations
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.sha }}

- uses: ./.github/actions/setup
with:
build: 'true'

- name: Create temporary wrangler config (base)
run: |
cat > wrangler.toml <<'EOF'
name = "relaycast-migration-check"
compatibility_date = "2024-12-01"

[[d1_databases]]
binding = "DB"
database_name = "relaycast"
database_id = "local"
migrations_dir = "packages/server/src/db/migrations"
EOF

- name: Apply base branch migrations
run: npx wrangler d1 migrations apply DB --local

# Check out PR branch overtop, preserving the local D1 database
- uses: actions/checkout@v4
with:
clean: false

- uses: ./.github/actions/setup
with:
build: 'true'

- name: Create temporary wrangler config (PR)
run: |
cat > wrangler.toml <<'EOF'
name = "relaycast-migration-check"
compatibility_date = "2024-12-01"

[[d1_databases]]
binding = "DB"
database_name = "relaycast"
database_id = "local"
migrations_dir = "packages/server/src/db/migrations"
EOF

- name: Apply PR migrations incrementally
run: npx wrangler d1 migrations apply DB --local
52 changes: 51 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -325,10 +325,59 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# Compact trajectories after release
compact-trajectories:
name: Compact Trajectories
needs: [build, create-release]
runs-on: ubuntu-latest
if: |
github.event.inputs.dry_run != 'true' &&
needs.create-release.result == 'success'

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"

- name: Install trail
run: npm install -g agent-trajectories

- name: Compact trajectories for release
run: |
NEW_VERSION="${{ needs.build.outputs.new_version }}"
# Get commits since previous tag
PREV_TAG=$(git tag --sort=-v:refname | grep '^v' | sed -n '2p')
OUTPUT=".trajectories/compacted/v${NEW_VERSION}.json"
mkdir -p .trajectories/compacted
if [ -n "$PREV_TAG" ]; then
COMMITS=$(git log "${PREV_TAG}..v${NEW_VERSION}" --format=%H | paste -sd, -)
if [ -n "$COMMITS" ]; then
npx trail compact --commits "$COMMITS" --output "$OUTPUT" || true
fi
else
npx trail compact --output "$OUTPUT" || true
fi

- name: Commit compacted trajectories
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add .trajectories/compacted/ || true
git diff --cached --quiet || \
(git commit -m "chore: compact trajectories for v${{ needs.build.outputs.new_version }}" && git push)

# Summary
summary:
name: Summary
needs: [build, publish-packages, publish-single, create-release]
needs: [build, publish-packages, publish-single, create-release, compact-trajectories]
runs-on: ubuntu-latest
if: always()

Expand All @@ -349,3 +398,4 @@ jobs:
echo "| Publish Packages | ${{ needs.publish-packages.result == 'success' && '✅' || (needs.publish-packages.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.publish-packages.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Publish Single | ${{ needs.publish-single.result == 'success' && '✅' || (needs.publish-single.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.publish-single.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Create Release | ${{ needs.create-release.result == 'success' && '✅' || (needs.create-release.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.create-release.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Compact Trajectories | ${{ needs.compact-trajectories.result == 'success' && '✅' || (needs.compact-trajectories.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.compact-trajectories.result }} |" >> $GITHUB_STEP_SUMMARY
95 changes: 95 additions & 0 deletions .trajectories/completed/2026-02/traj_6wned2xdggug.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{
"id": "traj_6wned2xdggug",
"version": 1,
"task": {
"title": "Workspace eviction: two-phase soft delete + hard delete"
},
"status": "completed",
"startedAt": "2026-02-21T10:36:30.207Z",
"agents": [
{
"name": "default",
"role": "lead",
"joinedAt": "2026-02-21T10:36:34.847Z"
}
],
"chapters": [
{
"id": "chap_h3qh4b72l29j",
"title": "Work",
"agentName": "default",
"startedAt": "2026-02-21T10:36:34.847Z",
"events": [
{
"ts": 1771670194848,
"type": "decision",
"content": "Two-phase eviction: suspend at 30 days inactivity (soft delete), hard delete at 60 days (30 days after suspend). This replaces the single-pass hard delete at 30 days.: Two-phase eviction: suspend at 30 days inactivity (soft delete), hard delete at 60 days (30 days after suspend). This replaces the single-pass hard delete at 30 days.",
"raw": {
"question": "Two-phase eviction: suspend at 30 days inactivity (soft delete), hard delete at 60 days (30 days after suspend). This replaces the single-pass hard delete at 30 days.",
"chosen": "Two-phase eviction: suspend at 30 days inactivity (soft delete), hard delete at 60 days (30 days after suspend). This replaces the single-pass hard delete at 30 days.",
"alternatives": [],
"reasoning": ""
},
"significance": "high"
},
{
"ts": 1771670200789,
"type": "decision",
"content": "Added suspendedAt column to workspaces table. touchWorkspaceActivity now clears suspendedAt to revive suspended workspaces on any activity.: Added suspendedAt column to workspaces table. touchWorkspaceActivity now clears suspendedAt to revive suspended workspaces on any activity.",
"raw": {
"question": "Added suspendedAt column to workspaces table. touchWorkspaceActivity now clears suspendedAt to revive suspended workspaces on any activity.",
"chosen": "Added suspendedAt column to workspaces table. touchWorkspaceActivity now clears suspendedAt to revive suspended workspaces on any activity.",
"alternatives": [],
"reasoning": ""
},
"significance": "high"
},
{
"ts": 1771670204868,
"type": "decision",
"content": "Added touchWorkspaceActivity to requireWorkspaceKey and requireAgentToken middleware (debounced, fire-and-forget) to fix missing activity tracking on those auth paths.: Added touchWorkspaceActivity to requireWorkspaceKey and requireAgentToken middleware (debounced, fire-and-forget) to fix missing activity tracking on those auth paths.",
"raw": {
"question": "Added touchWorkspaceActivity to requireWorkspaceKey and requireAgentToken middleware (debounced, fire-and-forget) to fix missing activity tracking on those auth paths.",
"chosen": "Added touchWorkspaceActivity to requireWorkspaceKey and requireAgentToken middleware (debounced, fire-and-forget) to fix missing activity tracking on those auth paths.",
"alternatives": [],
"reasoning": ""
},
"significance": "high"
},
{
"ts": 1771670211558,
"type": "decision",
"content": "R2 cleanup before hard delete: purgeWorkspaceR2 lists objects with workspace prefix and deletes in batches. Runs per-workspace during eviction phase 2.: R2 cleanup before hard delete: purgeWorkspaceR2 lists objects with workspace prefix and deletes in batches. Runs per-workspace during eviction phase 2.",
"raw": {
"question": "R2 cleanup before hard delete: purgeWorkspaceR2 lists objects with workspace prefix and deletes in batches. Runs per-workspace during eviction phase 2.",
"chosen": "R2 cleanup before hard delete: purgeWorkspaceR2 lists objects with workspace prefix and deletes in batches. Runs per-workspace during eviction phase 2.",
"alternatives": [],
"reasoning": ""
},
"significance": "high"
},
{
"ts": 1771670218924,
"type": "reflection",
"content": "All changes implemented and verified. Build passes, all 349 tests pass. Files modified: schema.ts (added suspendedAt), 0002_workspace_eviction.sql (added suspended_at column + index), eviction.ts (rewritten with two-phase logic + R2 cleanup), auth.ts (added touch to requireWorkspaceKey and requireAgentToken), worker.ts (updated cron to call both phases), test-helpers.ts and auth.test.ts (added lastActivityAt/suspendedAt to mocks).",
"significance": "high"
}
],
"endedAt": "2026-02-21T10:37:19.394Z"
}
],
"commits": [],
"filesChanged": [],
"projectId": "/Users/khaliqgant/Projects/agent-workforce/relaycast",
"tags": [],
"_trace": {
"startRef": "e36eb38ae271ddf374dfb10309e1dd0f7524e5b9",
"endRef": "e36eb38ae271ddf374dfb10309e1dd0f7524e5b9"
},
"completedAt": "2026-02-21T10:37:19.394Z",
"retrospective": {
"summary": "Implemented two-phase workspace eviction: soft delete (suspend) at 30 days, hard delete with R2 cleanup at 60 days. Fixed activity tracking gaps in requireWorkspaceKey and requireAgentToken middleware.",
"approach": "Schema-first: added suspendedAt column, updated migration SQL, rewrote eviction engine with suspend/evict/purge functions, patched auth middleware, updated cron handler. Fixed test mocks for new fields.",
"confidence": 0.95
}
}
27 changes: 27 additions & 0 deletions .trajectories/completed/2026-02/traj_6wned2xdggug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Trajectory: Workspace eviction: two-phase soft delete + hard delete

> **Status:** ✅ Completed
> **Confidence:** 95%
> **Started:** February 21, 2026 at 11:36 AM
> **Completed:** February 21, 2026 at 11:37 AM

---

## Summary

Implemented two-phase workspace eviction: soft delete (suspend) at 30 days, hard delete with R2 cleanup at 60 days. Fixed activity tracking gaps in requireWorkspaceKey and requireAgentToken middleware.

**Approach:** Schema-first: added suspendedAt column, updated migration SQL, rewrote eviction engine with suspend/evict/purge functions, patched auth middleware, updated cron handler. Fixed test mocks for new fields.

---

## Chapters

### 1. Work
*Agent: default*

- Two-phase eviction: suspend at 30 days inactivity (soft delete), hard delete at 60 days (30 days after suspend). This replaces the single-pass hard delete at 30 days.: Two-phase eviction: suspend at 30 days inactivity (soft delete), hard delete at 60 days (30 days after suspend). This replaces the single-pass hard delete at 30 days.
- Added suspendedAt column to workspaces table. touchWorkspaceActivity now clears suspendedAt to revive suspended workspaces on any activity.: Added suspendedAt column to workspaces table. touchWorkspaceActivity now clears suspendedAt to revive suspended workspaces on any activity.
- Added touchWorkspaceActivity to requireWorkspaceKey and requireAgentToken middleware (debounced, fire-and-forget) to fix missing activity tracking on those auth paths.: Added touchWorkspaceActivity to requireWorkspaceKey and requireAgentToken middleware (debounced, fire-and-forget) to fix missing activity tracking on those auth paths.
- R2 cleanup before hard delete: purgeWorkspaceR2 lists objects with workspace prefix and deletes in batches. Runs per-workspace during eviction phase 2.: R2 cleanup before hard delete: purgeWorkspaceR2 lists objects with workspace prefix and deletes in batches. Runs per-workspace during eviction phase 2.
- All changes implemented and verified. Build passes, all 349 tests pass. Files modified: schema.ts (added suspendedAt), 0002_workspace_eviction.sql (added suspended_at column + index), eviction.ts (rewritten with two-phase logic + R2 cleanup), auth.ts (added touch to requireWorkspaceKey and requireAgentToken), worker.ts (updated cron to call both phases), test-helpers.ts and auth.test.ts (added lastActivityAt/suspendedAt to mocks).
9 changes: 8 additions & 1 deletion .trajectories/index.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": 1,
"lastUpdated": "2026-02-21T00:57:30.181Z",
"lastUpdated": "2026-02-21T10:37:19.509Z",
"trajectories": {
"traj_o8g8ahqth5fx": {
"title": "Wave 0: add repo infrastructure files (docker, turbo, tsconfig, env)",
Expand Down Expand Up @@ -966,6 +966,13 @@
"startedAt": "2026-02-21T00:55:51.809Z",
"completedAt": "2026-02-21T00:57:30.112Z",
"path": "/Users/will/Projects/relaycast/.trajectories/completed/2026-02/traj_bic0crrp84gw.json"
},
"traj_6wned2xdggug": {
"title": "Workspace eviction: two-phase soft delete + hard delete",
"status": "completed",
"startedAt": "2026-02-21T10:36:30.207Z",
"completedAt": "2026-02-21T10:37:19.394Z",
"path": "/Users/khaliqgant/Projects/agent-workforce/relaycast/.trajectories/completed/2026-02/traj_6wned2xdggug.json"
}
}
}
Loading
Loading