loki-mode 6.4.0 → 6.6.0

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/events/emit.sh CHANGED
@@ -43,7 +43,7 @@ fi
43
43
 
44
44
  # JSON escape helper: handles \, ", and control characters
45
45
  json_escape() {
46
- printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g; s/\r/\\r/g' | tr -d '\n'
46
+ printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g; s/\r/\\r/g' | awk '{if(NR>1) printf "\\n"; printf "%s", $0}'
47
47
  }
48
48
 
49
49
  # Build payload JSON
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '6.4.0'
60
+ __version__ = '6.6.0'
package/mcp/server.py CHANGED
@@ -20,6 +20,7 @@ import os
20
20
  import json
21
21
  import logging
22
22
  import threading
23
+ import uuid
23
24
  from datetime import datetime, timezone
24
25
  from typing import Optional, List, Dict, Any
25
26
 
@@ -563,11 +564,11 @@ async def loki_memory_store_pattern(
563
564
  from memory.schemas import SemanticPattern
564
565
 
565
566
  base_path = safe_path_join('.loki', 'memory')
566
- engine = MemoryEngine(base_path)
567
+ engine = MemoryEngine(base_path=base_path)
567
568
  engine.initialize()
568
569
 
569
570
  pattern_obj = SemanticPattern(
570
- id=f"pattern-{datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S')}",
571
+ id=f"pattern-{datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S')}-{uuid.uuid4().hex[:8]}",
571
572
  pattern=pattern,
572
573
  category=category,
573
574
  conditions=[],
@@ -821,7 +822,7 @@ async def loki_state_get() -> str:
821
822
  try:
822
823
  from memory.engine import MemoryEngine
823
824
  memory_path = safe_path_join('.loki', 'memory')
824
- engine = MemoryEngine(memory_path)
825
+ engine = MemoryEngine(base_path=memory_path)
825
826
  state["memory_stats"] = engine.get_stats()
826
827
  except Exception:
827
828
  state["memory_stats"] = None
@@ -1004,9 +1005,13 @@ async def loki_start_project(prd_content: str = "", prd_path: str = "") -> str:
1004
1005
  try:
1005
1006
  content = prd_content
1006
1007
  if not content and prd_path:
1007
- resolved = safe_path_join('.', prd_path)
1008
- if os.path.exists(resolved):
1009
- with safe_open(resolved, 'r') as f:
1008
+ # Resolve relative paths against project root, absolute paths used as-is
1009
+ if os.path.isabs(prd_path):
1010
+ resolved = os.path.realpath(prd_path)
1011
+ else:
1012
+ resolved = os.path.realpath(os.path.join(get_project_root(), prd_path))
1013
+ if os.path.exists(resolved) and os.path.isfile(resolved):
1014
+ with open(resolved, 'r', encoding='utf-8') as f:
1010
1015
  content = f.read()
1011
1016
  else:
1012
1017
  return json.dumps({"error": f"PRD file not found: {prd_path}"})
@@ -307,6 +307,10 @@ class TextChunker:
307
307
  if len(text) <= max_size:
308
308
  return [text]
309
309
 
310
+ # Guard against infinite loop when overlap >= max_size
311
+ if overlap >= max_size:
312
+ overlap = 0
313
+
310
314
  chunks = []
311
315
  start = 0
312
316
  while start < len(text):
@@ -1062,12 +1066,17 @@ class EmbeddingEngine:
1062
1066
  # Find texts that need computing
1063
1067
  texts_to_compute = []
1064
1068
  indices_to_compute = []
1065
- for i, (text, key) in enumerate(zip(texts, cache_keys)):
1066
- if not self.config.cache_enabled or cached_results.get(key) is None:
1067
- texts_to_compute.append(text)
1068
- indices_to_compute.append(i)
1069
- else:
1070
- self._metrics["cache_hits"] += 1
1069
+ if not self.config.cache_enabled:
1070
+ # No cache - all texts need computing
1071
+ texts_to_compute = list(texts)
1072
+ indices_to_compute = list(range(len(texts)))
1073
+ else:
1074
+ for i, (text, key) in enumerate(zip(texts, cache_keys)):
1075
+ if cached_results.get(key) is None:
1076
+ texts_to_compute.append(text)
1077
+ indices_to_compute.append(i)
1078
+ else:
1079
+ self._metrics["cache_hits"] += 1
1071
1080
 
1072
1081
  # Compute missing embeddings
1073
1082
  new_embeddings = None
package/memory/engine.py CHANGED
@@ -881,6 +881,21 @@ class MemoryEngine:
881
881
  for e in errors_raw
882
882
  ]
883
883
 
884
+ # Parse last_accessed datetime
885
+ last_accessed = None
886
+ last_accessed_raw = data.get("last_accessed")
887
+ if last_accessed_raw:
888
+ if isinstance(last_accessed_raw, str):
889
+ if last_accessed_raw.endswith("Z"):
890
+ last_accessed_raw = last_accessed_raw[:-1]
891
+ last_accessed = datetime.fromisoformat(last_accessed_raw)
892
+ if last_accessed.tzinfo is None:
893
+ last_accessed = last_accessed.replace(tzinfo=timezone.utc)
894
+ elif isinstance(last_accessed_raw, datetime):
895
+ last_accessed = last_accessed_raw
896
+ if last_accessed.tzinfo is None:
897
+ last_accessed = last_accessed.replace(tzinfo=timezone.utc)
898
+
884
899
  return EpisodeTrace(
885
900
  id=data.get("id", ""),
886
901
  task_id=data.get("task_id", ""),
@@ -897,6 +912,9 @@ class MemoryEngine:
897
912
  tokens_used=data.get("tokens_used", 0),
898
913
  files_read=data.get("files_read", context.get("files_involved", [])),
899
914
  files_modified=data.get("files_modified", []),
915
+ importance=data.get("importance", 0.5),
916
+ last_accessed=last_accessed,
917
+ access_count=data.get("access_count", 0),
900
918
  )
901
919
 
902
920
  def _dict_to_pattern(self, data: Dict[str, Any]) -> SemanticPattern:
@@ -924,6 +942,21 @@ class MemoryEngine:
924
942
  for link in links_raw
925
943
  ]
