loki-mode 5.48.1 → 5.49.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/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes PRD to deployed product with zero human intervention. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v5.48.1
6
+ # Loki Mode v5.49.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -68,15 +68,16 @@ VERIFY: Run tests. Check build. Validate against spec.
68
68
 
69
69
  ## PRIORITY 3: Autonomy Rules
70
70
 
71
- These rules are ABSOLUTE. Violating them is a critical failure.
71
+ These rules guide autonomous operation. Test results and code quality always take precedence.
72
72
 
73
73
  | Rule | Meaning |
74
74
  |------|---------|
75
- | **NEVER ask** | Do not output questions. Decide and act. |
76
- | **NEVER wait** | Do not pause for confirmation. Execute immediately. |
77
- | **NEVER stop** | There is always another improvement. Find it. |
78
- | **ALWAYS verify** | Code without tests is incomplete. Run tests. |
75
+ | **Decide and act** | Make decisions autonomously. Do not ask the user questions. |
76
+ | **Keep momentum** | Do not pause for confirmation. Move to the next task. |
77
+ | **Iterate continuously** | There is always another improvement. Find it. |
78
+ | **ALWAYS verify** | Code without tests is incomplete. Run tests. **Never ignore or delete failing tests.** |
79
79
  | **ALWAYS commit** | Atomic commits after each task. Checkpoint progress. |
80
+ | **Tests are sacred** | If tests fail, fix the code -- never delete or skip the tests. A passing test suite is a hard requirement. |
80
81
 
81
82
  ---
82
83
 
@@ -262,4 +263,4 @@ The following features are documented in skill modules but not yet fully automat
262
263
  | Quality gates 3-reviewer system | Implemented (v5.35.0) | 5 specialist reviewers in `skills/quality-gates.md`; execution in run.sh |
263
264
  | Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
264
265
 
265
- **v5.48.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
266
+ **v5.49.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 5.48.1
1
+ 5.49.0
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  #===============================================================================
3
3
  # App Runner Module (v5.45.0)
4
4
  #
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  #===============================================================================
3
3
  # Completion Council - Multi-Agent Completion Verification
4
4
  #
@@ -45,6 +45,14 @@ COUNCIL_MIN_ITERATIONS=${LOKI_COUNCIL_MIN_ITERATIONS:-3}
45
45
  COUNCIL_CONVERGENCE_WINDOW=${LOKI_COUNCIL_CONVERGENCE_WINDOW:-3}
46
46
  COUNCIL_STAGNATION_LIMIT=${LOKI_COUNCIL_STAGNATION_LIMIT:-5}
47
47
 
48
+ # Error budget: severity-aware completion (v5.49.0)
49
+ # SEVERITY_THRESHOLD: minimum severity that blocks completion (critical, high, medium, low)
50
+ # "critical" = only critical issues block (most permissive)
51
+ # "low" = all issues block (strictest, default for backwards compat)
52
+ # ERROR_BUDGET: fraction of non-blocking issues allowed (0.0 = none, 0.1 = 10% tolerance)
53
+ COUNCIL_SEVERITY_THRESHOLD=${LOKI_COUNCIL_SEVERITY_THRESHOLD:-low}
54
+ COUNCIL_ERROR_BUDGET=${LOKI_COUNCIL_ERROR_BUDGET:-0.0}
55
+
48
56
  # Internal state
49
57
  COUNCIL_STATE_DIR=""
50
58
  COUNCIL_PRD_PATH=""
