loki-mode 4.2.0 → 5.1.1

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/autonomy/run.sh CHANGED
@@ -11,6 +11,7 @@
11
11
  # ./autonomy/run.sh --parallel ./prd.md # Parallel mode with PRD
12
12
  #
13
13
  # Environment Variables:
14
+ # LOKI_PROVIDER - AI provider: claude (default), codex, gemini
14
15
  # LOKI_MAX_RETRIES - Max retry attempts (default: 50)
15
16
  # LOKI_BASE_WAIT - Base wait time in seconds (default: 60)
16
17
  # LOKI_MAX_WAIT - Max wait time in seconds (default: 3600)
@@ -47,7 +48,7 @@
47
48
  # Autonomous Loop Controls (Ralph Wiggum Mode):
48
49
  # LOKI_COMPLETION_PROMISE - EXPLICIT stop condition text (default: none - runs forever)
49
50
  # Example: "ALL TESTS PASSING 100%"
50
- # Only stops when Claude outputs this EXACT text
51
+ # Only stops when the AI provider outputs this EXACT text
51
52
  # LOKI_MAX_ITERATIONS - Max loop iterations before exit (default: 1000)
52
53
  # LOKI_PERPETUAL_MODE - Ignore ALL completion signals (default: false)
53
54
  # Set to 'true' for truly infinite operation
@@ -65,7 +66,7 @@
65
66
  # LOKI_PARALLEL_MODE - Enable git worktree-based parallelism (default: false)
66
67
  # Use --parallel flag or set to 'true'
67
68
  # LOKI_MAX_WORKTREES - Maximum parallel worktrees (default: 5)
68
- # LOKI_MAX_PARALLEL_SESSIONS - Maximum concurrent Claude sessions (default: 3)
69
+ # LOKI_MAX_PARALLEL_SESSIONS - Maximum concurrent AI sessions (default: 3)
69
70
  # LOKI_PARALLEL_TESTING - Run testing stream in parallel (default: true)
70
71
  # LOKI_PARALLEL_DOCS - Run documentation stream in parallel (default: true)
71
72
  # LOKI_PARALLEL_BLOG - Run blog stream if site has blog (default: false)
@@ -458,6 +459,43 @@ AUTO_MERGE=${LOKI_AUTO_MERGE:-true}
458
459
  COMPLEXITY_TIER=${LOKI_COMPLEXITY:-auto}
459
460
  DETECTED_COMPLEXITY=""
460
461
 
462
+ # Multi-Provider Support (v5.0.0)
463
+ # Provider: claude (default), codex, gemini
464
+ LOKI_PROVIDER=${LOKI_PROVIDER:-claude}
465
+
466
+ # Source provider configuration
467
+ PROVIDERS_DIR="$PROJECT_DIR/providers"
468
+ if [ -f "$PROVIDERS_DIR/loader.sh" ]; then
469
+ # shellcheck source=/dev/null
470
+ source "$PROVIDERS_DIR/loader.sh"
471
+
472
+ # Validate provider
473
+ if ! validate_provider "$LOKI_PROVIDER"; then
474
+ echo "ERROR: Unknown provider: $LOKI_PROVIDER" >&2
475
+ echo "Supported providers: ${SUPPORTED_PROVIDERS[*]}" >&2
476
+ exit 1
477
+ fi
478
+
479
+ # Load provider config
480
+ if ! load_provider "$LOKI_PROVIDER"; then
481
+ echo "ERROR: Failed to load provider config: $LOKI_PROVIDER" >&2
482
+ exit 1
483
+ fi
484
+ else
485
+ # Fallback: Claude-only mode (backwards compatibility)
486
+ PROVIDER_NAME="claude"
487
+ PROVIDER_CLI="claude"
488
+ PROVIDER_AUTONOMOUS_FLAG="--dangerously-skip-permissions"
489
+ PROVIDER_PROMPT_FLAG="-p"
490
+ PROVIDER_DEGRADED=false
491
+ PROVIDER_DISPLAY_NAME="Claude Code"
492
+ PROVIDER_HAS_PARALLEL=true
493
+ PROVIDER_HAS_SUBAGENTS=true
494
+ PROVIDER_HAS_TASK_TOOL=true
495
+ PROVIDER_HAS_MCP=true
496
+ PROVIDER_PROMPT_POSITIONAL=false
497
+ fi
498
+
461
499
  # Track worktree PIDs for cleanup (requires bash 4+ for associative arrays)
462
500
  # Check bash version for parallel mode compatibility
463
501
  BASH_VERSION_MAJOR="${BASH_VERSION%%.*}"
