Compare commits
1 Commits
v0.2.10
...
214a31e4bd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
214a31e4bd |
@@ -2,6 +2,36 @@ You are a PM (Project Manager) for software development.
|
|||||||
|
|
||||||
Your role is COORDINATOR. You break down requests, delegate work, monitor progress, and report results. You NEVER write code. Not even small fixes. Not even one-liners. Not even documentation. If asked to write code: delegate it using `kugetsu start`.
|
Your role is COORDINATOR. You break down requests, delegate work, monitor progress, and report results. You NEVER write code. Not even small fixes. Not even one-liners. Not even documentation. If asked to write code: delegate it using `kugetsu start`.
|
||||||
|
|
||||||
|
## Verbosity Control
|
||||||
|
|
||||||
|
You have three verbosity modes. The DEFAULT is **total** (silent mode):
|
||||||
|
|
||||||
|
### total (DEFAULT - RECOMMENDED)
|
||||||
|
- Work silently in background
|
||||||
|
- ONLY post final summary/results when done
|
||||||
|
- Do NOT post every action, glob, read, or edit
|
||||||
|
- Use logs for intermediate steps
|
||||||
|
- Post notification only on completion
|
||||||
|
|
||||||
|
### verbose (current/legacy)
|
||||||
|
- Post every glob, read, edit as it happens
|
||||||
|
- Very noisy - floods notifications
|
||||||
|
- Use only for debugging
|
||||||
|
|
||||||
|
### hybrid
|
||||||
|
- Post on errors only
|
||||||
|
- Quiet on success
|
||||||
|
- Only interrupt if something goes wrong
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Set via KUGETSU_VERBOSITY environment variable (default: total):
|
||||||
|
```
|
||||||
|
KUGETSU_VERBOSITY=total # silent, results only
|
||||||
|
KUGETSU_VERBOSITY=verbose # noisy, all actions
|
||||||
|
KUGETSU_VERBOSITY=hybrid # errors only
|
||||||
|
```
|
||||||
|
|
||||||
## Critical: How to Delegate
|
## Critical: How to Delegate
|
||||||
|
|
||||||
Use `kugetsu start` to create dev agent sessions:
|
Use `kugetsu start` to create dev agent sessions:
|
||||||
@@ -21,14 +51,35 @@ You are the PM. Your job is to coordinate, not to code.
|
|||||||
- You break down complex requests into delegate-able tasks
|
- You break down complex requests into delegate-able tasks
|
||||||
- You monitor progress and keep stakeholders informed
|
- You monitor progress and keep stakeholders informed
|
||||||
|
|
||||||
|
## Queue-Based Delegation (Phase 2)
|
||||||
|
|
||||||
|
You read tasks from the queue instead of waiting for direct commands. Priority order:
|
||||||
|
1. dev_followups (highest) - Dev completed work, follow-up needed
|
||||||
|
2. user_interrupts - User requested something
|
||||||
|
3. background (lowest) - Passive discovery tasks
|
||||||
|
|
||||||
|
### Queue Commands
|
||||||
|
```
|
||||||
|
~/.kugetsu/scripts/dequeue # Get next task (highest priority)
|
||||||
|
~/.kugetsu/scripts/queue-list # See pending tasks
|
||||||
|
~/.kugetsu/scripts/enqueue <tier> <msg> # Add to queue
|
||||||
|
```
|
||||||
|
|
||||||
|
### Polling Loop
|
||||||
|
The PM poll loop continuously polls the queue and assigns work:
|
||||||
|
```
|
||||||
|
~/.kugetsu/scripts/pm-poll-loop # Start daemon
|
||||||
|
```
|
||||||
|
|
||||||
## Delegation is Your Default Behavior
|
## Delegation is Your Default Behavior
|
||||||
|
|
||||||
When a request comes in:
|
When a request comes in:
|
||||||
|
|
||||||
1. **Understand** - What needs to be built? What's the repo and issue?
|
1. **Check Queue** - Use `dequeue` to get next task (respects priority)
|
||||||
2. **Delegate** - Use `kugetsu start <issue-ref> <task>` to create a dev agent task
|
2. **Understand** - What needs to be built? What's the repo and issue?
|
||||||
3. **Monitor** - Watch for PR creation and review
|
3. **Delegate** - Use `kugetsu start <issue-ref> <task>` to create a dev agent task
|
||||||
4. **Report** - Post final results to the issue
|
4. **Monitor** - Watch for PR creation and review
|
||||||
|
5. **Report** - Post final results to the issue
|
||||||
|
|
||||||
## Few-Shot Examples
|
## Few-Shot Examples
|
||||||
|
|
||||||
@@ -55,4 +106,4 @@ This is not just a rule - it is your identity. The code you coordinate is built
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*PM Agent v3 - Coordinators coordinate, we do not code. We delegate with `kugetsu start`.*
|
*PM Agent v4 - Coordinators coordinate, we do not code. Verbosity: total (silent mode, results only).*
|
||||||
50
skills/kugetsu/scripts/dequeue
Executable file
50
skills/kugetsu/scripts/dequeue
Executable file
@@ -0,0 +1,50 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# dequeue - Remove and return next task from queue
|
||||||
|
# Usage: dequeue [tier]
|
||||||
|
# If tier not specified, dequeues from highest priority (dev_followups > user_interrupts > background)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
QUEUE_FILE="$HOME/.kugetsu/queue.json"
|
||||||
|
TIER="${1:-}"
|
||||||
|
|
||||||
|
python3 << EOF
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
queue_file = os.path.expanduser("$QUEUE_FILE")
|
||||||
|
preferred_tier = "$TIER" if "$TIER" else None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(queue_file, 'r') as f:
|
||||||
|
queue = json.load(f)
|
||||||
|
except:
|
||||||
|
print("Queue empty")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
tiers = ["dev_followups", "user_interrupts", "background"]
|
||||||
|
if preferred_tier:
|
||||||
|
if preferred_tier not in tiers:
|
||||||
|
print(f"Error: Invalid tier '{preferred_tier}'", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
tiers = [preferred_tier]
|
||||||
|
|
||||||
|
task = None
|
||||||
|
dequeued_tier = None
|
||||||
|
|
||||||
|
for tier in tiers:
|
||||||
|
if queue.get(tier) and len(queue[tier]) > 0:
|
||||||
|
task = queue[tier].pop(0)
|
||||||
|
dequeued_tier = tier
|
||||||
|
break
|
||||||
|
|
||||||
|
if task is None:
|
||||||
|
print("Queue empty")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
with open(queue_file, 'w') as f:
|
||||||
|
json.dump(queue, f, indent=2)
|
||||||
|
|
||||||
|
print(f"{dequeued_tier}|{task['id']}|{task['message']}")
|
||||||
|
EOF
|
||||||
55
skills/kugetsu/scripts/enqueue
Executable file
55
skills/kugetsu/scripts/enqueue
Executable file
@@ -0,0 +1,55 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# enqueue - Add task to queue
|
||||||
|
# Usage: enqueue <tier> <message>
|
||||||
|
# Tier: dev_followups | user_interrupts | background
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
QUEUE_FILE="$HOME/.kugetsu/queue.json"
|
||||||
|
TIER="${1:-}"
|
||||||
|
MESSAGE="${2:-}"
|
||||||
|
|
||||||
|
if [ -z "$TIER" ] || [ -z "$MESSAGE" ]; then
|
||||||
|
echo "Usage: enqueue <tier> <message>" >&2
|
||||||
|
echo " tier: dev_followups | user_interrupts | background" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! "$TIER" =~ ^(dev_followups|user_interrupts|background)$ ]]; then
|
||||||
|
echo "Error: Invalid tier '$TIER'" >&2
|
||||||
|
echo "Valid tiers: dev_followups, user_interrupts, background" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ID="qe-$(date +%s)-$$"
|
||||||
|
|
||||||
|
python3 << EOF
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
queue_file = os.path.expanduser("$QUEUE_FILE")
|
||||||
|
tier = "$TIER"
|
||||||
|
message = "$MESSAGE"
|
||||||
|
task_id = "$ID"
|
||||||
|
|
||||||
|
task = {
|
||||||
|
"id": task_id,
|
||||||
|
"message": message,
|
||||||
|
"created": datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(queue_file, 'r') as f:
|
||||||
|
queue = json.load(f)
|
||||||
|
except:
|
||||||
|
queue = {"dev_followups": [], "user_interrupts": [], "background": []}
|
||||||
|
|
||||||
|
queue[tier].append(task)
|
||||||
|
|
||||||
|
with open(queue_file, 'w') as f:
|
||||||
|
json.dump(queue, f, indent=2)
|
||||||
|
|
||||||
|
print(f"Enqueued: [{tier}] {message} (id: {task_id})")
|
||||||
|
EOF
|
||||||
@@ -558,7 +558,7 @@ cmd_delegate() {
|
|||||||
echo "Error: Max concurrent agents ($MAX_CONCURRENT_AGENTS) reached. Try again later." >&2
|
echo "Error: Max concurrent agents ($MAX_CONCURRENT_AGENTS) reached. Try again later." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
nohup bash -c "source /home/shoko/.local/bin/kugetsu; opencode run --continue --session '$pm_session' '$message' >> '$log_file' 2>&1; release_agent_slot" > /dev/null 2>&1 &
|
nohup sh -c "opencode run --continue --session '$pm_session' '$message' >> '$log_file' 2>&1; ~/.kugetsu/release-slot.sh" > /dev/null 2>&1 &
|
||||||
disown
|
disown
|
||||||
echo "Delegated to PM agent (logged to $(basename "$log_file"))"
|
echo "Delegated to PM agent (logged to $(basename "$log_file"))"
|
||||||
}
|
}
|
||||||
|
|||||||
72
skills/kugetsu/scripts/pm-poll-loop
Executable file
72
skills/kugetsu/scripts/pm-poll-loop
Executable file
@@ -0,0 +1,72 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# pm-poll-loop - Continuous PM polling daemon
|
||||||
|
# Continuously polls queue and assigns work to dev agents
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
QUEUE_FILE="$HOME/.kugetsu/queue.json"
|
||||||
|
LOCK_FILE="$HOME/.kugetsu/.pm-poll.lock"
|
||||||
|
PID_FILE="$HOME/.kugetsu/.pm-poll.pid"
|
||||||
|
POLL_INTERVAL="${POLL_INTERVAL:-600}" # 10 minutes default
|
||||||
|
VERBOSITY="${KUGETSU_VERBOSITY:-total}"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
if [ "$VERBOSITY" = "verbose" ]; then
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
acquire_lock() {
|
||||||
|
local my_pid=$$
|
||||||
|
if [ -f "$PID_FILE" ]; then
|
||||||
|
local old_pid=$(cat "$PID_FILE" 2>/dev/null)
|
||||||
|
if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then
|
||||||
|
echo "Error: PM poll loop already running (PID: $old_pid)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "$my_pid" > "$PID_FILE"
|
||||||
|
log "PM poll loop started (PID: $my_pid)"
|
||||||
|
}
|
||||||
|
|
||||||
|
release_lock() {
|
||||||
|
rm -f "$PID_FILE"
|
||||||
|
log "PM poll loop stopped"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
release_lock
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
acquire_lock
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
# Try to dequeue from highest priority tier
|
||||||
|
result=$(~/.kugetsu/scripts/dequeue 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ -n "$result" ] && [ "$result" != "Queue empty" ]; then
|
||||||
|
tier=$(echo "$result" | cut -d'|' -f1)
|
||||||
|
task_id=$(echo "$result" | cut -d'|' -f2)
|
||||||
|
message=$(echo "$result" | cut -d'|' -f3-)
|
||||||
|
|
||||||
|
log "Dequeued: [$tier] $message"
|
||||||
|
|
||||||
|
# Extract issue ref if present, otherwise use generic
|
||||||
|
if [[ "$message" =~ (github\.com/[^/]+/[^/]+#[0-9]+) ]]; then
|
||||||
|
issue_ref="${BASH_REMATCH[1]}"
|
||||||
|
kugetsu start "$issue_ref" "$message"
|
||||||
|
else
|
||||||
|
# Use a generic issue if none specified
|
||||||
|
echo "Warning: No issue ref in message, skipping: $message" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Assigned task: $task_id"
|
||||||
|
else
|
||||||
|
log "Queue empty, waiting ${POLL_INTERVAL}s..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep "$POLL_INTERVAL"
|
||||||
|
done
|
||||||
45
skills/kugetsu/scripts/queue-list
Executable file
45
skills/kugetsu/scripts/queue-list
Executable file
@@ -0,0 +1,45 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# queue-list - List pending tasks in queue
|
||||||
|
# Usage: queue-list [tier]
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
QUEUE_FILE="$HOME/.kugetsu/queue.json"
|
||||||
|
TIER="${1:-}"
|
||||||
|
|
||||||
|
python3 << EOF
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
queue_file = os.path.expanduser("$QUEUE_FILE")
|
||||||
|
tier_filter = "$TIER" if "$TIER" else None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(queue_file, 'r') as f:
|
||||||
|
queue = json.load(f)
|
||||||
|
except:
|
||||||
|
queue = {"dev_followups": [], "user_interrupts": [], "background": []}
|
||||||
|
|
||||||
|
tiers = ["dev_followups", "user_interrupts", "background"]
|
||||||
|
|
||||||
|
for tier in tiers:
|
||||||
|
if tier_filter and tier_filter != tier:
|
||||||
|
continue
|
||||||
|
|
||||||
|
tasks = queue.get(tier, [])
|
||||||
|
count = len(tasks)
|
||||||
|
print(f"\n{tier} ({count}):")
|
||||||
|
|
||||||
|
if count == 0:
|
||||||
|
print(" (empty)")
|
||||||
|
else:
|
||||||
|
for task in tasks:
|
||||||
|
msg = task.get('message', '')[:60]
|
||||||
|
created = task.get('created', '')[:19]
|
||||||
|
print(f" [{task['id']}] {msg}")
|
||||||
|
print(f" created: {created}")
|
||||||
|
|
||||||
|
total = sum(len(queue.get(t, [])) for t in tiers)
|
||||||
|
print(f"\nTotal queued: {total}")
|
||||||
|
EOF
|
||||||
Reference in New Issue
Block a user