Phase 1: Headless/SSH Access #11
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Currently, to interact with the agent (assign tasks, monitor progress), a user must:
This works well when the user is at their desk, but breaks down completely when:
Goal
Enable remote agent interaction without requiring physical access to the host.
Proposed Phases
Phase 1: Headless/SSH Access
Enables: Remote work via SSH (e.g.,
ssh user@host "kugetsu assign-task --issue 5")Phase 2: API Interface
Enables: Scriptable automation, CI integration, remote toolchains
Phase 3: Chat Integration
Enables: True mobile access — interact from any chat app
Phase 4: Web Dashboard (Monitoring)
Enables: Better visibility without needing to parse logs
Related Issues
Open Questions
Discussion Summary
Current Flow
User accesses agent via
incus exec [container] -- bash→ opencode TUI → interact → output to Gitea → exit.This works but requires physical access to host or reliable SSH to host + incus exec.
Proposed Two-Mode Approach
Both needed — different tools for different situations.
Key Requirements
Open Questions for Research
This is a tracking comment for our synchronous discussion.
Research Findings
1. Session Persistence ✅
opencode supports session resumption via:
opencode run --continue --session <id>— CLI session continuationprocess()tool with session_idRecovery after disconnect: YES, but requires explicit resume — no auto-reconnection.
2. Task Discovery ⚠️
No automatic polling mechanism exists. Current model:
Coding agents are assigned by PM, not self-discovering. For Option B ("agent go find work"), we would need to implement a task queue or polling mechanism.
3. Background Execution Safety ⚠️
Current state:
For Option B reliability, we need a wrapper with:
4. SSH in Incus Container ✅
Feasible:
openssh-serverinside containerincus config device add container sshd_proxy proxy listen=tcp:0.0.0.0:2222 connect=tcp:<container-ip>:22Alternative:
incus file mount(SFTP) for file access without full SSH daemon.Implications for Option A vs Option B
Proposed Next Steps
opencode run --continuefor session resumptioncan we assume right now in this phase that we will only use Opencode only? so no Hermes and its tooling. will this change the proposed approach?
what do you plan to handle this? maybe user given option to select sessions? if session closed gracefully, it shouldn't appears on the list?
I think for now, agent can choose whatever issue it want to work on. in the way that, we create a rule or mechanism how to prioritize issue selection. for example through user choice (best). but if user doesn't pick any issue, it prioritizes open PR that last activity is user feedback so best to assume that there is no agent that work on the feedback. next is based on the issues, we choose by impact/severity from critical, to high, medium, lastly low. and then we prioritize security, then bug, then feature, lastly is research. we should document this rule so in case in the fresh install, we always have the exact same issue choice rule.
Response to Questions
1. OpenCode Only (No Hermes)
Yes, for Phase 1 we should assume OpenCode only — no Hermes, no
process()tool. This simplifies things:opencode run --continue --session <id>onlyThis does not change the overall approach significantly — we just need to build the session management layer ourselves instead of relying on Hermes.
2. Session Resumption UX
Agree with your proposal:
opencode sessions list(we implement this)~/.kugetsu/sessions/with metadata (created_at, last_activity, workdir, status)3. Task Discovery Rules
Agree with the prioritization:
We should document this as
TASK_DISCOVERY_RULES.mdin the repo so behavior is consistent across fresh installs.Will post the test plan for session resumption as a separate comment.
Test Plan: Session Resumption
Test Cases
TC1: Duplicate Session ID — Two Instances Same Session
opencode run --session test-1 "echo phase1"opencode run --session test-1 "echo phase2"(while 1 is running)TC2: Session Lock Handling
opencode run --session test-2 "sleep 30"opencode run --session test-2 "echo hi"TC3: Resume After Graceful Exit
opencode run --session test-3 "echo done"opencode run --continue --session test-3TC4: Resume After Interrupt (Ctrl+C)
opencode run --session test-4 "sleep 100"opencode run --continue --session test-4TC5: Resume After SIGKILL (Hard Kill)
opencode run --session test-5 "sleep 100"kill -9 <pid>opencode run --continue --session test-5TC6: Concurrent Resume Attempts
opencode run --session test-6 "sleep 60", interruptopencode run --continue --session test-6TC7: Session List Filtering
opencode sessions listPhase 1 Implementation Plan
Goal: Enable SSH access to container + basic session management
Add SSH to container
Session management layer
~/.kugetsu/sessions/<session_id>/— store statekugetsu session list— show recoverable sessionskugetsu session resume <id>— resume with opencodeactive,interrupted,completedTask discovery
TASK_DISCOVERY_RULES.mdWatchdog for background tasks
Session Resumption Test Results
Test Results
TC1: Duplicate Session ID — Two Instances Same Session
opencode run --session dup-test "sleep 5 && echo done"opencode run --session dup-test "echo second"(while 1 is running)Finding: No lock mechanism — both ran with exit 0.
TC2: Session Lock Handling
opencode run --session tc2a "sleep 30"opencode run --session tc2a "echo hi"(while tc2a running)Finding: No session locking — multiple sessions with same ID can run simultaneously.
TC3: Resume After Graceful Exit
opencode run --session grace-test "echo graceful"opencode run --continue --session grace-testFinding:
--continuerequires a message — session was not resumable (correct for completed session).TC4: Resume After SIGINT (Ctrl+C)
opencode run --session sigint-test "sleep 100"opencode run --continue --session sigint-testTC5: Resume After SIGKILL (Hard Kill)
opencode run --session sigkill-test "sleep 30"kill -9 <pid>opencode run --continue --session sigkill-testFinding:
--continuealways requires a message argument. To resume, you must provide both--continueand a message.TC6: Concurrent Resume Attempts
opencode run --session tc6 "sleep 60", interruptopencode run --continue --session tc6Finding: No concurrency control on sessions.
TC7: Session List Filtering
opencode session listFinding:
opencode session listshows ALL sessions including completed ones. No automatic cleanup.Key Findings
--continuerequires message — you must doopencode run --continue --session <id> <message>, not just--continue --session <id>Implications for Phase 1
We need to implement:
active,interrupted,completedkugetsu session resume <id> [--last]that handles the message requirementUpdated Phase 1 Implementation Plan
~/.kugetsu/sessions/<session_id>/state— active/interrupted/completed~/.kugetsu/sessions/<session_id>/lock— PID file for lockingkugetsu session list— filter out completed sessionskugetsu session resume <id>— wrapper that does--continue --session <id> -- <last-message>Implementation Plan: kugetsu Shell Wrapper
Branch
feat/kugetsu-wrapper(new branch frommain)Location
tools/kugetsu/in repo:Installed structure in user home (
~/.kugetsu/):Commands
kugetsu start <session_id> <message>usedkugetsu list [--all]leftonly,--all: all states)kugetsu resume <session_id> [message]kugetsu stop <session_id>idlekugetsu helpState Machine
start→ state=used, store PID, store messagestop(SIGTERM) → state=idlekill/interrupt(detected on check) → state=leftresumeif state=used→ prompt confirmation before proceedingInstall Script:
tools/kugetsu-install.sh~/.kugetsu/structurebin/kugetsuto~/.kugetsu/bin/export PATH="$HOME/.kugetsu/bin:$PATH"to.bashrcand.zshrcWhat Without Install Means
Tradeoff vs OpenCode CLI Direct
used/idle/left)usedWhy Wrapper Is Better for Phase 1
Ready to implement. Will post implementation details + test results as separate comments.
Updated Implementation Plan: kugetsu as Skill
Branch
feat/kugetsu-wrapper(new branch frommain)Location
skills/kugetsu/(followingskills/opencode-worktree/pattern):How Agents Use It
skills/kugetsu/SKILL.mdscript/kugetsuto own PATH or sources it directlykugetsu start <session> <message>,kugetsu list,kugetsu resume <session>, etc.Installed Structure (user home)
Commands
kugetsu start <session_id> <message>usedkugetsu list [--all]leftonly,--all: all states)kugetsu resume <session_id> [message]kugetsu stop <session_id>idlekugetsu helpState Machine
start→ state=used, store PID, store messagestop(SIGTERM) → state=idlekill/interrupt(detected on check) → state=leftresumeif state=used→ prompt confirmation before proceedingresumeif state=idle→ error (not resumable)resumeif state=left→ proceed with auto-filled messageInstall Script (
kugetsu-install.sh)For human users (run once when setting up on new host):
~/.kugetsu/structurescript/kugetsuto~/.kugetsu/bin/export PATH="$HOME/.kugetsu/bin:$PATH"to.bashrc/.zshrcWhat Without Install Means
Agent Self-Installation
Agents reading the SKILL.md will:
script/kugetsuto their own PATH, or source it when neededReady to implement. Will post implementation details + test results as separate comments.
Test Report: kugetsu Session Manager
Environment
Test Cases & Results
kugetsu helpkugetsu listkugetsu list --allkugetsu start test "echo hello"kugetsu list(default)leftsessionsidle)kugetsu list --allkugetsu resume test-resume(state=left)kugetsu resume test-resumeKnown Issues
Signal handling: When opencode process is killed (SIGKILL), kugetsu wrapper may not detect the non-zero exit. This is due to opencode's own signal handling. Workaround: user can manually check session state.
Session state after interrupt: Due to opencode's internal handling, interrupted sessions may show as
idleinstead ofleft. Thecheck_and_update_state()function relies on PID file + process check.Manual Test Commands Used
Memory Limiting Options for Test Suite
When running the test suite (
test-kugetsu.sh) which spawnsopencode(TUI) instances, memory exhaustion can occur. Each opencode TUI uses ~600MB-1GB RSS. We evaluated four approaches:Comparison Table
dnf install libcgroup1. ulimit -v (Virtual Memory)
Installation: Built into bash - no installation needed
Usage:
ulimit -v 1572864(1.5GB virtual)Pros:
Cons:
2. cgroups v2 (Linux Kernel Built-in)
Installation: Built into kernel
Usage:
Pros:
Cons:
3. libcgroup (Tools + Kernel)
Installation:
dnf install libcgroup-toolsUsage:
Pros:
Cons:
4. systemd-run (Systemd + cgroups)
Installation: Built into systemd
Usage:
Pros:
--user(no root required)Cons:
System Status (Fedora 43)
Chosen Approach: systemd-run with ulimit -v fallback
Primary:
systemd-run --user -p MemoryMax=1536M <command>Fallback:
ulimit -v 1572864 <command>Rationale: Layered approach - try modern cgroups-based solution first, graceful degradation to built-in rlimit if unavailable.
Decision: Skip Memory Limiting via ulimit
Date: 2026-03-29
Investigation Findings
We tested four approaches for memory limiting in the test suite:
Why ulimit -v Fails with Bun
opencode is built on Bun runtime. When we tested:
Result:
Root cause: ulimit -v limits virtual address space, but Bun JIT compiler and memory-mapped regions require more virtual space than physical RSS. When constrained, CPU instruction errors occur (not OOM).
Chosen Approach
Skip memory limiting entirely and rely on:
Rationale
Phase 1 SSH Setup Plan
With kugetsu merged, Phase 1 remaining item is SSH access to the container.
Current State
SSH Setup Implementation Plan
1. Container-Side Setup (inside incus container)
2. Host-Side Setup (incus proxy device)
3. Authentication
~/.ssh/authorized_keysinside container4. Usage Flow
5. Documentation
Add to
docs/hermes-setup.md:Questions for Review
tools/ssh-setup.sh) or manual steps in docs?Status: Ready for implementation once questions are answered.
Research Complete: Issue #14
Deep-dived opencode v1.3.5 headless CLI patterns for programmatic agent orchestration. Key finding:
opencode run -s <session-id> --continueis the recommended workflow for CLI-based multi-agent task assignment.Bottom Line
opencode runis truly one-shot (prompt -> complete -> exit)opencode serve/acpare web UI servers only — no messaging API--continueattaches to existing session but each call is still a fresh processRecommended Workflow
Step 1: Start task, capture session ID
SESSION=$(opencode run 'task' --format json | jq -r '.sessionID')
Step 2+: Continue with explicit session ID (no collision risk)
opencode run -s $SESSION --continue 'next task'
Full research: Issue #14
This comment was added by Hermes Agent after opencode headless CLI research.
Phase 1 SSH Setup - PR #16
SSH setup for Phase 1 is now ready for review.
What was added
sshd-setup.sh (
skills/kugetsu/scripts/sshd-setup.sh)kugetsu)docs/kugetsu-setup.md
SKILL.md updated
Security notes
chmod +xexplicitly before executing (no hidden executable bits)PR
#16
Testing needed
Phase 1 Status Update
Phase 1a: SSH Setup ✅
Phase 1b: Tailscale VPN (See Issue #17)
For cases where host does not have public IP or user wants easier cross-network access, Tailscale provides:
Issue #17: #17
Implementation planned:
tailscale-setup.shscriptSupport remote agent control: headless → API → chat interfaceto Phase 1: Headless/SSH Access