loki-mode 5.33.0 → 5.34.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.33.0
6
+ # Loki Mode v5.34.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -149,6 +149,7 @@ GROWTH ──[continuous improvement loop]──> GROWTH
149
149
  | `.loki/queue/dead-letter.json` | Session start | On task failure (5+ attempts) |
150
150
  | `.loki/signals/CONTEXT_CLEAR_REQUESTED` | Never | When context heavy |
151
151
  | `.loki/signals/HUMAN_REVIEW_NEEDED` | Never | When human decision required |
152
+ | `.loki/state/checkpoints/` | After task completion | Automatic + manual via `loki checkpoint` |
152
153
 
153
154
  ---
154
155
 
@@ -259,4 +260,4 @@ The following features are documented in skill modules but not yet fully automat
259
260
  | Quality gates 3-reviewer system | Planned | Instructions in `skills/quality-gates.md`; not automated |
260
261
  | Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
261
262
 
262
- **v5.33.0 | audit cleanup, honest feature claims | ~260 lines core**
263
+ **v5.34.0 | checkpoint/restore, GitHub Action provider-agnostic | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 5.33.0
1
+ 5.34.0
package/autonomy/loki CHANGED
@@ -4032,6 +4032,9 @@ main() {
4032
4032
  compound)
4033
4033
  cmd_compound "$@"
4034
4034
  ;;
4035
+ checkpoint|cp)
4036
+ cmd_checkpoint "$@"
4037
+ ;;
4035
4038
  council)
4036
4039
  cmd_council "$@"
4037
4040
  ;;
@@ -5318,6 +5321,263 @@ COMPOUND_RUN_SCRIPT
5318
5321
  esac
5319
5322
  }
5320
5323
 