926
944
 
945
+ # Parse last_accessed datetime
946
+ last_accessed = None
947
+ last_accessed_raw = data.get("last_accessed")
948
+ if last_accessed_raw:
949
+ if isinstance(last_accessed_raw, str):
950
+ if last_accessed_raw.endswith("Z"):
951
+ last_accessed_raw = last_accessed_raw[:-1]
952
+ last_accessed = datetime.fromisoformat(last_accessed_raw)
953
+ if last_accessed.tzinfo is None:
954
+ last_accessed = last_accessed.replace(tzinfo=timezone.utc)
955
+ elif isinstance(last_accessed_raw, datetime):
956
+ last_accessed = last_accessed_raw
957
+ if last_accessed.tzinfo is None:
958
+ last_accessed = last_accessed.replace(tzinfo=timezone.utc)
959
+
927
960
  return SemanticPattern(
928
961
  id=data.get("id", ""),
929
962
  pattern=data.get("pattern", ""),
@@ -936,6 +969,9 @@ class MemoryEngine:
936
969
  usage_count=data.get("usage_count", 0),
937
970
  last_used=last_used,
938
971
  links=links,
972
+ importance=data.get("importance", 0.5),
973
+ last_accessed=last_accessed,
974
+ access_count=data.get("access_count", 0),
939
975
  )
940
976
 
941
977
  def _dict_to_skill(self, data: Dict[str, Any]) -> ProceduralSkill:
@@ -945,6 +981,22 @@ class MemoryEngine:
945
981
  ErrorFix.from_dict(e) if isinstance(e, dict) else e
946
982
  for e in raw_errors
947
983
  ]