@@ -235,6 +243,38 @@ council_vote() {
235
243
  local vote_result
236
244
  vote_result=$(echo "$verdict" | grep -oE "VOTE:\s*(APPROVE|REJECT)" | grep -oE "APPROVE|REJECT" | head -1)
237
245
 
246
+ # Extract severity-categorized issues (v5.49.0 error budget)
247
+ local member_issues=""
248
+ member_issues=$(echo "$verdict" | grep -oE "ISSUES:\s*(CRITICAL|HIGH|MEDIUM|LOW):.*" || true)
249
+
250
+ # If error budget is active and member rejected, check if rejection
251
+ # is based only on issues below the severity threshold
252
+ if [ "$vote_result" = "REJECT" ] && [ "$COUNCIL_SEVERITY_THRESHOLD" != "low" ] && [ -n "$member_issues" ]; then
253
+ local has_blocking_issue=false
254
+ local severity_order="critical high medium low"
255
+ local threshold_reached=false
256
+
257
+ while IFS= read -r issue_line; do
258
+ local issue_severity
259
+ issue_severity=$(echo "$issue_line" | grep -oE "(CRITICAL|HIGH|MEDIUM|LOW)" | head -1 | tr '[:upper:]' '[:lower:]')
260
+ # Check if this severity meets or exceeds the threshold
261
+ for sev in $severity_order; do
262
+ if [ "$sev" = "$COUNCIL_SEVERITY_THRESHOLD" ]; then
263
+ threshold_reached=true
264
+ fi
265
+ if [ "$sev" = "$issue_severity" ] && [ "$threshold_reached" = "false" ]; then
266
+ has_blocking_issue=true
267
+ break
268
+ fi
269
+ done
270
+ done <<< "$member_issues"
271
+
272
+ if [ "$has_blocking_issue" = "false" ]; then
273
+ log_info " Member $member ($role): REJECT overridden to APPROVE (issues below ${COUNCIL_SEVERITY_THRESHOLD} threshold)"
274
+ vote_result="APPROVE"
275
+ fi
276
+ fi
277
+
238
278
  if [ "$vote_result" = "APPROVE" ]; then
239
279
  ((approve_count++))
240
280
  log_info " Member $member ($role): APPROVE"
@@ -618,23 +658,37 @@ council_member_review() {
618
658
  ;;
619
659
  esac
620
660
 
661
+ local severity_instruction=""
662
+ if [ "$COUNCIL_SEVERITY_THRESHOLD" != "low" ]; then
663
+ severity_instruction="
664
+ ERROR BUDGET: This council uses severity-aware evaluation.
665
+ - Categorize each issue as CRITICAL, HIGH, MEDIUM, or LOW severity
666
+ - Blocking threshold: ${COUNCIL_SEVERITY_THRESHOLD} and above
667
+ - Only issues at ${COUNCIL_SEVERITY_THRESHOLD} severity or above should cause REJECT
668
+ - Issues below threshold are acceptable (error budget: ${COUNCIL_ERROR_BUDGET})
669
+ - List issues as ISSUES: SEVERITY:description (one per line)"
670
+ fi
671
+
621
672
  local prompt="You are a council member reviewing project completion.
622
673
 
623
674
  ${role_instruction}
624
675
 
625
676
  EVIDENCE:
626
677
  ${evidence}
678
+ ${severity_instruction}
627
679
 
628
680
  INSTRUCTIONS:
629
681
  1. Review the evidence carefully
630
682
  2. Determine if the project meets completion criteria
631
683
  3. Output EXACTLY one line starting with VOTE:APPROVE or VOTE:REJECT
632
684
  4. Output EXACTLY one line starting with REASON: explaining your decision
633
- 5. Be honest - do not approve incomplete work
685
+ 5. If issues found, output lines starting with ISSUES: SEVERITY:description
686
+ 6. Be honest - do not approve incomplete work
634
687
 
635
- Output format (exactly two lines):
688
+ Output format:
636
689
  VOTE:APPROVE or VOTE:REJECT
637
- REASON: your reasoning here"
690
+ REASON: your reasoning here
691
+ ISSUES: CRITICAL:description (optional, one per line per issue)"
638
692
 
639
693
  local verdict_file="$vote_dir/member-${member_id}.txt"
640
694
 
@@ -1300,5 +1354,5 @@ council_get_dashboard_state() {
1300
1354
  state_json=$(cat "$COUNCIL_STATE_DIR/state.json" 2>/dev/null || echo "{}")
1301
1355
  fi
1302
1356
 
1303
- echo "\"council\": {\"enabled\": true, \"size\": $COUNCIL_SIZE, \"threshold\": $COUNCIL_THRESHOLD, \"check_interval\": $COUNCIL_CHECK_INTERVAL, \"consecutive_no_change\": $COUNCIL_CONSECUTIVE_NO_CHANGE, \"done_signals\": $COUNCIL_DONE_SIGNALS, \"iteration\": $ITERATION_COUNT, \"state\": $state_json}"
1357
+ echo "\"council\": {\"enabled\": true, \"size\": $COUNCIL_SIZE, \"threshold\": $COUNCIL_THRESHOLD, \"check_interval\": $COUNCIL_CHECK_INTERVAL, \"consecutive_no_change\": $COUNCIL_CONSECUTIVE_NO_CHANGE, \"done_signals\": $COUNCIL_DONE_SIGNALS, \"iteration\": $ITERATION_COUNT, \"severity_threshold\": \"$COUNCIL_SEVERITY_THRESHOLD\", \"error_budget\": $COUNCIL_ERROR_BUDGET, \"state\": $state_json}"
1304
1358
  }
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  # Loki Mode Stop Hook - Quality Gate Verification
3
3
  # Runs quality checks before allowing completion
4
4
 
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  # Loki Mode SessionStart Hook
3
3
  # Loads memory context and initializes session state
4
4
 
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  # Loki Mode SessionEnd Hook - Episode Storage
3
3
  # Stores session as episodic memory
4
4
 
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  # Loki Mode PostToolUse Hook - Metrics Tracking
3
3
  # Tracks tool usage for efficiency metrics (async)
4
4
 
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  # Loki Mode PreToolUse Hook - Bash Command Validation
3
3
  # Blocks dangerous commands, logs all executions
4
4
 
@@ -30,11 +30,21 @@ BLOCKED_PATTERNS=(
30
30
  "wget.*\|.*sh"
31
31
  "curl.*\|.*bash"
32
32
  "wget.*\|.*bash"
33
+ # Config self-protection: prevent agents from corrupting internal state
34
+ "rm -rf \.loki"
35
+ "rm -rf \./\.loki"
36
+ "rm .*\.loki/council/"
37
+ "rm .*\.loki/config\.yaml"
38
+ "rm .*\.loki/logs/bash-audit"
39
+ "rm .*\.loki/session\.lock"
40
+ "> \.loki/council/"
41
+ "> \.loki/config\.yaml"
33
42
  )
34
43
 
35
- # Safe path patterns that override rm -rf / matches
44
+ # Safe path patterns that override blocked pattern matches
36
45
  SAFE_PATTERNS=(
37
46
  "rm -rf /tmp/"
47
+ "rm -rf \.loki/queue/dead-letter"
38
48
  )
39
49
 
40
50
  # Check for blocked patterns
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  #===============================================================================
3
3
  # Loki Mode - GitHub Issue Parser (v5.14.0)
4
4
  # Parses GitHub issues and extracts structured data for PRD generation
package/autonomy/loki CHANGED
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  #===============================================================================
3
3
  # Loki Mode CLI Wrapper
4
4
  # Command-line interface for Loki Mode
@@ -1460,9 +1460,12 @@ cmd_dashboard_start() {
1460
1460
  exit 1
1461
1461
  fi
1462
1462
 
1463
- # Determine python command
1463
+ # Determine python command -- prefer dashboard venv if available
1464
1464
  local python_cmd="python3"
1465
- if ! command -v python3 &> /dev/null; then
1465
+ local dashboard_venv="${SKILL_DIR}/dashboard/.venv"
1466
+ if [ -x "${dashboard_venv}/bin/python3" ]; then
1467
+ python_cmd="${dashboard_venv}/bin/python3"
1468
+ elif ! command -v python3 &> /dev/null; then
1466
1469
  python_cmd="python"
1467
1470
  fi
1468
1471
 
@@ -1510,19 +1513,29 @@ cmd_dashboard_start() {
1510
1513
  tls_info=" (TLS enabled)"
1511
1514
  fi
1512
1515
 
1513
- # Ensure dashboard Python dependencies are installed
1516
+ # Ensure dashboard Python dependencies via virtualenv (PEP 668 safe)
1514
1517
  if ! "$python_cmd" -c "import fastapi" 2>/dev/null; then
1515
- echo -e "${YELLOW}Installing dashboard dependencies...${NC}"
1518
+ echo -e "${YELLOW}Setting up dashboard virtualenv...${NC}"
1516
1519
  local req_file="${SKILL_DIR}/dashboard/requirements.txt"
1517
- if [ -f "$req_file" ]; then
1518
- pip3 install -q -r "$req_file" 2>/dev/null || pip install -q -r "$req_file" 2>/dev/null || {
1519
- echo -e "${RED}Failed to install dashboard dependencies${NC}"
1520
- echo "Run manually: pip install fastapi uvicorn pydantic websockets"
1521
- exit 1
1522
- }
1520
+ if ! [ -d "$dashboard_venv" ]; then
1521
+ python3 -m venv "$dashboard_venv" 2>/dev/null || python3.13 -m venv "$dashboard_venv" 2>/dev/null || true
1522
+ fi
1523
+ if [ -x "${dashboard_venv}/bin/python3" ]; then
1524
+ python_cmd="${dashboard_venv}/bin/python3"
1525
+ echo -e "${YELLOW}Installing dashboard dependencies into venv...${NC}"
1526
+ if [ -f "$req_file" ]; then
1527
+ "${dashboard_venv}/bin/pip" install -q -r "$req_file" 2>/dev/null || {
1528
+ echo -e "${YELLOW}Pinned deps failed, installing core deps...${NC}"
1529
+ "${dashboard_venv}/bin/pip" install -q fastapi uvicorn pydantic websockets 2>/dev/null || true
1530
+ }
1531
+ else
1532
+ "${dashboard_venv}/bin/pip" install -q fastapi uvicorn pydantic websockets 2>/dev/null || true
1533
+ fi
1523
1534
  else
1535
+ # Fallback: try direct pip (may fail on PEP 668 systems)
1524
1536
  pip3 install -q fastapi uvicorn pydantic websockets 2>/dev/null || pip install -q fastapi uvicorn pydantic websockets 2>/dev/null || {
1525
1537
  echo -e "${RED}Failed to install dashboard dependencies${NC}"
1538
+ echo "Run manually: python3 -m venv ${dashboard_venv} && ${dashboard_venv}/bin/pip install fastapi uvicorn pydantic websockets"
1526
1539
  exit 1
1527
1540
  }
1528
1541
  fi
@@ -3143,19 +3156,31 @@ cmd_api() {
3143
3156
  fi
3144
3157
  fi
3145
3158
 
3146
- # Ensure dashboard Python dependencies are installed
3147
- if ! python3 -c "import fastapi" 2>/dev/null; then
3148
- echo -e "${YELLOW}Installing dashboard dependencies...${NC}"
3149
- local req_file="${SKILL_DIR}/dashboard/requirements.txt"
3150
- if [ -f "$req_file" ]; then
3151
- pip3 install -q -r "$req_file" 2>/dev/null || pip install -q -r "$req_file" 2>/dev/null || {
3152
- echo -e "${RED}Failed to install dashboard dependencies${NC}"
3153
- echo "Run manually: pip install fastapi uvicorn pydantic websockets"
3154
- exit 1
3155
- }
3159
+ # Ensure dashboard Python dependencies via virtualenv (PEP 668 safe)
3160
+ local dashboard_venv="${SKILL_DIR}/dashboard/.venv"
3161
+ local api_python="python3"
3162
+ if [ -x "${dashboard_venv}/bin/python3" ]; then
3163
+ api_python="${dashboard_venv}/bin/python3"
3164
+ fi
3165
+ if ! "$api_python" -c "import fastapi" 2>/dev/null; then
3166
+ echo -e "${YELLOW}Setting up dashboard virtualenv...${NC}"
3167
+ if ! [ -d "$dashboard_venv" ]; then
3168
+ python3 -m venv "$dashboard_venv" 2>/dev/null || python3.13 -m venv "$dashboard_venv" 2>/dev/null || true
3169
+ fi
3170
+ if [ -x "${dashboard_venv}/bin/python3" ]; then
3171
+ api_python="${dashboard_venv}/bin/python3"
3172
+ local req_file="${SKILL_DIR}/dashboard/requirements.txt"
3173
+ if [ -f "$req_file" ]; then
3174
+ "${dashboard_venv}/bin/pip" install -q -r "$req_file" 2>/dev/null || {
3175
+ "${dashboard_venv}/bin/pip" install -q fastapi uvicorn pydantic websockets 2>/dev/null || true
3176
+ }
3177
+ else
3178
+ "${dashboard_venv}/bin/pip" install -q fastapi uvicorn pydantic websockets 2>/dev/null || true
3179
+ fi
3156
3180
  else
3157
3181
  pip3 install -q fastapi uvicorn pydantic websockets 2>/dev/null || {
3158
3182
  echo -e "${RED}Failed to install dashboard dependencies${NC}"
3183
+ echo "Run manually: python3 -m venv ${dashboard_venv} && ${dashboard_venv}/bin/pip install fastapi uvicorn pydantic websockets"
3159
3184
  exit 1
3160
3185
  }
3161
3186
  fi
@@ -3168,7 +3193,7 @@ cmd_api() {
3168
3193
  if [ -n "${LOKI_TLS_CERT:-}" ] && [ -n "${LOKI_TLS_KEY:-}" ]; then
3169
3194
  uvicorn_args="$uvicorn_args --ssl-certfile ${LOKI_TLS_CERT} --ssl-keyfile ${LOKI_TLS_KEY}"
3170
3195
  fi
3171
- LOKI_DIR="$LOKI_DIR" PYTHONPATH="$SKILL_DIR" nohup python3 -m uvicorn dashboard.server:app $uvicorn_args > "$LOKI_DIR/logs/api.log" 2>&1 &
3196
+ LOKI_DIR="$LOKI_DIR" PYTHONPATH="$SKILL_DIR" nohup "$api_python" -m uvicorn dashboard.server:app $uvicorn_args > "$LOKI_DIR/logs/api.log" 2>&1 &
3172
3197
  local new_pid=$!
3173
3198
  echo "$new_pid" > "$pid_file"
3174
3199
 
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  #===============================================================================
3
3
  # Playwright Smoke Test Module (v5.46.0)
4
4
  #
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  #===============================================================================
3
3
  # PRD Checklist Module (v5.44.0)
4
4
  #
package/autonomy/run.sh CHANGED
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  # shellcheck disable=SC2034 # Many variables are used by sourced scripts
3
3
  # shellcheck disable=SC2155 # Declare and assign separately (acceptable in this codebase)
4
4
  # shellcheck disable=SC2329 # Functions may be invoked indirectly or via dynamic dispatch
@@ -5147,19 +5147,40 @@ start_dashboard() {
5147
5147
  log_info "TLS enabled for dashboard"
5148
5148
  fi
5149
5149
 
5150
- # Ensure dashboard Python dependencies are installed
5150
+ # Ensure dashboard Python dependencies via virtualenv (PEP 668 safe)
5151
5151
  local skill_dir="${SCRIPT_DIR%/*}"
5152
5152
  local req_file="${skill_dir}/dashboard/requirements.txt"
5153
- if ! python3 -c "import fastapi" 2>/dev/null; then
5154
- log_step "Installing dashboard dependencies..."
5155
- if [ -f "$req_file" ]; then
5156
- pip3 install -q -r "$req_file" 2>/dev/null || pip install -q -r "$req_file" 2>/dev/null || {
5157
- log_warn "Failed to install dashboard dependencies"
5158
- log_warn "Run manually: pip install fastapi uvicorn pydantic websockets"
5153
+ local dashboard_venv="${skill_dir}/dashboard/.venv"
5154
+ local python_cmd="python3"
5155
+
5156
+ # Use venv python if available, otherwise set one up
5157
+ if [ -x "${dashboard_venv}/bin/python3" ]; then
5158
+ python_cmd="${dashboard_venv}/bin/python3"
5159
+ fi
5160
+
5161
+ if ! "$python_cmd" -c "import fastapi" 2>/dev/null; then
5162
+ log_step "Setting up dashboard virtualenv..."
5163
+ if ! [ -d "$dashboard_venv" ]; then
5164
+ python3 -m venv "$dashboard_venv" 2>/dev/null || python3.13 -m venv "$dashboard_venv" 2>/dev/null || {
5165
+ log_warn "Failed to create virtualenv, trying direct pip install..."
5159
5166
  }
5167
+ fi
5168
+ if [ -x "${dashboard_venv}/bin/python3" ]; then
5169
+ python_cmd="${dashboard_venv}/bin/python3"
5170
+ log_step "Installing dashboard dependencies into venv..."
5171
+ if [ -f "$req_file" ]; then
5172
+ "${dashboard_venv}/bin/pip" install -q -r "$req_file" 2>/dev/null || {
5173
+ log_warn "Pinned deps failed, installing core deps..."
5174
+ "${dashboard_venv}/bin/pip" install -q fastapi uvicorn pydantic websockets 2>/dev/null || true
5175
+ }
5176
+ else
5177
+ "${dashboard_venv}/bin/pip" install -q fastapi uvicorn pydantic websockets 2>/dev/null || true
5178
+ fi
5160
5179
  else
5180
+ # Fallback: try direct pip (may fail on PEP 668 systems)
5161
5181
  pip3 install -q fastapi uvicorn pydantic websockets 2>/dev/null || pip install -q fastapi uvicorn pydantic websockets 2>/dev/null || {
5162
5182
  log_warn "Failed to install dashboard dependencies"
5183
+ log_warn "Run manually: python3 -m venv ${dashboard_venv} && ${dashboard_venv}/bin/pip install fastapi uvicorn pydantic websockets"
5163
5184
  }
5164
5185
  fi
5165
5186
  fi
@@ -5168,7 +5189,7 @@ start_dashboard() {
5168
5189
  # Dashboard module is at project root (parent of autonomy/)
5169
5190
  # LOKI_SKILL_DIR tells server.py where to find static files
5170
5191
  LOKI_TLS_CERT="${LOKI_TLS_CERT:-}" LOKI_TLS_KEY="${LOKI_TLS_KEY:-}" \
5171
- LOKI_SKILL_DIR="${skill_dir}" PYTHONPATH="${skill_dir}" nohup python3 -m dashboard.server > "$log_file" 2>&1 &
5192
+ LOKI_SKILL_DIR="${skill_dir}" PYTHONPATH="${skill_dir}" nohup "$python_cmd" -m dashboard.server > "$log_file" 2>&1 &
5172
5193
  DASHBOARD_PID=$!
5173
5194
 
5174
5195
  # Save PID for later cleanup
@@ -5660,6 +5681,132 @@ load_handoff_context() {
5660
5681
  fi
5661
5682
  }
5662
5683
 
5684
+ # Write structured handoff document (v5.49.0)
5685
+ # Produces both JSON (machine-readable) and markdown (human-readable) handoffs
5686
+ # Called at end of session or before context clear
5687
+ write_structured_handoff() {
5688
+ local reason="${1:-session_end}"
5689
+ local handoff_dir=".loki/memory/handoffs"
5690
+ mkdir -p "$handoff_dir"
5691
+
5692
+ local timestamp
5693
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
5694
+ local file_ts
5695
+ file_ts=$(date +"%Y%m%d-%H%M%S")
5696
+ local handoff_json="$handoff_dir/${file_ts}.json"
5697
+ local handoff_md="$handoff_dir/${file_ts}.md"
5698
+
5699
+ # Gather structured data
5700
+ local files_modified=""
5701
+ files_modified=$(git diff --name-only HEAD 2>/dev/null | head -20 | tr '\n' ',' | sed 's/,$//')
5702
+ local recent_commits=""
5703
+ recent_commits=$(git log --oneline -5 2>/dev/null | tr '\n' '|' | sed 's/|$//')
5704
+ local pending_tasks=0
5705
+ local completed_tasks=0
5706
+ if [ -f ".loki/queue/pending.json" ]; then
5707
+ pending_tasks=$(_QF=".loki/queue/pending.json" python3 -c "import json,os;print(len(json.load(open(os.environ['_QF']))))" 2>/dev/null || echo "0")
5708
+ fi
5709
+ if [ -f ".loki/queue/completed.json" ]; then
5710
+ completed_tasks=$(_QF=".loki/queue/completed.json" python3 -c "import json,os;print(len(json.load(open(os.environ['_QF']))))" 2>/dev/null || echo "0")
5711
+ fi
5712
+
5713
+ # Write JSON handoff
5714
+ _H_TS="$timestamp" \
5715
+ _H_REASON="$reason" \
5716
+ _H_ITER="${ITERATION_COUNT:-0}" \
5717
+ _H_FILES="$files_modified" \
5718
+ _H_COMMITS="$recent_commits" \
5719
+ _H_PENDING="$pending_tasks" \
5720
+ _H_COMPLETED="$completed_tasks" \
5721
+ _H_JSON="$handoff_json" \
5722
+ python3 -c "
5723
+ import json, os
5724
+ handoff = {
5725
+ 'schema_version': '1.0.0',
5726
+ 'timestamp': os.environ['_H_TS'],
5727
+ 'reason': os.environ['_H_REASON'],
5728
+ 'iteration': int(os.environ['_H_ITER']),
5729
+ 'files_modified': [f for f in os.environ['_H_FILES'].split(',') if f],
5730
+ 'recent_commits': [c for c in os.environ['_H_COMMITS'].split('|') if c],
5731
+ 'task_status': {
5732
+ 'pending': int(os.environ['_H_PENDING']),
5733
+ 'completed': int(os.environ['_H_COMPLETED'])
5734
+ },
5735
+ 'open_questions': [],
5736
+ 'key_decisions': [],
5737
+ 'blockers': []
5738
+ }
5739
+ with open(os.environ['_H_JSON'], 'w') as f:
5740
+ json.dump(handoff, f, indent=2)
5741
+ " 2>/dev/null || log_warn "Failed to write structured handoff JSON"
5742
+
5743
+ # Write markdown companion
5744
+ cat > "$handoff_md" << HANDOFF_EOF
5745
+ # Session Handoff - $timestamp
5746
+
5747
+ **Reason:** $reason
5748
+ **Iteration:** ${ITERATION_COUNT:-0}
5749
+
5750
+ ## Files Modified
5751
+ $files_modified
5752
+
5753
+ ## Recent Commits
5754
+ $(git log --oneline -5 2>/dev/null || echo "none")
5755
+
5756
+ ## Task Status
5757
+ - Pending: $pending_tasks
5758
+ - Completed: $completed_tasks
5759
+
5760
+ ## Notes
5761
+ Session handoff generated automatically.
5762
+ HANDOFF_EOF
5763
+
5764
+ log_info "Structured handoff written to $handoff_json"
5765
+ }
5766
+
5767
+ # Load recent handoffs for context (reads both JSON and markdown)
5768
+ load_handoff_context() {
5769
+ local handoff_content=""
5770
+
5771
+ # Prefer JSON handoffs (structured, v5.49.0+)
5772
+ local recent_json
5773
+ recent_json=$(find .loki/memory/handoffs -name "*.json" -mtime -1 2>/dev/null | sort -r | head -1)
5774
+
5775
+ if [ -n "$recent_json" ] && [ -f "$recent_json" ]; then
5776
+ handoff_content=$(_HF="$recent_json" python3 -c "
5777
+ import json, os
5778
+ try:
5779
+ h = json.load(open(os.environ['_HF']))
5780
+ parts = []
5781
+ parts.append(f\"Handoff from {h.get('timestamp','unknown')} (reason: {h.get('reason','unknown')})\")
5782
+ parts.append(f\"Iteration: {h.get('iteration',0)}\")
5783
+ files = h.get('files_modified', [])
5784
+ if files:
5785
+ parts.append(f\"Modified files: {', '.join(files[:10])}\")
5786
+ tasks = h.get('task_status', {})
5787
+ parts.append(f\"Tasks - pending: {tasks.get('pending',0)}, completed: {tasks.get('completed',0)}\")
5788
+ for q in h.get('open_questions', []):
5789
+ parts.append(f\"Open question: {q}\")
5790
+ for b in h.get('blockers', []):
5791
+ parts.append(f\"Blocker: {b}\")
5792
+ print(' | '.join(parts))
5793
+ except Exception as e:
5794
+ print(f'Error reading handoff: {e}')
5795
+ " 2>/dev/null)
5796
+ echo "$handoff_content"
5797
+ return
5798
+ fi
5799
+
5800
+ # Fallback to markdown handoffs (pre-v5.49.0)
5801
+ local recent_handoff
5802
+ recent_handoff=$(find .loki/memory/handoffs -name "*.md" -mtime -1 2>/dev/null | sort -r | head -1)
5803
+
5804
+ if [ -n "$recent_handoff" ] && [ -f "$recent_handoff" ]; then
5805
+ handoff_content=$(cat "$recent_handoff" | head -80)
5806
+ echo "$handoff_content"
5807
+ fi
5808
+ }
5809
+
5663
5810
  # Load relevant learnings
5664
5811
  load_learnings_context() {
5665
5812
  local learnings=""
@@ -7468,6 +7615,9 @@ main() {
7468
7615
  --context "{\"provider\":\"${PROVIDER_NAME:-claude}\",\"iterations\":$ITERATION_COUNT,\"exit_code\":$result}"
7469
7616
  fi
7470
7617
 
7618
+ # Write structured handoff for future sessions (v5.49.0)
7619
+ write_structured_handoff "session_end_result_${result}" 2>/dev/null || true
7620
+
7471
7621
  # Create PR from agent branch if branch protection was enabled
7472
7622
  create_session_pr
7473
7623
  audit_agent_action "session_stop" "Session ended" "result=$result,iterations=$ITERATION_COUNT"
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  #===============================================================================
3
3
  # Loki Mode - Docker Sandbox Manager
4
4
  # Provides isolated container execution for enhanced security
@@ -843,6 +843,15 @@ start_sandbox() {
843
843
  docker_args+=("--volume" "$PROJECT_DIR:/workspace:rw")
844
844
  fi
845
845
 
846
+ # Config self-protection: mount critical .loki/ paths as read-only
847
+ # Prevents agents from corrupting council state, config, or audit trail
848
+ if [[ -d "$PROJECT_DIR/.loki/council" ]]; then
849
+ docker_args+=("--volume" "$PROJECT_DIR/.loki/council:/workspace/.loki/council:ro")
850
+ fi
851
+ if [[ -f "$PROJECT_DIR/.loki/config.yaml" ]]; then
852
+ docker_args+=("--volume" "$PROJECT_DIR/.loki/config.yaml:/workspace/.loki/config.yaml:ro")
853
+ fi
854
+
846
855
  # Mount git config (read-only) - mount to /home/loki since container runs as user loki
847
856
  if [[ -f "$HOME/.gitconfig" ]]; then
848
857
  docker_args+=("--volume" "$HOME/.gitconfig:/home/loki/.gitconfig:ro")
package/autonomy/serve.sh CHANGED
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  # shellcheck disable=SC2034 # Unused variables are for future use or exported
3
3
  # shellcheck disable=SC2155 # Declare and assign separately
4
4
  #===============================================================================
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "5.48.1"
10
+ __version__ = "5.49.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v5.48.1
5
+ **Version:** v5.49.0
6
6
 
7
7
  ---
8
8
 
package/events/emit.sh CHANGED
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  # Loki Mode Event Emitter - Bash helper for emitting events
3
3
  #
4
4
  # Usage:
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  # Loki Mode Learning Aggregator - Bash CLI helper
3
3
  #
4
4
  # Runs learning signal aggregation and displays results.
package/learning/emit.sh CHANGED
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  # Loki Mode Learning Signal Emitter - Bash helper
3
3
  #
4
4
  # Emits learning signals by calling the Python learning emitter.
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  # Loki Mode Learning Suggestions - Bash CLI helper
3
3
  #
4
4
  # Shows context-aware suggestions based on aggregated learnings.
package/mcp/__init__.py CHANGED
@@ -21,4 +21,4 @@ try:
21
21
  except ImportError:
22
22
  __all__ = ['mcp']
23
23
 
24
- __version__ = '5.48.1'
24
+ __version__ = '5.49.0'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "5.48.1",
3
+ "version": "5.49.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
  "autonomi",
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  # Claude Code Provider Configuration
3
3
  # Shell-sourceable config for loki-mode multi-provider support
4
4
 
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  # OpenAI Codex CLI Provider Configuration
3
3
  # Shell-sourceable config for loki-mode multi-provider support
4
4
 
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  # Google Gemini CLI Provider Configuration
3
3
  # Shell-sourceable config for loki-mode multi-provider support
4
4
 
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  # Provider Loader for loki-mode
3
3
  # Sources the appropriate provider configuration based on LOKI_PROVIDER
4
4