5324
+ # Checkpoint management - save and restore session state (v5.34.0)
5325
+ cmd_checkpoint() {
5326
+ local subcommand="${1:-list}"
5327
+ shift 2>/dev/null || true
5328
+
5329
+ local checkpoints_dir=".loki/state/checkpoints"
5330
+ local index_file="$checkpoints_dir/index.jsonl"
5331
+
5332
+ case "$subcommand" in
5333
+ list|ls)
5334
+ echo -e "${BOLD}Session Checkpoints${NC}"
5335
+ echo ""
5336
+
5337
+ if [ ! -f "$index_file" ]; then
5338
+ echo " No checkpoints yet."
5339
+ echo ""
5340
+ echo " Create one with: loki checkpoint create [message]"
5341
+ return 0
5342
+ fi
5343
+
5344
+ local count=0
5345
+ local lines=()
5346
+ while IFS= read -r line; do
5347
+ lines+=("$line")
5348
+ done < "$index_file"
5349
+
5350
+ # Show last 10 entries (most recent last)
5351
+ local total=${#lines[@]}
5352
+ local start=0
5353
+ if [ "$total" -gt 10 ]; then
5354
+ start=$((total - 10))
5355
+ fi
5356
+
5357
+ printf " ${DIM}%-14s %-20s %-10s %s${NC}\n" "ID" "TIMESTAMP" "GIT SHA" "MESSAGE"
5358
+
5359
+ local i
5360
+ for (( i=start; i<total; i++ )); do
5361
+ local entry="${lines[$i]}"
5362
+ local cp_id=$(echo "$entry" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null)
5363
+ local cp_ts=$(echo "$entry" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('timestamp',''))" 2>/dev/null)
5364
+ local cp_sha=$(echo "$entry" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('git_sha','')[:8])" 2>/dev/null)
5365
+ local cp_msg=$(echo "$entry" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('message','')[:50])" 2>/dev/null)
5366
+
5367
+ if [ -n "$cp_id" ]; then
5368
+ printf " ${GREEN}%-14s${NC} %-20s ${CYAN}%-10s${NC} %s\n" "$cp_id" "$cp_ts" "$cp_sha" "$cp_msg"
5369
+ count=$((count + 1))
5370
+ fi
5371
+ done
5372
+
5373
+ if [ "$count" -eq 0 ]; then
5374
+ echo " No valid checkpoints found."
5375
+ else
5376
+ echo ""
5377
+ echo " Showing ${count} of ${total} checkpoints"
5378
+ fi
5379
+ ;;
5380
+
5381
+ create)
5382
+ local message="${*:-manual checkpoint}"
5383
+
5384
+ echo -e "${BOLD}Creating checkpoint...${NC}"
5385
+ echo ""
5386
+
5387
+ # Ensure .loki exists
5388
+ if [ ! -d ".loki" ]; then
5389
+ echo -e "${RED}Error: No .loki directory found. Are you in a Loki project?${NC}"
5390
+ return 1
5391
+ fi
5392
+
5393
+ # Capture git info
5394
+ local git_sha=""
5395
+ local git_branch=""
5396
+ if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
5397
+ git_sha=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
5398
+ git_branch=$(git branch --show-current 2>/dev/null || echo "unknown")
5399
+ else
5400
+ git_sha="not-a-git-repo"
5401
+ git_branch="none"
5402
+ fi
5403
+
5404
+ # Generate checkpoint ID with timestamp
5405
+ local ts=$(date -u '+%Y%m%d-%H%M%S')
5406
+ local cp_id="cp-${ts}"
5407
+ local cp_dir="$checkpoints_dir/$cp_id"
5408
+
5409
+ # Create checkpoint directory
5410
+ mkdir -p "$cp_dir"
5411
+
5412
+ # Copy state files
5413
+ local copied=0
5414
+ for item in .loki/session.json .loki/dashboard-state.json .loki/queue .loki/memory .loki/metrics .loki/council; do
5415
+ if [ -e "$item" ]; then
5416
+ cp -r "$item" "$cp_dir/" 2>/dev/null && copied=$((copied + 1))
5417
+ fi
5418
+ done
5419
+
5420
+ # Write metadata
5421
+ local iso_ts=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
5422
+ cat > "$cp_dir/metadata.json" << METADATA_EOF
5423
+ {
5424
+ "id": "$cp_id",
5425
+ "timestamp": "$iso_ts",
5426
+ "git_sha": "$git_sha",
5427
+ "git_branch": "$git_branch",
5428
+ "message": "$message",
5429
+ "files_copied": $copied,
5430
+ "created_by": "loki checkpoint create"
5431
+ }
5432
+ METADATA_EOF
5433
+
5434
+ # Append to index
5435
+ mkdir -p "$checkpoints_dir"
5436
+ echo "{\"id\":\"$cp_id\",\"timestamp\":\"$iso_ts\",\"git_sha\":\"$git_sha\",\"git_branch\":\"$git_branch\",\"message\":\"$message\"}" >> "$index_file"
5437
+
5438
+ echo -e " Checkpoint: ${GREEN}$cp_id${NC}"
5439
+ echo -e " Git SHA: ${CYAN}${git_sha:0:8}${NC} ($git_branch)"
5440
+ echo -e " Message: $message"
5441
+ echo -e " Files: $copied state items copied"
5442
+ echo -e " Location: $cp_dir"
5443
+ echo ""
5444
+ echo " Restore with: loki checkpoint rollback $cp_id"
5445
+ ;;
5446
+
5447
+ show)
5448
+ local cp_id="${1:-}"
5449
+ if [ -z "$cp_id" ]; then
5450
+ echo -e "${RED}Error: Specify a checkpoint ID${NC}"
5451
+ echo "Usage: loki checkpoint show <id>"
5452
+ echo "Run 'loki checkpoint list' to see available checkpoints."
5453
+ return 1
5454
+ fi
5455
+
5456
+ local cp_dir="$checkpoints_dir/$cp_id"
5457
+ local metadata="$cp_dir/metadata.json"
5458
+
5459
+ if [ ! -f "$metadata" ]; then
5460
+ echo -e "${RED}Error: Checkpoint not found: $cp_id${NC}"
5461
+ echo "Run 'loki checkpoint list' to see available checkpoints."
5462
+ return 1
5463
+ fi
5464
+
5465
+ echo -e "${BOLD}Checkpoint: $cp_id${NC}"
5466
+ echo ""
5467
+
5468
+ python3 << SHOW_EOF
5469
+ import json, os
5470
+ with open("$metadata", "r") as f:
5471
+ d = json.load(f)
5472
+ print(f" ID: {d.get('id', 'unknown')}")
5473
+ print(f" Timestamp: {d.get('timestamp', 'unknown')}")
5474
+ print(f" Git SHA: {d.get('git_sha', 'unknown')}")
5475
+ print(f" Git Branch: {d.get('git_branch', 'unknown')}")
5476
+ print(f" Message: {d.get('message', 'none')}")
5477
+ print(f" Files: {d.get('files_copied', 0)} state items")
5478
+ print(f" Created By: {d.get('created_by', 'unknown')}")
5479
+ SHOW_EOF
5480
+
5481
+ echo ""
5482
+ echo " Contents:"
5483
+ for item in "$cp_dir"/*; do
5484
+ [ -e "$item" ] || continue
5485
+ local name=$(basename "$item")
5486
+ [ "$name" = "metadata.json" ] && continue
5487
+ if [ -d "$item" ]; then
5488
+ local fcount=$(find "$item" -type f 2>/dev/null | wc -l | tr -d ' ')
5489
+ echo -e " ${DIM}[dir]${NC} $name/ ($fcount files)"
5490
+ else
5491
+ local fsize=$(wc -c < "$item" 2>/dev/null | tr -d ' ')
5492
+ echo -e " ${DIM}[file]${NC} $name (${fsize} bytes)"
5493
+ fi
5494
+ done
5495
+ ;;
5496
+
5497
+ rollback)
5498
+ local cp_id="${1:-}"
5499
+ if [ -z "$cp_id" ]; then
5500
+ echo -e "${RED}Error: Specify a checkpoint ID${NC}"
5501
+ echo "Usage: loki checkpoint rollback <id>"
5502
+ echo "Run 'loki checkpoint list' to see available checkpoints."
5503
+ return 1
5504
+ fi
5505
+
5506
+ local cp_dir="$checkpoints_dir/$cp_id"
5507
+ local metadata="$cp_dir/metadata.json"
5508
+
5509
+ if [ ! -f "$metadata" ]; then
5510
+ echo -e "${RED}Error: Checkpoint not found: $cp_id${NC}"
5511
+ echo "Run 'loki checkpoint list' to see available checkpoints."
5512
+ return 1
5513
+ fi
5514
+
5515
+ echo -e "${BOLD}Rolling back to checkpoint: $cp_id${NC}"
5516
+ echo ""
5517
+
5518
+ # Restore state files
5519
+ local restored=0
5520
+ for item in "$cp_dir"/*; do
5521
+ [ -e "$item" ] || continue
5522
+ local name=$(basename "$item")
5523
+ [ "$name" = "metadata.json" ] && continue
5524
+
5525
+ if [ -d "$item" ]; then
5526
+ rm -rf ".loki/$name"
5527
+ cp -r "$item" ".loki/$name" 2>/dev/null && restored=$((restored + 1))
5528
+ else
5529
+ cp "$item" ".loki/$name" 2>/dev/null && restored=$((restored + 1))
5530
+ fi
5531
+ done
5532
+
5533
+ echo -e " Restored: ${GREEN}$restored${NC} state items from $cp_id"
5534
+
5535
+ # Show git info for manual code rollback
5536
+ local cp_sha=$(python3 -c "import json; d=json.load(open('$metadata')); print(d.get('git_sha','unknown'))" 2>/dev/null)
5537
+ if [ -n "$cp_sha" ] && [ "$cp_sha" != "unknown" ] && [ "$cp_sha" != "not-a-git-repo" ]; then
5538
+ echo ""
5539
+ echo -e " ${YELLOW}Note:${NC} Session state has been restored, but code is unchanged."
5540
+ echo " To also roll back code to the checkpoint's git state:"
5541
+ echo ""
5542
+ echo -e " ${DIM}git reset --hard ${cp_sha:0:8}${NC}"
5543
+ echo ""
5544
+ echo -e " ${RED}Warning:${NC} git reset --hard will discard uncommitted changes."
5545
+ echo " Consider 'git stash' first to preserve current work."
5546
+ fi
5547
+ ;;
5548
+
5549
+ help|--help|-h)
5550
+ echo -e "${BOLD}loki checkpoint${NC} - Session state checkpoints"
5551
+ echo ""
5552
+ echo "Save and restore session state snapshots during autonomous runs."
5553
+ echo "Checkpoints capture .loki/ state files and record the git SHA"
5554
+ echo "at the time of creation."
5555
+ echo ""
5556
+ echo "Usage: loki checkpoint <command> [args]"
5557
+ echo " loki cp <command> [args]"
5558
+ echo ""
5559
+ echo "Commands:"
5560
+ echo " list List recent checkpoints (default)"
5561
+ echo " create [message] Create a new checkpoint"
5562
+ echo " show <id> Show checkpoint details"
5563
+ echo " rollback <id> Restore state from a checkpoint"
5564
+ echo " help Show this help"
5565
+ echo ""
5566
+ echo "Examples:"
5567
+ echo " loki checkpoint create 'before refactor'"
5568
+ echo " loki cp list"
5569
+ echo " loki cp show cp-20260212-143022"
5570
+ echo " loki cp rollback cp-20260212-143022"
5571
+ ;;
5572
+
5573
+ *)
5574
+ echo -e "${RED}Unknown checkpoint command: $subcommand${NC}"
5575
+ echo "Run 'loki checkpoint help' for usage."
5576
+ return 1
5577
+ ;;
5578
+ esac
5579
+ }
5580
+
5321
5581
  # Completion Council management
5322
5582
  cmd_council() {
5323
5583
  local subcommand="${1:-status}"
package/autonomy/run.sh CHANGED
@@ -3879,6 +3879,167 @@ if top:
3879
3879
  SOLUTIONS_SCRIPT
3880
3880
  }
3881
3881
 
3882
+ # ============================================================================
3883
+ # Checkpoint/Snapshot System (v5.34.0)
3884
+ # Git-based checkpoints after task completion with state snapshots
3885
+ # Inspired by Cursor Self-Driving Codebases + Entire.io provenance tracking
3886
+ # ============================================================================
3887
+
3888
+ create_checkpoint() {
3889
+ # Create a git checkpoint after task completion
3890
+ # Args: $1 = task description, $2 = task_id (optional)
3891
+ local task_desc="${1:-task completed}"
3892
+ local task_id="${2:-unknown}"
3893
+ local checkpoint_dir=".loki/state/checkpoints"
3894
+ local iteration="${ITERATION_COUNT:-0}"
3895
+
3896
+ mkdir -p "$checkpoint_dir"
3897
+
3898
+ # Only checkpoint if there are uncommitted changes
3899
+ if ! git diff --quiet 2>/dev/null && ! git diff --cached --quiet 2>/dev/null; then
3900
+ log_info "No uncommitted changes to checkpoint"
3901
+ return 0
3902
+ fi
3903
+
3904
+ # Capture git state
3905
+ local git_sha
3906
+ git_sha=$(git rev-parse HEAD 2>/dev/null || echo "no-git")
3907
+ local git_branch
3908
+ git_branch=$(git branch --show-current 2>/dev/null || echo "unknown")
3909
+
3910
+ # Snapshot .loki state files
3911
+ local timestamp
3912
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
3913
+ local checkpoint_id="cp-${iteration}-$(date +%s)"
3914
+ local cp_dir="${checkpoint_dir}/${checkpoint_id}"
3915
+
3916
+ mkdir -p "$cp_dir"
3917
+
3918
+ # Copy critical state files (lightweight -- not full .loki/)
3919
+ for f in state/orchestrator.json queue/pending.json queue/completed.json queue/in-progress.json queue/current-task.json; do
3920
+ if [ -f ".loki/$f" ]; then
3921
+ local target_dir="$cp_dir/$(dirname "$f")"
3922
+ mkdir -p "$target_dir"
3923
+ cp ".loki/$f" "$cp_dir/$f" 2>/dev/null || true
3924
+ fi
3925
+ done
3926
+
3927
+ # Write checkpoint metadata
3928
+ local safe_desc
3929
+ safe_desc=$(printf '%s' "$task_desc" | sed 's/\\/\\\\/g; s/"/\\"/g' | head -c 200)
3930
+ cat > "$cp_dir/metadata.json" << CPEOF
3931
+ {
3932
+ "id": "${checkpoint_id}",
3933
+ "timestamp": "${timestamp}",
3934
+ "iteration": ${iteration},
3935
+ "task_id": "${task_id}",
3936
+ "task_description": "${safe_desc}",
3937
+ "git_sha": "${git_sha}",
3938
+ "git_branch": "${git_branch}",
3939
+ "provider": "${PROVIDER_NAME:-claude}",
3940
+ "phase": "$(cat .loki/state/orchestrator.json 2>/dev/null | python3 -c 'import sys,json; print(json.load(sys.stdin).get("currentPhase","unknown"))' 2>/dev/null || echo 'unknown')"
3941
+ }
3942
+ CPEOF
3943
+
3944
+ # Maintain checkpoint index for fast listing
3945
+ local index_file="${checkpoint_dir}/index.jsonl"
3946
+ printf '{"id":"%s","ts":"%s","iter":%d,"task":"%s","sha":"%s"}\n' \
3947
+ "$checkpoint_id" "$timestamp" "$iteration" "$safe_desc" "$git_sha" \
3948
+ >> "$index_file"
3949
+
3950
+ # Retention: keep last 50 checkpoints, prune older
3951
+ local cp_count
3952
+ cp_count=$(find "$checkpoint_dir" -maxdepth 1 -type d -name "cp-*" 2>/dev/null | wc -l | tr -d ' ')
3953
+ if [ "$cp_count" -gt 50 ]; then
3954
+ local to_remove=$((cp_count - 50))
3955
+ find "$checkpoint_dir" -maxdepth 1 -type d -name "cp-*" 2>/dev/null | sort | head -n "$to_remove" | while read -r old_cp; do
3956
+ rm -r "$old_cp" 2>/dev/null || true
3957
+ done
3958
+ # Rebuild index from remaining checkpoints
3959
+ : > "$index_file"
3960
+ for remaining in "$checkpoint_dir"/cp-*/metadata.json; do
3961
+ [ -f "$remaining" ] || continue
3962
+ python3 -c "
3963
+ import json,sys
3964
+ m=json.load(open('$remaining'))
3965
+ print(json.dumps({'id':m['id'],'ts':m['timestamp'],'iter':m['iteration'],'task':m.get('task_description',''),'sha':m['git_sha']}))
3966
+ " >> "$index_file" 2>/dev/null || true
3967
+ done
3968
+ fi
3969
+
3970
+ log_info "Checkpoint created: ${checkpoint_id} (git: ${git_sha:0:8})"
3971
+ }
3972
+
3973
+ rollback_to_checkpoint() {
3974
+ # Rollback state files to a specific checkpoint
3975
+ # Args: $1 = checkpoint_id
3976
+ local checkpoint_id="$1"
3977
+ local checkpoint_dir=".loki/state/checkpoints"
3978
+ local cp_dir="${checkpoint_dir}/${checkpoint_id}"
3979
+
3980
+ if [ ! -d "$cp_dir" ]; then
3981
+ log_error "Checkpoint not found: ${checkpoint_id}"
3982
+ return 1
3983
+ fi
3984
+
3985
+ # Read checkpoint metadata
3986
+ local git_sha
3987
+ git_sha=$(python3 -c "import json; print(json.load(open('${cp_dir}/metadata.json'))['git_sha'])" 2>/dev/null || echo "")
3988
+
3989
+ log_warn "Rolling back to checkpoint: ${checkpoint_id}"
3990
+
3991
+ # Create a pre-rollback checkpoint first
3992
+ create_checkpoint "pre-rollback snapshot" "rollback"
3993
+
3994
+ # Restore state files
3995
+ for f in state/orchestrator.json queue/pending.json queue/completed.json queue/in-progress.json queue/current-task.json; do
3996
+ if [ -f "${cp_dir}/${f}" ]; then
3997
+ local target_dir=".loki/$(dirname "$f")"
3998
+ mkdir -p "$target_dir"
3999
+ cp "${cp_dir}/${f}" ".loki/${f}" 2>/dev/null || true
4000
+ fi
4001
+ done
4002
+
4003
+ # Log the rollback
4004
+ local timestamp
4005
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
4006
+ printf '{"event":"rollback","checkpoint":"%s","git_sha":"%s","timestamp":"%s"}\n' \
4007
+ "$checkpoint_id" "$git_sha" "$timestamp" \
4008
+ >> ".loki/events.jsonl" 2>/dev/null || true
4009
+
4010
+ log_info "State files restored from checkpoint: ${checkpoint_id}"
4011
+
4012
+ if [ -n "$git_sha" ] && [ "$git_sha" != "no-git" ]; then
4013
+ log_info "Git SHA at checkpoint: ${git_sha}"
4014
+ log_info "To rollback code: git reset --hard ${git_sha}"
4015
+ fi
4016
+ }
4017
+
4018
+ list_checkpoints() {
4019
+ # List recent checkpoints
4020
+ local checkpoint_dir=".loki/state/checkpoints"
4021
+ local index_file="${checkpoint_dir}/index.jsonl"
4022
+ local limit="${1:-10}"
4023
+
4024
+ if [ ! -f "$index_file" ]; then
4025
+ echo "No checkpoints found."
4026
+ return
4027
+ fi
4028
+
4029
+ tail -n "$limit" "$index_file" | python3 -c "
4030
+ import sys, json
4031
+ lines = sys.stdin.readlines()
4032
+ for line in reversed(lines):
4033
+ try:
4034
+ cp = json.loads(line)
4035
+ sha = cp.get('sha','')[:8]
4036
+ task = cp.get('task','')[:60]
4037
+ print(f\" {cp['id']} {cp['ts']} [{sha}] {task}\")
4038
+ except:
4039
+ continue
4040
+ "
4041
+ }
4042
+
3882
4043
  start_dashboard() {
3883
4044
  log_header "Starting Loki Dashboard"
3884
4045
 
@@ -5795,6 +5956,9 @@ main() {
5795
5956
  # Compound learnings into structured solution files (v5.30.0)
5796
5957
  compound_session_to_solutions
5797
5958
 
5959
+ # Create session-end checkpoint (v5.34.0)
5960
+ create_checkpoint "session end (iterations=$ITERATION_COUNT)" "session-end"
5961
+
5798
5962
  # Log session end for audit
5799
5963
  audit_log "SESSION_END" "result=$result,prd=$PRD_PATH"
5800
5964
 
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "5.33.0"
10
+ __version__ = "5.34.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -1984,6 +1984,149 @@ async def force_council_review():
1984
1984
  return {"success": True, "message": "Council review requested"}
1985
1985
 
1986
1986
 
1987
+ # =============================================================================
1988
+ # Checkpoint API (v5.34.0)
1989
+ # =============================================================================
1990
+
1991
+ class CheckpointCreate(BaseModel):
1992
+ """Schema for creating a checkpoint."""
1993
+ message: Optional[str] = Field(None, description="Optional description for the checkpoint")
1994
+
1995
+
1996
+ def _sanitize_checkpoint_id(checkpoint_id: str) -> str:
1997
+ """Validate checkpoint_id contains only safe characters for file paths."""
1998
+ if not checkpoint_id or ".." in checkpoint_id or not _SAFE_ID_RE.match(checkpoint_id):
1999
+ raise HTTPException(
2000
+ status_code=400,
2001
+ detail="Invalid checkpoint_id: must contain only alphanumeric characters, hyphens, and underscores",
2002
+ )
2003
+ return checkpoint_id
2004
+
2005
+
2006
+ @app.get("/api/checkpoints")
2007
+ async def list_checkpoints(limit: int = Query(default=20, ge=1, le=200)):
2008
+ """List recent checkpoints from index.jsonl."""
2009
+ loki_dir = _get_loki_dir()
2010
+ index_file = loki_dir / "state" / "checkpoints" / "index.jsonl"
2011
+ checkpoints = []
2012
+
2013
+ if index_file.exists():
2014
+ try:
2015
+ for line in index_file.read_text().strip().split("\n"):
2016
+ if line.strip():
2017
+ try:
2018
+ checkpoints.append(json.loads(line))
2019
+ except json.JSONDecodeError:
2020
+ pass
2021
+ except Exception:
2022
+ pass
2023
+
2024
+ # Return most recent first, limited
2025
+ checkpoints.reverse()
2026
+ return checkpoints[:limit]
2027
+
2028
+
2029
+ @app.get("/api/checkpoints/{checkpoint_id}")
2030
+ async def get_checkpoint(checkpoint_id: str):
2031
+ """Get checkpoint details by ID."""
2032
+ checkpoint_id = _sanitize_checkpoint_id(checkpoint_id)
2033
+ loki_dir = _get_loki_dir()
2034
+ metadata_file = loki_dir / "state" / "checkpoints" / checkpoint_id / "metadata.json"
2035
+
2036
+ if not metadata_file.exists():
2037
+ raise HTTPException(status_code=404, detail="Checkpoint not found")
2038
+
2039
+ try:
2040
+ return json.loads(metadata_file.read_text())
2041
+ except (json.JSONDecodeError, IOError) as e:
2042
+ raise HTTPException(status_code=500, detail=f"Failed to read checkpoint: {e}")
2043
+
2044
+
2045
+ @app.post("/api/checkpoints", status_code=201)
2046
+ async def create_checkpoint(body: CheckpointCreate = None):
2047
+ """Create a new checkpoint capturing current state."""
2048
+ import subprocess
2049
+ import shutil
2050
+
2051
+ loki_dir = _get_loki_dir()
2052
+ checkpoints_dir = loki_dir / "state" / "checkpoints"
2053
+ checkpoints_dir.mkdir(parents=True, exist_ok=True)
2054
+
2055
+ # Generate checkpoint ID from timestamp
2056
+ now = datetime.now(timezone.utc)
2057
+ checkpoint_id = now.strftime("chk-%Y%m%d-%H%M%S")
2058
+
2059
+ # Create checkpoint directory
2060
+ checkpoint_dir = checkpoints_dir / checkpoint_id
2061
+ checkpoint_dir.mkdir(parents=True, exist_ok=True)
2062
+
2063
+ # Capture git SHA
2064
+ git_sha = ""
2065
+ try:
2066
+ result = subprocess.run(
2067
+ ["git", "rev-parse", "HEAD"],
2068
+ capture_output=True, text=True, timeout=5,
2069
+ )
2070
+ if result.returncode == 0:
2071
+ git_sha = result.stdout.strip()
2072
+ except Exception:
2073
+ pass
2074
+
2075
+ # Copy key state files into checkpoint
2076
+ state_files = [
2077
+ "dashboard-state.json",
2078
+ "session.json",
2079
+ ]
2080
+ for fname in state_files:
2081
+ src = loki_dir / fname
2082
+ if src.exists():
2083
+ try:
2084
+ shutil.copy2(str(src), str(checkpoint_dir / fname))
2085
+ except Exception:
2086
+ pass
2087
+
2088
+ # Copy queue directory if present
2089
+ queue_src = loki_dir / "queue"
2090
+ if queue_src.exists():
2091
+ try:
2092
+ shutil.copytree(str(queue_src), str(checkpoint_dir / "queue"), dirs_exist_ok=True)
2093
+ except Exception:
2094
+ pass
2095
+
2096
+ # Build metadata
2097
+ message = ""
2098
+ if body and body.message:
2099
+ message = body.message
2100
+
2101
+ metadata = {
2102
+ "id": checkpoint_id,
2103
+ "created_at": now.isoformat(),
2104
+ "git_sha": git_sha,
2105
+ "message": message,
2106
+ "files": [f.name for f in checkpoint_dir.iterdir() if f.is_file()],
2107
+ }
2108
+
2109
+ # Write metadata.json
2110
+ (checkpoint_dir / "metadata.json").write_text(json.dumps(metadata, indent=2))
2111
+
2112
+ # Append to index.jsonl
2113
+ index_file = checkpoints_dir / "index.jsonl"
2114
+ with open(str(index_file), "a") as f:
2115
+ f.write(json.dumps(metadata) + "\n")
2116
+
2117
+ # Retention policy: keep last 50 checkpoints
2118
+ MAX_CHECKPOINTS = 50
2119
+ all_dirs = sorted(
2120
+ [d for d in checkpoints_dir.iterdir() if d.is_dir()],
2121
+ key=lambda d: d.name,
2122
+ )
2123
+ while len(all_dirs) > MAX_CHECKPOINTS:
2124
+ oldest = all_dirs.pop(0)
2125
+ shutil.rmtree(str(oldest), ignore_errors=True)
2126
+
2127
+ return metadata
2128
+
2129
+
1987
2130
  # =============================================================================
1988
2131
  # Agent Management API (v5.25.0)
1989
2132
  # =============================================================================
@@ -2,7 +2,7 @@
2
2
 
3
3
  Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v5.33.0
5
+ **Version:** v5.34.0
6
6
 
7
7
  ---
8
8
 
@@ -90,7 +90,7 @@ Open VS Code Settings and search for "loki":
90
90
  | Setting | Default | Description |
91
91
  |---------|---------|-------------|
92
92
  | `loki.provider` | `claude` | Default AI provider |
93
- | `loki.apiPort` | `9898` | API server port |
93
+ | `loki.apiPort` | `57374` | API server port |
94
94
  | `loki.apiHost` | `localhost` | API server host |
95
95
  | `loki.autoConnect` | `true` | Auto-connect on activation |
96
96
  | `loki.showStatusBar` | `true` | Show status bar item |
@@ -110,10 +110,10 @@ loki start
110
110
  ./autonomy/run.sh
111
111
 
112
112
  # Option C: Direct API server start
113
- node autonomy/api-server.js
113
+ loki serve
114
114
  ```
115
115
 
116
- The extension will automatically connect when it detects the server is running at `localhost:9898`.
116
+ The extension will automatically connect when it detects the server is running at `localhost:57374`.
117
117
 
118
118
  **Troubleshooting:** If you see "API server is not running" errors, make sure you started the server first using one of the commands above.
119
119
 
@@ -579,15 +579,14 @@ Loki Mode uses two network ports for different services:
579
579
 
580
580
  | Port | Service | Description |
581
581
  |------|---------|-------------|
582
- | **57374** | Dashboard (FastAPI) | Web dashboard UI with real-time monitoring, task board, Completion Council views, memory browser, and log streaming. Served by `dashboard/server.py`. |
583
- | **9898** | REST API Server | JSON API used by the VS Code extension, CLI tools, and programmatic access. Serves endpoints like `/api/status`, `/api/tasks`, `/api/memory`, etc. |
582
+ | **57374** | Dashboard + API (FastAPI) | Unified server serving both the web dashboard UI (real-time monitoring, task board, Completion Council, memory browser, log streaming) and the REST API (used by VS Code extension, CLI tools, programmatic access). Served by `dashboard/server.py`. |
584
583
 
585
584
  ### When to Use Which Port
586
585
 
587
586
  - **Browser access** (dashboard, monitoring): Use port **57374** -- `http://localhost:57374`
588
- - **API calls** (REST, programmatic): Use port **9898** -- `http://localhost:9898`
589
- - **VS Code extension**: Connects to API on port **9898** automatically (configurable via `loki.apiPort` setting)
590
- - **Both ports** are started automatically when you run `loki start` or `./autonomy/run.sh`. No manual configuration is needed.
587
+ - **API calls** (REST, programmatic): Use port **57374** -- `http://localhost:57374`
588
+ - **VS Code extension**: Connects to API on port **57374** automatically (configurable via `loki.apiPort` setting)
589
+ - The server is started automatically when you run `loki start` or `./autonomy/run.sh`. No manual configuration is needed.
591
590
 
592
591
  ### Port Configuration
593
592
 
@@ -595,8 +594,8 @@ Loki Mode uses two network ports for different services:
595
594
  # Dashboard port (default: 57374)
596
595
  LOKI_DASHBOARD_PORT=57374 loki dashboard start
597
596
 
598
- # API port (default: 9898)
599
- loki serve --port 9898
597
+ # API port (default: 57374)
598
+ loki serve --port 57374
600
599
  ```
601
600
 
602
601
  ### CORS Configuration
@@ -72,7 +72,7 @@ loki memory index
72
72
  loki memory retrieve "authentication"
73
73
 
74
74
  # Manage API server (enables VS Code, dashboard)
75
- loki serve --port 9898
75
+ loki serve --port 57374
76
76
  loki api start
77
77
 
78
78
  # View dashboard (web UI)
@@ -24,7 +24,7 @@ open http://localhost:57374
24
24
 
25
25
  The dashboard automatically syncs with Loki Mode when it's running, polling `dashboard-state.json` every 2 seconds.
26
26
 
27
- **Ports:** The dashboard runs on port **57374**. The REST API server runs separately on port **9898**. See [INSTALLATION.md](INSTALLATION.md#ports) for details.
27
+ **Ports:** The dashboard and API run on unified port **57374** (FastAPI serves both). See [INSTALLATION.md](INSTALLATION.md#ports) for details.
28
28
 
29
29
  ---
30
30
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "5.33.0",
3
+ "version": "5.34.0",
4
4
  "description": "Multi-agent autonomous startup system for Claude Code, Codex CLI, and Gemini CLI",
5
5
  "keywords": [
6
6
  "claude",