Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
904b1f6
docs: design native Discord task control
Jul 17, 2026
7d17724
docs: plan native Discord task control
Jul 17, 2026
06070d7
chore: ignore local worktrees
Jul 17, 2026
00f86e0
chore: ignore development ledger
Jul 17, 2026
25d63f7
feat(runtime): route source-less Discord tasks persistently
Jul 17, 2026
9e4778e
fix(runtime): preserve trusted Discord task routing
Jul 17, 2026
6600444
docs: design passive GitHub Discord intake
Jul 17, 2026
ad4586c
feat(runtime): recover Codex app-server sessions
Jul 17, 2026
54a2400
fix(runtime): close recovery races
Jul 17, 2026
fae374e
fix(runtime): guard recovery cancellation
Jul 17, 2026
ca54cc8
feat(discord): persist task lifecycle state
Jul 17, 2026
ec9774d
fix(discord): harden durable task invariants
Jul 17, 2026
ed156ea
test(discord): await spinner frames deterministically
Jul 17, 2026
b91b615
fix(discord): preserve task store invariants
Jul 17, 2026
530bba4
feat(discord): authorize task actions and classify failures
Jul 17, 2026
cd99a64
fix(discord): bind task actions to interaction channel
Jul 17, 2026
bc35dbb
feat(discord): bound task attachments and delivery retries
Jul 17, 2026
6d77b66
fix(discord): harden attachment and delivery ownership
Jul 17, 2026
6443ff8
fix(discord): enforce bounded delivery ownership
Jul 17, 2026
3329b84
docs: harden Discord execution contracts
Jul 17, 2026
3a7a092
docs: remove automatic GitHub write paths
Jul 17, 2026
8df6ae4
docs: pin offline GitHub review policy
Jul 17, 2026
c12e4df
feat(discord): coordinate durable agent tasks
Jul 17, 2026
5589f50
fix(discord): harden task retry and completion
Jul 17, 2026
f73e9d5
fix(discord): close task action races
Jul 17, 2026
49e7843
fix(discord): preserve terminal task state on shutdown
Jul 17, 2026
8568e62
fix(discord): keep stop responsive
Jul 17, 2026
8d67a6d
feat(discord): add persistent task cards and controls
Jul 17, 2026
d1ff5cf
fix(discord): harden persistent task controls
Jul 17, 2026
fe2beb6
feat(discord): add native task commands
Jul 17, 2026
8847c57
feat(runtime): enforce restricted app server policies
Jul 17, 2026
5afb77e
feat(discord): wire native task control into bot
Jul 17, 2026
b848b13
feat(discord): create threads for substantial tasks
Jul 17, 2026
f20ff00
test(discord): consolidate task contract matrices
Jul 17, 2026
05a7325
feat(discord): mirror GitHub intake cards
Jul 17, 2026
c04ecd9
fix(github): harden mirror state consistency
Jul 17, 2026
6c9c5b7
fix(github): fence mirror publication state
Jul 17, 2026
f5b69dc
fix(github): reconcile passive mirror delivery
Jul 17, 2026
a8bae7e
fix(github): recover passive mirror delivery
Jul 17, 2026
0b44292
fix(github): harden passive mirror delivery
Jul 17, 2026
abd342e
test(discord): cover combined mirror startup
Jul 17, 2026
b45da17
feat(discord): add durable GitHub action boundary
Jul 17, 2026
8d86839
feat(discord): persist typed task execution intent
Jul 17, 2026
b362f07
feat(discord): launch typed tasks from GitHub cards
Jul 17, 2026
1b9e94c
fix(runtime): isolate restricted GitHub tasks
Jul 17, 2026
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
11 changes: 1 addition & 10 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,32 +1,23 @@
DISCORD_TOKEN=
DISCORD_GUILD_ID=
# Required for GitHub notifications and poller summaries. Not needed for mention-only testing.
# Required for passive GitHub notification mirrors. Not needed for mention-only testing.
DISCORD_PR_CHANNEL_ID=

# Required only when configuring GitHub webhooks.
GITHUB_WEBHOOK_SECRET=
GITHUB_TOKEN=
GITHUB_REPOSITORY=
GITHUB_POLL_ENABLED=false
GITHUB_POLL_INTERVAL_SECONDS=1800
GITHUB_POLL_LIMIT=20

