Issue to deployment¶
End-to-end path from a GitHub issue to code running in production on the operator host. This is the label-gated Cursor agent flow plus the Watchtower delivery layer that follows every merge to main.
For day-to-day contributor rules, see CONTRIBUTING.md. For repo settings and secrets, see REPO_SETTINGS.md.
At a glance¶
flowchart TB
subgraph intake["1 · Intake"]
A["Open GitHub issue<br/>(Agent task template optional)"]
B["@radgey-cmd reviews"]
C{"Label?"}
A --> B --> C
end
subgraph agent["2 · Cursor cloud agent"]
D["GitHub Action:<br/>cursor-issue-triage.yml"]
E["Cursor API starts agent<br/>branch ai-triage/fix-issue-N"]
F["Agent implements + opens PR<br/>Fixes #N in body"]
G["Issue gets ai-triage-enqueued"]
C -->|ai-triage or ai-safe| D --> E --> F --> G
C -->|human-needed| H["Human implements<br/>normal PR flow"]
end
subgraph ci["3 · CI & merge"]
I["Required check: ci<br/>(test, gitleaks, semgrep, audit)"]
J{"no-automerge or<br/>human-needed?"}
K["pr-automerge.yml<br/>mark ready + squash auto-merge"]
L["Wait / manual merge"]
F --> I
H --> I
I --> J
J -->|no| K
J -->|yes| L
end
subgraph ship["4 · Ship (release + image)"]
M["PR merged to main<br/>(auto or human)"]
N["ship-main.yml<br/>pull_request closed + push"]
O["create-release-if-needed.sh<br/>feat → minor tag, fix → patch"]
P["Build + push GHCR :latest"]
M --> N --> O --> P
end
subgraph deploy["5 · Production deploy"]
Q["watchtower-minutely<br/>scope=minutely, label-enable"]
R["Recreate jellybot container<br/>~/docker/jellybot"]
P --> Q --> R
end
subgraph announce["6 · Discord announce"]
S["Bot on_ready one-shot"]
T{"Major/minor<br/>release?"}
U["Embed to NOTIFICATION_CHANNEL_ID<br/>+ Feature credits from GitHub"]
V["Patch: silent DB bump only"]
R --> S --> T
T -->|yes| U
T -->|patch| V
end
K --> M
L --> M
style intake fill:#1e293b,stroke:#64748b,color:#f8fafc
style agent fill:#312e81,stroke:#818cf8,color:#f8fafc
style ci fill:#14532d,stroke:#4ade80,color:#f8fafc
style ship fill:#7c2d12,stroke:#fb923c,color:#f8fafc
style deploy fill:#713f12,stroke:#fbbf24,color:#f8fafc
style announce fill:#581c87,stroke:#c084fc,color:#f8fafc
Roles¶
| Role | Who / what | Responsibility |
|---|---|---|
| Operator | You | File issues, set acceptance criteria, merge non-automated PRs, prod .env |
| Triage | @radgey-cmd |
Adds ai-triage or ai-safe when an issue is ready for Cursor |
| Cursor cloud agent | cursor.com/agents | Implements on ai-triage/fix-issue-N, opens PR with Fixes #N |
| GitHub Actions | Workflows under .github/workflows/ |
Enqueue agent, run CI, optional auto-merge |
| Watchtower | watchtower-minutely on operator host |
Pulls new :latest and recreates labeled containers ~every 60s |
Phase 1 — Intake¶
- Open an issue describing the goal, constraints, and acceptance criteria. The Agent task template (
.github/ISSUE_TEMPLATE/agent_task.yml) is a good starting point. - Do not add
ai-triageyourself. Triage is intentional:@radgey-cmdreviews scope and risk first. - Guard labels (manual):
ai-investigate-only,ai-no-db,ai-no-auth,human-needed,no-automerge.human-neededandno-automergeblock auto-merge.
Phase 2 — Agent enqueue¶
When @radgey-cmd adds ai-triage or ai-safe:
- Workflow:
.github/workflows/cursor-issue-triage.yml - Trigger:
issues.labeled— only from senderradgey-cmd, only those two labels. - Action: POST to Cursor Cloud Agents API (
CURSOR_API_KEYrepo secret). - Branch contract:
ai-triage/fix-issue-{number}. - Marker: label
ai-triage-enqueuedon the issue (prevents duplicate starts). - Discord ops: pipeline stuck/failed posts to
DISCORD_BOTSPAM_CHANNEL_ID(#botspam) from the running bot — never to the feature suggestions / movies channel.
Monitor the run at cursor.com/agents (Cloud Agents — not Cursor Dashboard Automations, which are PR-scoped).
One-time setup: Cursor GitHub integration for this repo + CURSOR_API_KEY in repo secrets. See REPO_SETTINGS.md.
Phase 3 — Pull request & CI¶
The agent (or a human) opens a PR to main. Substantive PRs need:
- Non-empty Summary and Test plan
Fixes #NorCloses #Nwhen the issue should close on merge
Required check: aggregate job ci (Docker test suite, bun audit, gitleaks, Semgrep).
Auto-merge (all PRs when CI + scope review are green)¶
.github/workflows/pr-automerge.yml enables squash auto-merge when all of:
| Condition | |
|---|---|
| Base branch | main |
| Head repo | same repository (not a fork) |
| Blockers | PR and linked issues must not have human-needed or no-automerge |
| CI | required checks ci + scope-review = success |
| Merge state | mergeable |
Draft PRs are marked ready for review automatically when eligible.
Draft agent PRs are marked ready for review automatically. Issues labeled ai-triage without ai-safe still need a human squash merge after green CI.
Phase 4 — Ship (release + container image)¶
Important: Merges performed via PR auto-merge (GitHub Actions enables
--auto, GitHub squash-merges when CI is green) do not emitpushorpull_requestworkflow events — soship-main.ymlwill not start from itson:triggers alone..github/workflows/pr-automerge.ymlpolls until the PR is merged, thenworkflow_dispatches Ship main. Human squash merges via the GitHub UI still emitpushand run Ship main directly.
When a PR merges to main, Ship main:
- Runs
scripts/create-release-if-needed.sh— if conventionalfeat:commits landed since the last tag, cuts a new minor GitHub Release;fix:only → patch release (Discord-silent on boot). - Builds and pushes
ghcr.io/introvrt-lounge/jellybotwith tags:latest,:main,:sha-<commit>, and the semver tag when a release was created.
Manual semver tag pushes still build via .github/workflows/docker-image.yml.
Package: github.com/introVRt-Lounge/jellybot/pkgs/container/jellybot
Phase 5 — Production deploy (automatic)¶
Production compose lives on the operator host at ~/docker/jellybot/ (not the dev checkout). The running service:
# ~/docker/jellybot/docker-compose.yml (excerpt)
image: ghcr.io/introvrt-lounge/jellybot:latest
labels:
- com.centurylinklabs.watchtower.enable=true
- com.centurylinklabs.watchtower.scope=minutely
watchtower-minutely (server core stack) runs with --interval 60 --scope minutely --label-enable. When :latest digest changes, Watchtower recreates jellybot — no manual docker compose pull in normal ops.
| Event | Prod container updates? |
|---|---|
Merge to main (Ship main runs) |
Yes — :latest within ~60s after image push |
| Patch GitHub Release only | Deploy yes; Discord announce silent |
.env change only |
No — until next recreate (or manual compose up -d --force-recreate) |
Dev tree ~/coding/jellybot-dev: use make dev-refresh for local container jellybot-dev. See DEVELOPMENT.md.
Phase 6 — Release announce¶
On ClientReady (once per container start), the bot checks GitHub Releases:
- Compare latest tag to
last_announced_releaseinbot-state.db - Patch releases: update DB silently — no Discord post
- Major/minor: optional grace period, summarize notes (OpenAI if configured), post embed to
NOTIFICATION_CHANNEL_ID - Feature credits: embed field listing
feat:commits in the release range with GitHub display names (PR author when(#NNN)present)
Details: architecture.md — Production release announce.
Human vs agent quick reference¶
| Step | Agent path | Human path |
|---|---|---|
| Issue filed | Yes | Yes |
| Radgey labels | ai-safe or ai-triage |
Optional |
| Implementation | Cursor cloud agent | Human or agent |
| Merge | Auto after green ci (unless no-automerge) |
Same |
| Deploy | Watchtower on :latest |
Same |
| Announce | On next major/minor boot | Same |
Troubleshooting¶
| Symptom | Likely cause | Check |
|---|---|---|
| Label added, no agent | Wrong labeler, workflow error, missing CURSOR_API_KEY |
Actions → Cursor Issue Triage; issue labels |
| Agent runs, no PR | Still working or failed in Cursor UI; issue closed before bless | cursor.com/agents; /feature status; issue pipeline comment |
| PR green, not merging | no-automerge / human-needed, fork PR, or CI still pending |
Actions → PR auto-merge |
| Merged, prod unchanged | Ship main did not run (pre-fix) or Watchtower | Actions → Ship main; docker logs watchtower-minutely |
| Merged, no Discord post | Patch-only release or announce already recorded | Check latest GitHub Release semver; bot-state.db |
Stuck at building in Discord |
No pipeline telemetry before #85 | /feature status; GitHub issue Jellybot pipeline status comment; Actions → Feature pipeline watchdog |
Pipeline observability (#85)¶
Every blessed suggestion should be traceable end-to-end:
| Layer | What it records |
|---|---|
SQLite feature_pipeline_events |
Stage transitions from bot reconcile loop |
Discord /feature status |
Live checklist + blocker for maintainers |
| GitHub issue comment | Auto-updated jellybot-pipeline-status table (watchdog workflow) |
| GitHub issue comment | jellybot-pipeline-agent-id: when Cursor agent starts |
| GitHub issue comment | jellybot-agent-conversation when agent finishes (Cursor agent conversation archive workflow) |
| Actions | Feature pipeline watchdog updates GitHub issue comments only (no Discord webhook) |
#82 class failure: branch pushed, no PR, issue already closed — stage awaiting_pr, blocker explains manual PR or reopen issue.
Related files¶
| File | Purpose |
|---|---|
.github/workflows/cursor-issue-triage.yml |
Start Cursor agent from issue labels |
.github/workflows/cursor-agent-conversation.yml |
Poll agent; post /conversation transcript to issue |
scripts/archive-cursor-agent-conversation.sh |
Archive script (also workflow_dispatch with skip_poll=true for backfill) |
.github/workflows/pr-scope-review.yml |
LLM scope + quality gate (scope-review) |
.github/workflows/pr-automerge.yml |
Auto-merge when ci + scope-review green |
.github/workflows/ci.yml |
Required ci gate |
.github/workflows/ship-main.yml |
Release tag + GHCR image after merge to main |
.github/workflows/docker-image.yml |
Manual semver tag image builds |
scripts/create-release-if-needed.sh |
Conventional-commit semver release helper |
deploy/prod/docker-compose.yml |
Production compose template |
src/release/release-announcer.ts |
Discord release embeds |
docs/DEVELOPMENT.md |
Dev vs prod trees |
docs/architecture.md |
Data paths, announce config |