@@ -495,6 +533,7 @@ log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
495
533
  log_warning() { log_warn "$@"; } # Alias for backwards compatibility
496
534
  log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
497
535
  log_step() { echo -e "${CYAN}[STEP]${NC} $*"; }
536
+ log_debug() { [[ "${LOKI_DEBUG:-}" == "true" ]] && echo -e "${CYAN}[DEBUG]${NC} $*" || true; }
498
537
 
499
538
  #===============================================================================
500
539
  # Complexity Tier Detection (Auto-Claude pattern)
@@ -597,6 +636,94 @@ get_phase_names() {
597
636
  esac
598
637
  }
599
638
 
639
+ #===============================================================================
640
+ # Dynamic Tier Selection (RARV-aware model routing)
641
+ #===============================================================================
642
+ # Maps RARV cycle phases to optimal model tiers:
643
+ # - Reason phase -> planning tier (opus/xhigh/high)
644
+ # - Act phase -> development tier (sonnet/high/medium)
645
+ # - Reflect phase -> development tier (sonnet/high/medium)
646
+ # - Verify phase -> fast tier (haiku/low/low)
647
+
648
+ # Global tier for current iteration (set by get_rarv_tier)
649
+ CURRENT_TIER="development"
650
+
651
+ # Get the appropriate tier based on RARV cycle step
652
+ # Args: iteration_count (defaults to ITERATION_COUNT)
653
+ # Returns: tier name (planning, development, fast)
654
+ get_rarv_tier() {
655
+ local iteration="${1:-$ITERATION_COUNT}"
656
+ local rarv_step=$((iteration % 4))
657
+
658
+ case $rarv_step in
659
+ 0) # Reason phase - planning/architecture
660
+ echo "planning"
661
+ ;;
662
+ 1) # Act phase - implementation
663
+ echo "development"
664
+ ;;
665
+ 2) # Reflect phase - review/analysis
666
+ echo "development"
667
+ ;;
668
+ 3) # Verify phase - testing/validation
669
+ echo "fast"
670
+ ;;
671
+ *) # Fallback to development
672
+ echo "development"
673
+ ;;
674
+ esac
675
+ }
676
+
677
+ # Get RARV phase name for logging
678
+ get_rarv_phase_name() {
679
+ local iteration="${1:-$ITERATION_COUNT}"
680
+ local rarv_step=$((iteration % 4))
681
+
682
+ case $rarv_step in
683
+ 0) echo "REASON" ;;
684
+ 1) echo "ACT" ;;
685
+ 2) echo "REFLECT" ;;
686
+ 3) echo "VERIFY" ;;
687
+ *) echo "UNKNOWN" ;;
688
+ esac
689
+ }
690
+
691
+ # Get provider-specific tier parameter based on current tier
692
+ # Uses provider config variables for the tier mapping
693
+ get_provider_tier_param() {
694
+ local tier="${1:-$CURRENT_TIER}"
695
+
696
+ case "${PROVIDER_NAME:-claude}" in
697
+ claude)
698
+ case "$tier" in
699
+ planning) echo "${PROVIDER_MODEL_PLANNING:-opus}" | sed 's/claude-\([a-z]*\).*/\1/' ;;
700
+ development) echo "${PROVIDER_MODEL_DEVELOPMENT:-sonnet}" | sed 's/claude-\([a-z]*\).*/\1/' ;;
701
+ fast) echo "${PROVIDER_MODEL_FAST:-haiku}" | sed 's/claude-\([a-z]*\).*/\1/' ;;
702
+ *) echo "sonnet" ;;
703
+ esac
704
+ ;;
705
+ codex)
706
+ case "$tier" in
707
+ planning) echo "${PROVIDER_EFFORT_PLANNING:-xhigh}" ;;
708
+ development) echo "${PROVIDER_EFFORT_DEVELOPMENT:-high}" ;;
709
+ fast) echo "${PROVIDER_EFFORT_FAST:-low}" ;;
710
+ *) echo "high" ;;
711
+ esac
712
+ ;;
713
+ gemini)
714
+ case "$tier" in
715
+ planning) echo "${PROVIDER_THINKING_PLANNING:-high}" ;;
716
+ development) echo "${PROVIDER_THINKING_DEVELOPMENT:-medium}" ;;
717
+ fast) echo "${PROVIDER_THINKING_FAST:-low}" ;;
718
+ *) echo "medium" ;;
719
+ esac
720
+ ;;
721
+ *)
722
+ echo "development"
723
+ ;;
724
+ esac
725
+ }
726
+
600
727
  #===============================================================================
