loki-mode 5.48.2 → 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.2
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
 
@@ -263,4 +263,4 @@ The following features are documented in skill modules but not yet fully automat
263
263
  | Quality gates 3-reviewer system | Implemented (v5.35.0) | 5 specialist reviewers in `skills/quality-gates.md`; execution in run.sh |
264
264
  | Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
265
265
 
266
- **v5.48.2 | [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.2
1
+ 5.49.0
@@ -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
  }
@@ -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
package/autonomy/run.sh CHANGED
@@ -5681,6 +5681,132 @@ load_handoff_context() {
5681
5681
  fi
5682
5682
  }
5683
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
+
5684
5810
  # Load relevant learnings
5685
5811
  load_learnings_context() {
5686
5812
  local learnings=""
@@ -7489,6 +7615,9 @@ main() {
7489
7615
  --context "{\"provider\":\"${PROVIDER_NAME:-claude}\",\"iterations\":$ITERATION_COUNT,\"exit_code\":$result}"
7490
7616
  fi
7491
7617
 
7618
+ # Write structured handoff for future sessions (v5.49.0)
7619
+ write_structured_handoff "session_end_result_${result}" 2>/dev/null || true
7620
+
7492
7621
  # Create PR from agent branch if branch protection was enabled
7493
7622
  create_session_pr
7494
7623
  audit_agent_action "session_stop" "Session ended" "result=$result,iterations=$ITERATION_COUNT"
@@ -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")
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "5.48.2"
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.2
5
+ **Version:** v5.49.0
6
6
 
7
7
  ---
8
8
 
package/mcp/__init__.py CHANGED
@@ -21,4 +21,4 @@ try:
21
21
  except ImportError:
22
22
  __all__ = ['mcp']
23
23
 
24
- __version__ = '5.48.2'
24
+ __version__ = '5.49.0'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "5.48.2",
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",