DISCORD_MESSAGE_AGENT_ENABLED=true
DISCORD_ATTACHMENT_DIR=/tmp/studyos-discord-attachments
DISCORD_ARTIFACT_ALLOWED_ROOTS=/tmp/studyos-artifacts,/workspaces,/workspace
DISCORD_ARTIFACT_MAX_BYTES=8000000
DISCORD_PROACTIVE_AGENT_ENABLED=false
DISCORD_PROACTIVE_INTERVAL_SECONDS=900
DISCORD_PROACTIVE_RECENT_ACTIVITY_SECONDS=1800
DISCORD_PROACTIVE_MIN_POST_INTERVAL_SECONDS=3600
DISCORD_PROACTIVE_DRY_RUN=true

# Use either an external webhook agent or a local CLI runner.
AGENT_WEBHOOK_URL=
AGENT_COMMAND=
AGENT_WORKDIR=
AGENT_TIMEOUT_SECONDS=900
AGENT_AUTO_REVIEW_ENABLED=false
AGENT_CHANNEL_SESSIONS_ENABLED=true
AGENT_SESSION_STORE_PATH=
AGENT_DISCORD_WORKTREE_ROOT=/workspaces/.studyos-discord-worktrees
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ dist/
.learnings/
.journal/
.secrets/
.worktrees/
.superpowers/
67 changes: 25 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ Discord and GitHub gateway for shared StudyOS coding agents.

`StudyOS Agent Gateway` connects the StudyOS Discord server with GitHub pull requests, issues, and agent workflows. It is a gateway tool for the StudyOS course monorepo, not the course repository itself.

The goal is to give the whole StudyOS cohort one shared interface to a few deployed coding-agent instances. Not every participant needs their own Codex or Claude subscription. A small number of authenticated agent servers can listen to Discord messages, GitHub webhooks, and scheduled triage jobs, then help with issues, reviews, pull requests, and repository maintenance through the same GitHub and Discord surfaces everyone already uses.
The goal is to give the whole StudyOS cohort one shared interface to a few deployed coding-agent instances. Not every participant needs their own Codex or Claude subscription. A small number of authenticated agent servers can handle explicit Discord requests while passive GitHub webhook cards keep issues and pull requests visible in the same collaboration surface.

The Python service receives Discord mentions and optional GitHub webhooks, then invokes the configured agent command. Repository writes are done by the authenticated agent runtime through tools like `gh`, while implementation starts, PR approval, and PR merges remain human-gated.
Discord mentions and explicit card actions invoke the configured agent command. GitHub webhooks only update passive Discord mirrors; they never invoke the agent. Repository writes are therefore tied to a human action, while implementation starts, PR approval, and PR merges remain human-gated.

## Features

Expand All @@ -24,18 +24,16 @@ The Python service receives Discord mentions and optional GitHub webhooks, then
- FastAPI webhook endpoint for GitHub `pull_request`, `issues`, and `issue_comment` events.
- HMAC verification for GitHub webhook payloads.
- Configurable Discord channel for PR and issue notifications.
- Read-only GitHub REST client for polling open PRs and issues.
- Durable, restart-reconciled Discord mirrors for GitHub events.
- Agent runner through `AGENT_COMMAND`, or external bridge through `AGENT_WEBHOOK_URL`.
- Local `studyos-discord-context` tool so agents can fetch recent channel context on demand.
- Codex channel sessions: Discord channels can resume their own persisted Codex CLI session.
- Discord attachment handoff: uploaded files are saved locally and image attachments are passed to Codex with `-i` when possible.
- Discord artifact uploads: agents can return generated diagrams, images, or documents for the bot to post back into the channel.
- `studyos-render-diagram` helper for rendering Graphviz DOT diagrams to PNG/SVG/PDF inside the agent image.
- Disabled-by-default, high-signal proactive monitor for private `group-*` spaces; it stays silent unless a settled technical blocker has a concrete answer.
- Short Discord-native replies, with long Markdown, fenced code, logs, and structured write-ups moved into attachments automatically.
- Optional PR review summaries and issue refinement prompts on GitHub webhook events.
- Periodic GitHub triage loop for open PRs and issues.
- Seeded Codex app automations for triage, review nudges, issue refinement, implementation candidate discovery, a coordinator heartbeat, and a weekly digest.
- Human-triggered PR review, security review, vulnerability scan, and implementation actions from mirror cards.
- Paused Codex app automation templates for optional reporting workflows.
- Docker and Docker Compose setup, including an agent image with `gh`, `git`, SSH, Node/npm, and Codex CLI installed.
- Shared-agent deployment model for StudyOS course participants.