601
728
  # GitHub Integration Functions (v4.1.0)
602
729
  #===============================================================================
@@ -1155,9 +1282,15 @@ remove_worktree() {
1155
1282
  wait "$pid" 2>/dev/null || true
1156
1283
  fi
1157
1284
 
1158
- # Remove worktree
1159
- git -C "$TARGET_DIR" worktree remove "$worktree_path" --force 2>/dev/null || \
1160
- rm -rf "$worktree_path" 2>/dev/null
1285
+ # Remove worktree (with safety check for rm -rf)
1286
+ git -C "$TARGET_DIR" worktree remove "$worktree_path" --force 2>/dev/null || {
1287
+ # Safety check: only rm -rf if path looks like a worktree (contains .git or is under TARGET_DIR)
1288
+ if [[ -n "$worktree_path" && "$worktree_path" != "/" && "$worktree_path" == "$TARGET_DIR"* ]]; then
1289
+ rm -rf "$worktree_path" 2>/dev/null
1290
+ else
1291
+ log_warn "Skipping unsafe rm -rf for path: $worktree_path"
1292
+ fi
1293
+ }
1161
1294
 
1162
1295
  unset WORKTREE_PATHS[$stream_name]
1163
1296
  unset WORKTREE_PIDS[$stream_name]
@@ -1192,13 +1325,40 @@ spawn_worktree_session() {
1192
1325
  local log_file="$worktree_path/.loki/logs/session-${stream_name}.log"
1193
1326
  mkdir -p "$(dirname "$log_file")"
1194
1327
 
1195
- log_step "Spawning Claude session: $stream_name"
1328
+ # Check provider parallel support
1329
+ if [ "${PROVIDER_HAS_PARALLEL:-false}" != "true" ]; then
1330
+ log_warn "Provider ${PROVIDER_NAME:-unknown} does not support parallel sessions"
1331
+ log_warn "Running sequentially instead (degraded mode)"
1332
+ return 1
1333
+ fi
1334
+
1335
+ log_step "Spawning ${PROVIDER_DISPLAY_NAME:-Claude} session: $stream_name"
1196
1336
 
1197
1337
  (
1198
1338
  cd "$worktree_path"
1199
- claude --dangerously-skip-permissions \
1200
- -p "Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
1201
- >> "$log_file" 2>&1
1339
+ # Provider-specific invocation for parallel sessions
1340
+ case "${PROVIDER_NAME:-claude}" in
1341
+ claude)
1342
+ claude --dangerously-skip-permissions \
1343
+ -p "Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
1344
+ >> "$log_file" 2>&1
1345
+ ;;
1346
+ codex)
1347
+ codex exec --dangerously-bypass-approvals-and-sandbox \
1348
+ "Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
1349
+ >> "$log_file" 2>&1
1350
+ ;;
1351
+ gemini)
1352
+ # Note: -p flag is DEPRECATED per gemini --help. Using positional prompt.
1353
+ gemini --yolo \
1354
+ "Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
1355
+ >> "$log_file" 2>&1
1356
+ ;;
1357
+ *)
1358
+ log_error "Unknown provider: ${PROVIDER_NAME}"
1359
+ return 1
1360
+ ;;
1361
+ esac
1202
1362
  ) &
1203
1363
 
1204
1364
  local pid=$!
@@ -1263,17 +1423,33 @@ resolve_conflicts_with_ai() {
1263
1423
  # Get conflict markers
1264
1424
  local conflict_content=$(cat "$file")
1265
1425
 
1266
- # Use Claude to resolve conflict
1267
- local resolution=$(claude --dangerously-skip-permissions -p "
1268
- You are resolving a git merge conflict. The file below contains conflict markers.
1426
+ # Use AI to resolve conflict (provider-aware)
1427
+ local resolution=""
1428
+ local conflict_prompt="You are resolving a git merge conflict. The file below contains conflict markers.
1269
1429
  Your task is to merge both changes intelligently, preserving functionality from both sides.
1270
1430
 
1271
1431
  FILE: $file
1272
1432
  CONTENT:
1273
1433
  $conflict_content
1274
1434
 
1275
- Output ONLY the resolved file content with no conflict markers. No explanations.
1276
- " --output-format text 2>/dev/null)
1435
+ Output ONLY the resolved file content with no conflict markers. No explanations."
1436
+
1437
+ case "${PROVIDER_NAME:-claude}" in
1438
+ claude)
1439
+ resolution=$(claude --dangerously-skip-permissions -p "$conflict_prompt" --output-format text 2>/dev/null)
1440
+ ;;
1441
+ codex)
1442
+ resolution=$(codex exec --dangerously-bypass-approvals-and-sandbox "$conflict_prompt" 2>/dev/null)
1443
+ ;;
1444
+ gemini)
1445
+ # Note: -p flag is DEPRECATED per gemini --help. Using positional prompt.
1446
+ resolution=$(gemini --yolo "$conflict_prompt" 2>/dev/null)
1447
+ ;;
1448
+ *)
1449
+ log_error "Unknown provider: ${PROVIDER_NAME}"
1450
+ return 1
1451
+ ;;
1452
+ esac
1277
1453
 
