Files
kugetsu/skills/kugetsu/SKILL.md
shokollm 54aa6419eb fix(kugetsu): prevent excess agent spawning with flock + sequential processing
- count_active_dev_sessions() now excludes pm-agent.json from count
- process_queue() now calls kugetsu start directly (not opencode run)
- process_queue() uses dynamic batch size = available_slots
- process_queue() has retry logic (max 3 attempts) on failure
- cmd_start() now uses flock around critical section
- Added notification types: task_queued, task_dequeued, task_started, task_completed, task_error
- Removed QUEUE_DAEMON_BATCH_SIZE config (no longer needed)

Fixes issue #146
2026-04-05 08:44:45 +00:00

16 KiB

name, description, license, compatibility, metadata
name description license compatibility metadata
kugetsu Issue-driven session manager for opencode CLI. Manages base sessions and per-issue forked sessions with automatic indexing for headless orchestration. MIT Requires opencode CLI, bash, python3, and filesystem access.
author version
shoko 2.2

kugetsu - OpenCode Session Manager (Issue-Driven)

Manages opencode sessions with a base session + forked session pattern optimized for headless orchestration. Each issue gets an isolated git worktree to prevent workspace conflicts.

Installation

For Human Users

Run once on a new host:

. skills/kugetsu/scripts/kugetsu-install.sh

For Agents (Self-Install)

Copy the script to your PATH:

cp skills/kugetsu/scripts/kugetsu ~/.local/bin/kugetsu
chmod +x ~/.local/bin/kugetsu

Configuration

User overrides can be set in ~/.kugetsu/config. This file is sourced on each kugetsu command call, so changes take effect immediately without re-initialization.

A default config file is created during kugetsu init with commented examples:

# User configuration overrides
# Values set here take precedence over defaults
# Changes take effect immediately (no re-init needed)

# Max concurrent dev agents (default: 3)
# MAX_CONCURRENT_AGENTS=5

Available Config Options

Variable Default Description
MAX_CONCURRENT_AGENTS 3 Maximum number of concurrent dev agents
KUGETSU_TEMP_DIR ~/.local/share/opencode/tool-output Temp directory for subagent tool output (useful in headless environments where /tmp is restricted)
KUGETSU_VERBOSITY default PM agent verbosity level: verbose, default, or quiet
QUEUE_DAEMON_INTERVAL_MINUTES 5 How often daemon polls queue (in minutes)
QUEUE_CLEANUP_AGE_DAYS 7 Auto-cleanup completed/error items older than N days

Environment Variables for Agents

Agents receive environment variables through env files, not command-line injection. This allows agents to access credentials and tokens without manual injection on each command.

Files created during kugetsu init:

  • ~/.kugetsu/env/default.env - Variables for all agents
  • ~/.kugetsu/env/pm-agent.env - Variables for PM agent (overrides default)

Commands:

kugetsu env list                    # List all env files
kugetsu env show [agent]           # Show env file contents (values masked)
kugetsu env set <key> <value> [agent]  # Set a variable
kugetsu env get <key> [agent]      # Get a variable value
kugetsu env rm <key> [agent]       # Remove a variable

Example - Setting GITEA_TOKEN:

# Set token for PM agent
kugetsu env set GITEA_TOKEN ghp_xxx pm-agent

# Verify (token masked in output)
kugetsu env show pm-agent

# Agent now has GITEA_TOKEN when delegated to

Sensitive values are automatically masked in logs and display:

  • GITEA_TOKEN, GITHUB_TOKEN, GITLAB_TOKEN
  • API_KEY, PASSWORD, TOKEN, SECRET

Usage in delegation:

# PM agent will have GITEA_TOKEN from pm-agent.env
kugetsu delegate "post comment on #69"

Architecture

Session Pattern

  • Base Session: Created once via TUI, used for forking dev agents
  • PM Agent Session: Created during init, persistent coordinator for task management
  • Forked Sessions: One per issue, branched from base via opencode run --fork --session <base>

Git Worktree Isolation

Each issue session gets its own git worktree to prevent conflicts:

  • Isolated working directory (no file collisions)
  • Isolated branch (no checkout conflicts)
  • Shared .git objects (efficient storage)