Expand All @@ -45,20 +43,22 @@ The Python service receives Discord mentions and optional GitHub webhooks, then
flowchart TD
Discord["Discord mentions"] --> Bot["Discord bot"]
GitHub["Optional GitHub webhooks"] --> API["FastAPI webhook"]
API --> Queue["async event queue"]
Queue --> Bot
Poller["GitHub poller"] --> Bot
API --> Store["durable mirror store"]
Store --> Queue["reconciliation queue"]
Queue --> Mirror["passive card publisher"]
Mirror --> Channel["Discord GitHub channel"]
Bot --> Runner["Agent runner"]
Runner --> Bot
Runner --> Codex["Codex CLI or other agent CLI"]
Codex --> Workspaces["/workspaces cloned repos"]
Codex --> GHCLI["authenticated gh CLI"]
GHCLI --> GitHubRepo["GitHub issues and PRs"]
Bot --> Channel["Discord GitHub channel"]
Bot --> Channel
Automations["Codex automations"] --> CodexHome["$CODEX_HOME"]
Memory["StudyOS memory"] --> CodexHome
```

The gateway owns Discord, webhook intake, lightweight polling, prompt assembly, and delivery back to Discord. The agent runtime owns reasoning and repository work in cloned repositories under `/workspaces`. Codex app automations are seeded into the Codex automation path; their TOML `status` controls whether a deployment with a real Codex automation runner executes them.
The gateway owns Discord, authenticated webhook intake, durable mirror reconciliation, prompt assembly for explicit user actions, and delivery back to Discord. The agent runtime owns reasoning and repository work in cloned repositories under `/workspaces`. Codex app automation templates are seeded paused; their TOML `status` must be changed explicitly before a separate automation runner can execute them.

## Quick Start

Expand Down Expand Up @@ -190,25 +190,17 @@ docker compose -f docker-compose.agent.yml exec studyos-agent-gateway codex logi
| --- | --- |
| `DISCORD_TOKEN` | Discord bot token |
| `DISCORD_GUILD_ID` | Optional guild ID used to clear old commands faster |
| `DISCORD_PR_CHANNEL_ID` | Optional Discord channel ID for GitHub notification mirrors and poller summaries |
| `DISCORD_PR_CHANNEL_ID` | Discord channel ID for passive GitHub notification mirrors |
| `GITHUB_WEBHOOK_SECRET` | Secret configured on the GitHub webhook |
| `GITHUB_TOKEN` | Optional fallback token; `gh auth login` is the preferred agent-server path |
| `GITHUB_REPOSITORY` | Default repository in `owner/name` form |
| `GITHUB_POLL_ENABLED` | Periodically asks the agent to triage open PRs/issues |
| `GITHUB_POLL_INTERVAL_SECONDS` | Poll interval, for example `900` or `1800` |
| `DISCORD_MESSAGE_AGENT_ENABLED` | Enables mention-based Discord collaboration |
| `DISCORD_ATTACHMENT_DIR` | Local directory for Discord message attachments |
| `DISCORD_ARTIFACT_ALLOWED_ROOTS` | Comma-separated roots the bot may upload files from |
| `DISCORD_ARTIFACT_MAX_BYTES` | Maximum generated file size for Discord upload |
| `DISCORD_PROACTIVE_AGENT_ENABLED` | Enables the opt-in high-signal monitor for private `group-*` channels and their threads |
| `DISCORD_PROACTIVE_INTERVAL_SECONDS` | Proactive monitor interval |
| `DISCORD_PROACTIVE_RECENT_ACTIVITY_SECONDS` | Maximum age of latest human message before a channel is skipped |
| `DISCORD_PROACTIVE_MIN_POST_INTERVAL_SECONDS` | Per-channel cooldown after the bot sends a proactive reply |
| `DISCORD_PROACTIVE_DRY_RUN` | Logs proactive replies instead of sending them |
| `AGENT_COMMAND` | Local agent CLI command, prompt is passed on stdin |
| `AGENT_WORKDIR` | Working directory for the agent command |
| `AGENT_TIMEOUT_SECONDS` | Max runtime for one agent invocation |
| `AGENT_AUTO_REVIEW_ENABLED` | Runs the agent on useful GitHub webhook events |
| `AGENT_CHANNEL_SESSIONS_ENABLED` | Resume one Codex session per Discord channel when `AGENT_COMMAND` is `codex exec` |
| `AGENT_SESSION_STORE_PATH` | Optional override for the Discord channel to Codex session JSON store |
| `AGENT_DISCORD_WORKTREE_ROOT` | Per Discord channel/thread root for parallel Codex worktrees |
Expand All @@ -235,26 +227,23 @@ Recommended events if you want webhook-triggered updates:

Set the webhook content type to `application/json` and use the same secret as `GITHUB_WEBHOOK_SECRET`.

For mention-only testing, you can skip GitHub webhooks. Set `GITHUB_WEBHOOK_SECRET`
and `AGENT_AUTO_REVIEW_ENABLED=true` when you want GitHub events to trigger the
agent. Set `DISCORD_PR_CHANNEL_ID` only when you also want webhook notifications
or scheduled triage summaries mirrored into Discord.
For mention-only testing, you can skip GitHub webhooks. A configured webhook
requires `GITHUB_WEBHOOK_SECRET`, `DISCORD_GUILD_ID`, and
`DISCORD_PR_CHANNEL_ID`. Signed events update passive mirror cards. Only a
Discord user clicking a card action or explicitly mentioning the bot can start
agent work.

## GitHub Permissions

The preferred deployment path is `gh auth login` inside the agent container or on the host. The Python GitHub client will use `GITHUB_TOKEN` when set, otherwise it will try `gh auth token`.

For a fine-grained token fallback used by polling, grant only the repositories you need:

- Pull requests: read
- Issues: read
- Metadata: read
The passive webhook mirror does not require a GitHub API token. Use `gh auth
login` inside the agent container only for human-triggered agent work, and scope
that identity to the repositories and operations participants actually need.

Agent-side write access, if enabled through `gh auth login`, should be governed by branch protection and the runtime prompt. The bot does not expose a merge command. StudyOS students approve and merge PRs manually through GitHub.

## Agent Runner

The bot does not embed one specific agent framework. Instead, Discord mentions, optional webhooks, and the poller call one configured runner.
The bot does not embed one specific agent framework. Discord mentions and explicit mirror-card actions call one configured runner.

Examples:

Expand All @@ -266,20 +255,14 @@ AGENT_COMMAND="/opt/picoclaw/bin/picoclaw run --stdin"

For Codex, authenticate once in the agent container or mount an existing `CODEX_HOME`. For GitHub, authenticate with `gh auth login` in the same container. For Claude Code, authenticate on the deployment machine or use its supported long-lived token setup. The point is to run a few trusted StudyOS agent instances for the cohort, while keeping repository writes protected by branch protection, review norms, and GitHub token scopes.

The simplest operating mode does not require GitHub webhooks: run the authenticated CLI runtime and let Codex periodically inspect issues, comments, and PRs with `gh`. Webhooks are only a low-latency trigger when that is worth the extra setup.
The simplest operating mode does not require GitHub webhooks: participants mention the bot for scoped work. Add webhooks when a passive, low-latency Discord view of GitHub activity is useful.

Implementation is intentionally human-gated. The agent may refine issues, propose plans, summarize PRs, and answer review questions. It should start a branch or PR only after a human explicitly asks for implementation in Discord or a GitHub issue comment. It must never merge PRs.

## Scheduled Work

Set `GITHUB_POLL_ENABLED=true` to make the bot check open PRs and issues every `GITHUB_POLL_INTERVAL_SECONDS`. The poller builds one triage prompt and sends it to the configured agent runner. That is the right place for tasks like:

- summarize stale PRs
- unify duplicate issues
- ask refinement questions on blocked work
- invite reviewers for new PRs

Codex automations are seeded under `$CODEX_HOME/automations/`:
The gateway has no periodic GitHub agent loop. Codex automation templates are
seeded under `$CODEX_HOME/automations/`, paused by default:

- `studyos-github-triage`: inspect issues, PRs, comments, and review activity every 30 minutes.
- `studyos-pr-review-nudge`: find PRs that need review attention every 2 hours.
Expand Down
3 changes: 2 additions & 1 deletion codex/automations/studyos-coordinator-thread/automation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ PRs.
Only report when there is something actionable: blocked PRs, unclear tickets,
missing reviewers, ready implementation candidates, or a useful course-level
coordination update. Do not start implementation unless a human explicitly asked
the agent to implement a specific issue."""
the agent to implement a specific issue. Do not comment, label, assign, close,
edit, or otherwise mutate GitHub from this heartbeat."""
status = "PAUSED"
rrule = "FREQ=MINUTELY;INTERVAL=30"
model = "gpt-5.6-sol"
Expand Down
1 change: 1 addition & 0 deletions codex/automations/studyos-github-triage/automation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Goals:
- Suggest next implementation steps only when scope is clear.
- Do not start implementation, create branches, or create PRs from this
unattended triage run.
- Do not comment, label, assign, close, edit, or otherwise mutate GitHub.
- Treat implementation as human-gated: a student must explicitly ask the agent
to implement an issue in Discord or in a GitHub issue comment.
- Never merge PRs. Humans approve and merge.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ criteria, and expected behavior are clear. Propose a small PR plan and tests.
Do not implement from this automation. Implementation requires a separate human
trigger such as a Discord mention asking the bot to implement an issue or a
GitHub issue comment that explicitly asks the agent to start. Never merge PRs.
Do not comment, label, assign, close, edit, or otherwise mutate GitHub.

Output ready candidates, why they are ready, and any remaining risks."""
status = "PAUSED"
Expand Down
3 changes: 2 additions & 1 deletion codex/automations/studyos-issue-refinement/automation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ Find issues that are vague, duplicated, too broad, missing acceptance criteria,
or blocked on product/UX/API decisions. Ask concise clarifying questions and
suggest issue consolidation when useful. Do not start implementation from this
automation.
Do not comment, label, assign, close, edit, or otherwise mutate GitHub.

Output issue numbers and the exact refinement comment or next question that
Output issue numbers and a draft refinement comment or next question that
would unblock the students."""
status = "PAUSED"
rrule = "RRULE:FREQ=HOURLY;INTERVAL=6"
Expand Down
1 change: 1 addition & 0 deletions codex/automations/studyos-pr-review-nudge/automation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ waiting for CI. Recommend reviewer attention without shaming anyone. Prefer
inviting 2 student reviewers for feature PRs and 1 reviewer for docs or small
maintenance PRs. Prefer channel nudges over direct pings unless reviewers are
already assigned or the PR is blocked. Never merge PRs.
Do not comment, label, assign, close, edit, or otherwise mutate GitHub.

Output a Discord-friendly summary with PR numbers, review status, suggested
reviewers if visible from GitHub metadata, and the smallest next action."""
Expand Down
3 changes: 2 additions & 1 deletion codex/automations/studyos-weekly-digest/automation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ prompt = """Create a weekly StudyOS repository digest from GitHub activity.

Summarize merged PRs, open PRs needing review, issues created or refined, stale
or duplicate tickets, blockers, and suggested next milestones for the course.
Keep it constructive and friendly. Never merge PRs."""
Keep it constructive and friendly. Never merge PRs. Do not comment, label,
assign, close, edit, or otherwise mutate GitHub."""
status = "PAUSED"
rrule = "RRULE:FREQ=WEEKLY;BYDAY=TH;BYHOUR=16;BYMINUTE=0"
model = "gpt-5.6-sol"
Expand Down
Loading
Loading