loki-mode 5.48.2 → 5.49.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/README.md +25 -40
- package/SKILL.md +3 -3
- package/VERSION +1 -1
- package/autonomy/CONSTITUTION.md +2 -2
- package/autonomy/app-runner.sh +9 -0
- package/autonomy/completion-council.sh +58 -4
- package/autonomy/hooks/validate-bash.sh +11 -1
- package/autonomy/loki +107 -0
- package/autonomy/run.sh +299 -4
- package/autonomy/sandbox.sh +9 -0
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +39 -1
- package/docs/COMPARISON.md +10 -10
- package/docs/COMPETITIVE-ANALYSIS.md +3 -3
- package/docs/INSTALLATION.md +20 -12
- package/docs/auto-claude-comparison.md +1 -1
- package/docs/cursor-comparison.md +3 -3
- package/docs/thick2thin.md +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/references/agent-types.md +2 -2
- package/references/agents.md +1 -1
- package/references/competitive-analysis.md +1 -1
- package/skills/agents.md +3 -3
- package/skills/parallel-workflows.md +1 -1
- package/skills/quality-gates.md +1 -1
package/autonomy/run.sh
CHANGED
|
@@ -667,6 +667,146 @@ log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
|
|
|
667
667
|
log_step() { echo -e "${CYAN}[STEP]${NC} $*"; }
|
|
668
668
|
log_debug() { [[ "${LOKI_DEBUG:-}" == "true" ]] && echo -e "${CYAN}[DEBUG]${NC} $*" || true; }
|
|
669
669
|
|
|
670
|
+
#===============================================================================
|
|
671
|
+
# Process Registry (PID Supervisor)
|
|
672
|
+
# Central registry of all spawned child processes for reliable cleanup
|
|
673
|
+
#===============================================================================
|
|
674
|
+
|
|
675
|
+
PID_REGISTRY_DIR=""
|
|
676
|
+
|
|
677
|
+
# Initialize the PID registry directory
|
|
678
|
+
init_pid_registry() {
|
|
679
|
+
PID_REGISTRY_DIR="${TARGET_DIR:-.}/.loki/pids"
|
|
680
|
+
mkdir -p "$PID_REGISTRY_DIR"
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
# Parse a field from a JSON registry entry (python3 with shell fallback)
|
|
684
|
+
# Usage: _parse_json_field <file> <field>
|
|
685
|
+
_parse_json_field() {
|
|
686
|
+
local file="$1" field="$2"
|
|
687
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
688
|
+
python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get(sys.argv[2],''))" "$file" "$field" 2>/dev/null
|
|
689
|
+
else
|
|
690
|
+
# Shell fallback: extract value for simple flat JSON
|
|
691
|
+
sed 's/.*"'"$field"'":\s*//' "$file" 2>/dev/null | sed 's/[",}].*//' | head -1
|
|
692
|
+
fi
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
# Register a spawned process in the central registry
|
|
696
|
+
# Usage: register_pid <pid> <label> [<extra_info>]
|
|
697
|
+
# Example: register_pid $! "dashboard" "port=57374"
|
|
698
|
+
register_pid() {
|
|
699
|
+
local pid="$1"
|
|
700
|
+
# Sanitize label and extra for JSON safety (escape backslash first, then double-quote, strip newlines)
|
|
701
|
+
local label="${2//\\/\\\\}"
|
|
702
|
+
label="${label//\"/\\\"}"
|
|
703
|
+
label="$(printf '%s' "$label" | tr -d '\n\r')"
|
|
704
|
+
local extra="${3:-}"
|
|
705
|
+
extra="${extra//\\/\\\\}"
|
|
706
|
+
extra="${extra//\"/\\\"}"
|
|
707
|
+
extra="$(printf '%s' "$extra" | tr -d '\n\r')"
|
|
708
|
+
[ -z "$PID_REGISTRY_DIR" ] && init_pid_registry
|
|
709
|
+
local entry_file="$PID_REGISTRY_DIR/${pid}.json"
|
|
710
|
+
cat > "$entry_file" << EOF
|
|
711
|
+
{"pid":$pid,"label":"$label","started":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","ppid":$$,"extra":"$extra"}
|
|
712
|
+
EOF
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
# Unregister a process from the registry (called on clean shutdown)
|
|
716
|
+
# Usage: unregister_pid <pid>
|
|
717
|
+
unregister_pid() {
|
|
718
|
+
local pid="$1"
|
|
719
|
+
[ -z "$PID_REGISTRY_DIR" ] && init_pid_registry
|
|
720
|
+
rm -f "$PID_REGISTRY_DIR/${pid}.json" 2>/dev/null
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
# Kill a registered process with SIGTERM -> wait -> SIGKILL escalation
|
|
724
|
+
# Usage: kill_registered_pid <pid>
|
|
725
|
+
kill_registered_pid() {
|
|
726
|
+
local pid="$1"
|
|
727
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
728
|
+
kill "$pid" 2>/dev/null || true
|
|
729
|
+
# Wait up to 2 seconds for graceful exit
|
|
730
|
+
local waited=0
|
|
731
|
+
while [ $waited -lt 4 ] && kill -0 "$pid" 2>/dev/null; do
|
|
732
|
+
sleep 0.5
|
|
733
|
+
waited=$((waited + 1))
|
|
734
|
+
done
|
|
735
|
+
# Escalate to SIGKILL if still alive
|
|
736
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
737
|
+
kill -9 "$pid" 2>/dev/null || true
|
|
738
|
+
fi
|
|
739
|
+
fi
|
|
740
|
+
unregister_pid "$pid"
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
# Scan registry for orphaned processes and kill them
|
|
744
|
+
# Called on startup and by `loki cleanup`
|
|
745
|
+
# Returns: number of orphans killed
|
|
746
|
+
cleanup_orphan_pids() {
|
|
747
|
+
[ -z "$PID_REGISTRY_DIR" ] && init_pid_registry
|
|
748
|
+
local orphan_count=0
|
|
749
|
+
|
|
750
|
+
if [ ! -d "$PID_REGISTRY_DIR" ]; then
|
|
751
|
+
echo "0"
|
|
752
|
+
return 0
|
|
753
|
+
fi
|
|
754
|
+
|
|
755
|
+
for entry_file in "$PID_REGISTRY_DIR"/*.json; do
|
|
756
|
+
[ -f "$entry_file" ] || continue
|
|
757
|
+
local pid
|
|
758
|
+
pid=$(basename "$entry_file" .json)
|
|
759
|
+
|
|
760
|
+
# Skip non-numeric filenames
|
|
761
|
+
case "$pid" in
|
|
762
|
+
''|*[!0-9]*) continue ;;
|
|
763
|
+
esac
|
|
764
|
+
|
|
765
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
766
|
+
# Process is alive -- check if its parent session is dead
|
|
767
|
+
local ppid_val=""
|
|
768
|
+
ppid_val=$(_parse_json_field "$entry_file" "ppid") || true
|
|
769
|
+
|
|
770
|
+
# Validate ppid_val is numeric before using with kill
|
|
771
|
+
case "$ppid_val" in ''|*[!0-9]*) ppid_val="" ;; esac
|
|
772
|
+
if [ -n "$ppid_val" ] && [ "$ppid_val" != "$$" ]; then
|
|
773
|
+
if ! kill -0 "$ppid_val" 2>/dev/null; then
|
|
774
|
+
# Parent is dead -- this is an orphan
|
|
775
|
+
local label=""
|
|
776
|
+
label=$(_parse_json_field "$entry_file" "label") || label="unknown"
|
|
777
|
+
log_warn "Killing orphaned process: PID=$pid label=$label (parent $ppid_val is dead)" >&2
|
|
778
|
+
kill_registered_pid "$pid"
|
|
779
|
+
orphan_count=$((orphan_count + 1))
|
|
780
|
+
fi
|
|
781
|
+
fi
|
|
782
|
+
else
|
|
783
|
+
# Process is dead -- clean up stale registry entry
|
|
784
|
+
rm -f "$entry_file" 2>/dev/null
|
|
785
|
+
fi
|
|
786
|
+
done
|
|
787
|
+
|
|
788
|
+
echo "$orphan_count"
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
# Kill ALL registered processes (used during full shutdown)
|
|
792
|
+
kill_all_registered() {
|
|
793
|
+
[ -z "$PID_REGISTRY_DIR" ] && init_pid_registry
|
|
794
|
+
|
|
795
|
+
if [ ! -d "$PID_REGISTRY_DIR" ]; then
|
|
796
|
+
return 0
|
|
797
|
+
fi
|
|
798
|
+
|
|
799
|
+
for entry_file in "$PID_REGISTRY_DIR"/*.json; do
|
|
800
|
+
[ -f "$entry_file" ] || continue
|
|
801
|
+
local pid
|
|
802
|
+
pid=$(basename "$entry_file" .json)
|
|
803
|
+
case "$pid" in
|
|
804
|
+
''|*[!0-9]*) continue ;;
|
|
805
|
+
esac
|
|
806
|
+
kill_registered_pid "$pid"
|
|
807
|
+
done
|
|
808
|
+
}
|
|
809
|
+
|
|
670
810
|
#===============================================================================
|
|
671
811
|
# Event Emission (Dashboard Integration)
|
|
672
812
|
# Writes events to .loki/events.jsonl for dashboard consumption
|
|
@@ -1688,6 +1828,7 @@ create_worktree() {
|
|
|
1688
1828
|
) &
|
|
1689
1829
|
# Capture install PID for cleanup on exit
|
|
1690
1830
|
WORKTREE_INSTALL_PIDS+=($!)
|
|
1831
|
+
register_pid "$!" "worktree-install" "stream=$stream_name"
|
|
1691
1832
|
|
|
1692
1833
|
log_info "Created worktree: $worktree_path"
|
|
1693
1834
|
return 0
|
|
@@ -1796,6 +1937,7 @@ spawn_worktree_session() {
|
|
|
1796
1937
|
|
|
1797
1938
|
local pid=$!
|
|
1798
1939
|
WORKTREE_PIDS[$stream_name]=$pid
|
|
1940
|
+
register_pid "$pid" "worktree-session" "stream=$stream_name"
|
|
1799
1941
|
|
|
1800
1942
|
log_info "Session spawned: $stream_name (PID: $pid)"
|
|
1801
1943
|
return 0
|
|
@@ -2002,6 +2144,7 @@ cleanup_parallel_streams() {
|
|
|
2002
2144
|
if kill -0 "$pid" 2>/dev/null; then
|
|
2003
2145
|
kill "$pid" 2>/dev/null || true
|
|
2004
2146
|
fi
|
|
2147
|
+
unregister_pid "$pid"
|
|
2005
2148
|
done
|
|
2006
2149
|
WORKTREE_INSTALL_PIDS=()
|
|
2007
2150
|
|
|
@@ -2012,6 +2155,7 @@ cleanup_parallel_streams() {
|
|
|
2012
2155
|
log_step "Stopping session: $stream"
|
|
2013
2156
|
kill "$pid" 2>/dev/null || true
|
|
2014
2157
|
fi
|
|
2158
|
+
unregister_pid "$pid"
|
|
2015
2159
|
done
|
|
2016
2160
|
|
|
2017
2161
|
# Wait for all to finish
|
|
@@ -2620,8 +2764,8 @@ write_dashboard_state() {
|
|
|
2620
2764
|
# Get complexity tier
|
|
2621
2765
|
local complexity="${DETECTED_COMPLEXITY:-standard}"
|
|
2622
2766
|
|
|
2623
|
-
# Get RARV cycle step (
|
|
2624
|
-
local rarv_step=$((ITERATION_COUNT % 4))
|
|
2767
|
+
# Get RARV cycle step from actual phase tracking (falls back to iteration-based)
|
|
2768
|
+
local rarv_step=${RARV_CURRENT_STEP:-$((ITERATION_COUNT % 4))}
|
|
2625
2769
|
local rarv_stages='["reason", "act", "reflect", "verify"]'
|
|
2626
2770
|
|
|
2627
2771
|
# Get memory system stats (if available)
|
|
@@ -2634,9 +2778,9 @@ write_dashboard_state() {
|
|
|
2634
2778
|
[ -d ".loki/memory/skills" ] && procedural_count=$(find ".loki/memory/skills" -type f -name "*.json" 2>/dev/null | wc -l | tr -d ' ')
|
|
2635
2779
|
|
|
2636
2780
|
# Get quality gates status (if available)
|
|
2637
|
-
local quality_gates='
|
|
2781
|
+
local quality_gates='null'
|
|
2638
2782
|
if [ -f ".loki/state/quality-gates.json" ]; then
|
|
2639
|
-
quality_gates=$(cat ".loki/state/quality-gates.json" 2>/dev/null || echo
|
|
2783
|
+
quality_gates=$(cat ".loki/state/quality-gates.json" 2>/dev/null || echo 'null')
|
|
2640
2784
|
fi
|
|
2641
2785
|
|
|
2642
2786
|
# Get Completion Council state (v5.25.0)
|
|
@@ -3037,6 +3181,7 @@ start_status_monitor() {
|
|
|
3037
3181
|
done
|
|
3038
3182
|
) &
|
|
3039
3183
|
STATUS_MONITOR_PID=$!
|
|
3184
|
+
register_pid "$STATUS_MONITOR_PID" "status-monitor"
|
|
3040
3185
|
|
|
3041
3186
|
log_info "Status monitor started"
|
|
3042
3187
|
log_info "Monitor progress: ${CYAN}watch -n 2 cat .loki/STATUS.txt${NC}"
|
|
@@ -3046,6 +3191,7 @@ stop_status_monitor() {
|
|
|
3046
3191
|
if [ -n "$STATUS_MONITOR_PID" ]; then
|
|
3047
3192
|
kill "$STATUS_MONITOR_PID" 2>/dev/null || true
|
|
3048
3193
|
wait "$STATUS_MONITOR_PID" 2>/dev/null || true
|
|
3194
|
+
unregister_pid "$STATUS_MONITOR_PID"
|
|
3049
3195
|
fi
|
|
3050
3196
|
stop_resource_monitor
|
|
3051
3197
|
}
|
|
@@ -3621,6 +3767,7 @@ start_resource_monitor() {
|
|
|
3621
3767
|
done
|
|
3622
3768
|
) &
|
|
3623
3769
|
RESOURCE_MONITOR_PID=$!
|
|
3770
|
+
register_pid "$RESOURCE_MONITOR_PID" "resource-monitor"
|
|
3624
3771
|
|
|
3625
3772
|
log_info "Resource monitor started (CPU threshold: ${RESOURCE_CPU_THRESHOLD}%, Memory threshold: ${RESOURCE_MEM_THRESHOLD}%)"
|
|
3626
3773
|
log_info "Check status: ${CYAN}cat .loki/state/resources.json${NC}"
|
|
@@ -3630,6 +3777,7 @@ stop_resource_monitor() {
|
|
|
3630
3777
|
if [ -n "$RESOURCE_MONITOR_PID" ]; then
|
|
3631
3778
|
kill "$RESOURCE_MONITOR_PID" 2>/dev/null || true
|
|
3632
3779
|
wait "$RESOURCE_MONITOR_PID" 2>/dev/null || true
|
|
3780
|
+
unregister_pid "$RESOURCE_MONITOR_PID"
|
|
3633
3781
|
fi
|
|
3634
3782
|
}
|
|
3635
3783
|
|
|
@@ -4720,12 +4868,14 @@ BUILD_PROMPT
|
|
|
4720
4868
|
esac
|
|
4721
4869
|
) &
|
|
4722
4870
|
pids+=($!)
|
|
4871
|
+
register_pid "$!" "code-reviewer" "name=$reviewer_name"
|
|
4723
4872
|
done
|
|
4724
4873
|
|
|
4725
4874
|
# Wait for all reviewers to complete
|
|
4726
4875
|
log_info "Waiting for $reviewer_count reviewers to complete (blind review)..."
|
|
4727
4876
|
for pid in "${pids[@]}"; do
|
|
4728
4877
|
wait "$pid" || true
|
|
4878
|
+
unregister_pid "$pid"
|
|
4729
4879
|
done
|
|
4730
4880
|
|
|
4731
4881
|
log_info "All reviewers complete. Aggregating verdicts..."
|
|
@@ -5191,6 +5341,7 @@ start_dashboard() {
|
|
|
5191
5341
|
LOKI_TLS_CERT="${LOKI_TLS_CERT:-}" LOKI_TLS_KEY="${LOKI_TLS_KEY:-}" \
|
|
5192
5342
|
LOKI_SKILL_DIR="${skill_dir}" PYTHONPATH="${skill_dir}" nohup "$python_cmd" -m dashboard.server > "$log_file" 2>&1 &
|
|
5193
5343
|
DASHBOARD_PID=$!
|
|
5344
|
+
register_pid "$DASHBOARD_PID" "dashboard" "port=${DASHBOARD_PORT:-57374}"
|
|
5194
5345
|
|
|
5195
5346
|
# Save PID for later cleanup
|
|
5196
5347
|
mkdir -p .loki/dashboard
|
|
@@ -5224,6 +5375,7 @@ stop_dashboard() {
|
|
|
5224
5375
|
if [ -n "$DASHBOARD_PID" ]; then
|
|
5225
5376
|
kill "$DASHBOARD_PID" 2>/dev/null || true
|
|
5226
5377
|
wait "$DASHBOARD_PID" 2>/dev/null || true
|
|
5378
|
+
unregister_pid "$DASHBOARD_PID"
|
|
5227
5379
|
fi
|
|
5228
5380
|
|
|
5229
5381
|
# Also try PID file
|
|
@@ -5231,6 +5383,7 @@ stop_dashboard() {
|
|
|
5231
5383
|
local saved_pid=$(cat ".loki/dashboard/dashboard.pid" 2>/dev/null)
|
|
5232
5384
|
if [ -n "$saved_pid" ]; then
|
|
5233
5385
|
kill "$saved_pid" 2>/dev/null || true
|
|
5386
|
+
unregister_pid "$saved_pid"
|
|
5234
5387
|
fi
|
|
5235
5388
|
rm -f ".loki/dashboard/dashboard.pid"
|
|
5236
5389
|
fi
|
|
@@ -5681,6 +5834,132 @@ load_handoff_context() {
|
|
|
5681
5834
|
fi
|
|
5682
5835
|
}
|
|
5683
5836
|
|
|
5837
|
+
# Write structured handoff document (v5.49.0)
|
|
5838
|
+
# Produces both JSON (machine-readable) and markdown (human-readable) handoffs
|
|
5839
|
+
# Called at end of session or before context clear
|
|
5840
|
+
write_structured_handoff() {
|
|
5841
|
+
local reason="${1:-session_end}"
|
|
5842
|
+
local handoff_dir=".loki/memory/handoffs"
|
|
5843
|
+
mkdir -p "$handoff_dir"
|
|
5844
|
+
|
|
5845
|
+
local timestamp
|
|
5846
|
+
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
5847
|
+
local file_ts
|
|
5848
|
+
file_ts=$(date +"%Y%m%d-%H%M%S")
|
|
5849
|
+
local handoff_json="$handoff_dir/${file_ts}.json"
|
|
5850
|
+
local handoff_md="$handoff_dir/${file_ts}.md"
|
|
5851
|
+
|
|
5852
|
+
# Gather structured data
|
|
5853
|
+
local files_modified=""
|
|
5854
|
+
files_modified=$(git diff --name-only HEAD 2>/dev/null | head -20 | tr '\n' ',' | sed 's/,$//')
|
|
5855
|
+
local recent_commits=""
|
|
5856
|
+
recent_commits=$(git log --oneline -5 2>/dev/null | tr '\n' '|' | sed 's/|$//')
|
|
5857
|
+
local pending_tasks=0
|
|
5858
|
+
local completed_tasks=0
|
|
5859
|
+
if [ -f ".loki/queue/pending.json" ]; then
|
|
5860
|
+
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")
|
|
5861
|
+
fi
|
|
5862
|
+
if [ -f ".loki/queue/completed.json" ]; then
|
|
5863
|
+
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")
|
|
5864
|
+
fi
|
|
5865
|
+
|
|
5866
|
+
# Write JSON handoff
|
|
5867
|
+
_H_TS="$timestamp" \
|
|
5868
|
+
_H_REASON="$reason" \
|
|
5869
|
+
_H_ITER="${ITERATION_COUNT:-0}" \
|
|
5870
|
+
_H_FILES="$files_modified" \
|
|
5871
|
+
_H_COMMITS="$recent_commits" \
|
|
5872
|
+
_H_PENDING="$pending_tasks" \
|
|
5873
|
+
_H_COMPLETED="$completed_tasks" \
|
|
5874
|
+
_H_JSON="$handoff_json" \
|
|
5875
|
+
python3 -c "
|
|
5876
|
+
import json, os
|
|
5877
|
+
handoff = {
|
|
5878
|
+
'schema_version': '1.0.0',
|
|
5879
|
+
'timestamp': os.environ['_H_TS'],
|
|
5880
|
+
'reason': os.environ['_H_REASON'],
|
|
5881
|
+
'iteration': int(os.environ['_H_ITER']),
|
|
5882
|
+
'files_modified': [f for f in os.environ['_H_FILES'].split(',') if f],
|
|
5883
|
+
'recent_commits': [c for c in os.environ['_H_COMMITS'].split('|') if c],
|
|
5884
|
+
'task_status': {
|
|
5885
|
+
'pending': int(os.environ['_H_PENDING']),
|
|
5886
|
+
'completed': int(os.environ['_H_COMPLETED'])
|
|
5887
|
+
},
|
|
5888
|
+
'open_questions': [],
|
|
5889
|
+
'key_decisions': [],
|
|
5890
|
+
'blockers': []
|
|
5891
|
+
}
|
|
5892
|
+
with open(os.environ['_H_JSON'], 'w') as f:
|
|
5893
|
+
json.dump(handoff, f, indent=2)
|
|
5894
|
+
" 2>/dev/null || log_warn "Failed to write structured handoff JSON"
|
|
5895
|
+
|
|
5896
|
+
# Write markdown companion
|
|
5897
|
+
cat > "$handoff_md" << HANDOFF_EOF
|
|
5898
|
+
# Session Handoff - $timestamp
|
|
5899
|
+
|
|
5900
|
+
**Reason:** $reason
|
|
5901
|
+
**Iteration:** ${ITERATION_COUNT:-0}
|
|
5902
|
+
|
|
5903
|
+
## Files Modified
|
|
5904
|
+
$files_modified
|
|
5905
|
+
|
|
5906
|
+
## Recent Commits
|
|
5907
|
+
$(git log --oneline -5 2>/dev/null || echo "none")
|
|
5908
|
+
|
|
5909
|
+
## Task Status
|
|
5910
|
+
- Pending: $pending_tasks
|
|
5911
|
+
- Completed: $completed_tasks
|
|
5912
|
+
|
|
5913
|
+
## Notes
|
|
5914
|
+
Session handoff generated automatically.
|
|
5915
|
+
HANDOFF_EOF
|
|
5916
|
+
|
|
5917
|
+
log_info "Structured handoff written to $handoff_json"
|
|
5918
|
+
}
|
|
5919
|
+
|
|
5920
|
+
# Load recent handoffs for context (reads both JSON and markdown)
|
|
5921
|
+
load_handoff_context() {
|
|
5922
|
+
local handoff_content=""
|
|
5923
|
+
|
|
5924
|
+
# Prefer JSON handoffs (structured, v5.49.0+)
|
|
5925
|
+
local recent_json
|
|
5926
|
+
recent_json=$(find .loki/memory/handoffs -name "*.json" -mtime -1 2>/dev/null | sort -r | head -1)
|
|
5927
|
+
|
|
5928
|
+
if [ -n "$recent_json" ] && [ -f "$recent_json" ]; then
|
|
5929
|
+
handoff_content=$(_HF="$recent_json" python3 -c "
|
|
5930
|
+
import json, os
|
|
5931
|
+
try:
|
|
5932
|
+
h = json.load(open(os.environ['_HF']))
|
|
5933
|
+
parts = []
|
|
5934
|
+
parts.append(f\"Handoff from {h.get('timestamp','unknown')} (reason: {h.get('reason','unknown')})\")
|
|
5935
|
+
parts.append(f\"Iteration: {h.get('iteration',0)}\")
|
|
5936
|
+
files = h.get('files_modified', [])
|
|
5937
|
+
if files:
|
|
5938
|
+
parts.append(f\"Modified files: {', '.join(files[:10])}\")
|
|
5939
|
+
tasks = h.get('task_status', {})
|
|
5940
|
+
parts.append(f\"Tasks - pending: {tasks.get('pending',0)}, completed: {tasks.get('completed',0)}\")
|
|
5941
|
+
for q in h.get('open_questions', []):
|
|
5942
|
+
parts.append(f\"Open question: {q}\")
|
|
5943
|
+
for b in h.get('blockers', []):
|
|
5944
|
+
parts.append(f\"Blocker: {b}\")
|
|
5945
|
+
print(' | '.join(parts))
|
|
5946
|
+
except Exception as e:
|
|
5947
|
+
print(f'Error reading handoff: {e}')
|
|
5948
|
+
" 2>/dev/null)
|
|
5949
|
+
echo "$handoff_content"
|
|
5950
|
+
return
|
|
5951
|
+
fi
|
|
5952
|
+
|
|
5953
|
+
# Fallback to markdown handoffs (pre-v5.49.0)
|
|
5954
|
+
local recent_handoff
|
|
5955
|
+
recent_handoff=$(find .loki/memory/handoffs -name "*.md" -mtime -1 2>/dev/null | sort -r | head -1)
|
|
5956
|
+
|
|
5957
|
+
if [ -n "$recent_handoff" ] && [ -f "$recent_handoff" ]; then
|
|
5958
|
+
handoff_content=$(cat "$recent_handoff" | head -80)
|
|
5959
|
+
echo "$handoff_content"
|
|
5960
|
+
fi
|
|
5961
|
+
}
|
|
5962
|
+
|
|
5684
5963
|
# Load relevant learnings
|
|
5685
5964
|
load_learnings_context() {
|
|
5686
5965
|
local learnings=""
|
|
@@ -6995,6 +7274,7 @@ cleanup() {
|
|
|
6995
7274
|
fi
|
|
6996
7275
|
stop_dashboard
|
|
6997
7276
|
stop_status_monitor
|
|
7277
|
+
kill_all_registered
|
|
6998
7278
|
rm -f "$loki_dir/loki.pid" 2>/dev/null
|
|
6999
7279
|
if [ -f "$loki_dir/session.json" ]; then
|
|
7000
7280
|
_LOKI_SESSION_FILE="$loki_dir/session.json" python3 -c "
|
|
@@ -7022,6 +7302,7 @@ except (json.JSONDecodeError, OSError): pass
|
|
|
7022
7302
|
fi
|
|
7023
7303
|
stop_dashboard
|
|
7024
7304
|
stop_status_monitor
|
|
7305
|
+
kill_all_registered
|
|
7025
7306
|
rm -f .loki/loki.pid .loki/PAUSE 2>/dev/null
|
|
7026
7307
|
# Mark session.json as stopped
|
|
7027
7308
|
if [ -f ".loki/session.json" ]; then
|
|
@@ -7209,6 +7490,7 @@ main() {
|
|
|
7209
7490
|
LOKI_RUNNING_FROM_TEMP='' nohup "$original_script" "${cmd_args[@]}" > "$log_file" 2>&1 &
|
|
7210
7491
|
local bg_pid=$!
|
|
7211
7492
|
echo "$bg_pid" > "$pid_file"
|
|
7493
|
+
register_pid "$bg_pid" "background-session" "log=$log_file"
|
|
7212
7494
|
|
|
7213
7495
|
echo ""
|
|
7214
7496
|
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
@@ -7331,6 +7613,14 @@ main() {
|
|
|
7331
7613
|
# Write PID file for ALL modes (foreground + background)
|
|
7332
7614
|
echo "$$" > "$pid_file"
|
|
7333
7615
|
|
|
7616
|
+
# Initialize PID registry and clean up orphans from previous sessions
|
|
7617
|
+
init_pid_registry
|
|
7618
|
+
local orphan_count
|
|
7619
|
+
orphan_count=$(cleanup_orphan_pids)
|
|
7620
|
+
if [ "$orphan_count" -gt 0 ]; then
|
|
7621
|
+
log_warn "Killed $orphan_count orphaned process(es) from previous session"
|
|
7622
|
+
fi
|
|
7623
|
+
|
|
7334
7624
|
# Copy skill files to .loki/skills/ - makes CLI self-contained
|
|
7335
7625
|
# No need to install Claude Code skill separately
|
|
7336
7626
|
copy_skill_files
|
|
@@ -7410,10 +7700,12 @@ main() {
|
|
|
7410
7700
|
run_autonomous "$PRD_PATH"
|
|
7411
7701
|
) &
|
|
7412
7702
|
local main_pid=$!
|
|
7703
|
+
register_pid "$main_pid" "parallel-main" ""
|
|
7413
7704
|
|
|
7414
7705
|
# Run parallel orchestrator
|
|
7415
7706
|
run_parallel_orchestrator &
|
|
7416
7707
|
local orchestrator_pid=$!
|
|
7708
|
+
register_pid "$orchestrator_pid" "parallel-orchestrator" ""
|
|
7417
7709
|
|
|
7418
7710
|
# Wait for main session (orchestrator continues watching)
|
|
7419
7711
|
wait $main_pid || result=$?
|
|
@@ -7489,6 +7781,9 @@ main() {
|
|
|
7489
7781
|
--context "{\"provider\":\"${PROVIDER_NAME:-claude}\",\"iterations\":$ITERATION_COUNT,\"exit_code\":$result}"
|
|
7490
7782
|
fi
|
|
7491
7783
|
|
|
7784
|
+
# Write structured handoff for future sessions (v5.49.0)
|
|
7785
|
+
write_structured_handoff "session_end_result_${result}" 2>/dev/null || true
|
|
7786
|
+
|
|
7492
7787
|
# Create PR from agent branch if branch protection was enabled
|
|
7493
7788
|
create_session_pr
|
|
7494
7789
|
audit_agent_action "session_stop" "Session ended" "result=$result,iterations=$ITERATION_COUNT"
|
package/autonomy/sandbox.sh
CHANGED
|
@@ -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/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -404,7 +404,20 @@ async def get_status() -> StatusResponse:
|
|
|
404
404
|
iteration = state.get("iteration", 0)
|
|
405
405
|
complexity = state.get("complexity", "standard")
|
|
406
406
|
mode = state.get("mode", "")
|
|
407
|
-
|
|
407
|
+
# Count only agents with alive PIDs (not raw array length)
|
|
408
|
+
agents_list = state.get("agents", [])
|
|
409
|
+
running_agents = 0
|
|
410
|
+
for agent in agents_list:
|
|
411
|
+
agent_pid = agent.get("pid")
|
|
412
|
+
if agent_pid:
|
|
413
|
+
try:
|
|
414
|
+
os.kill(int(agent_pid), 0)
|
|
415
|
+
running_agents += 1
|
|
416
|
+
except (OSError, ValueError, TypeError):
|
|
417
|
+
pass
|
|
418
|
+
else:
|
|
419
|
+
# No PID field -- count as running (legacy data)
|
|
420
|
+
running_agents += 1
|
|
408
421
|
|
|
409
422
|
tasks = state.get("tasks", {})
|
|
410
423
|
pending_tasks = len(tasks.get("pending", []))
|
|
@@ -3194,6 +3207,31 @@ async def get_process_health(token: Optional[dict] = Depends(auth.get_current_to
|
|
|
3194
3207
|
except Exception:
|
|
3195
3208
|
pass
|
|
3196
3209
|
|
|
3210
|
+
# PID registry (central process supervisor)
|
|
3211
|
+
pids_dir = loki_dir / "pids"
|
|
3212
|
+
registered: list[dict[str, Any]] = []
|
|
3213
|
+
if pids_dir.exists():
|
|
3214
|
+
for entry_file in sorted(pids_dir.glob("*.json")):
|
|
3215
|
+
try:
|
|
3216
|
+
pid_str = entry_file.stem
|
|
3217
|
+
pid = int(pid_str)
|
|
3218
|
+
entry = json.loads(entry_file.read_text())
|
|
3219
|
+
try:
|
|
3220
|
+
os.kill(pid, 0)
|
|
3221
|
+
status = "alive"
|
|
3222
|
+
except OSError:
|
|
3223
|
+
status = "dead"
|
|
3224
|
+
registered.append({
|
|
3225
|
+
"pid": pid,
|
|
3226
|
+
"label": entry.get("label", "unknown"),
|
|
3227
|
+
"started": entry.get("started", ""),
|
|
3228
|
+
"ppid": entry.get("ppid"),
|
|
3229
|
+
"status": status,
|
|
3230
|
+
})
|
|
3231
|
+
except (ValueError, json.JSONDecodeError, OSError):
|
|
3232
|
+
continue
|
|
3233
|
+
result["registered_processes"] = registered
|
|
3234
|
+
|
|
3197
3235
|
watchdog_enabled = os.environ.get("LOKI_WATCHDOG", "false").lower() == "true"
|
|
3198
3236
|
result["watchdog_enabled"] = watchdog_enabled
|
|
3199
3237
|
|
package/docs/COMPARISON.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
| Feature | **Loki Mode** | **Zencoder** | **Devin** | **OpenAI Codex** | **Cursor** | **Claude Code** | **Kiro** | **Antigravity** | **Amazon Q** | **OpenCode** |
|
|
13
13
|
|---------|--------------|--------------|-----------|-----------------|------------|-----------------|----------|-----------------|--------------|--------------|
|
|
14
14
|
| **Type** | Skill/Framework | Enterprise Platform | Standalone Agent | Cloud Agent | AI IDE | CLI Agent | AI IDE | AI IDE | Cloud Agent | AI IDE (OSS) |
|
|
15
|
-
| **Autonomy Level** |
|
|
15
|
+
| **Autonomy Level** | High (minimal human) | High | Full | High | Medium-High | High | High | High | High | High |
|
|
16
16
|
| **Max Runtime** | Unlimited | Async/Scheduled | Hours | Per-task | Session | Session | Days | Async | Per-task | Session |
|
|
17
17
|
| **Pricing** | Free (OSS) | Enterprise | $20/mo | ChatGPT Plus | $20/mo | API costs | Free preview | Free preview | $19/mo | Free (OSS) |
|
|
18
18
|
| **Open Source** | Yes | No | No | No | No | No | No | No | No | Yes |
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
| Feature | **Loki Mode** | **Devin** | **Codex** | **Cursor** | **Kiro** | **Antigravity** | **Amazon Q** | **OpenCode** |
|
|
26
26
|
|---------|--------------|-----------|-----------|------------|----------|-----------------|--------------|--------------|
|
|
27
|
-
| **Multi-Agent** | 41 agents in
|
|
27
|
+
| **Multi-Agent** | 41 agents in 8 swarms | Single | Single | Up to 8 parallel | Background | Manager Surface | Multiple types | 4 built-in |
|
|
28
28
|
| **Orchestration** | Full orchestrator | N/A | N/A | Git worktree | Hooks | Manager view | Workflow | Subagents |
|
|
29
29
|
| **Parallel Exec** | 10+ Haiku, 4 impl (worktree) | No | No | 8 max | Yes | Yes | Yes | Yes |
|
|
30
30
|
| **Agent Swarms** | Eng, Ops, Business, Data, Product, Growth, Review | N/A | N/A | N/A | N/A | N/A | 3 types | N/A |
|
|
@@ -180,9 +180,9 @@
|
|
|
180
180
|
|
|
181
181
|
1. **Quality Control**: 7 explicit gates + blind review + devil's advocate vs built-in loops
|
|
182
182
|
2. **Memory System**: 3-tier (episodic/semantic/procedural) with cross-project learning
|
|
183
|
-
3. **Agent Specialization**: 41 pre-defined specialized agents across
|
|
183
|
+
3. **Agent Specialization**: 41 pre-defined specialized agents across 8 swarms
|
|
184
184
|
4. **Anti-Sycophancy**: CONSENSAGENT patterns prevent reviewer groupthink
|
|
185
|
-
5. **Autonomy Design**:
|
|
185
|
+
5. **Autonomy Design**: Minimal human intervention from PRD to production
|
|
186
186
|
6. **Research Foundation**: 10+ academic papers integrated vs proprietary
|
|
187
187
|
|
|
188
188
|
### Where Zencoder EXCEEDS Loki Mode
|
|
@@ -203,13 +203,13 @@
|
|
|
203
203
|
|---------|--------------|---------|-----------------|------------|-----------------|---------------------|----------------|
|
|
204
204
|
| **Stars** | 594 | 11,903 | 35K+ | 26K+ | 13.7K | N/A | N/A |
|
|
205
205
|
| **npm/wk** | 6.1K | 21.4K | N/A | N/A | N/A | N/A | N/A |
|
|
206
|
-
| **Agents** | 41 in
|
|
206
|
+
| **Agents** | 41 in 8 swarms | 11 agents | Fresh per task | 108 agents | Swarm-based | 32 agents | N/A |
|
|
207
207
|
| **Skills** | Progressive disclosure | 6 slash commands | N/A | 129 skills | N/A | 35 skills | Memory focus |
|
|
208
208
|
| **Multi-Provider** | Yes (Claude/Codex/Gemini) | 3 CLIs (separate) | No | No | No | No | No |
|
|
209
209
|
| **Memory System** | 3-tier (episodic/semantic/procedural) | None | N/A | N/A | Hybrid | N/A | SQLite+FTS5 |
|
|
210
210
|
| **Quality Gates** | 7 gates + Completion Council | User verify only | Two-Stage Review | N/A | Consensus | Tiered | N/A |
|
|
211
211
|
| **Context Mgmt** | Standard | Fresh per task (core innovation) | Fresh per task | N/A | N/A | N/A | Progressive |
|
|
212
|
-
| **Autonomy** |
|
|
212
|
+
| **Autonomy** | High (minimal human) | Semi (checkpoints) | Human-guided | Human-guided | Orchestrated | Human-guided | N/A |
|
|
213
213
|
|
|
214
214
|
### What Loki Mode LACKS (Honest Assessment)
|
|
215
215
|
|
|
@@ -236,7 +236,7 @@ These are patterns from competing projects that are **practically and scientific
|
|
|
236
236
|
| **Constitutional AI Integration** | Principles-based self-critique from Anthropic research | None have this |
|
|
237
237
|
| **Anti-Sycophancy (CONSENSAGENT)** | Blind review + devil's advocate prevents groupthink | None have this |
|
|
238
238
|
| **Provider Abstraction Layer** | Clean degradation from full-featured to sequential-only | Claude-only projects can't degrade |
|
|
239
|
-
| **41 Specialized Agents** | Purpose-built agents in
|
|
239
|
+
| **41 Specialized Agents** | Purpose-built agents in 8 swarms vs generic | agents (108) has more but less organized |
|
|
240
240
|
| **Research Foundation** | 10+ academic papers integrated with citations | Most have no research backing |
|
|
241
241
|
|
|
242
242
|
### Superpowers Deep-Dive (35K+ Stars)
|
|
@@ -342,7 +342,7 @@ Tiered agent architecture with explicit escalation:
|
|
|
342
342
|
|
|
343
343
|
| Agent | Killer Feature |
|
|
344
344
|
|-------|---------------|
|
|
345
|
-
| **Loki Mode** |
|
|
345
|
+
| **Loki Mode** | Minimal-human-intervention full SDLC, 41 agents in 8 swarms, Constitutional AI, anti-sycophancy, cross-project learning, code transformation, property-based testing |
|
|
346
346
|
| **Devin** | Full software engineer persona, Slack integration, 67% PR merge rate |
|
|
347
347
|
| **OpenAI Codex** | Skills marketplace, $skill-creator, GPT-5.2-Codex, secure sandbox |
|
|
348
348
|
| **Cursor** | 8 parallel agents, BugBot, Memories, $10B valuation, Composer model (250 tok/s) |
|
|
@@ -357,8 +357,8 @@ Tiered agent architecture with explicit escalation:
|
|
|
357
357
|
|
|
358
358
|
| Dimension | Loki Mode Advantage |
|
|
359
359
|
|-----------|-------------------|
|
|
360
|
-
| **Autonomy** |
|
|
361
|
-
| **Multi-Agent** | 41 specialized agents in
|
|
360
|
+
| **Autonomy** | Designed for high autonomy with minimal human intervention |
|
|
361
|
+
| **Multi-Agent** | 41 specialized agents in 8 swarms vs 1-8 in competitors |
|
|
362
362
|
| **Quality** | 7 gates + blind review + devil's advocate + property-based testing |
|
|
363
363
|
| **Research** | 10+ academic papers integrated vs proprietary/undisclosed |
|
|
364
364
|
| **Anti-Sycophancy** | Only agent with CONSENSAGENT-based blind review |
|
|
@@ -37,9 +37,9 @@ GSD is the closest competitor -- a context engineering system that spawns fresh
|
|
|
37
37
|
|---------|-----------|-------------|---------|--------|--------------|-------|
|
|
38
38
|
| **GitHub Stars** | 594 | 13,700 | 62,400 | 25,000+ | N/A (Commercial) | N/A (Commercial) |
|
|
39
39
|
| **Agent Count** | 41 types | 64+ agents | 5 roles | Unlimited | 8 parallel | 1 autonomous |
|
|
40
|
-
| **Parallel Execution** | Yes (
|
|
41
|
-
| **Published Benchmarks** |
|
|
42
|
-
| **SWE-bench Score** |
|
|
40
|
+
| **Parallel Execution** | Yes (multi-agent) | Yes (swarms) | Sequential | Yes (crews) | Yes (8 worktrees) | Yes (fleet) |
|
|
41
|
+
| **Published Benchmarks** | 98.78% HumanEval (self-reported, max 3 retries) | None | 85.9-87.7% HumanEval | None | ~250 tok/s | 15% complex tasks |
|
|
42
|
+
| **SWE-bench Score** | 99.67% patch gen (unevaluated, 299/300) | Unknown | Unknown | Unknown | Unknown | 15% complex |
|
|
43
43
|
| **Full SDLC** | Yes (8 phases) | Yes | Partial | Partial | No | Partial |
|
|
44
44
|
| **Business Ops** | **Yes (8 agents)** | No | No | No | No | No |
|
|
45
45
|
| **Enterprise Security** | `--dangerously-skip-permissions` | MCP sandboxed | Sandboxed | Audit logs, RBAC | Staged autonomy | Sandboxed |
|