@ai-dev-methodologies/rlp-desk 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/architecture.md +1 -1
- package/package.json +1 -1
- package/src/scripts/run_ralph_desk.zsh +37 -28
package/docs/architecture.md
CHANGED
|
@@ -50,7 +50,7 @@ RLP Desk supports two modes for running the Leader loop. Both honor the same gov
|
|
|
50
50
|
|
|
51
51
|
**Agent() mode** is synchronous and simple: each `Agent()` call blocks until the subprocess finishes, then the Leader reads the filesystem. No polling, no signal files, no tmux.
|
|
52
52
|
|
|
53
|
-
**Tmux mode** trades dynamic routing for visibility and independence. The shell Leader writes prompts to files, sends short trigger commands via `tmux send-keys`, and polls structured JSON signal files (`iter-signal.json`, `verify-verdict.json`) for control flow. It uses proven
|
|
53
|
+
**Tmux mode** trades dynamic routing for visibility and independence. The shell Leader writes prompts to files, sends short trigger commands via `tmux send-keys`, and polls structured JSON signal files (`iter-signal.json`, `verify-verdict.json`) for control flow. It uses proven tmux patterns — write-then-notify, pane ID stability, copy-mode guards, heartbeat monitoring — for reliable, race-free orchestration.
|
|
54
54
|
|
|
55
55
|
The tmux script is a second implementation of the governance protocol. Traceability is maintained via governance.md section 7 step-number comments throughout the script.
|
|
56
56
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-dev-methodologies/rlp-desk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Fresh-context iterative loops for Claude Code — autonomous task completion with independent verification",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"postinstall": "node scripts/postinstall.js",
|
|
@@ -7,7 +7,7 @@ set -uo pipefail
|
|
|
7
7
|
# Ralph Desk Tmux Runner
|
|
8
8
|
#
|
|
9
9
|
# Implements the Leader loop from governance.md section 7 as a shell script.
|
|
10
|
-
# Uses
|
|
10
|
+
# Uses tmux proven patterns: write-then-notify, pane IDs (%N),
|
|
11
11
|
# copy-mode guards, verification-based retry, heartbeat monitoring,
|
|
12
12
|
# idle pane nudging, exponential backoff restarts, atomic file writes.
|
|
13
13
|
#
|
|
@@ -103,7 +103,7 @@ log_error() {
|
|
|
103
103
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
# --- governance.md s7: Atomic file writes (
|
|
106
|
+
# --- governance.md s7: Atomic file writes (tmux pattern) ---
|
|
107
107
|
# All file writes by the Leader use tmp+mv to prevent corruption.
|
|
108
108
|
atomic_write() {
|
|
109
109
|
local target="$1"
|
|
@@ -180,7 +180,7 @@ validate_scaffold() {
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
# =============================================================================
|
|
183
|
-
# Session Management (
|
|
183
|
+
# Session Management (tmux pattern: pane IDs)
|
|
184
184
|
# =============================================================================
|
|
185
185
|
|
|
186
186
|
# --- governance.md s7 step 1: Check for existing sessions ---
|
|
@@ -205,7 +205,7 @@ check_existing_sessions() {
|
|
|
205
205
|
create_session() {
|
|
206
206
|
log "Creating tmux session: $SESSION_NAME"
|
|
207
207
|
|
|
208
|
-
#
|
|
208
|
+
# tmux split-pane pattern
|
|
209
209
|
if [[ -n "${TMUX:-}" ]]; then
|
|
210
210
|
# Inside tmux: split CURRENT pane in place
|
|
211
211
|
# Current pane stays as-is (leader/user stays here)
|
|
@@ -220,7 +220,7 @@ create_session() {
|
|
|
220
220
|
VERIFIER_PANE=$(tmux split-window -v -d -t "$WORKER_PANE" -P -F '#{pane_id}' -c "$ROOT")
|
|
221
221
|
else
|
|
222
222
|
# Outside tmux: wrap current terminal into a new tmux session and attach
|
|
223
|
-
#
|
|
223
|
+
# tmux pattern: user sees panes immediately, no separate attach needed
|
|
224
224
|
tmux new-session -d -s "$SESSION_NAME" -x 200 -y 50 -c "$ROOT"
|
|
225
225
|
LEADER_PANE=$(tmux display-message -p -t "$SESSION_NAME" '#{pane_id}')
|
|
226
226
|
WORKER_PANE=$(tmux split-window -h -d -t "$LEADER_PANE" -P -F '#{pane_id}' -c "$ROOT")
|
|
@@ -263,7 +263,7 @@ create_session() {
|
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
# =============================================================================
|
|
266
|
-
# Copy-Mode Guard (
|
|
266
|
+
# Copy-Mode Guard (tmux pattern)
|
|
267
267
|
# =============================================================================
|
|
268
268
|
|
|
269
269
|
# --- governance.md s7 step 5: Check pane_in_mode before every send-keys ---
|
|
@@ -278,7 +278,7 @@ check_copy_mode() {
|
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
# =============================================================================
|
|
281
|
-
# Verification-Based Send Retry (
|
|
281
|
+
# Verification-Based Send Retry (tmux pattern)
|
|
282
282
|
# =============================================================================
|
|
283
283
|
|
|
284
284
|
# --- governance.md s7 step 5: Send with copy-mode guard and retry ---
|
|
@@ -286,7 +286,7 @@ safe_send_keys() {
|
|
|
286
286
|
local pane_id="$1"
|
|
287
287
|
local text="$2"
|
|
288
288
|
|
|
289
|
-
# --- Exact
|
|
289
|
+
# --- Exact tmux sendToWorker pattern (tmux-session.js:527-626) ---
|
|
290
290
|
|
|
291
291
|
# Guard: copy-mode captures keys; skip entirely
|
|
292
292
|
if ! check_copy_mode "$pane_id"; then
|
|
@@ -313,7 +313,7 @@ safe_send_keys() {
|
|
|
313
313
|
log_debug " Sending text to pane $pane_id (${#text} chars)"
|
|
314
314
|
tmux send-keys -t "$pane_id" -l -- "$text"
|
|
315
315
|
|
|
316
|
-
# Allow input buffer to settle (
|
|
316
|
+
# Allow input buffer to settle (tmux: 150ms)
|
|
317
317
|
sleep 0.15
|
|
318
318
|
|
|
319
319
|
# Submit: up to 6 rounds of C-m double-press
|
|
@@ -321,7 +321,7 @@ safe_send_keys() {
|
|
|
321
321
|
while (( round < 6 )); do
|
|
322
322
|
sleep 0.1
|
|
323
323
|
if (( round == 0 && pane_busy )); then
|
|
324
|
-
# Busy pane: Tab+C-m queue semantics (
|
|
324
|
+
# Busy pane: Tab+C-m queue semantics (tmux pattern)
|
|
325
325
|
tmux send-keys -t "$pane_id" Tab
|
|
326
326
|
sleep 0.08
|
|
327
327
|
tmux send-keys -t "$pane_id" C-m
|
|
@@ -349,7 +349,7 @@ safe_send_keys() {
|
|
|
349
349
|
return 1
|
|
350
350
|
fi
|
|
351
351
|
|
|
352
|
-
# Adaptive fallback: C-u clear line, resend (
|
|
352
|
+
# Adaptive fallback: C-u clear line, resend (tmux pattern)
|
|
353
353
|
log_debug " Adaptive retry — clearing line and resending"
|
|
354
354
|
tmux send-keys -t "$pane_id" C-u
|
|
355
355
|
sleep 0.08
|
|
@@ -385,19 +385,19 @@ safe_send_keys() {
|
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
# =============================================================================
|
|
388
|
-
# Wait for Pane Ready (
|
|
388
|
+
# Wait for Pane Ready (tmux pattern: paneLooksReady)
|
|
389
389
|
# =============================================================================
|
|
390
390
|
|
|
391
391
|
wait_for_pane_ready() {
|
|
392
392
|
local pane_id="$1"
|
|
393
|
-
local timeout="${2:-10}" #
|
|
393
|
+
local timeout="${2:-10}" # tmux default: 10s
|
|
394
394
|
local start=$(date +%s)
|
|
395
395
|
log " Waiting for pane $pane_id ready..."
|
|
396
396
|
while (( $(date +%s) - start < timeout )); do
|
|
397
397
|
local captured
|
|
398
398
|
captured=$(tmux capture-pane -t "$pane_id" -p -S -20 2>/dev/null)
|
|
399
399
|
|
|
400
|
-
# Auto-dismiss trust prompt (
|
|
400
|
+
# Auto-dismiss trust prompt (tmux pattern: paneHasTrustPrompt)
|
|
401
401
|
if echo "$captured" | grep -q "Do you trust" 2>/dev/null; then
|
|
402
402
|
log " Trust prompt detected, auto-dismissing..."
|
|
403
403
|
tmux send-keys -t "$pane_id" Enter
|
|
@@ -407,7 +407,7 @@ wait_for_pane_ready() {
|
|
|
407
407
|
continue
|
|
408
408
|
fi
|
|
409
409
|
|
|
410
|
-
#
|
|
410
|
+
# tmux paneLooksReady: check each line for prompt char at line start
|
|
411
411
|
local ready=0
|
|
412
412
|
echo "$captured" | while IFS= read -r line; do
|
|
413
413
|
local trimmed="${line## }"
|
|
@@ -437,7 +437,7 @@ wait_for_pane_ready() {
|
|
|
437
437
|
}
|
|
438
438
|
|
|
439
439
|
# =============================================================================
|
|
440
|
-
# Heartbeat Monitoring (
|
|
440
|
+
# Heartbeat Monitoring (tmux pattern)
|
|
441
441
|
# =============================================================================
|
|
442
442
|
|
|
443
443
|
# --- governance.md s7 step 5+6: Check heartbeat freshness ---
|
|
@@ -473,7 +473,7 @@ check_heartbeat_exited() {
|
|
|
473
473
|
}
|
|
474
474
|
|
|
475
475
|
# =============================================================================
|
|
476
|
-
# Idle Pane Nudging (
|
|
476
|
+
# Idle Pane Nudging (tmux pattern)
|
|
477
477
|
# =============================================================================
|
|
478
478
|
|
|
479
479
|
# --- governance.md s7 step 5+6: Nudge idle panes ---
|
|
@@ -503,7 +503,7 @@ check_and_nudge_idle_pane() {
|
|
|
503
503
|
}
|
|
504
504
|
|
|
505
505
|
# =============================================================================
|
|
506
|
-
# Exponential Backoff Restart (
|
|
506
|
+
# Exponential Backoff Restart (tmux pattern)
|
|
507
507
|
# =============================================================================
|
|
508
508
|
|
|
509
509
|
# --- governance.md s7 step 5: Restart dead workers with backoff ---
|
|
@@ -529,14 +529,14 @@ restart_worker() {
|
|
|
529
529
|
tmux send-keys -t "$pane_id" "/exit" Enter 2>/dev/null
|
|
530
530
|
sleep 2
|
|
531
531
|
|
|
532
|
-
# Re-launch claude (
|
|
532
|
+
# Re-launch claude (tmux interactive pattern)
|
|
533
533
|
safe_send_keys "$pane_id" "$CLAUDE_BIN --model $WORKER_MODEL --dangerously-skip-permissions"
|
|
534
534
|
WORKER_RESTARTS[$iter]=$((restart_count + 1))
|
|
535
535
|
return 0
|
|
536
536
|
}
|
|
537
537
|
|
|
538
538
|
# =============================================================================
|
|
539
|
-
# Write-Then-Notify: Trigger Script Generation (
|
|
539
|
+
# Write-Then-Notify: Trigger Script Generation (tmux CRITICAL pattern)
|
|
540
540
|
# =============================================================================
|
|
541
541
|
|
|
542
542
|
# --- governance.md s7 step 4+5: Write prompt and trigger to files ---
|
|
@@ -586,7 +586,7 @@ write_worker_trigger() {
|
|
|
586
586
|
|
|
587
587
|
HEARTBEAT_FILE="$WORKER_HEARTBEAT"
|
|
588
588
|
|
|
589
|
-
# Background heartbeat writer (
|
|
589
|
+
# Background heartbeat writer (tmux pattern)
|
|
590
590
|
(
|
|
591
591
|
while true; do
|
|
592
592
|
echo '{"epoch":'\$(date +%s)',"pid":'"\$\$"'}' > "\${HEARTBEAT_FILE}.tmp.\$\$"
|
|
@@ -641,7 +641,7 @@ write_verifier_trigger() {
|
|
|
641
641
|
|
|
642
642
|
HEARTBEAT_FILE="$VERIFIER_HEARTBEAT"
|
|
643
643
|
|
|
644
|
-
# Background heartbeat writer (
|
|
644
|
+
# Background heartbeat writer (tmux pattern)
|
|
645
645
|
(
|
|
646
646
|
while true; do
|
|
647
647
|
echo '{"epoch":'\$(date +%s)',"pid":'"\$\$"'}' > "\${HEARTBEAT_FILE}.tmp.\$\$"
|
|
@@ -841,7 +841,7 @@ poll_for_signal() {
|
|
|
841
841
|
return 0 # success
|
|
842
842
|
fi
|
|
843
843
|
|
|
844
|
-
# Check heartbeat freshness (
|
|
844
|
+
# Check heartbeat freshness (tmux pattern)
|
|
845
845
|
if [[ -f "$heartbeat_file" ]]; then
|
|
846
846
|
if check_heartbeat_exited "$heartbeat_file"; then
|
|
847
847
|
# Process exited but no signal file -- give a brief grace period
|
|
@@ -887,7 +887,7 @@ poll_for_signal() {
|
|
|
887
887
|
fi
|
|
888
888
|
fi
|
|
889
889
|
|
|
890
|
-
# Idle pane nudging (
|
|
890
|
+
# Idle pane nudging (tmux pattern)
|
|
891
891
|
check_and_nudge_idle_pane "$pane_id" "nudge_count"
|
|
892
892
|
|
|
893
893
|
sleep "$POLL_INTERVAL"
|
|
@@ -1021,14 +1021,14 @@ main() {
|
|
|
1021
1021
|
|
|
1022
1022
|
update_status "worker" "running"
|
|
1023
1023
|
|
|
1024
|
-
# --- governance.md s7 step 5: Execute Worker (interactive claude,
|
|
1024
|
+
# --- governance.md s7 step 5: Execute Worker (interactive claude, tmux pattern) ---
|
|
1025
1025
|
# Step 5a: Launch interactive claude in Worker pane
|
|
1026
1026
|
local worker_launch="$CLAUDE_BIN --model $WORKER_MODEL --dangerously-skip-permissions"
|
|
1027
1027
|
log " Launching Worker claude in pane $WORKER_PANE..."
|
|
1028
1028
|
tmux send-keys -t "$WORKER_PANE" -l -- "$worker_launch"
|
|
1029
1029
|
tmux send-keys -t "$WORKER_PANE" Enter
|
|
1030
1030
|
|
|
1031
|
-
# Step 5b: Wait for claude TUI to be ready (
|
|
1031
|
+
# Step 5b: Wait for claude TUI to be ready (tmux pattern)
|
|
1032
1032
|
if ! wait_for_pane_ready "$WORKER_PANE" 30; then
|
|
1033
1033
|
log_error "Worker claude failed to start"
|
|
1034
1034
|
write_blocked_sentinel "Worker claude failed to start in pane"
|
|
@@ -1051,10 +1051,19 @@ main() {
|
|
|
1051
1051
|
# --- governance.md s7 step 5+6: Poll for Worker completion ---
|
|
1052
1052
|
log " Polling for iter-signal.json..."
|
|
1053
1053
|
if ! poll_for_signal "$SIGNAL_FILE" "$WORKER_HEARTBEAT" "$WORKER_PANE" "$worker_launch" "Worker"; then
|
|
1054
|
-
#
|
|
1054
|
+
# Check if Worker is still actively running (not stuck)
|
|
1055
|
+
local worker_cmd
|
|
1056
|
+
worker_cmd=$(tmux display-message -p -t "$WORKER_PANE" '#{pane_current_command}' 2>/dev/null)
|
|
1057
|
+
if [[ "$worker_cmd" == "node" || "$worker_cmd" == "claude" ]]; then
|
|
1058
|
+
# Worker is still active — timeout but not a failure, just slow
|
|
1059
|
+
log " Worker timed out but still active ($worker_cmd). Extending..."
|
|
1060
|
+
update_status "worker" "slow"
|
|
1061
|
+
continue
|
|
1062
|
+
fi
|
|
1063
|
+
# Worker is truly dead/stuck
|
|
1055
1064
|
(( MONITOR_FAILURE_COUNT++ ))
|
|
1056
1065
|
if (( MONITOR_FAILURE_COUNT >= 3 )); then
|
|
1057
|
-
write_blocked_sentinel "3 consecutive monitor failures"
|
|
1066
|
+
write_blocked_sentinel "3 consecutive monitor failures (worker not active)"
|
|
1058
1067
|
update_status "blocked" "monitor_failures"
|
|
1059
1068
|
return 1
|
|
1060
1069
|
fi
|