feat(phase3): add notification system and kugetsu notify command
Phase 3c implementation - Notification System: ### New kugetsu commands: - `kugetsu notify list` - Show unread notifications from PM Agent - `kugetsu notify clear` - Mark notifications as read ### Notification system: - PM Agent writes task events to ~/.kugetsu/notifications.json - Events: task_complete, task_blocked, task_assigned - Supports issue_ref and gitea_url for linking - Hermes/Chat Agent reads notifications on user messages ### kugetsu-pm v2.0: - Updated documentation with notification behavior - PM Agent monitors Gitea for task completion - Two review modes: PM reviews immediately OR asks dev if ready - Notification triggers documented ### File renamed: - phase3a-setup.md → kugetsu-chat-setup.md (more descriptive) ### Hermes gateway analysis: - Gateway is a client (connects to Telegram), not a server - Cannot push messages directly to Telegram from external process - Notifications stored locally for Hermes to pick up on next user message
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
name: kugetsu-pm
|
name: kugetsu-pm
|
||||||
description: PM (Project Manager) Agent skill for kugetsu. Handles task coordination, delegation, and Gitea integration.
|
description: PM (Project Manager) Agent skill for kugetsu. Handles task coordination, delegation, and notifications.
|
||||||
license: MIT
|
license: MIT
|
||||||
compatibility: Requires kugetsu CLI, opencode sessions, Gitea API access.
|
compatibility: Requires kugetsu CLI, opencode sessions, Gitea API access.
|
||||||
metadata:
|
metadata:
|
||||||
author: shoko
|
author: shoko
|
||||||
version: "1.0"
|
version: "2.0"
|
||||||
---
|
---
|
||||||
|
|
||||||
# kugetsu-pm - PM Agent Skill
|
# kugetsu-pm - PM Agent Skill
|
||||||
@@ -18,37 +18,44 @@ The PM Agent is a persistent opencode session managed by kugetsu. It:
|
|||||||
|
|
||||||
1. **Receives** task requests from Chat Agent (via Hermes)
|
1. **Receives** task requests from Chat Agent (via Hermes)
|
||||||
2. **Coordinates** task execution via Dev Agents
|
2. **Coordinates** task execution via Dev Agents
|
||||||
3. **Monitors** Gitea for issue updates
|
3. **Monitors** Gitea for issue/PR updates
|
||||||
4. **Notifies** users of task completion (if in notify mode)
|
4. **Notifies** users of task completion and status changes
|
||||||
5. **Maintains** context across interactions
|
5. **Maintains** context across interactions
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
Chat Agent (Hermes/Telegram)
|
User (Telegram) → Hermes → Chat Agent → PM Agent
|
||||||
│
|
│
|
||||||
├── Routes task requests
|
├── kugetsu start → Dev Agent
|
||||||
│
|
│ └── Work on issue
|
||||||
▼
|
│
|
||||||
PM Agent (opencode session via kugetsu)
|
└── notify ← Gitea (optional)
|
||||||
│
|
└── ~/.kugetsu/notifications.json
|
||||||
├── Creates Dev Agent sessions via kugetsu
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
Dev Agents (opencode sessions via kugetsu)
|
|
||||||
│
|
|
||||||
├── Work on issues autonomously
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
Gitea (Issues, PRs, Comments)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## PM Agent Modes
|
## Notification System
|
||||||
|
|
||||||
| Mode | Behavior | Command |
|
PM Agent notifies users via two channels:
|
||||||
|------|----------|---------|
|
|
||||||
| **notify** (default) | Send completion notifications | "pm notify" |
|
### 1. Local Notifications (Default)
|
||||||
| **silent** | Work quietly, no notifications | "pm silent" |
|
- PM Agent writes to `~/.kugetsu/notifications.json`
|
||||||
|
- Hermes/Chat Agent reads this file when user sends a message
|
||||||
|
- User can view with `kugetsu notify list`
|
||||||
|
|
||||||
|
### 2. Gitea Comments (When Available)
|
||||||
|
- If task is issue/PR-related, PM Agent posts to Gitea
|
||||||
|
- User receives notification via Gitea's native notification system
|
||||||
|
- If Gitea is unavailable, PM Agent logs to notifications.json with a note
|
||||||
|
|
||||||
|
### Notification Triggers
|
||||||
|
|
||||||
|
| Event | Action |
|
||||||
|
|-------|--------|
|
||||||
|
| Task assigned to Dev Agent | Write to notifications.json |
|
||||||
|
| Task completed (PR merged/closed) | Write to notifications.json + Gitea |
|
||||||
|
| Task blocked | Write to notifications.json |
|
||||||
|
| Dev agent needs review | Two options: review immediately OR ask dev if ready |
|
||||||
|
|
||||||
## Task Flow
|
## Task Flow
|
||||||
|
|
||||||
@@ -79,77 +86,73 @@ PM Agent decides:
|
|||||||
kugetsu start <issue-ref> "<task description>"
|
kugetsu start <issue-ref> "<task description>"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. Monitor and Notify
|
### 5. Monitor for Completion
|
||||||
|
|
||||||
- PM monitors Gitea for PR status
|
PM Agent monitors Gitea for task completion by checking:
|
||||||
- When complete, notifies user (if in notify mode)
|
- Issue comments
|
||||||
|
- PR commits
|
||||||
|
- PR status (merged/open)
|
||||||
|
|
||||||
## Gitea Integration
|
**Query pattern for completion:**
|
||||||
|
```bash
|
||||||
### Context Fetching
|
# Check if issue/PR has recent activity
|
||||||
|
curl -s "https://git.fbrns.co/api/v1/repos/{owner}/{repo}/issues/{number}/comments"
|
||||||
PM Agent fetches from Gitea when:
|
# Check for commits in PR branch
|
||||||
- Initial task load (no context)
|
|
||||||
- Explicit request (agent decides)
|
|
||||||
- Insufficient context
|
|
||||||
|
|
||||||
### Context Merge Strategy
|
|
||||||
|
|
||||||
- **Default**: Append new context to existing
|
|
||||||
- **Threshold**: Summarize + replace at 40% of context window
|
|
||||||
|
|
||||||
## Session Context
|
|
||||||
|
|
||||||
PM Agent maintains:
|
|
||||||
|
|
||||||
### Managed Repositories
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"repos": [
|
|
||||||
"github.com/shoko/kugetsu",
|
|
||||||
"gitlab.com/team/core"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Active Tasks
|
### 6. Review Decision
|
||||||
```json
|
|
||||||
{
|
|
||||||
"tasks": {
|
|
||||||
"issue-5": {
|
|
||||||
"status": "in_progress",
|
|
||||||
"dev_agent": "ses_xyz789",
|
|
||||||
"created_at": "2026-03-30T10:00:00Z"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Notification Preferences
|
When dev agent signals completion, PM Agent chooses:
|
||||||
|
|
||||||
|
**Option A: Review immediately**
|
||||||
|
- PM Agent reviews the PR/changes
|
||||||
|
- If good, merges or approves
|
||||||
|
- If issues, posts review comments
|
||||||
|
|
||||||
|
**Option B: Ask dev if ready**
|
||||||
|
- PM Agent posts comment: "Dev work complete. Please review and confirm if ready for merge."
|
||||||
|
- Waits for dev agent confirmation
|
||||||
|
- Then proceeds with review
|
||||||
|
|
||||||
|
### 7. Notify on Completion
|
||||||
|
|
||||||
|
After task completion:
|
||||||
|
1. Write to `~/.kugetsu/notifications.json`
|
||||||
|
2. Post to Gitea issue/PR comment (if available)
|
||||||
|
3. If Gitea fails, note in notifications.json
|
||||||
|
|
||||||
|
**Notification format:**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"mode": "notify"
|
"type": "task_complete",
|
||||||
|
"message": "Issue #5 fixed. PR #12 created and merged.",
|
||||||
|
"issue_ref": "github.com/shoko/kugetsu#5",
|
||||||
|
"gitea_url": "https://git.fbrns.co/shoko/kugetsu/pulls/12",
|
||||||
|
"timestamp": "2026-03-31T10:00:00Z"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Delegation Commands
|
## Delegation Commands
|
||||||
|
|
||||||
### Create Dev Agent Session
|
### Send message to PM Agent
|
||||||
|
```bash
|
||||||
|
kugetsu delegate "<message>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create Dev Agent Session
|
||||||
```bash
|
```bash
|
||||||
kugetsu start <issue-ref> "<task>"
|
kugetsu start <issue-ref> "<task>"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Continue Dev Agent Session
|
### Continue Dev Agent Session
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kugetsu continue <issue-ref> "<update>"
|
kugetsu continue <issue-ref> "<update>"
|
||||||
```
|
```
|
||||||
|
|
||||||
### List Active Sessions
|
### Check Notifications
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kugetsu list
|
kugetsu notify list
|
||||||
|
kugetsu notify clear
|
||||||
```
|
```
|
||||||
|
|
||||||
## Response Format
|
## Response Format
|
||||||
@@ -165,6 +168,7 @@ PM Agent responses should be:
|
|||||||
"Created task for issue #5. Dev agent started."
|
"Created task for issue #5. Dev agent started."
|
||||||
"Issue #5 is complete. PR created: [link]"
|
"Issue #5 is complete. PR created: [link]"
|
||||||
"Task blocked: Need clarification on requirements."
|
"Task blocked: Need clarification on requirements."
|
||||||
|
"Dev work ready for review. Merging PR #12."
|
||||||
```
|
```
|
||||||
|
|
||||||
## Error Handling
|
## Error Handling
|
||||||
@@ -172,7 +176,7 @@ PM Agent responses should be:
|
|||||||
### Dev Agent Failure
|
### Dev Agent Failure
|
||||||
- Analyze failure reason
|
- Analyze failure reason
|
||||||
- Retry or escalate to user
|
- Retry or escalate to user
|
||||||
- Log to Gitea issue comment
|
- Log to notifications.json
|
||||||
|
|
||||||
### Session Not Found
|
### Session Not Found
|
||||||
- Check kugetsu status: `kugetsu list`
|
- Check kugetsu status: `kugetsu list`
|
||||||
@@ -182,18 +186,7 @@ PM Agent responses should be:
|
|||||||
### Gitea API Errors
|
### Gitea API Errors
|
||||||
- Retry with backoff
|
- Retry with backoff
|
||||||
- Cache last known state
|
- Cache last known state
|
||||||
- Inform user if persistent
|
- Log to notifications.json (user will be notified there)
|
||||||
|
|
||||||
## Skills
|
|
||||||
|
|
||||||
### kugetsu (for session management)
|
|
||||||
- Session creation and continuation
|
|
||||||
- Worktree management
|
|
||||||
|
|
||||||
### github (for Gitea API)
|
|
||||||
- Issue fetching
|
|
||||||
- PR creation
|
|
||||||
- Comment posting
|
|
||||||
|
|
||||||
## Implementation Notes
|
## Implementation Notes
|
||||||
|
|
||||||
@@ -204,15 +197,36 @@ The PM Agent session is stored in:
|
|||||||
~/.kugetsu/index.json → "pm_agent" field
|
~/.kugetsu/index.json → "pm_agent" field
|
||||||
```
|
```
|
||||||
|
|
||||||
### Accessing PM Agent
|
### PM Context File (Optional)
|
||||||
|
|
||||||
```bash
|
Customize PM Agent behavior by creating:
|
||||||
PM_SESSION=$(cat ~/.kugetsu/index.json | python3 -c "import sys,json; print(json.load(sys.stdin).get('pm_agent', ''))")
|
```
|
||||||
opencode run --continue --session "$PM_SESSION" "<message>"
|
~/.kugetsu/pm-agent.md
|
||||||
|
```
|
||||||
|
|
||||||
|
This file is injected into the PM Agent session at init time.
|
||||||
|
|
||||||
|
### Notifications File
|
||||||
|
|
||||||
|
Location: `~/.kugetsu/notifications.json`
|
||||||
|
|
||||||
|
Format:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "task_complete|task_blocked|task_assigned",
|
||||||
|
"message": "Human-readable message",
|
||||||
|
"issue_ref": "github.com/user/repo#5",
|
||||||
|
"gitea_url": "https://git.fbrns.co/user/repo/pulls/5",
|
||||||
|
"timestamp": "ISO8601",
|
||||||
|
"read": false
|
||||||
|
}
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
|
|
||||||
- [kugetsu-architecture.md](../../docs/kugetsu-architecture.md)
|
- [kugetsu-architecture.md](../../docs/kugetsu-architecture.md)
|
||||||
- [kugetsu-chat.md](../../docs/kugetsu-chat.md)
|
- [kugetsu-chat.md](../../docs/kugetsu-chat.md)
|
||||||
|
- [kugetsu-chat-setup.md](../../docs/kugetsu-chat-setup.md)
|
||||||
- [hermes-setup.md](../../docs/hermes-setup.md)
|
- [hermes-setup.md](../../docs/hermes-setup.md)
|
||||||
@@ -6,6 +6,7 @@ SESSIONS_DIR="$KUGETSU_DIR/sessions"
|
|||||||
WORKTREES_DIR="$KUGETSU_DIR/worktrees"
|
WORKTREES_DIR="$KUGETSU_DIR/worktrees"
|
||||||
REPOS_CONFIG="$KUGETSU_DIR/repos.json"
|
REPOS_CONFIG="$KUGETSU_DIR/repos.json"
|
||||||
INDEX_FILE="$KUGETSU_DIR/index.json"
|
INDEX_FILE="$KUGETSU_DIR/index.json"
|
||||||
|
NOTIFICATIONS_FILE="$KUGETSU_DIR/notifications.json"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat << 'EOF'
|
cat << 'EOF'
|
||||||
@@ -18,6 +19,7 @@ Usage:
|
|||||||
kugetsu delegate <message> Send message to PM agent
|
kugetsu delegate <message> Send message to PM agent
|
||||||
kugetsu status Check kugetsu initialization status
|
kugetsu status Check kugetsu initialization status
|
||||||
kugetsu doctor [--fix] Diagnose and fix kugetsu issues
|
kugetsu doctor [--fix] Diagnose and fix kugetsu issues
|
||||||
|
kugetsu notify [list|clear] Show or clear notifications
|
||||||
kugetsu list List all tracked sessions
|
kugetsu list List all tracked sessions
|
||||||
kugetsu prune [--force] Remove orphaned sessions (keeps base + pm-agent)
|
kugetsu prune [--force] Remove orphaned sessions (keeps base + pm-agent)
|
||||||
kugetsu destroy <issue-ref> [-y] Delete session for issue
|
kugetsu destroy <issue-ref> [-y] Delete session for issue
|
||||||
@@ -39,6 +41,8 @@ Commands:
|
|||||||
PM context is loaded once at init time.
|
PM context is loaded once at init time.
|
||||||
status Check if kugetsu is initialized and PM agent is active.
|
status Check if kugetsu is initialized and PM agent is active.
|
||||||
doctor Diagnose kugetsu issues. Use --fix to attempt repairs.
|
doctor Diagnose kugetsu issues. Use --fix to attempt repairs.
|
||||||
|
notify Show or clear notifications from PM agent.
|
||||||
|
Use 'kugetsu notify list' to see unread notifications.
|
||||||
list Show all sessions (base + pm-agent + forked issues).
|
list Show all sessions (base + pm-agent + forked issues).
|
||||||
prune Remove sessions not in index (orphaned from opencode).
|
prune Remove sessions not in index (orphaned from opencode).
|
||||||
Use --force to skip confirmation.
|
Use --force to skip confirmation.
|
||||||
@@ -52,12 +56,18 @@ PM Context:
|
|||||||
into the PM agent session at init time. This allows customizing PM
|
into the PM agent session at init time. This allows customizing PM
|
||||||
behavior without recreating the session.
|
behavior without recreating the session.
|
||||||
|
|
||||||
|
Notifications:
|
||||||
|
PM Agent writes task completion notifications to ~/.kugetsu/notifications.json
|
||||||
|
Use 'kugetsu notify list' to see unread notifications.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
kugetsu init
|
kugetsu init
|
||||||
kugetsu status
|
kugetsu status
|
||||||
kugetsu delegate "work on issue #5"
|
kugetsu delegate "work on issue #5"
|
||||||
kugetsu doctor
|
kugetsu doctor
|
||||||
kugetsu doctor --fix
|
kugetsu doctor --fix
|
||||||
|
kugetsu notify list
|
||||||
|
kugetsu notify clear
|
||||||
kugetsu start github.com/shoko/kugetsu#14 "fix bug"
|
kugetsu start github.com/shoko/kugetsu#14 "fix bug"
|
||||||
kugetsu continue github.com/shoko/kugetsu#14 "add tests"
|
kugetsu continue github.com/shoko/kugetsu#14 "add tests"
|
||||||
kugetsu list
|
kugetsu list
|
||||||
@@ -305,6 +315,143 @@ kugetsu_get_pm_context() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kugetsu_add_notification() {
|
||||||
|
local type="$1"
|
||||||
|
local message="$2"
|
||||||
|
local issue_ref="${3:-}"
|
||||||
|
local gitea_url="${4:-}"
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$NOTIFICATIONS_FILE")"
|
||||||
|
|
||||||
|
local notification=$(python3 << PYEOF
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
notification = {
|
||||||
|
"type": "$type",
|
||||||
|
"message": "$message",
|
||||||
|
"issue_ref": "$issue_ref" if "$issue_ref" else None,
|
||||||
|
"gitea_url": "$gitea_url" if "$gitea_url" else None,
|
||||||
|
"timestamp": datetime.now().isoformat(),
|
||||||
|
"read": False
|
||||||
|
}
|
||||||
|
|
||||||
|
file_path = os.path.expanduser("$NOTIFICATIONS_FILE")
|
||||||
|
notifications = []
|
||||||
|
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r') as f:
|
||||||
|
notifications = json.load(f)
|
||||||
|
except:
|
||||||
|
notifications = []
|
||||||
|
|
||||||
|
notifications.append(notification)
|
||||||
|
|
||||||
|
with open(file_path, 'w') as f:
|
||||||
|
json.dump(notifications, f, indent=2)
|
||||||
|
|
||||||
|
print("Notification added")
|
||||||
|
PYEOF
|
||||||
|
)
|
||||||
|
echo "$notification"
|
||||||
|
}
|
||||||
|
|
||||||
|
kugetsu_get_notifications() {
|
||||||
|
local limit="${1:-10}"
|
||||||
|
|
||||||
|
if [ ! -f "$NOTIFICATIONS_FILE" ]; then
|
||||||
|
echo "[]"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 << PYEOF
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
file_path = os.path.expanduser("$NOTIFICATIONS_FILE")
|
||||||
|
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
print("[]")
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r') as f:
|
||||||
|
notifications = json.load(f)
|
||||||
|
|
||||||
|
unread = [n for n in notifications if not n.get("read", False)]
|
||||||
|
unread.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
|
||||||
|
|
||||||
|
for n in unread[:$limit]:
|
||||||
|
ts = n.get("timestamp", "unknown")
|
||||||
|
ntype = n.get("type", "info")
|
||||||
|
msg = n.get("message", "")
|
||||||
|
issue = n.get("issue_ref", "")
|
||||||
|
gitea = n.get("gitea_url", "")
|
||||||
|
|
||||||
|
print(f"[{ts}] {ntype}: {msg}")
|
||||||
|
if issue:
|
||||||
|
print(f" Issue: {issue}")
|
||||||
|
if gitea:
|
||||||
|
print(f" Link: {gitea}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
if not unread:
|
||||||
|
print("No unread notifications.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error reading notifications: {e}")
|
||||||
|
PYEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
kugetsu_clear_notifications() {
|
||||||
|
if [ ! -f "$NOTIFICATIONS_FILE" ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 << PYEOF
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
file_path = os.path.expanduser("$NOTIFICATIONS_FILE")
|
||||||
|
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r') as f:
|
||||||
|
notifications = json.load(f)
|
||||||
|
|
||||||
|
for n in notifications:
|
||||||
|
n["read"] = True
|
||||||
|
|
||||||
|
with open(file_path, 'w') as f:
|
||||||
|
json.dump(notifications, f, indent=2)
|
||||||
|
|
||||||
|
print("Notifications marked as read")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
PYEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_notify() {
|
||||||
|
local action="${1:-}"
|
||||||
|
|
||||||
|
case "$action" in
|
||||||
|
""|"list"|"show")
|
||||||
|
kugetsu_get_notifications 10
|
||||||
|
;;
|
||||||
|
"clear")
|
||||||
|
kugetsu_clear_notifications
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: kugetsu notify [list|clear]"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
cmd_status() {
|
cmd_status() {
|
||||||
if [ ! -f "$INDEX_FILE" ]; then
|
if [ ! -f "$INDEX_FILE" ]; then
|
||||||
echo "kugetsu_not_initialized"
|
echo "kugetsu_not_initialized"
|
||||||
@@ -978,6 +1125,9 @@ main() {
|
|||||||
doctor)
|
doctor)
|
||||||
cmd_doctor "$@"
|
cmd_doctor "$@"
|
||||||
;;
|
;;
|
||||||
|
notify)
|
||||||
|
cmd_notify "$@"
|
||||||
|
;;
|
||||||
list)
|
list)
|
||||||
cmd_list "$@"
|
cmd_list "$@"
|
||||||
;;
|
;;
|
||||||
|
|||||||
Reference in New Issue
Block a user