Directory Structure

~/.kugetsu/
├── sessions/
│   ├── base.json                    # Base session metadata
│   ├── pm-agent.json               # PM agent session metadata
│   └── github.com-shoko-kugetsu-14.json  # Forked session per issue
├── worktrees/
│   ├── github.com-shoko-kugetsu-14/     # Isolated workdir for issue #14
│   └── github.com-shoko-kugetsu-15/     # Isolated workdir for issue #15
├── queue/
│   ├── items/                      # Queue item JSON files
│   ├── daemon.pid                   # Daemon process ID
│   └── daemon.log                  # Daemon log output
└── index.json                       # Maps session IDs and issue refs to session files

Index File

{
  "base": "ses_abc123",
  "pm_agent": "ses_pm_xyz789",
  "issues": {
    "github.com/shoko/kugetsu#14": "github.com-shoko-kugetsu-14.json"
  }
}

Session File

{
  "type": "forked",
  "issue_ref": "github.com/shoko/kugetsu#14",
  "opencode_session_id": "ses_xyz789",
  "worktree_path": "/home/user/.kugetsu/worktrees/github.com-shoko-kugetsu-14",
  "created_at": "2026-03-29T18:16:10+02:00",
  "state": "idle"
}

Issue Ref Format

All issue references use the format: instance/user/repo#identifier

Examples:

  • github.com/shoko/kugetsu#14 (issue number)
  • github.com/shoko/kugetsu#-discuss (discussion, no issue number yet)
  • gitlab.com/username/project#42 (issue number)

Worktree Behavior

On kugetsu start

  1. Derives worktree path from issue ref: ~/.kugetsu/worktrees/{sanitized-ref}/
  2. If worktree exists: removes and recreates (guaranteed clean state)
  3. If worktree doesn't exist: creates fresh
  4. Clones repo, creates branch fix/issue-{id}
  5. Runs opencode with --workdir pointing to worktree

On kugetsu destroy

  1. Removes worktree via git worktree remove
  2. Deletes session file and index entry

Repo Configuration

If the repo URL cannot be derived from the issue ref, add to ~/.kugetsu/repos.json:

{
  "github.com/shoko kugetsu#14": "https://custom.repo.url/owner/repo.git"
}

Commands

kugetsu init [--force]

Initialize base + PM agent sessions via TUI:

kugetsu init
  • Requires a terminal (TTY) to spawn the opencode TUI
  • Creates base session and PM agent session
  • Stores both session IDs in index.json
  • Subsequent runs error unless --force is used

kugetsu start <issue-ref> <message> [--debug]

Start task for an issue by forking from base session:

kugetsu start github.com/shoko/kugetsu#14 "fix authentication bug"
kugetsu start github.com/shoko/kugetsu#-discuss "research auth options"
  • Creates isolated git worktree for the issue
  • Forks new session from base
  • Requires PM agent to exist (created by init)
  • Uses opencode run --fork --session <base-session-id> "<message>" --workdir <worktree>

kugetsu continue <issue-ref> <message> [--debug]

Continue work on an existing issue session:

kugetsu continue github.com/shoko/kugetsu#14 "add unit tests"
  • Looks up session file from index
  • Uses opencode run --continue --session <opencode-session-id> "<message>" --workdir <worktree>

kugetsu list

List all tracked sessions:

kugetsu list

Output:

ISSUE_REF                                          TYPE      SESSION_ID              WORKTREE
────────────────────────────────────────────────────────────────────────────────────────────────────────
(base)                                             base      ses_abc123              N/A
(pm-agent)                                         pm_agent   ses_pm_xyz789           N/A
github.com/shoko/kugetsu#14                        forked     ses_xyz789              /home/user/.kugetsu/worktrees/github.com-shoko-kugetsu-14

kugetsu prune [--force]

Remove orphaned sessions and worktrees:

kugetsu prune              # Shows what would be deleted
kugetsu prune --force      # Deletes orphaned items
  • Orphaned = session files or worktrees not in index
  • Always keeps base.json and pm-agent.json
  • Useful after opencode session cleanup