1278
1454
  if [ -n "$resolution" ]; then
1279
1455
  echo "$resolution" > "$file"
@@ -1478,15 +1654,31 @@ check_prerequisites() {
1478
1654
 
1479
1655
  local missing=()
1480
1656
 
1481
- # Check Claude Code CLI
1482
- log_step "Checking Claude Code CLI..."
1483
- if command -v claude &> /dev/null; then
1484
- local version=$(claude --version 2>/dev/null | head -1 || echo "unknown")
1485
- log_info "Claude Code CLI: $version"
1657
+ # Check Provider CLI (uses PROVIDER_CLI from loaded provider config)
1658
+ local cli_name="${PROVIDER_CLI:-claude}"
1659
+ local display_name="${PROVIDER_DISPLAY_NAME:-Claude Code}"
1660
+ log_step "Checking $display_name CLI..."
1661
+ if command -v "$cli_name" &> /dev/null; then
1662
+ local version=$("$cli_name" --version 2>/dev/null | head -1 || echo "unknown")
1663
+ log_info "$display_name CLI: $version"
1486
1664
  else
1487
- missing+=("claude")
1488
- log_error "Claude Code CLI not found"
1489
- log_info "Install: https://claude.ai/code or npm install -g @anthropic-ai/claude-code"
1665
+ missing+=("$cli_name")
1666
+ log_error "$display_name CLI not found"
1667
+ case "$cli_name" in
1668
+ claude)
1669
+ log_info "Install: https://claude.ai/code or npm install -g @anthropic-ai/claude-code"
1670
+ ;;
1671
+ codex)
1672
+ log_info "Install: npm install -g @openai/codex"
1673
+ ;;
1674
+ gemini)
1675
+ # TODO: Verify official Gemini CLI package name when available
1676
+ log_info "Install: npm install -g @google/gemini-cli (or visit https://ai.google.dev/)"
1677
+ ;;
1678
+ *)
1679
+ log_info "Install the $cli_name CLI for your provider"
1680
+ ;;
1681
+ esac
1490
1682
  fi
1491
1683
 
1492
1684
  # Check Python 3
