Compare commits

..

20 Commits

Author SHA1 Message Date
shokollm
4d3205de86 fix: remove obsolete slot-based concurrency mechanism
The session-counting approach (PR #65) now properly handles agent
concurrency limits. Remove the broken slot-based mechanism:

- Remove acquire_agent_slot() and release_agent_slot() functions
- Remove AGENT_COUNT_FILE and AGENT_LOCK_FILE variables
- Remove unused run_with_limit() function
- Remove release-slot.sh script
- Update cmd_delegate to use fire-and-forget without slot management

Both cmd_start and cmd_delegate now use count_active_dev_sessions()
for concurrency checking.
2026-04-01 06:56:45 +00:00
6c23d4f5e9 Merge pull request 'fix(pm): add explicit write permissions boundary (fixes #52)' (#55) from fix/issue-52-pm-write-boundaries into main 2026-04-01 08:09:31 +02:00
21e4054634 Merge pull request 'fix: implement session-counting for MAX_CONCURRENT_AGENTS limit (fixes #63)' (#65) from fix/issue-63-session-counting into main 2026-04-01 07:40:00 +02:00
shokollm
e38cf6bc8b docs: update benchmark with cloud architecture and memory analysis 2026-04-01 05:18:30 +00:00
shokollm
3cc2082a21 docs: update benchmark with session-counting and PM inclusion 2026-04-01 05:03:44 +00:00
shokollm
7ac4578369 fix: include PM in session count (all sessions count toward limit) 2026-04-01 04:57:52 +00:00
shokollm
7342a9a394 fix: implement session-counting for MAX_CONCURRENT_AGENTS limit (fixes #63)
Replaced broken slot-based mechanism with session-counting:

- Added count_active_dev_sessions() function that counts actual
  session files in ~/.kugetsu/sessions/, excluding base.json and pm-agent.json
- Modified cmd_start() to check session count before creating new session:
  - If count >= MAX_CONCURRENT_AGENTS, reject with error
  - Otherwise allow new session creation
- Removed wait  since --fork returns immediately
- cmd_continue() no longer counts toward limit (existing sessions
  can always continue via --continue)

This properly enforces MAX_CONCURRENT_AGENTS while preserving --fork
functionality. The slot mechanism didn't work because opencode run
--fork returns immediately after forking, not after child completes.
2026-04-01 04:14:15 +00:00
shokollm
bd4e8587b4 fix: enforce MAX_CONCURRENT_AGENTS limit properly (fixes #63)
- Fixed cmd_start() and cmd_continue() to wait for forked child
- Capture child PID with $! and wait before release_agent_slot
- This ensures slot is only released after child process completes
2026-04-01 03:05:13 +00:00
shokollm
bc60e644bf docs: add agent concurrency benchmark results 2026-04-01 01:46:27 +00:00
43aa1ac330 Merge pull request 'fix: replace --workdir with --dir for opencode CLI (fixes #60)' (#61) from fix/issue-60-workdir-to-dir into main 2026-04-01 03:35:45 +02:00
shokollm
a95d1d556d fix: replace --workdir with --dir for opencode CLI
Issue #60: kugetsu uses --workdir flag but opencode expects --dir.

Changed all 4 occurrences in cmd_start() and cmd_continue() functions.
2026-04-01 01:32:49 +00:00
79dc3ee3b9 Merge pull request 'fix: change git clone --bare to git clone in create_worktree (fixes #57)' (#59) from fix/issue-57-worktree-creation into main 2026-04-01 02:59:43 +02:00
shokollm
6ad51f3c0b fix: change git clone --bare to git clone in create_worktree()
Issue #57: worktree creation was creating bare repos instead of
proper worktrees, breaking parallel agent workflow.

The --bare flag created repos with no working directory, making them
useless for development. Changed to regular clone.
2026-04-01 00:51:05 +00:00
0d408e8fd8 Merge pull request 'fix: opencode message argument must come before flags in init' (#58) from fix/kugetsu-init-opencode-arg-order into main 2026-04-01 02:44:54 +02:00
shokollm
d5866d4b0f fix: opencode message argument must come before flags in init
PR #54 fixed opencode arg order in cmd_delegate and cmd_start,
but kugetsu init function still had message AFTER flags.

Fixed in:
- kugetsu_init function: message before --fork --session
- Similar patterns in session creation code

Closes related to #53
2026-04-01 00:00:05 +00:00
shokollm
71cab655fc make delegation format agnostic to git server
Replace hardcoded git.fbrns.co/shoko/kugetsu with dynamic
<domain>/<user>/<repo> format pulled from git remote and config.
Makes PM skill usable with github.com, gitlab.com, or any git server.
2026-03-31 22:20:15 +00:00
shokollm
cb0ada9e1c address PR #55 review: tighten write permissions to queue.json and logs/* only
- PM can ONLY write to ~/.kugetsu/queue.json and ~/.kugetsu/logs/* (was entire ~/.kugetsu/)
- Update delegation format to git.fbrns.co/shoko/kugetsu#<issue>
- PM must not write new kugetsu scripts - delegate via issue/PR workflow
- Update examples and violation cases to reflect stricter boundaries
2026-03-31 22:13:51 +00:00
shokollm
449dfaecc6 fix(pm): add explicit write permissions boundary to prevent repo file writes
Issue #52: PM violated NEVER write code constraint by writing directly to
repo files (SKILL.md) instead of delegating to a dev agent.

Added explicit Write Permissions section defining:
- PM can ONLY write to ~/.kugetsu/
- PM can NEVER write to repositories/*, skills/*, or any dir outside ~/.kugetsu/
- If asked to write outside ~/.kugetsu/, must delegate via kugetsu start
2026-03-31 22:00:16 +00:00
d126cf0f00 Merge pull request 'fix: opencode message argument must come before flags' (#54) from fix/opencode-arg-order into main 2026-03-31 23:35:34 +02:00
shokollm
251b22500c fix: opencode message argument must come before flags
opencode CLI requires: opencode run "message" --session sid --workdir /path
But kugetsu was placing message AFTER flags, causing all commands to fail.

Fixed in:
- cmd_delegate: nohup sh -c with message first
- cmd_start: --fork --session with message first
- fork_session_for_issue: --continue --session with message first

Closes #53
2026-03-31 21:29:37 +00:00
8 changed files with 197 additions and 363 deletions

View File

@@ -0,0 +1,123 @@
# Agent Concurrency Benchmark
**Date:** 2026-04-01
**Hardware:** 8GB RAM, 16 CPU cores
## Test Results
| Limit (PM+Dev) | Status | Rejection Test | Notes |
|----------------|--------|---------------|-------|
| 1 | ✓ Works | 1 dev rejected (PM=1, at limit) | Too strict for normal use |
| 3 | ✓ Works | 4th dev rejected (PM + 3 devs = 4, at limit) | Recommended |
| 5 | ✓ Works | 6th dev rejected (PM + 5 devs = 6, at limit) | Works, monitor memory |
## Architecture
OpenCode is a **cloud client** - agents run on OpenCode's server (MiniMax), not locally.
```
┌─────────────────┐ ┌─────────────────┐
│ Local Host │ │ OpenCode │
│ │ HTTPS │ Server │
│ kugetsu CLI │◄───────►│ (MiniMax) │
│ worktrees/ │ API │ Agents run │
│ sessions/ │ Key │ here │
│ opencode.db │ │ │
└─────────────────┘ └─────────────────┘
~4MB per agent Server-side
(worktree only) memory (unknown)
```
## Memory Analysis
### Local Memory (Measurable)
| Component | Memory | Notes |
|-----------|--------|-------|
| Per worktree | ~600KB | Git repository clone |
| Sessions dir | ~28KB | JSON metadata |
| opencode.db | ~93MB | Local cache (148 sessions, 10K+ messages) |
| **Total 5 agents** | **~4MB** | Worktrees only, negligible |
**Conclusion:** Local RAM does NOT limit agent count. A 1GB or 2GB system can run MAX=10 agents.
### Server Memory (Not Measurable)
- OpenCode server runs on MiniMax's infrastructure
- No local process to measure RSS/memory
- Agent computation happens server-side
- Memory limit determined by OpenCode service, not local hardware
### Local Bottleneck
The only local constraint is `MAX_CONCURRENT_AGENTS` limit, which:
- Counts session files (PM + dev agents)
- Enforced in kugetsu before spawning
- Prevents resource overload on OpenCode server
## Behavior
With MAX_CONCURRENT_AGENTS=N:
- PM agent counts toward the limit (along with all dev agents)
- At limit: NEW sessions are REJECTED
- Existing sessions can ALWAYS be continued (--continue doesn't count toward limit)
- PM is still accessible when at limit (user can wait or cancel tasks)
## Configuration
Default limit is set to **5 concurrent agents** in `skills/kugetsu/scripts/kugetsu`:
```bash
MAX_CONCURRENT_AGENTS="${MAX_CONCURRENT_AGENTS:-5}"
```
The limit can be overridden via environment variable:
```bash
MAX_CONCURRENT_AGENTS=3 kugetsu start <issue> <message>
```
## Implementation
Session counting approach (vs broken slot mechanism):
```bash
# Count all session files except base.json
count_active_dev_sessions() {
local count=0
if [ -d "$SESSIONS_DIR" ]; then
for session_file in "$SESSIONS_DIR"/*.json; do
if [ -f "$session_file" ]; then
local filename=$(basename "$session_file")
if [ "$filename" != "base.json" ]; then
count=$((count + 1))
fi
fi
done
fi
echo "$count"
}
```
## Session Files
```
~/.kugetsu/sessions/
base.json - base session (NOT counted)
pm-agent.json - PM agent (COUNTED)
github.com-user-repo#1.json - dev agent (COUNTED)
github.com-user-repo#2.json - dev agent (COUNTED)
```
## Recommendations
- **1 agent:** Too strict - just PM + 0 dev agents
- **3 agents:** Recommended - PM + 2 dev agents, leaves room for PM to coordinate
- **5 agents:** Works - PM + 4 dev agents, monitor OpenCode service limits
- **More than 5:** Not tested - depends on OpenCode server capacity
## Session Cleanup
Sessions persist until explicitly destroyed:
- `kugetsu destroy <issue-ref>` - destroy specific session
- `kugetsu destroy --pm-agent -y` - destroy PM agent
- PM should destroy sessions after PR merged (on natural breakpoints)

View File

@@ -2,44 +2,53 @@ 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`.
## Verbosity Control
## Write Permissions: Strict Boundary
You have three verbosity modes. The DEFAULT is **total** (silent mode):
PM has EXPLICIT write boundaries. You can ONLY write to two specific locations.
### 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
### PM can ONLY write to:
- `~/.kugetsu/queue.json` - Queue state
- `~/.kugetsu/logs/*` - Your logs
### verbose (current/legacy)
- Post every glob, read, edit as it happens
- Very noisy - floods notifications
- Use only for debugging
### PM can NEVER write to (read-only):
- `~/.kugetsu/` - Everything else in this directory is read-only
- `repositories/*` - All repository code
- `skills/*` - All skill files, including PM skill files
- **ANY directory outside `~/.kugetsu/`**
- Any `.md` files, config files, scripts, or code
### hybrid
- Post on errors only
- Quiet on success
- Only interrupt if something goes wrong
## Configuration
Set via KUGETSU_VERBOSITY environment variable (default: total):
### If Asked to Write Outside ~/.kugetsu/:
You MUST delegate to a dev agent:
```
KUGETSU_VERBOSITY=total # silent, results only
KUGETSU_VERBOSITY=verbose # noisy, all actions
KUGETSU_VERBOSITY=hybrid # errors only
kugetsu start <domain>/<user>/<repo>#<issue> <task description>
```
Where:
- `<domain>` = git server (e.g., `github.com`, `gitlab.com`, `git.fbrns.co`)
- `<user>` = git username (from `git config user.name`)
- `<repo>` = repository name (from `git remote -v`)
- `<issue>` = issue number to address
### New Kugetsu Scripts:
Do NOT write new kugetsu scripts yourself (even for internal use). Delegate to a dev agent via the normal workflow:
1. Create an issue describing the needed script
2. Delegate: `kugetsu start <domain>/<user>/<repo>#<issue> Create new kugetsu script`
3. After PR is merged, you may test the new script
**Example violations (DO NOT DO THESE):**
- "Update SKILL.md" → DELEGATE, don't edit it yourself
- "Fix the bug in login.js" → DELEGATE, don't write to repositories/
- "Add a new script for queue management" → DELEGATE via issue/PR workflow
## Critical: How to Delegate
Use `kugetsu start` to create dev agent sessions:
```
kugetsu start github.com/user/repo#123 <task description>
kugetsu start <domain>/<user>/<repo>#<issue> <task description>
```
**Domain/User/Repo**: Pull from `git remote -v` and `git config user.name` to make this agnostic to any git server.
**NOT `kugetsu delegate`** - that routes back to the PM (you). Use `kugetsu start` to create a NEW dev agent.
## Your Identity
@@ -51,52 +60,31 @@ You are the PM. Your job is to coordinate, not to code.
- You break down complex requests into delegate-able tasks
- 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
When a request comes in:
1. **Check Queue** - Use `dequeue` to get next task (respects priority)
2. **Understand** - What needs to be built? What's the repo and issue?
3. **Delegate** - Use `kugetsu start <issue-ref> <task>` to create a dev agent task
4. **Monitor** - Watch for PR creation and review
5. **Report** - Post final results to the issue
1. **Understand** - What needs to be built? What's the repo and issue?
2. **Delegate** - Use `kugetsu start <issue-ref> <task>` to create a dev agent task
3. **Monitor** - Watch for PR creation and review
4. **Report** - Post final results to the issue
## Few-Shot Examples
**User:** "Fix the bug in login.js"
**You:** `kugetsu start github.com/user/repo#123 Investigate and fix the login bug in login.js`
**You:** `kugetsu start <domain>/<user>/<repo>#123 Investigate and fix the login bug in login.js`
**User:** "Add tests for the API"
**You:** `kugetsu start github.com/user/repo#124 Write tests for the API module`
**You:** `kugetsu start <domain>/<user>/<repo>#124 Write tests for the API module`
**User:** "Can you write a quick script to parse this JSON?"
**You:** `kugetsu start github.com/user/repo#125 Create a script to parse the JSON file`
**You:** `kugetsu start <domain>/<user>/<repo>#125 Create a script to parse the JSON file`
**User:** "Update the README with installation instructions"
**You:** `kugetsu start github.com/user/repo#126 Update README with installation instructions`
**You:** `kugetsu start <domain>/<user>/<repo>#126 Update README with installation instructions`
**User:** "Create a file at /tmp/test.txt"
**You:** `kugetsu start github.com/user/repo#127 Create a file at /tmp/test.txt`
**You:** `kugetsu start <domain>/<user>/<repo>#127 Create a file at /tmp/test.txt`
Notice: In every example, the correct response is to DELEGATE using `kugetsu start`, not to do it yourself.
@@ -106,4 +94,4 @@ This is not just a rule - it is your identity. The code you coordinate is built
---
*PM Agent v4 - Coordinators coordinate, we do not code. Verbosity: total (silent mode, results only).*
*PM Agent v4 - Coordinators coordinate, we do not code. Strict write boundary: ONLY ~/.kugetsu/.*

View File

@@ -1,50 +0,0 @@
#!/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

View File

@@ -1,55 +0,0 @@
#!/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

View File

@@ -9,54 +9,20 @@ INDEX_FILE="$KUGETSU_DIR/index.json"
NOTIFICATIONS_FILE="$KUGETSU_DIR/notifications.json"
LOGS_DIR="$KUGETSU_DIR/logs"
MAX_CONCURRENT_AGENTS="${MAX_CONCURRENT_AGENTS:-3}"
AGENT_COUNT_FILE="$KUGETSU_DIR/.agent_count"
AGENT_LOCK_FILE="$KUGETSU_DIR/.agent_lock"
acquire_agent_slot() {
local timeout="${1:-300}"
local waited=0
(
flock -w 1 200 || { echo "Error: Could not acquire lock" >&2; exit 1; }
local count
count=$(cat "$AGENT_COUNT_FILE" 2>/dev/null || echo 0)
if [ "$count" -lt "$MAX_CONCURRENT_AGENTS" ]; then
echo $((count + 1)) > "$AGENT_COUNT_FILE"
exit 0
fi
exit 1
) 200>"$AGENT_LOCK_FILE"
local result=$?
if [ $result -ne 0 ]; then
local count
count=$(cat "$AGENT_COUNT_FILE" 2>/dev/null || echo 0)
if [ $waited -ge $timeout ]; then
echo "Error: Timeout waiting for agent slot (max: $MAX_CONCURRENT_AGENTS, current: $count)" >&2
fi
return 1
count_active_dev_sessions() {
local count=0
if [ -d "$SESSIONS_DIR" ]; then
for session_file in "$SESSIONS_DIR"/*.json; do
if [ -f "$session_file" ]; then
local filename=$(basename "$session_file")
if [ "$filename" != "base.json" ]; then
count=$((count + 1))
fi
fi
done
fi
return 0
}
release_agent_slot() {
(
flock -w 1 200 || true
local count
count=$(cat "$AGENT_COUNT_FILE" 2>/dev/null || echo 0)
if [ "$count" -gt 0 ]; then
echo $((count - 1)) > "$AGENT_COUNT_FILE"
fi
) 200>"$AGENT_LOCK_FILE"
}
run_with_limit() {
local log_file="$1"
shift
local cmd=("$@")
(
"${cmd[@]}" >> "$log_file" 2>&1
release_agent_slot
) &
disown
echo "$count"
}
usage() {
@@ -133,8 +99,6 @@ EOF
ensure_dirs() {
mkdir -p "$SESSIONS_DIR"
[ -f "$AGENT_COUNT_FILE" ] || echo 0 > "$AGENT_COUNT_FILE"
}
ensure_worktree_dir() {
@@ -208,7 +172,7 @@ create_worktree() {
fi
echo "Creating worktree at '$worktree_path'..."
git clone --bare "$repo_url" "$worktree_path" 2>/dev/null || {
git clone "$repo_url" "$worktree_path" 2>/dev/null || {
echo "Error: Failed to clone repository" >&2
exit 1
}
@@ -554,11 +518,7 @@ cmd_delegate() {
mkdir -p "$LOGS_DIR"
local log_file="$LOGS_DIR/delegate-$(date +%s).log"
if ! acquire_agent_slot; then
echo "Error: Max concurrent agents ($MAX_CONCURRENT_AGENTS) reached. Try again later." >&2
exit 1
fi
nohup sh -c "opencode run --continue --session '$pm_session' '$message' >> '$log_file' 2>&1; ~/.kugetsu/release-slot.sh" > /dev/null 2>&1 &
nohup sh -c "opencode run '$message' --continue --session '$pm_session' >> '$log_file' 2>&1" > /dev/null 2>&1 &
disown
echo "Delegated to PM agent (logged to $(basename "$log_file"))"
}
@@ -654,9 +614,9 @@ cmd_doctor() {
local pm_context=$(kugetsu_get_pm_context)
if [ -n "$pm_context" ]; then
opencode run --fork --session "$base" "You are a PM (Project Manager) agent. Your role is to coordinate task delegation and review PRs. $pm_context" 2>&1 || true
opencode run "You are a PM (Project Manager) agent. Your role is to coordinate task delegation and review PRs. $pm_context" --fork --session "$base" 2>&1 || true
else
opencode run --fork --session "$base" "You are a PM (Project Manager) agent. Your role is to coordinate task delegation and review PRs. Wait for instructions." 2>&1 || true
opencode run "You are a PM (Project Manager) agent. Your role is to coordinate task delegation and review PRs. Wait for instructions." --fork --session "$base" 2>&1 || true
fi
local after_sessions=$(opencode session list 2>/dev/null | grep -oP '^ses_\w+' | sort)
@@ -774,7 +734,7 @@ cmd_init() {
pm_prompt="You are a PM (Project Manager) agent. Your role is to coordinate task delegation and review PRs. $pm_context"
fi
opencode run --fork --session "$new_session_id" "$pm_prompt" 2>&1 || true
opencode run "$pm_prompt" --fork --session "$new_session_id" 2>&1 || true
local after_sessions=$(opencode session list 2>/dev/null | grep -oP '^ses_\w+' | sort)
local new_pm_session_id=""
@@ -852,19 +812,21 @@ cmd_start() {
local before_set="${before_sessions//$'\n'/|}"
echo "Forking session for '$issue_ref'..."
if ! acquire_agent_slot; then
echo "Error: Max concurrent agents ($MAX_CONCURRENT_AGENTS) reached. Try again later." >&2
# Session-counting: count actual dev sessions, reject if at limit
local active_count=$(count_active_dev_sessions)
if [ "$active_count" -ge "$MAX_CONCURRENT_AGENTS" ]; then
echo "Error: Max concurrent agents ($MAX_CONCURRENT_AGENTS) reached" >&2
echo "Active sessions: $active_count" >&2
remove_worktree_for_issue "$issue_ref"
exit 1
fi
trap release_agent_slot EXIT
if [ "$DEBUG_MODE" = true ]; then
opencode run --fork --session "$base_session_id" "$message" --workdir "$worktree_path" 2>&1 | tee "$SESSIONS_DIR/$session_file.debug.log"
opencode run "$message" --fork --session "$base_session_id" --dir "$worktree_path" 2>&1 | tee "$SESSIONS_DIR/$session_file.debug.log" &
else
opencode run --fork --session "$base_session_id" "$message" --workdir "$worktree_path" 2>&1
opencode run "$message" --fork --session "$base_session_id" --dir "$worktree_path" 2>&1 &
fi
release_agent_slot
trap - EXIT
local after_sessions=$(opencode session list 2>/dev/null | grep -oP '^ses_\w+' | sort)
local new_session_id=""
@@ -933,27 +895,21 @@ cmd_continue() {
local worktree_path=$(python3 -c "import json; print(json.load(open('$session_path')).get('worktree_path', ''))" 2>/dev/null || echo "")
echo "Continuing session for '$session_name'..."
if ! acquire_agent_slot; then
echo "Error: Max concurrent agents ($MAX_CONCURRENT_AGENTS) reached. Try again later." >&2
exit 1
fi
trap release_agent_slot EXIT
# Note: --continue always allowed (existing sessions don't count toward limit)
if [ -n "$worktree_path" ] && [ -d "$worktree_path" ]; then
echo "Using worktree: $worktree_path"
if [ "$DEBUG_MODE" = true ]; then
opencode run --continue --session "$opencode_session_id" "$message" --workdir "$worktree_path" 2>&1 | tee "$session_path.debug.log"
opencode run "$message" --continue --session "$opencode_session_id" --dir "$worktree_path" 2>&1 | tee "$session_path.debug.log" &
else
opencode run --continue --session "$opencode_session_id" "$message" --workdir "$worktree_path"
opencode run "$message" --continue --session "$opencode_session_id" --dir "$worktree_path" 2>&1 &
fi
else
if [ "$DEBUG_MODE" = true ]; then
opencode run --continue --session "$opencode_session_id" "$message" 2>&1 | tee "$session_path.debug.log"
opencode run "$message" --continue --session "$opencode_session_id" 2>&1 | tee "$session_path.debug.log" &
else
opencode run --continue --session "$opencode_session_id" "$message"
opencode run "$message" --continue --session "$opencode_session_id" 2>&1 &
fi
fi
release_agent_slot
trap - EXIT
}
cmd_list() {

View File

@@ -1,72 +0,0 @@
#!/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

View File

@@ -1,45 +0,0 @@
#!/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

View File

@@ -1,11 +0,0 @@
#!/bin/bash
KUGETSU_DIR="${KUGETSU_DIR:-$HOME/.kugetsu}"
AGENT_COUNT_FILE="$KUGETSU_DIR/.agent_count"
AGENT_LOCK_FILE="$KUGETSU_DIR/.agent_lock"
(
flock -w 1 200 || true
count=$(cat "$AGENT_COUNT_FILE" 2>/dev/null || echo 0)
if [ "$count" -gt 0 ]; then
echo $((count - 1)) > "$AGENT_COUNT_FILE"
fi
) 200>"$AGENT_LOCK_FILE"