kugetsu destroy <issue-ref> [-y]

Delete session and worktree for specific issue:

kugetsu destroy github.com/shoko/kugetsu#14     # Prompts for confirmation
kugetsu destroy github.com/shoko/kugetsu#14 -y  # Skips confirmation

kugetsu destroy --pm-agent [-y]

Delete PM agent session (requires explicit --pm-agent):

kugetsu destroy --pm-agent -y

kugetsu destroy --base [-y]

Delete base session (requires explicit --base):

kugetsu destroy --base -y

Note: Destroying base also destroys PM agent since PM depends on base.

kugetsu delegate <message>

Send a message to the PM agent for task coordination via queue:

kugetsu delegate "work on issue #14"
kugetsu delegate "review PR #92"
  • Always enqueues (fire-and-forget): returns immediately
  • Queue daemon polls queue and invokes PM when slots available
  • Tasks are processed FIFO (first-in-first-out)
  • Use kugetsu queue list to see pending tasks
  • Use kugetsu queue-daemon logs to debug queue processing

kugetsu logs [n]

Show recent delegation logs:

kugetsu logs           # Show last 10 logs
kugetsu logs 20       # Show last 20 logs
  • Logs are stored in ~/.kugetsu/logs/
  • Automatically deletes logs older than 7 days

kugetsu status

Check if kugetsu is properly initialized:

kugetsu status

Output:

  • kugetsu_not_initialized - No index file
  • base_session_missing - Base session not found
  • pm_agent_missing - PM agent not found
  • ok - Everything is initialized

kugetsu doctor [--fix]

Diagnose and fix kugetsu issues:

kugetsu doctor              # Show diagnostic info
kugetsu doctor --fix        # Attempt automatic repairs
  • Checks index file existence
  • Validates base and PM agent sessions
  • With --fix: recreates PM agent if missing
  • With --fix-permissions: fixes session permissions in opencode database

kugetsu notify [list|clear]

Show or clear notifications from PM agent:

kugetsu notify list         # Show unread notifications (default)
kugetsu notify clear       # Mark all as read
  • PM agent writes task completion notifications to ~/.kugetsu/notifications.json
  • Shows timestamp, type, message, and issue ref for each notification

kugetsu server <list|add|remove|default|get>

Manage git server configurations:

kugetsu server list              # List all configured servers
kugetsu server add github https://github.com    # Add a server
kugetsu server remove gitlab    # Remove a server
kugetsu server default github   # Set default server
kugetsu server get github       # Get server URL

kugetsu queue <list|stats|clear>

Manage task queue for autonomous PM operation:

kugetsu queue list              # Show queued tasks with status
kugetsu queue stats            # Show queue statistics (total, pending, notified, completed, error)
kugetsu queue clear            # Clean up old completed/error items
kugetsu queue enqueue <issue-ref> <message>  # Manually enqueue a task

Queue Item States:

  • pending - Waiting in queue, daemon can pick up
  • notified - PM agent has picked up the task
  • completed - Dev agent finished, PR created
  • error - Timeout or failure

kugetsu queue-daemon <start|stop|restart|status|logs>

Manage the queue daemon background process:

kugetsu queue-daemon start     # Start daemon in background
kugetsu queue-daemon stop      # Stop daemon
kugetsu queue-daemon restart    # Restart daemon
kugetsu queue-daemon status    # Check if daemon is running
kugetsu queue-daemon logs       # Show recent daemon logs

Daemon Behavior:

  1. Runs at configurable interval (default: 5 minutes)
  2. Checks if active agents < MAX_CONCURRENT_AGENTS
  3. Picks 1-N pending items (configurable batch size)
  4. Forks PM session for each picked item
  5. PM decides whether to use start or continue

Queue Directory:

~/.kugetsu/queue/
├── items/                     # Queue item JSON files
│   ├── q_1234567890.json    # One file per queued task
│   └── q_1234567891.json
├── daemon.pid                # Daemon process ID
├── daemon.lock               # Daemon lock file
└── daemon.log                # Daemon log output