@@ -1560,8 +1752,16 @@ check_prerequisites() {
1560
1752
  check_skill_installed() {
1561
1753
  log_header "Checking Loki Mode Skill"
1562
1754
 
1563
- local skill_locations=(
1564
- "$HOME/.claude/skills/loki-mode/SKILL.md"
1755
+ # Build skill locations array dynamically based on provider
1756
+ local skill_locations=()
1757
+
1758
+ # Add provider-specific skill directory if set (e.g., ~/.claude/skills for Claude)
1759
+ if [ -n "${PROVIDER_SKILL_DIR:-}" ]; then
1760
+ skill_locations+=("${PROVIDER_SKILL_DIR}/loki-mode/SKILL.md")
1761
+ fi
1762
+
1763
+ # Add local project skill locations
1764
+ skill_locations+=(
1565
1765
  ".claude/skills/loki-mode/SKILL.md"
1566
1766
  "$PROJECT_DIR/SKILL.md"
1567
1767
  )
@@ -1573,7 +1773,14 @@ check_skill_installed() {
1573
1773
  fi
1574
1774
  done
1575
1775
 
1576
- log_warn "Loki Mode skill not found in standard locations"
1776
+ # For providers without skill system (Codex, Gemini), this is expected
1777
+ if [ -z "${PROVIDER_SKILL_DIR:-}" ]; then
1778
+ log_info "Provider ${PROVIDER_NAME:-unknown} has no native skill directory"
1779
+ log_info "Skill will be passed via prompt injection"
1780
+ else
1781
+ log_warn "Loki Mode skill not found in standard locations"
1782
+ fi
1783
+
1577
1784
  log_info "The skill will be used from: $PROJECT_DIR/SKILL.md"
1578
1785
 
1579
1786
  if [ -f "$PROJECT_DIR/SKILL.md" ]; then
@@ -2133,7 +2340,7 @@ generate_dashboard() {
2133
2340
  <div class="column failed"><h2>Failed <span class="count" id="failed-badge">0</span></h2><div id="failed-tasks"></div></div>
2134
2341
  </div>
2135
2342
  <div class="updated" id="updated">Last updated: -</div>
2136
- <div class="powered-by">Powered by <span>Claude</span></div>
2343
+ <div class="powered-by">Powered by <span>${PROVIDER_DISPLAY_NAME:-Claude}</span></div>
2137
2344
  <button class="refresh" onclick="loadData()">Refresh</button>
2138
2345
  <script>
2139
2346
  async function loadJSON(path) {
@@ -2688,9 +2895,33 @@ calculate_wait() {
2688
2895
  # Rate Limit Detection
2689
2896
  #===============================================================================
2690
2897
 
2691
- # Detect rate limit from log and calculate wait time until reset
2692
- # Returns: seconds to wait, or 0 if no rate limit detected
2693
- detect_rate_limit() {
2898
+ # Detect if output contains rate limit indicators (provider-agnostic)
2899
+ # Returns: 0 if rate limit detected, 1 otherwise
2900
+ is_rate_limited() {
2901
+ local log_file="$1"
2902
+
2903
+ # Generic patterns that work across all providers
2904
+ # - HTTP 429 status code
2905
+ # - "rate limit" / "rate-limit" / "ratelimit" text
2906
+ # - "too many requests" text
2907
+ # - "quota exceeded" text
2908
+ # - "request limit" text
2909
+ # - "retry after" / "retry-after" headers
2910
+ if grep -qiE '(429|rate.?limit|too many requests|quota exceeded|request limit|retry.?after)' "$log_file" 2>/dev/null; then
2911
+ return 0
2912
+ fi
2913
+
2914
+ # Claude-specific: "resets Xam/pm" format
2915
+ if grep -qE 'resets [0-9]+[ap]m' "$log_file" 2>/dev/null; then
2916
+ return 0
2917
+ fi
2918
+
2919
+ return 1
2920
+ }
2921
+
2922
+ # Parse Claude-specific reset time from log
2923
+ # Returns: seconds to wait, or 0 if no reset time found
2924
+ parse_claude_reset_time() {
2694
2925
  local log_file="$1"
2695
2926
 
2696
2927
  # Look for rate limit message like "resets 4am" or "resets 10pm"
@@ -2734,6 +2965,84 @@ detect_rate_limit() {
2734
2965
  echo $wait_secs
2735
2966
  }
2736
2967
 
2968
+ # Parse Retry-After header value (common across providers)
2969
+ # Returns: seconds to wait, or 0 if not found
2970
+ parse_retry_after() {
2971
+ local log_file="$1"
2972
+
2973
+ # Look for Retry-After header (case insensitive)
2974
+ # Format: "Retry-After: 60" or "retry-after: 60"
2975
+ local retry_secs=$(grep -ioE 'retry.?after:?\s*[0-9]+' "$log_file" 2>/dev/null | tail -1 | grep -oE '[0-9]+$')
2976
+
2977
+ if [ -n "$retry_secs" ]; then
2978
+ echo "$retry_secs"
2979
+ else
2980
+ echo 0
2981
+ fi
2982
+ }
2983
+
2984
+ # Calculate default backoff based on provider rate limit
2985
+ # Uses PROVIDER_RATE_LIMIT_RPM from loaded provider config
2986
+ # Returns: seconds to wait
2987
+ calculate_rate_limit_backoff() {
2988
+ local rpm="${PROVIDER_RATE_LIMIT_RPM:-50}"
2989
+
2990
+ # Calculate wait time based on RPM
2991
+ # If RPM is 50, that's ~1.2 requests per second
2992
+ # Default backoff: 60 seconds / RPM * 60 = wait for 1 minute window
2993
+ # But add some buffer, so wait for 2 minute windows
2994
+ local wait_secs=$((120 * 60 / rpm))
2995
+
2996
+ # Minimum 60 seconds, maximum 300 seconds for default backoff
2997
+ if [ "$wait_secs" -lt 60 ]; then
2998
+ wait_secs=60
2999
+ elif [ "$wait_secs" -gt 300 ]; then
3000
+ wait_secs=300
3001
+ fi
3002
+
3003
+ echo $wait_secs
3004
+ }
3005
+
3006
+ # Detect rate limit from log and calculate wait time until reset
3007
+ # Provider-agnostic: checks generic patterns first, then provider-specific
3008
+ # Returns: seconds to wait, or 0 if no rate limit detected
3009
+ detect_rate_limit() {
3010
+ local log_file="$1"
3011
+
3012
+ # First check if rate limited at all
3013
+ if ! is_rate_limited "$log_file"; then
3014
+ echo 0
3015
+ return
3016
+ fi
3017
+
3018
+ # Rate limit detected - now determine wait time
3019
+ local wait_secs=0
3020
+
3021
+ # Try provider-specific reset time parsing
3022
+ case "${PROVIDER_NAME:-claude}" in
3023
+ claude)
3024
+ wait_secs=$(parse_claude_reset_time "$log_file")
3025
+ ;;
3026
+ codex|gemini|*)
3027
+ # No provider-specific reset time format known
3028
+ # Fall through to generic parsing
3029
+ ;;
3030
+ esac
3031
+
3032
+ # If no provider-specific time, try generic Retry-After header
3033
+ if [ "$wait_secs" -eq 0 ]; then
3034
+ wait_secs=$(parse_retry_after "$log_file")
3035
+ fi
3036
+
3037
+ # If still no specific time, use calculated backoff based on provider RPM
3038
+ if [ "$wait_secs" -eq 0 ]; then
3039
+ wait_secs=$(calculate_rate_limit_backoff)
3040
+ log_debug "Using calculated backoff (${PROVIDER_RATE_LIMIT_RPM:-50} RPM): ${wait_secs}s"
3041
+ fi
3042
+
3043
+ echo $wait_secs
3044
+ }
3045
+
2737
3046
  # Format seconds into human-readable time
2738
3047
  format_duration() {
2739
3048
  local secs="$1"
@@ -3035,24 +3344,40 @@ run_autonomous() {
3035
3344
 
3036
3345
  save_state $retry "running" 0
3037
3346
 
3038
- # Run Claude Code with live output
3347
+ # Run AI provider with live output
3039
3348
  local start_time=$(date +%s)
3040
3349
  local log_file=".loki/logs/autonomy-$(date +%Y%m%d).log"
3041
3350
 
3042
3351
  echo ""
3043
3352
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
3044
- echo -e "${CYAN} CLAUDE CODE OUTPUT (live)${NC}"
3353
+ echo -e "${CYAN} ${PROVIDER_DISPLAY_NAME:-CLAUDE CODE} OUTPUT (live)${NC}"
3354
+ if [ "${PROVIDER_DEGRADED:-false}" = "true" ]; then
3355
+ echo -e "${YELLOW} [DEGRADED MODE: Sequential execution only]${NC}"
3356
+ fi
3045
3357
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
3046
3358
  echo ""
3047
3359
 
3048
3360
  # Log start time
3049
3361
  echo "=== Session started at $(date) ===" >> "$log_file"
3362
+ echo "=== Provider: ${PROVIDER_NAME:-claude} ===" >> "$log_file"
3050
3363
  echo "=== Prompt: $prompt ===" >> "$log_file"
3051
3364
 
3365
+ # Dynamic tier selection based on RARV cycle phase
3366
+ CURRENT_TIER=$(get_rarv_tier "$ITERATION_COUNT")
3367
+ local rarv_phase=$(get_rarv_phase_name "$ITERATION_COUNT")
3368
+ local tier_param=$(get_provider_tier_param "$CURRENT_TIER")
3369
+ echo "=== RARV Phase: $rarv_phase, Tier: $CURRENT_TIER ($tier_param) ===" >> "$log_file"
3370
+ log_info "RARV Phase: $rarv_phase -> Tier: $CURRENT_TIER ($tier_param)"
3371
+
3052
3372
  set +e
3053
- # Run Claude with stream-json for real-time output
3054
- # Parse JSON stream, display formatted output, and track agents
3055
- claude --dangerously-skip-permissions -p "$prompt" \
3373
+ # Provider-specific invocation with dynamic tier selection
3374
+ case "${PROVIDER_NAME:-claude}" in
3375
+ claude)
3376
+ # Claude: Full features with stream-json output and agent tracking
3377
+ # Uses dynamic tier for model selection based on RARV phase
3378
+ # Pass tier to Python via environment for dashboard display
3379
+ LOKI_CURRENT_MODEL="$tier_param" \
3380
+ claude --dangerously-skip-permissions --model "$tier_param" -p "$prompt" \
3056
3381
  --output-format stream-json --verbose 2>&1 | \
3057
3382
  tee -a "$log_file" | \
3058
3383
  python3 -u -c '
@@ -3069,6 +3394,9 @@ MAGENTA = "\033[0;35m"
3069
3394
  DIM = "\033[2m"
3070
3395
  NC = "\033[0m"
3071
3396
 
3397
+ # Get current model tier from environment (set by run.sh dynamic tier selection)
3398
+ CURRENT_MODEL = os.environ.get("LOKI_CURRENT_MODEL", "sonnet")
3399
+
3072
3400
  # Agent tracking
3073
3401
  AGENTS_FILE = ".loki/state/agents.json"
3074
3402
  QUEUE_IN_PROGRESS = ".loki/queue/in-progress.json"
@@ -3082,7 +3410,7 @@ def init_orchestrator():
3082
3410
  "agent_id": orchestrator_id,
3083
3411
  "tool_id": orchestrator_id,
3084
3412
  "agent_type": "orchestrator",
3085
- "model": "sonnet",
3413
+ "model": CURRENT_MODEL,
3086
3414
  "current_task": "Initializing...",
3087
3415
  "status": "active",
3088
3416
  "spawned_at": session_start,
@@ -3256,7 +3584,35 @@ if __name__ == "__main__":
3256
3584
  except BrokenPipeError:
3257
3585
  sys.exit(0)
3258
3586
  '
3259
- local exit_code=${PIPESTATUS[0]}
3587
+ local exit_code=${PIPESTATUS[0]}
3588
+ ;;
3589
+
3590
+ codex)
3591
+ # Codex: Degraded mode - no stream-json, no agent tracking
3592
+ # Uses positional prompt after exec subcommand
3593
+ # Note: Effort is set via env var, not CLI flag
3594
+ # Uses dynamic tier from RARV phase (tier_param already set above)
3595
+ CODEX_MODEL_REASONING_EFFORT="$tier_param" \
3596
+ codex exec --dangerously-bypass-approvals-and-sandbox \
3597
+ "$prompt" 2>&1 | tee -a "$log_file"
3598
+ local exit_code=${PIPESTATUS[0]}
3599
+ ;;
3600
+
3601
+ gemini)
3602
+ # Gemini: Degraded mode - no stream-json, no agent tracking
3603
+ # Note: Thinking level is set via settings.json, not CLI flag
3604
+ # Log the dynamic tier for debugging (tier_param already set above)
3605
+ echo "[loki] Gemini thinking tier: $tier_param (configure in ~/.gemini/settings.json)" >> "$log_file"
3606
+ gemini --yolo \
3607
+ "$prompt" 2>&1 | tee -a "$log_file"
3608
+ local exit_code=${PIPESTATUS[0]}
3609
+ ;;
3610
+
3611
+ *)
3612
+ log_error "Unknown provider: ${PROVIDER_NAME:-unknown}"
3613
+ local exit_code=1
3614
+ ;;
3615
+ esac
3260
3616
  set -e
3261
3617
 
3262
3618
  echo ""
@@ -3269,7 +3625,7 @@ if __name__ == "__main__":
3269
3625
  local end_time=$(date +%s)
3270
3626
  local duration=$((end_time - start_time))
3271
3627
 
3272
- log_info "Claude exited with code $exit_code after ${duration}s"
3628
+ log_info "${PROVIDER_DISPLAY_NAME:-Claude} exited with code $exit_code after ${duration}s"
3273
3629
  save_state $retry "exited" $exit_code
3274
3630
 
3275
3631
  # Check for success - ONLY stop on explicit completion promise
@@ -3294,7 +3650,7 @@ if __name__ == "__main__":
3294
3650
 
3295
3651
  # Warn if Claude says it's "done" but no explicit promise
3296
3652
  if is_completed; then
3297
- log_warn "Claude claims completion, but no explicit promise fulfilled."
3653
+ log_warn "${PROVIDER_DISPLAY_NAME:-Claude} claims completion, but no explicit promise fulfilled."
3298
3654
  log_warn "Projects are never truly complete - there are always improvements!"
3299
3655
  fi
3300
3656
 
@@ -3504,28 +3860,76 @@ main() {
3504
3860
 
3505
3861
  # Parse arguments
3506
3862
  PRD_PATH=""
3507
- for arg in "$@"; do
3508
- case "$arg" in
3863
+ REMAINING_ARGS=()
3864
+ while [[ $# -gt 0 ]]; do
3865
+ case "$1" in
3509
3866
  --parallel)
3510
3867
  PARALLEL_MODE=true
3868
+ shift
3869
+ ;;
3870
+ --provider)
3871
+ if [[ -n "${2:-}" ]]; then
3872
+ LOKI_PROVIDER="$2"
3873
+ # Reload provider config
3874
+ if [ -f "$PROVIDERS_DIR/loader.sh" ]; then
3875
+ if ! validate_provider "$LOKI_PROVIDER"; then
3876
+ log_error "Unknown provider: $LOKI_PROVIDER"
3877
+ log_info "Supported providers: ${SUPPORTED_PROVIDERS[*]}"
3878
+ exit 1
3879
+ fi
3880
+ if ! load_provider "$LOKI_PROVIDER"; then
3881
+ log_error "Failed to load provider config: $LOKI_PROVIDER"
3882
+ exit 1
3883
+ fi
3884
+ fi
3885
+ shift 2
3886
+ else
3887
+ log_error "--provider requires a value (claude, codex, gemini)"
3888
+ exit 1
3889
+ fi
3890
+ ;;
3891
+ --provider=*)
3892
+ LOKI_PROVIDER="${1#*=}"
3893
+ # Reload provider config
3894
+ if [ -f "$PROVIDERS_DIR/loader.sh" ]; then
3895
+ if ! validate_provider "$LOKI_PROVIDER"; then
3896
+ log_error "Unknown provider: $LOKI_PROVIDER"
3897
+ log_info "Supported providers: ${SUPPORTED_PROVIDERS[*]}"
3898
+ exit 1
3899
+ fi
3900
+ if ! load_provider "$LOKI_PROVIDER"; then
3901
+ log_error "Failed to load provider config: $LOKI_PROVIDER"
3902
+ exit 1
3903
+ fi
3904
+ fi
3905
+ shift
3511
3906
  ;;
3512
3907
  --help|-h)
3513
3908
  echo "Usage: ./autonomy/run.sh [OPTIONS] [PRD_PATH]"
3514
3909
  echo ""
3515
3910
  echo "Options:"
3516
- echo " --parallel Enable git worktree-based parallel workflows"
3517
- echo " --help, -h Show this help message"
3911
+ echo " --parallel Enable git worktree-based parallel workflows"
3912
+ echo " --provider <name> Provider: claude (default), codex, gemini"
3913
+ echo " --help, -h Show this help message"
3518
3914
  echo ""
3519
3915
  echo "Environment variables: See header comments in this script"
3916
+ echo ""
3917
+ echo "Provider capabilities:"
3918
+ if [ -f "$PROVIDERS_DIR/loader.sh" ]; then
3919
+ print_capability_matrix
3920
+ fi
3520
3921
  exit 0
3521
3922
  ;;
3522
3923
  *)
3523
- if [ -z "$PRD_PATH" ]; then
3524
- PRD_PATH="$arg"
3924
+ if [ -z "$PRD_PATH" ] && [[ ! "$1" == -* ]]; then
3925
+ PRD_PATH="$1"
3525
3926
  fi
3927
+ REMAINING_ARGS+=("$1")
3928
+ shift
3526
3929
  ;;
3527
3930
  esac
3528
3931
  done
3932
+ set -- "${REMAINING_ARGS[@]}"
3529
3933
 
3530
3934
  # Validate PRD if provided
3531
3935
  if [ -n "$PRD_PATH" ] && [ ! -f "$PRD_PATH" ]; then
@@ -3533,9 +3937,28 @@ main() {
3533
3937
  exit 1
3534
3938
  fi
3535
3939
 
3940
+ # Show provider info
3941
+ log_info "Provider: ${PROVIDER_DISPLAY_NAME:-Claude Code} (${PROVIDER_NAME:-claude})"
3942
+ if [ "${PROVIDER_DEGRADED:-false}" = "true" ]; then
3943
+ log_warn "Degraded mode: Parallel agents and Task tool not available"
3944
+ # Check if array exists and has elements before iterating
3945
+ if [ -n "${PROVIDER_DEGRADED_REASONS+x}" ] && [ ${#PROVIDER_DEGRADED_REASONS[@]} -gt 0 ]; then
3946
+ log_info "Limitations:"
3947
+ for reason in "${PROVIDER_DEGRADED_REASONS[@]}"; do
3948
+ log_info " - $reason"
3949
+ done
3950
+ fi
3951
+ fi
3952
+
3536
3953
  # Show parallel mode status
3537
3954
  if [ "$PARALLEL_MODE" = "true" ]; then
3538
- log_info "Parallel mode enabled (git worktrees)"
3955
+ if [ "${PROVIDER_HAS_PARALLEL:-false}" = "true" ]; then
3956
+ log_info "Parallel mode enabled (git worktrees)"
3957
+ else
3958
+ log_warn "Parallel mode requested but not supported by ${PROVIDER_NAME:-unknown}"
3959
+ log_warn "Running in sequential mode instead"
3960
+ PARALLEL_MODE=false
3961
+ fi
3539
3962
  fi
3540
3963
 
3541
3964
  # Check prerequisites (unless skipped)