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 +8 -7
- package/VERSION +1 -1
- package/autonomy/app-runner.sh +1 -1
- package/autonomy/completion-council.sh +59 -5
- package/autonomy/hooks/quality-gate.sh +1 -1
- package/autonomy/hooks/session-init.sh +1 -1
- package/autonomy/hooks/store-episode.sh +1 -1
- package/autonomy/hooks/track-metrics.sh +1 -1
- package/autonomy/hooks/validate-bash.sh +12 -2
- package/autonomy/issue-parser.sh +1 -1
- package/autonomy/loki +47 -22
- package/autonomy/playwright-verify.sh +1 -1
- package/autonomy/prd-checklist.sh +1 -1
- package/autonomy/run.sh +159 -9
- package/autonomy/sandbox.sh +10 -1
- package/autonomy/serve.sh +1 -1
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/events/emit.sh +1 -1
- package/learning/aggregate.sh +1 -1
- package/learning/emit.sh +1 -1
- package/learning/suggest.sh +1 -1
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/providers/claude.sh +1 -1
- package/providers/codex.sh +1 -1
- package/providers/gemini.sh +1 -1
- package/providers/loader.sh +1 -1
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.
|
|
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
|
|
71
|
+
These rules guide autonomous operation. Test results and code quality always take precedence.
|
|
72
72
|
|
|
73
73
|
| Rule | Meaning |
|
|
74
74
|
|------|---------|
|
|
75
|
-
| **
|
|
76
|
-
| **
|
|
77
|
-
| **
|
|
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.
|
|
266
|
+
**v5.49.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.
|
|
1
|
+
5.49.0
|
package/autonomy/app-runner.sh
CHANGED
|
@@ -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.
|
|
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
|
|
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 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
|
|
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/issue-parser.sh
CHANGED
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
|
-
|
|
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
|
|
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}
|
|
1518
|
+
echo -e "${YELLOW}Setting up dashboard virtualenv...${NC}"
|
|
1516
1519
|
local req_file="${SKILL_DIR}/dashboard/requirements.txt"
|
|
1517
|
-
if [ -
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
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
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
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
|
|
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"
|
package/autonomy/sandbox.sh
CHANGED
|
@@ -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
package/dashboard/__init__.py
CHANGED
package/docs/INSTALLATION.md
CHANGED
package/events/emit.sh
CHANGED
package/learning/aggregate.sh
CHANGED
package/learning/emit.sh
CHANGED
package/learning/suggest.sh
CHANGED
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
package/providers/claude.sh
CHANGED
package/providers/codex.sh
CHANGED
package/providers/gemini.sh
CHANGED
package/providers/loader.sh
CHANGED