Workflow Example

First-time Setup

# Initialize kugetsu (requires TTY)
kugetsu init

# Start the queue daemon (for autonomous operation)
kugetsu queue-daemon start

Normal Workflow

# Enqueue tasks via delegate - agents will process them automatically
kugetsu delegate "work on issue #14"
kugetsu delegate "review PR #92"

# Check queue status
kugetsu queue list           # See pending tasks
kugetsu queue stats         # See statistics

# Debug queue daemon
kugetsu queue-daemon status  # Is daemon running?
kugetsu queue-daemon logs    # See daemon logs

# Continue work on existing issue
kugetsu continue github.com/shoko/kugetsu#14 "add tests"

# List all sessions
kugetsu list

# Clean up orphaned items
kugetsu prune --force

# Delete session and worktree when done
kugetsu destroy github.com/shoko/kugetsu#14

Queue Daemon Management

# Check if daemon is running
kugetsu queue-daemon status

# View daemon logs for debugging
kugetsu queue-daemon logs

# Restart daemon if needed
kugetsu queue-daemon restart

# Stop daemon
kugetsu queue-daemon stop

Headless Operation

This design solves the headless CLI limitation discovered in Issue #14:

  1. Problem: opencode run --session <new> doesn't work headlessly (SSE stream terminates)
  2. Solution: Fork from existing base session, which works headlessly

The pattern:

  • Base session created once via TUI (interactive)
  • PM agent session created during init (persistent coordinator)
  • All subsequent work uses --fork --session <base> or --continue --session <forked>
  • Each session works in isolated git worktree

Recovery

If opencode sessions become out of sync:

  1. kugetsu list shows tracked sessions
  2. kugetsu prune removes orphaned files and worktrees
  3. For full reset: kugetsu destroy --base -y && kugetsu init

Remote Access via SSH (Optional)

To access kugetsu from a remote machine, SSH setup is required.

Automated Setup

Run the SSH setup script inside your container:

chmod +x skills/kugetsu/scripts/sshd-setup.sh
bash skills/kugetsu/scripts/sshd-setup.sh <username>

Omit <username> to use default user kugetsu.

What It Does

  • Checks systemd prerequisite
  • Creates non-root user
  • Configures SSH for key-only authentication
  • Enables passwordless sudo for the user
  • Starts sshd via systemd

After Setup

  1. Add your SSH public key to ~/.ssh/authorized_keys on the container
  2. Configure port forwarding on the host (see docs/kugetsu-setup.md)
  3. Connect: ssh -p 2222 <username>@<host-ip>

Remote Usage

Once connected via SSH, kugetsu works the same as local:

kugetsu list
kugetsu start github.com/shoko/kugetsu#14 "fix bug"
kugetsu continue github.com/shoko/kugetsu#14

Documentation

See docs/kugetsu-setup.md for full remote access setup including host-side port forwarding and firewall configuration.

Tailscale VPN (Alternative)

If your host does not have a public IP or you need access across different networks, Tailscale provides a VPN solution.

Benefits:

  • No public IP required
  • Each container gets its own unique Tailscale IP
  • Access from anywhere via Tailscale network
  • Normal internet access still works

Setup:

chmod +x skills/kugetsu/scripts/tailscale-setup.sh
bash skills/kugetsu/scripts/tailscale-setup.sh <username> <device-name>

The script will:

  1. Install Tailscale (supports Debian/Ubuntu, Fedora)
  2. Start the tailscaled daemon
  3. Prompt for AUTHKEY or browser-based login
  4. Configure device name (defaults to current hostname)

After Setup:

  • From any Tailscale device: ssh <username>@<device-name>
  • Works across different networks without port forwarding

See docs/kugetsu-setup.md for full Tailscale setup documentation.

Without kugetsu

If kugetsu is not available, use opencode directly:

# Create base session (requires TTY)
opencode
# Note the session ID from: opencode session list

# Fork for issue
opencode run --fork --session <base-session-id> "task"

# Continue
opencode run --continue --session <forked-session-id> "continue"

Tradeoff: No issue mapping, no index, manual session tracking, no worktree isolation.