Control Kōan directly from GitHub PR and issue comments using @mention commands.
Introduced in: PR #251 — 10 commits, 6 new modules, 102 tests.
Instead of switching to Telegram to tell Kōan to rebase a PR or review an issue, you can post a comment on the PR/issue itself:
@koan-bot rebase
Kōan polls GitHub notifications, detects the @mention, validates the command and the user's permissions, reacts with 👍 to acknowledge, and queues a mission — all without webhooks or external services.
In instance/config.yaml:
github:
nickname: "koan-bot" # Your bot's GitHub username (required)
commands_enabled: true # Master switch
authorized_users: ["*"] # "*" = anyone with write access, or ["alice", "bob"]
max_age_hours: 24 # Ignore notifications older than this (default: 24)Kōan uses the gh CLI for all GitHub API calls. Verify it works:
gh auth status
gh api notifications --paginate | head@koan-bot rebase
Kōan will:
- React with 👍 on the comment (acknowledgment)
- Create a pending mission:
- [project:myapp] /rebase https://github.com/owner/repo/pull/42 - Execute it in the next agent loop iteration
| Command | Aliases | What it does | Context-aware |
|---|---|---|---|
rebase |
rb |
Rebase a PR onto latest upstream | No |
recreate |
rc |
Recreate a diverged PR from scratch | No |
review |
rv |
Queue a code review for a PR or issue | No |
implement |
impl |
Implement a GitHub issue | Yes |
refactor |
rf |
Queue a refactoring mission | No |
Some commands accept additional context after the command word. For example:
@koan-bot implement phase 1 only
This creates a mission: /implement https://github.com/owner/repo/issues/42 phase 1 only
Only skills with github_context_aware: true in their SKILL.md receive the extra context. For other commands, trailing text is ignored.
If the context contains a GitHub URL, it overrides the default subject URL from the notification:
@koan-bot implement https://github.com/owner/other-repo/issues/99 phase 2
github:
nickname: "koan-bot" # Bot's GitHub @mention name (required if enabled)
commands_enabled: false # Master switch (default: false)
authorized_users: ["*"] # Allowlist: "*" for all with write access, or explicit usernames
max_age_hours: 24 # Stale notification threshold (default: 24 hours)nickname: The GitHub username Kōan uses. Must match the account behindGH_TOKEN. This is the@nameusers will mention.commands_enabled: Feature toggle. Whenfalse, notification polling is completely skipped.authorized_users: Controls who can trigger commands. Even with["*"], Kōan always verifies the user has write access to the repository via the GitHub API. This prevents drive-by command injection from random commenters.max_age_hours: Notifications older than this are silently discarded. Protects against processing a backlog of stale mentions after downtime.
Override authorized_users for specific repositories:
projects:
sensitive-repo:
path: "/path/to/sensitive-repo"
github:
authorized_users: ["alice", "bob"] # Only these users, not the global wildcardThis is useful when the global config allows ["*"] but a specific repo needs tighter control.
| Variable | Purpose |
|---|---|
GH_TOKEN |
GitHub authentication for the gh CLI (required) |
GITHUB_USER |
Override bot username for API calls (optional, falls back to github.nickname) |
The feature spans 6 modules in koan/app/:
loop_manager.py ← Polls during sleep cycle (throttled)
↓
github_notifications.py ← Fetches & filters notifications, parses @mentions
↓
github_command_handler.py ← Validates commands, checks permissions, creates missions
↓
github_config.py ← Reads config.yaml / projects.yaml settings
↓
github_skill_helpers.py ← Shared URL extraction, project resolution, mission queuing
↓
skills.py ← Skill flags: github_enabled, github_context_aware
1. Sleep cycle tick → process_github_notifications()
2. Fetch unread notifications (reason: "mention", filtered to known repos)
3. For each notification:
a. Skip if stale (> max_age_hours)
b. Fetch triggering comment
c. Skip if self-mention (bot's own comments)
d. Check in-memory + reaction-based deduplication
e. Parse @mention → extract (command, context)
f. Validate command → skill must have github_enabled: true
g. Check user permission → allowlist + GitHub write access
h. Insert mission into missions.md (BEFORE reacting — crash-safe)
i. React with 👍 on comment (marks as processed)
j. Mark notification thread as read
Two-tier approach to prevent duplicate missions:
- In-memory set:
_processed_commentstracks comment IDs within a session. Fast, but lost on restart. - GitHub 👍 reaction: Persistent marker. On restart, Kōan checks if it already reacted before processing.
The mission is inserted before the reaction is added. If Kōan crashes between these two steps, the worst case is a duplicate mission — never a lost command.
Notifications are checked during the agent's interruptible sleep cycle, with exponential backoff:
| Condition | Check interval |
|---|---|
| Notifications found | 60 seconds (base) |
| 1 empty check | 120 seconds |
| 2 consecutive empty | 240 seconds |
| 3+ consecutive empty | 300 seconds (cap) |
Backoff resets immediately when any notification is found. This reduces unnecessary API calls during quiet periods while maintaining fast response when activity resumes.
When a command fails validation (unknown command, permission denied), Kōan:
- Posts an error reply on the GitHub comment thread (❌ with explanation)
- Includes the list of available commands for "unknown command" errors
- Deduplicates error replies to avoid spam
@mentions inside code blocks are ignored:
Here's an example:
```
@koan-bot rebase ← This is NOT processed
```
@koan-bot rebase ← This IS processedAny skill can opt into GitHub @mention triggering by adding flags to its SKILL.md:
---
name: my-skill
github_enabled: true # Allow triggering via @mentions
github_context_aware: true # Pass extra text as context (optional)
commands:
- name: my-command
description: "Does something useful"
handler: handler.py
---The skill's handler receives the same SkillContext whether triggered from Telegram or GitHub. The mission format is identical: /my-command <url> [context].
See koan/skills/README.md for the full skill authoring guide.
Every command goes through two gates:
- Allowlist check: User must be in
authorized_users(or wildcard*is set) - Write access verification: Even with wildcard auth, Kōan always calls the GitHub API to verify the user has
writeoradminpermission on the repository
This means a random person commenting @koan-bot rebase on a public repo will be rejected — they need actual write access, not just the ability to comment.
Notifications older than max_age_hours (default: 24h) are silently discarded and marked as read. This prevents processing an accumulated backlog after extended downtime.
Comments posted by the bot itself are always ignored, preventing infinite loops.
The mission is written to missions.md before the 👍 reaction is added. This guarantees:
- No lost commands: If Kōan crashes after writing the mission but before reacting, the mission persists. On restart, it will re-process the notification but find the mission already exists.
- At-most-once reaction: The reaction serves as a durable "processed" marker.
- Check feature is enabled:
commands_enabled: truein config.yaml - Verify nickname matches:
github.nicknamemust match the GitHub account behindGH_TOKEN - Check notification visibility:
gh api notifications --paginateshould show the mention - Check logs:
make logs— look forGitHub:log entries - Verify write access: The commenting user needs write/admin permission on the repo
The 👍 means Kōan acknowledged the command and created a mission. Check:
instance/missions.md— the mission should be in the Pending section- Agent loop logs — the mission will be picked up in the next iteration
The repo must be configured in projects.yaml with a valid path. Kōan resolves the notification's repository against known projects. If there's no match, it can't determine where to execute.
Expected behavior when Kōan was interrupted between mission creation and reaction. The duplicate will be harmless — the agent detects already-completed missions.
- Skills README — Skill authoring guide with
github_enabledflag documentation - Messaging: Telegram — Alternative command interface via Telegram
- Messaging: Slack — Alternative command interface via Slack
- PR #251 — Original implementation
- Issue #243 — Feature request and design plan