984
+
985
+ # Parse last_accessed datetime
986
+ last_accessed = None
987
+ last_accessed_raw = data.get("last_accessed")
988
+ if last_accessed_raw:
989
+ if isinstance(last_accessed_raw, str):
990
+ if last_accessed_raw.endswith("Z"):
991
+ last_accessed_raw = last_accessed_raw[:-1]
992
+ last_accessed = datetime.fromisoformat(last_accessed_raw)
993
+ if last_accessed.tzinfo is None:
994
+ last_accessed = last_accessed.replace(tzinfo=timezone.utc)
995
+ elif isinstance(last_accessed_raw, datetime):
996
+ last_accessed = last_accessed_raw
997
+ if last_accessed.tzinfo is None:
998
+ last_accessed = last_accessed.replace(tzinfo=timezone.utc)
999
+
948
1000
  return ProceduralSkill(
949
1001
  id=data.get("id", ""),
950
1002
  name=data.get("name", ""),
@@ -953,6 +1005,10 @@ class MemoryEngine:
953
1005
  steps=data.get("steps", []),
954
1006
  common_errors=common_errors,
955
1007
  exit_criteria=data.get("exit_criteria", []),
1008
+ example_usage=data.get("example_usage"),
1009
+ importance=data.get("importance", 0.5),
1010
+ last_accessed=last_accessed,
1011
+ access_count=data.get("access_count", 0),
956
1012
  )
957
1013
 
958
1014
  def _skill_to_markdown(self, skill: Dict[str, Any]) -> str:
package/memory/schemas.py CHANGED
@@ -20,9 +20,13 @@ from typing import Optional, List, Dict, Any
20
20
  def _to_utc_isoformat(dt: datetime) -> str:
21
21
  """Convert datetime to UTC ISO 8601 string with Z suffix.
22
22
 
23
- Handles both timezone-aware and timezone-naive datetimes,
24
- and avoids double-suffixing if already has timezone info.
23
+ Handles both timezone-aware and timezone-naive datetimes.
24
+ If dt has a non-UTC timezone, converts to UTC first.
25
25
  """
26
+ # If timezone-aware and not UTC, convert to UTC
27
+ if dt.tzinfo is not None and dt.utcoffset() != timezone.utc.utcoffset(None):
28
+ dt = dt.astimezone(timezone.utc)
29
+
26
30
  iso = dt.isoformat()
27
31
  # If already has timezone offset like +00:00, replace with Z
28
32
  if iso.endswith("+00:00"):
package/memory/storage.py CHANGED
@@ -233,7 +233,7 @@ class MemoryStorage:
233
233
  path: Path to JSON file
234
234
 
235
235
  Returns:
236
- Parsed JSON as dictionary, or None if file doesn't exist
236
+ Parsed JSON as dictionary, or None if file doesn't exist or is corrupted
237
237
  """
238
238
  path = Path(path)
239
239
  if not path.exists():
@@ -241,7 +241,10 @@ class MemoryStorage:
241
241
 
242
242
  with self._file_lock(path, exclusive=False):
243
243
  with open(path, "r") as f:
244
- return json.load(f)
244
+ try:
245
+ return json.load(f)
246
+ except json.JSONDecodeError:
247
+ return None
245
248
 
246
249
  def _generate_id(self, prefix: str) -> str:
247
250
  """
@@ -505,9 +505,12 @@ class TokenEconomics:
505
505
  Ratio of discovery_tokens / read_tokens (0.0 if no reads)
506
506
  """
507
507
  read_tokens = self.metrics["read_tokens"]
508
+ discovery_tokens = self.metrics["discovery_tokens"]
508
509
  if read_tokens == 0:
510
+ if discovery_tokens > 0:
511
+ return 999.99 # Sentinel: all discovery, no productive reads
509
512
  return 0.0
510
- return self.metrics["discovery_tokens"] / read_tokens
513
+ return discovery_tokens / read_tokens
511
514
 
512
515
  def get_savings_percent(self) -> float:
513
516
  """
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "6.4.0",
3
+ "version": "6.6.0",
4
4
  "description": "Loki Mode by Autonomi - Multi-agent autonomous startup system for Claude Code, Codex CLI, and Gemini CLI",
