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:
shokollm
2026-03-31 02:19:50 +00:00
parent b3171ed632
commit 3d00ddbc1b
3 changed files with 255 additions and 91 deletions

View File

@@ -1,11 +1,11 @@
---
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
compatibility: Requires kugetsu CLI, opencode sessions, Gitea API access.
metadata:
author: shoko
version: "1.0"
version: "2.0"
---
# 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)
2. **Coordinates** task execution via Dev Agents
3. **Monitors** Gitea for issue updates
4. **Notifies** users of task completion (if in notify mode)
3. **Monitors** Gitea for issue/PR updates
4. **Notifies** users of task completion and status changes
5. **Maintains** context across interactions
## Architecture
```
Chat Agent (Hermes/Telegram)
├── Routes task requests
PM Agent (opencode session via kugetsu)
├── Creates Dev Agent sessions via kugetsu
Dev Agents (opencode sessions via kugetsu)
├── Work on issues autonomously
Gitea (Issues, PRs, Comments)
User (Telegram) → Hermes → Chat Agent → PM Agent
├── kugetsu start → Dev Agent
│ └── Work on issue
└── notify ← Gitea (optional)
└── ~/.kugetsu/notifications.json
```
## PM Agent Modes
## Notification System
| Mode | Behavior | Command |
|------|----------|---------|
| **notify** (default) | Send completion notifications | "pm notify" |
| **silent** | Work quietly, no notifications | "pm silent" |
PM Agent notifies users via two channels:
### 1. Local Notifications (Default)
- 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
@@ -79,77 +86,73 @@ PM Agent decides:
kugetsu start <issue-ref> "<task description>"
```
### 5. Monitor and Notify
### 5. Monitor for Completion
- PM monitors Gitea for PR status
- When complete, notifies user (if in notify mode)
PM Agent monitors Gitea for task completion by checking:
- Issue comments
- PR commits
- PR status (merged/open)
## Gitea Integration
### Context Fetching
PM Agent fetches from Gitea when:
- 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"
]
}
**Query pattern for completion:**
```bash
# Check if issue/PR has recent activity
curl -s "https://git.fbrns.co/api/v1/repos/{owner}/{repo}/issues/{number}/comments"
# Check for commits in PR branch
```
### Active Tasks
```json
{
"tasks": {
"issue-5": {
"status": "in_progress",
"dev_agent": "ses_xyz789",
"created_at": "2026-03-30T10:00:00Z"
}
}
}
```
### 6. Review Decision
### 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
{
"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
### Create Dev Agent Session
### Send message to PM Agent
```bash
kugetsu delegate "<message>"
```
### Create Dev Agent Session
```bash
kugetsu start <issue-ref> "<task>"
```
### Continue Dev Agent Session
```bash
kugetsu continue <issue-ref> "<update>"
```
### List Active Sessions
### Check Notifications
```bash
kugetsu list
kugetsu notify list
kugetsu notify clear
```
## Response Format
@@ -165,6 +168,7 @@ PM Agent responses should be:
"Created task for issue #5. Dev agent started."
"Issue #5 is complete. PR created: [link]"
"Task blocked: Need clarification on requirements."
"Dev work ready for review. Merging PR #12."
```
## Error Handling
@@ -172,7 +176,7 @@ PM Agent responses should be:
### Dev Agent Failure
- Analyze failure reason
- Retry or escalate to user
- Log to Gitea issue comment
- Log to notifications.json
### Session Not Found
- Check kugetsu status: `kugetsu list`
@@ -182,18 +186,7 @@ PM Agent responses should be:
### Gitea API Errors
- Retry with backoff
- Cache last known state
- Inform user if persistent
## Skills
### kugetsu (for session management)
- Session creation and continuation
- Worktree management
### github (for Gitea API)
- Issue fetching
- PR creation
- Comment posting
- Log to notifications.json (user will be notified there)
## Implementation Notes
@@ -204,15 +197,36 @@ The PM Agent session is stored in:
~/.kugetsu/index.json → "pm_agent" field
```
### Accessing PM Agent
### PM Context File (Optional)
```bash
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>"
Customize PM Agent behavior by creating:
```
~/.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
- [kugetsu-architecture.md](../../docs/kugetsu-architecture.md)
- [kugetsu-chat.md](../../docs/kugetsu-chat.md)
- [kugetsu-chat-setup.md](../../docs/kugetsu-chat-setup.md)
- [hermes-setup.md](../../docs/hermes-setup.md)

View File

@@ -6,6 +6,7 @@ SESSIONS_DIR="$KUGETSU_DIR/sessions"
WORKTREES_DIR="$KUGETSU_DIR/worktrees"
REPOS_CONFIG="$KUGETSU_DIR/repos.json"
INDEX_FILE="$KUGETSU_DIR/index.json"
NOTIFICATIONS_FILE="$KUGETSU_DIR/notifications.json"
usage() {
cat << 'EOF'
@@ -18,6 +19,7 @@ Usage:
kugetsu delegate <message> Send message to PM agent
kugetsu status Check kugetsu initialization status
kugetsu doctor [--fix] Diagnose and fix kugetsu issues
kugetsu notify [list|clear] Show or clear notifications
kugetsu list List all tracked sessions
kugetsu prune [--force] Remove orphaned sessions (keeps base + pm-agent)
kugetsu destroy <issue-ref> [-y] Delete session for issue
@@ -39,6 +41,8 @@ Commands:
PM context is loaded once at init time.
status Check if kugetsu is initialized and PM agent is active.
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).
prune Remove sessions not in index (orphaned from opencode).
Use --force to skip confirmation.
@@ -52,12 +56,18 @@ PM Context:
into the PM agent session at init time. This allows customizing PM
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:
kugetsu init
kugetsu status
kugetsu delegate "work on issue #5"
kugetsu doctor
kugetsu doctor --fix
kugetsu notify list
kugetsu notify clear
kugetsu start github.com/shoko/kugetsu#14 "fix bug"
kugetsu continue github.com/shoko/kugetsu#14 "add tests"
kugetsu list
@@ -305,6 +315,143 @@ kugetsu_get_pm_context() {
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() {
if [ ! -f "$INDEX_FILE" ]; then
echo "kugetsu_not_initialized"
@@ -978,6 +1125,9 @@ main() {
doctor)
cmd_doctor "$@"
;;
notify)
cmd_notify "$@"
;;
list)
cmd_list "$@"
;;