5
5
  "keywords": [
6
6
  "agent",
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env bash
2
+ # Aider Provider Configuration (18+ Providers, Tier 3)
3
+ # Shell-sourceable config for loki-mode multi-provider support
4
+
5
+ # Provider Functions (for external use)
6
+ # =====================================
7
+ # These functions provide a clean interface for external scripts:
8
+ # provider_detect() - Check if CLI is installed
9
+ # provider_version() - Get CLI version
10
+ # provider_invoke() - Invoke with prompt (autonomous mode)
11
+ # provider_invoke_with_tier() - Invoke with tier-specific model selection
12
+ # provider_get_tier_param() - Map tier name to model name
13
+ #
14
+ # Usage:
15
+ # source providers/aider.sh
16
+ # if provider_detect; then
17
+ # provider_invoke "Your prompt here"
18
+ # fi
19
+ #
20
+ # Note: autonomy/run.sh uses inline invocation for streaming support
21
+ # and real-time agent tracking. These functions are intended for
22
+ # simpler scripts, wrappers, and external integrations.
23
+ # =====================================
24
+
25
+ # Provider Identity
26
+ PROVIDER_NAME="aider"
27
+ PROVIDER_DISPLAY_NAME="Aider (18+ Providers)"
28
+ PROVIDER_CLI="aider"
29
+
30
+ # CLI Invocation
31
+ # Aider uses --message for single-instruction mode, --yes-always for auto-approve
32
+ # Prompt is passed via --message flag, not positional
33
+ PROVIDER_AUTONOMOUS_FLAG="--yes-always"
34
+ PROVIDER_PROMPT_FLAG="--message"
35
+ PROVIDER_PROMPT_POSITIONAL=false
36
+
37
+ # Skill System
38
+ # Aider does not have a native skills directory
39
+ PROVIDER_SKILL_DIR=""
40
+ PROVIDER_SKILL_FORMAT="none"
41
+
42
+ # Capability Flags
43
+ PROVIDER_HAS_SUBAGENTS=false
44
+ PROVIDER_HAS_PARALLEL=false
45
+ PROVIDER_HAS_TASK_TOOL=false
46
+ PROVIDER_HAS_MCP=false
47
+ PROVIDER_MAX_PARALLEL=1
48
+
49
+ # Model Configuration
50
+ # Aider supports 18+ providers; model configured via LOKI_AIDER_MODEL env var
51
+ # or provider-specific env vars (OPENAI_API_KEY, OPENAI_API_BASE, etc.)
52
+ PROVIDER_MODEL_PLANNING="${LOKI_AIDER_MODEL:-claude-3.7-sonnet}"
53
+ PROVIDER_MODEL_DEVELOPMENT="${LOKI_AIDER_MODEL:-claude-3.7-sonnet}"
54
+ PROVIDER_MODEL_FAST="${LOKI_AIDER_MODEL:-claude-3.7-sonnet}"
55
+
56
+ # No Task tool - model is configured externally
57
+ PROVIDER_TASK_MODEL_PARAM=""
58
+ PROVIDER_TASK_MODEL_VALUES=()
59
+
60
+ # Context and Limits (varies by underlying model, conservative defaults)
61
+ PROVIDER_CONTEXT_WINDOW=200000
62
+ PROVIDER_MAX_OUTPUT_TOKENS=128000
63
+ PROVIDER_RATE_LIMIT_RPM=60
64
+
65
+ # Cost (varies by underlying provider, not tracked by loki)
66
+ PROVIDER_COST_INPUT_PLANNING=0.003
67
+ PROVIDER_COST_OUTPUT_PLANNING=0.015
68
+ PROVIDER_COST_INPUT_DEV=0.003
69
+ PROVIDER_COST_OUTPUT_DEV=0.015
70
+ PROVIDER_COST_INPUT_FAST=0.003
71
+ PROVIDER_COST_OUTPUT_FAST=0.015
72
+
73
+ # Degraded Mode
74
+ PROVIDER_DEGRADED=true
75
+ PROVIDER_DEGRADED_REASONS=(
76
+ "No subagent support"
77
+ "Sequential execution only"
78
+ "No Task tool or MCP"
79
+ )
80
+
81
+ # Detection function - check if provider CLI is available
82
+ provider_detect() {
83
+ command -v aider >/dev/null 2>&1
84
+ }
85
+
86
+ # Version check function
87
+ provider_version() {
88
+ aider --version 2>/dev/null | head -1
89
+ }
90
+
91
+ # Invocation function
92
+ # --message: single instruction mode (process and exit)
93
+ # --yes-always: auto-approve all prompts
94
+ # --no-auto-commits: loki manages git
95
+ provider_invoke() {
96
+ local prompt="$1"
97
+ shift
98
+ local model="${LOKI_AIDER_MODEL:-claude-3.7-sonnet}"
99
+ local extra_flags="${LOKI_AIDER_FLAGS:-}"
100
+ # shellcheck disable=SC2086
101
+ aider --message "$prompt" \
102
+ --yes-always \
103
+ --no-auto-commits \
104
+ --model "$model" \
105
+ $extra_flags "$@" 2>&1
106
+ }
107
+
108
+ # Model tier to parameter (Aider uses single model, returns model name)
109
+ provider_get_tier_param() {
110
+ local tier="$1"
111
+ echo "${LOKI_AIDER_MODEL:-claude-3.7-sonnet}"
112
+ }
113
+
114
+ # Dynamic model resolution (v6.0.0)
115
+ # Aider uses a single externally-configured model, so tier resolution
116
+ # just returns the configured model name. maxTier has no effect.
117
+ resolve_model_for_tier() {
118
+ local tier="$1"
119
+ echo "${LOKI_AIDER_MODEL:-claude-3.7-sonnet}"
120
+ }
121
+
122
+ # Tier-aware invocation
123
+ # Aider uses a single model configured externally, tier has no effect
124
+ provider_invoke_with_tier() {
125
+ local tier="$1"
126
+ local prompt="$2"
127
+ shift 2
128
+ provider_invoke "$prompt" "$@"
129
+ }
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env bash
2
+ # Cline CLI Provider Configuration (Multi-Provider, Tier 2)
3
+ # Shell-sourceable config for loki-mode multi-provider support
4
+
5
+ # Provider Functions (for external use)
6
+ # =====================================
7
+ # These functions provide a clean interface for external scripts:
8
+ # provider_detect() - Check if CLI is installed
9
+ # provider_version() - Get CLI version
10
+ # provider_invoke() - Invoke with prompt (autonomous mode)
11
+ # provider_invoke_with_tier() - Invoke with tier-specific model selection
12
+ # provider_get_tier_param() - Map tier name to model name
13
+ #
14
+ # Usage:
15
+ # source providers/cline.sh
16
+ # if provider_detect; then
17
+ # provider_invoke "Your prompt here"
18
+ # fi
19
+ #
20
+ # Note: autonomy/run.sh uses inline invocation for streaming support
21
+ # and real-time agent tracking. These functions are intended for
22
+ # simpler scripts, wrappers, and external integrations.
23
+ # =====================================
24
+
25
+ # Provider Identity
26
+ PROVIDER_NAME="cline"
27
+ PROVIDER_DISPLAY_NAME="Cline CLI (Multi-Provider)"
28
+ PROVIDER_CLI="cline"
29
+
30
+ # CLI Invocation
31
+ # Cline CLI v2.5+ uses -y / --yolo for autonomous mode
32
+ # Prompt is passed as positional argument
33
+ PROVIDER_AUTONOMOUS_FLAG="-y"
34
+ PROVIDER_PROMPT_FLAG=""
35
+ PROVIDER_PROMPT_POSITIONAL=true
36
+
37
+ # Skill System
38
+ # Cline does not have a native skills directory
39
+ PROVIDER_SKILL_DIR=""
40
+ PROVIDER_SKILL_FORMAT="none"
41
+
42
+ # Capability Flags
43
+ # Cline has subagents and MCP -- NOT fully degraded (Tier 2)
44
+ PROVIDER_HAS_SUBAGENTS=true
45
+ PROVIDER_HAS_PARALLEL=false
46
+ PROVIDER_HAS_TASK_TOOL=false
47
+ PROVIDER_HAS_MCP=true
48
+ PROVIDER_MAX_PARALLEL=1
49
+
50
+ # Model Configuration
51
+ # Cline supports 12+ providers; model configured via LOKI_CLINE_MODEL env var
52
+ # or `cline auth` one-time setup. Defaults are placeholders.
53
+ PROVIDER_MODEL_PLANNING="${LOKI_CLINE_MODEL:-claude-sonnet-4-5-20250929}"
54
+ PROVIDER_MODEL_DEVELOPMENT="${LOKI_CLINE_MODEL:-claude-sonnet-4-5-20250929}"
55
+ PROVIDER_MODEL_FAST="${LOKI_CLINE_MODEL:-claude-sonnet-4-5-20250929}"
56
+
57
+ # No Task tool - model is configured externally
58
+ PROVIDER_TASK_MODEL_PARAM=""
59
+ PROVIDER_TASK_MODEL_VALUES=()
60
+
61
+ # Context and Limits (varies by underlying provider, conservative defaults)
62
+ PROVIDER_CONTEXT_WINDOW=200000
63
+ PROVIDER_MAX_OUTPUT_TOKENS=128000
64
+ PROVIDER_RATE_LIMIT_RPM=60
65
+
66
+ # Cost (varies by underlying provider, not tracked by loki)
67
+ PROVIDER_COST_INPUT_PLANNING=0.003
68
+ PROVIDER_COST_OUTPUT_PLANNING=0.015
69
+ PROVIDER_COST_INPUT_DEV=0.003
70
+ PROVIDER_COST_OUTPUT_DEV=0.015
71
+ PROVIDER_COST_INPUT_FAST=0.003
72
+ PROVIDER_COST_OUTPUT_FAST=0.015
73
+
74
+ # Degraded Mode
75
+ # Cline is NOT degraded -- it has subagents and MCP (Tier 2: near-full)
76
+ PROVIDER_DEGRADED=false
77
+ PROVIDER_DEGRADED_REASONS=()
78
+
79
+ # Detection function - check if provider CLI is available
80
+ provider_detect() {
81
+ command -v cline >/dev/null 2>&1
82
+ }
83
+
84
+ # Version check function
85
+ provider_version() {
86
+ cline --version 2>/dev/null | head -1
87
+ }
88
+
89
+ # Invocation function
90
+ # Uses -y (YOLO) for autonomous mode, positional prompt
91
+ provider_invoke() {
92
+ local prompt="$1"
93
+ shift
94
+ local model="${LOKI_CLINE_MODEL:-}"
95
+ local model_flag=""
96
+ [[ -n "$model" ]] && model_flag="-m $model"
97
+ # shellcheck disable=SC2086
98
+ cline -y $model_flag "$prompt" "$@" 2>&1
99
+ }
100
+
101
+ # Model tier to parameter (Cline uses single model, returns model name)
102
+ provider_get_tier_param() {
103
+ local tier="$1"
104
+ echo "${LOKI_CLINE_MODEL:-default}"
105
+ }
106
+
107
+ # Dynamic model resolution (v6.0.0)
108
+ # Cline uses a single externally-configured model, so tier resolution
109
+ # just returns the configured model name. maxTier has no effect.
110
+ resolve_model_for_tier() {
111
+ local tier="$1"
112
+ echo "${LOKI_CLINE_MODEL:-default}"
113
+ }
114
+
115
+ # Tier-aware invocation
116
+ # Cline uses a single model configured externally, tier has no effect
117
+ provider_invoke_with_tier() {
118
+ local tier="$1"
119
+ local prompt="$2"
120
+ shift 2
121
+ provider_invoke "$prompt" "$@"
122
+ }
@@ -5,7 +5,7 @@
5
5
  PROVIDERS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
6
 
7
7
  # List of supported providers
8
- SUPPORTED_PROVIDERS=("claude" "codex" "gemini")
8
+ SUPPORTED_PROVIDERS=("claude" "codex" "gemini" "cline" "aider")
9
9
 
10
10
  # Default provider
11
11
  DEFAULT_PROVIDER="claude"
@@ -2,13 +2,15 @@
2
2
 
3
3
  ## Multi-Provider Support (v5.0.0)
4
4
 
5
- Loki Mode supports three AI providers. Claude has full features; Codex and Gemini run in **degraded mode** (sequential execution only, no Task tool, no parallel agents).
5
+ Loki Mode supports five AI providers. Claude has full features; all others run in **degraded mode** (sequential execution only, no Task tool, no parallel agents).
6
6
 
7
7
  | Provider | Full Features | Degraded | CLI Flag |
8
8
  |----------|---------------|----------|----------|
9
9
  | **Claude Code** | Yes | No | `--provider claude` (default) |
10
10
  | **OpenAI Codex CLI** | No | Yes | `--provider codex` |
11
11
  | **Google Gemini CLI** | No | Yes | `--provider gemini` |
12
+ | **Cline CLI** | No | Yes | `--provider cline` |
13
+ | **Aider** | No | Yes | `--provider aider` |
12
14
 
13
15
  **Degraded mode limitations:**
14
16
  - No Task tool (cannot spawn subagents)