loki-mode 11.1.0 → 11.2.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.
Files changed (91) hide show
  1. package/README.md +1 -1
  2. package/SKILL.md +2 -2
  3. package/VERSION +1 -1
  4. package/autonomy/lib/skill-link-heal.sh +54 -0
  5. package/autonomy/loki +60 -6
  6. package/autonomy/run.sh +11 -3
  7. package/bin/loki +8 -0
  8. package/bin/loki-postinstall.js +3 -1
  9. package/dashboard/__init__.py +1 -1
  10. package/docs/CLI-REFERENCE.md +1 -1
  11. package/docs/INSTALLATION.md +1 -1
  12. package/docs/WANG-PRINCIPLES-PLAN.md +1 -1
  13. package/docs/v10/DECISIONS.md +4 -0
  14. package/docs/v10/FAILURE-CLASSES.md +27 -1
  15. package/docs/v10/GUIDE.md +85 -0
  16. package/docs/v11/ROUTER-1.md +15 -13
  17. package/loki-ts/dist/loki.js +687 -680
  18. package/mcp/__init__.py +1 -1
  19. package/mcp/cp_tools.py +333 -0
  20. package/mcp/server.py +21 -0
  21. package/mcp/tests/_read_only_wall_probe.py +10 -0
  22. package/mcp/tests/fixtures/cp_api/artifact_report.md +3 -0
  23. package/mcp/tests/fixtures/cp_api/cost.json +1 -0
  24. package/mcp/tests/fixtures/cp_api/repos.json +1 -0
  25. package/mcp/tests/fixtures/cp_api/run_alpha.json +1 -0
  26. package/mcp/tests/fixtures/cp_api/run_beta.json +1 -0
  27. package/mcp/tests/fixtures/cp_api/run_events.json +1 -0
  28. package/mcp/tests/fixtures/cp_api/runs_list.json +1 -0
  29. package/mcp/tests/fixtures/cp_api/stats.json +1 -0
  30. package/mcp/tests/test_cp_tools.py +290 -0
  31. package/mcp/tests/test_read_only.py +6 -3
  32. package/package.json +1 -1
  33. package/packages/control-plane/dist/server.js +252 -65
  34. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  35. package/web-app/dist/assets/{AdminPage-Bqx993m_.js → AdminPage-BZ1QNSju.js} +1 -1
  36. package/web-app/dist/assets/{Avatar-BHjV5Csv.js → Avatar-Dnzzsk2U.js} +1 -1
  37. package/web-app/dist/assets/{Badge-DGbSCV8L.js → Badge-DWtsAw6M.js} +1 -1
  38. package/web-app/dist/assets/{Button-CnCZf-0w.js → Button-zEMFjJEr.js} +1 -1
  39. package/web-app/dist/assets/{CockpitPage-CyZC0Q4e.js → CockpitPage-Ddo5Zhnz.js} +1 -1
  40. package/web-app/dist/assets/{ComparePage-REwWeDu9.js → ComparePage-kCkyRrrz.js} +1 -1
  41. package/web-app/dist/assets/{ErrorBoundary-C0sG0fj4.js → ErrorBoundary-BZrVi8Ui.js} +1 -1
  42. package/web-app/dist/assets/{EvidenceReceiptPanel-nXcOrxiA.js → EvidenceReceiptPanel-B9eDIWUr.js} +1 -1
  43. package/web-app/dist/assets/{GitHubIssuesPanel-D8CUAA7b.js → GitHubIssuesPanel-DX5A2UkV.js} +1 -1
  44. package/web-app/dist/assets/{GitHubPRsPanel-B68Bf309.js → GitHubPRsPanel-DKKxBe6-.js} +1 -1
  45. package/web-app/dist/assets/{HomePage-mgJEeXH_.js → HomePage-BSn_-fHq.js} +2 -2
  46. package/web-app/dist/assets/{LoginPage-TM_L3-ws.js → LoginPage-C1BGwj0s.js} +1 -1
  47. package/web-app/dist/assets/{MagicPage-CoE5Yz73.js → MagicPage-B9JJw_BQ.js} +1 -1
  48. package/web-app/dist/assets/{MetricsPage-BubxRsx4.js → MetricsPage-B-lNQr8Q.js} +1 -1
  49. package/web-app/dist/assets/{NotFoundPage-udeE-1w6.js → NotFoundPage-DkktBNvI.js} +1 -1
  50. package/web-app/dist/assets/{ProjectPage-BuYFdDIy.js → ProjectPage-DUAraQw9.js} +3 -3
  51. package/web-app/dist/assets/{ProjectsPage-DP4T7hAW.js → ProjectsPage-DgMo4QHm.js} +1 -1
  52. package/web-app/dist/assets/{SettingsPage-fIgNrA9e.js → SettingsPage-fnL3nqGC.js} +1 -1
  53. package/web-app/dist/assets/{ShowcasePage-9lILj-je.js → ShowcasePage-Bn7rWTXn.js} +1 -1
  54. package/web-app/dist/assets/{SystemSettingsPage-BYBBdIyC.js → SystemSettingsPage-DxrytIXT.js} +1 -1
  55. package/web-app/dist/assets/{TeamsPage-qhHtMpzb.js → TeamsPage-BQDC12Mn.js} +1 -1
  56. package/web-app/dist/assets/{TemplatesPage-DIClsS3d.js → TemplatesPage-C_JEygcZ.js} +1 -1
  57. package/web-app/dist/assets/{TerminalOutput-DKczDGbC.js → TerminalOutput-H0584wn0.js} +1 -1
  58. package/web-app/dist/assets/{activity-CU5PK4CB.js → activity-Bse7FP2H.js} +1 -1
  59. package/web-app/dist/assets/{bell-Czwm7c23.js → bell-BMtgCGSW.js} +1 -1
  60. package/web-app/dist/assets/{bot-BNB-olYj.js → bot-6SW_XTZN.js} +1 -1
  61. package/web-app/dist/assets/{check-DEmFNYuC.js → check-C3f9aTHx.js} +1 -1
  62. package/web-app/dist/assets/{chevron-left-DcSPJ4KZ.js → chevron-left-Bi60XjBc.js} +1 -1
  63. package/web-app/dist/assets/{circle-alert-CQ0-gHsx.js → circle-alert-Bepfb6dn.js} +1 -1
  64. package/web-app/dist/assets/{clock-CvLXmhWQ.js → clock-jQIU5AuJ.js} +1 -1
  65. package/web-app/dist/assets/{cloud-6BuQFMTx.js → cloud-BLZn6J9A.js} +1 -1
  66. package/web-app/dist/assets/{code-xml-BMf3egg0.js → code-xml-CRgKi92z.js} +1 -1
  67. package/web-app/dist/assets/{database-BBTbPQOn.js → database-DWg9XT6Y.js} +1 -1
  68. package/web-app/dist/assets/{dollar-sign-B8vHgGQG.js → dollar-sign-Cfif5nlf.js} +1 -1
  69. package/web-app/dist/assets/{file-code-corner-DwAQcHOd.js → file-code-corner-DSiLXED8.js} +1 -1
  70. package/web-app/dist/assets/{file-plus-BlhGOB-I.js → file-plus-8lIzYfAS.js} +1 -1
  71. package/web-app/dist/assets/{globe-6a-K3mZa.js → globe-DZkvR077.js} +1 -1
  72. package/web-app/dist/assets/{hammer-CU_u8tsP.js → hammer-BtQyfpf_.js} +1 -1
  73. package/web-app/dist/assets/{index-BN6SdA1r.js → index-B-uQiPRR.js} +3 -3
  74. package/web-app/dist/assets/{layers-VJW1LqHm.js → layers-Dlfo3jwV.js} +1 -1
  75. package/web-app/dist/assets/{loader-circle-DfL0cxR3.js → loader-circle-BQ2hL7m5.js} +1 -1
  76. package/web-app/dist/assets/{lock-D2LRX42k.js → lock-LzpJKEAP.js} +1 -1
  77. package/web-app/dist/assets/{package-_JNG_ljQ.js → package-zp-qZxpp.js} +1 -1
  78. package/web-app/dist/assets/{plus-qYicH17Y.js → plus-CuiDkGsm.js} +1 -1
  79. package/web-app/dist/assets/{refresh-cw-Bqb0Vbp-.js → refresh-cw-Dx57uk3-.js} +1 -1
  80. package/web-app/dist/assets/{rotate-ccw-Blheo-H1.js → rotate-ccw-Bp1YOkM6.js} +1 -1
  81. package/web-app/dist/assets/{scroll-text-3OO4nVP7.js → scroll-text-Dc8LjRT5.js} +1 -1
  82. package/web-app/dist/assets/{server-34C8ObzM.js → server-CE0Mzyfz.js} +1 -1
  83. package/web-app/dist/assets/{shield-alert-D7-nkuq3.js → shield-alert-DBj71EDP.js} +1 -1
  84. package/web-app/dist/assets/{thumbs-up-CvfrKMma.js → thumbs-up-C0q9J-0y.js} +1 -1
  85. package/web-app/dist/assets/{trash-2-4bt3fZC1.js → trash-2-YNPSnz_h.js} +1 -1
  86. package/web-app/dist/assets/{trending-down-C7Uu-rJL.js → trending-down-BlyQB_Gh.js} +1 -1
  87. package/web-app/dist/assets/{trending-up-B7TF-ynr.js → trending-up-CStScgAm.js} +1 -1
  88. package/web-app/dist/assets/{upload-YlZG_g5B.js → upload-CLs-rKu9.js} +1 -1
  89. package/web-app/dist/assets/{usePolling-BDZJZdnI.js → usePolling-BmFkptqQ.js} +1 -1
  90. package/web-app/dist/assets/{user-C2Z3KTt0.js → user-CV0TBz6A.js} +1 -1
  91. package/web-app/dist/index.html +1 -1
package/README.md CHANGED
@@ -17,7 +17,7 @@ CLI reference: [docs/CLI-REFERENCE.md](docs/CLI-REFERENCE.md) (generated from th
17
17
 
18
18
  Loki Mode is a free, source-available autonomous coding agent by [Autonomi](https://www.autonomi.dev/). Hand it a PRD, GitHub issue, OpenAPI doc or one-line brief. It derives a delivery contract (the acceptance criteria), builds against it, and ends with a signed Evidence Receipt that states what was proven and what was not. If the contract cannot be derived, it stops and asks one question instead of guessing.
19
19
 
20
- Before a build counts as done, a review council selects reviewers from a specialist pool (`agents/types.json`, scored by `run.sh:FOCUS_KEYWORDS`). The bundled MCP server exposes 39 tools over stdio (`mcp/server.py`), including `loki_v10_run`, `loki_v10_status` and `loki_v10_verify` to start a Loki 10 run in the background, read its phase, verdict and cost, and verify its receipt (`BLOCKED` runs report their question). Add it with `claude mcp add loki-mode -- python3 -m mcp.server` from the install directory.
20
+ Before a build counts as done, a review council selects reviewers from a specialist pool (`agents/types.json`, scored by `run.sh:FOCUS_KEYWORDS`). The bundled MCP server exposes 47 tools over stdio (`mcp/server.py`), including `loki_v10_run`, `loki_v10_status` and `loki_v10_verify` to start a Loki 10 run in the background, read its phase, verdict and cost, and verify its receipt (`BLOCKED` runs report their question). Add it with `claude mcp add loki-mode -- python3 -m mcp.server` from the install directory.
21
21
 
22
22
  ## Contents
23
23
 
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: An autonomous software factory that knows what it is supposed to deliver, and proves it did. Use when the user says Loki Mode or asks to build, fix or verify software autonomously.
4
4
  ---
5
5
 
6
- # Loki Mode v11.1.0
6
+ # Loki Mode v11.2.1
7
7
 
8
8
  **Current entry points (Loki 10 engine):** `loki "<task>"`, `loki owner/repo#N` and `loki quick "<task>"` run the Loki 10 engine and end with a signed receipt (`loki verify`). `loki backlog owner/repo --all` runs many issues in parallel. Guide: `docs/v10/GUIDE.md`. The skill instructions below, and `loki start`, describe the legacy engine, which still ships and is being removed (`loki start owner/repo#N` still routes to it; prefer `loki owner/repo#N`).
9
9
 
@@ -483,4 +483,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
483
483
 
484
484
  ---
485
485
 
486
- **v11.1.0 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
486
+ **v11.2.1 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 11.1.0
1
+ 11.2.1
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env bash
2
+ # FC-30: keep the provider skill links Loki created pointing at the running
3
+ # install. One shared mechanism, called from npm postinstall and from the bin/loki
4
+ # shim (first invocation after an install or upgrade). Cost: a few lstat/readlink
5
+ # calls, no network, no subprocess beyond readlink/ln/mv on the repair path.
6
+ #
7
+ # Opt out with LOKI_NO_SKILL_LINK_HEAL=1.
8
+ #
9
+ # A link is repointed only when ALL hold:
10
+ # - it is a symlink (a real directory or file is never touched)
11
+ # - its target is a Loki install path (…/node_modules/loki-mode, or a Homebrew
12
+ # Cellar/opt loki-mode dir), so a foreign link is never touched
13
+ # - its target is missing, or resolves to a different install than the running one
14
+ # - the running root is itself an install path with a SKILL.md, so a dev checkout
15
+ # or worktree never hijacks the user's global link
16
+
17
+ _loki_skill_is_install_path() {
18
+ case "${1%/}" in
19
+ */node_modules/loki-mode | */Cellar/loki-mode/* | */opt/loki-mode | */opt/loki-mode/libexec) return 0 ;;
20
+ esac
21
+ return 1
22
+ }
23
+
24
+ loki_skill_link_heal() {
25
+ [ "${LOKI_NO_SKILL_LINK_HEAL:-}" = "1" ] && return 0
26
+ local root="${1:-}" home="${HOME:-}" root_p rel link tgt live_p tmp
27
+ [ -n "$root" ] && [ -n "$home" ] || return 0
28
+ [ -f "$root/SKILL.md" ] || return 0
29
+ root_p="$(cd "$root" 2>/dev/null && pwd -P)" || return 0
30
+ _loki_skill_is_install_path "$root_p" || _loki_skill_is_install_path "$root" || return 0
31
+
32
+ for rel in .claude .codex .cline .aider; do
33
+ link="$home/$rel/skills/loki-mode"
34
+ [ -L "$link" ] || continue
35
+ tgt="$(readlink "$link" 2>/dev/null)" || continue
36
+ case "$tgt" in /*) ;; *) tgt="$(dirname "$link")/$tgt" ;; esac
37
+ _loki_skill_is_install_path "$tgt" || continue
38
+ if [ -e "$link" ]; then
39
+ live_p="$(cd "$link" 2>/dev/null && pwd -P)" || live_p=""
40
+ [ "$live_p" = "$root_p" ] && continue
41
+ fi
42
+ tmp="${link}.heal.$$"
43
+ rm -f "$tmp" 2>/dev/null
44
+ ln -s "$root_p" "$tmp" 2>/dev/null || continue
45
+ # Atomic replace. -T (GNU) / -h (BSD) stop mv from descending into a
46
+ # link that points at a directory.
47
+ if mv -fT "$tmp" "$link" 2>/dev/null || mv -fh "$tmp" "$link" 2>/dev/null; then
48
+ printf 'loki: repointed skill link %s -> %s (was %s)\n' "$link" "$root_p" "$tgt" >&2
49
+ else
50
+ rm -f "$tmp" 2>/dev/null
51
+ fi
52
+ done
53
+ return 0
54
+ }
package/autonomy/loki CHANGED
@@ -1150,6 +1150,53 @@ except Exception:
1150
1150
  PY
1151
1151
  }
1152
1152
 
1153
+ # loki_verify_root: the ONE place that decides which tree the receipt verifier
1154
+ # hashes (FC-29). The verifier walks and digests the directory it is given, so
1155
+ # an ambient cwd (a huge checkout, $HOME, a non-project directory) must never be
1156
+ # the default. Order: explicit TARGET_DIR; the parent of an absolute LOKI_DIR;
1157
+ # the cwd if it holds .loki; the repo top-level if it holds .loki. Anything else
1158
+ # prints one plain sentence on stderr and returns 1 (callers report NOT CHECKED).
1159
+ loki_verify_root() {
1160
+ # Helpers live inside so callers that extract this one function stay whole.
1161
+ _loki_verify_trim() {
1162
+ local p="$1"
1163
+ while [ "${#p}" -gt 1 ] && [ "${p%/}" != "$p" ]; do p="${p%/}"; done
1164
+ printf '%s' "$p"
1165
+ }
1166
+
1167
+ _loki_verify_canon() {
1168
+ local p r
1169
+ p="$(_loki_verify_trim "$1")"
1170
+ if [ -n "$p" ] && [ -d "$p" ] && r="$(cd "$p" 2>/dev/null && pwd -P)"; then p="$r"; fi
1171
+ printf '%s' "$p"
1172
+ }
1173
+ local d="${TARGET_DIR:-}" top="" _cd _ch
1174
+ d="$(_loki_verify_trim "$d")"
1175
+ if [ -z "$d" ]; then
1176
+ case "${LOKI_DIR:-}" in
1177
+ /*) [ -d "$LOKI_DIR" ] && d="$(dirname "$LOKI_DIR")" ;;
1178
+ esac
1179
+ fi
1180
+ if [ -z "$d" ]; then
1181
+ if [ -d "$PWD/.loki" ]; then
1182
+ d="$PWD"
1183
+ else
1184
+ top="$(git -C "$PWD" rev-parse --show-toplevel 2>/dev/null || true)"
1185
+ if [ -n "$top" ] && [ -d "$top/.loki" ]; then d="$top"; fi
1186
+ fi
1187
+ fi
1188
+ # Compare canonical paths: every trailing slash stripped, symlinks resolved,
1189
+ # identically for the candidate and HOME (mirrored by resolveVerifyRoot in
1190
+ # loki-ts/src/commands/proof.ts).
1191
+ _cd="$(_loki_verify_canon "$d")"
1192
+ _ch="$(_loki_verify_canon "${HOME:-}")"
1193
+ if [ -z "$d" ] || [ "$_cd" = "/" ] || { [ -n "$_ch" ] && [ "$_cd" = "$_ch" ]; }; then
1194
+ echo "Cannot tell which project tree to verify against: set TARGET_DIR or run from a project directory that contains .loki." >&2
1195
+ return 1
1196
+ fi
1197
+ printf '%s\n' "$d"
1198
+ }
1199
+
1153
1200
  loki_remote_verify_receipt() {
1154
1201
  local pj="$1"
1155
1202
  local verifier="${_LOKI_SCRIPT_DIR}/lib/proof-verify.py"
@@ -1181,7 +1228,9 @@ loki_remote_verify_receipt() {
1181
1228
  # empty for a local receipt, which simply means no attestation is checked.
1182
1229
  local _attest_url="${2:-}" _attest=""
1183
1230
  _attest="$(loki_remote_attestation_status "$pj" "$_attest_url")"
1184
- out="$(python3 -E "$verifier" "$pj" "${TARGET_DIR:-.}" 2>/dev/null)"
1231
+ local _vroot
1232
+ _vroot="$(loki_verify_root)" || { echo " NOT VERIFIED: no project tree to check against" >&2; return 0; }
1233
+ out="$(python3 -E "$verifier" "$pj" "$_vroot" 2>/dev/null)"
1185
1234
  hash_ok="$(printf '%s' "$out" | jq -r '.hash_ok // false' 2>/dev/null)"
1186
1235
  gpg_ok="$(printf '%s' "$out" | jq -r '.gpg_ok | tostring' 2>/dev/null)"
1187
1236
 
@@ -9547,8 +9596,9 @@ _deploy_receipt_verdict() {
9547
9596
  printf '%s' "UNCHECKED"; return 0
9548
9597
  fi
9549
9598
 
9550
- local out hash_ok
9551
- out="$(python3 -E "$verifier" "$pj" "${TARGET_DIR:-.}" 2>/dev/null || true)"
9599
+ local out hash_ok _vroot
9600
+ _vroot="$(loki_verify_root 2>/dev/null)" || { printf '%s' "UNCHECKED"; return 0; }
9601
+ out="$(python3 -E "$verifier" "$pj" "$_vroot" 2>/dev/null || true)"
9552
9602
 
9553
9603
  # "The verifier did not RUN" and "the verifier says the hash is bad" are
9554
9604
  # different facts and must not collapse. If they do, a broken python3
@@ -37656,7 +37706,11 @@ print(json.dumps(json.load(open(os.environ['LOKI_PROOF_JSON'])), indent=2))
37656
37706
  echo -e "${YELLOW}NOT CHECKED${NC}: python3 is not on PATH, so the verifier could not run (exit 2)." >&2
37657
37707
  exit 2
37658
37708
  fi
37659
- local _pv_rc=0 _pv_out="" _pv_base_rc=0
37709
+ local _pv_rc=0 _pv_out="" _pv_base_rc=0 _pv_root=""
37710
+ _pv_root="$(loki_verify_root)" || {
37711
+ echo -e "${YELLOW}NOT CHECKED${NC}: no project tree to verify against (exit 2)." >&2
37712
+ exit 2
37713
+ }
37660
37714
  # Captured, not printed directly, so that --jwks (below) can patch
37661
37715
  # it: BACKLOG 49 -- when --jwks later downgrades _pv_rc to 1
37662
37716
  # (ABSENT) or 2 (NOT CHECKED), this output's own "ok: true"/
@@ -37666,9 +37720,9 @@ print(json.dumps(json.load(open(os.environ['LOKI_PROOF_JSON'])), indent=2))
37666
37720
  # route (proof.ts verifyProof) -- ok already agrees with the exit
37667
37721
  # code there, so there is nothing to patch.
37668
37722
  if [ "$_pv_human" -eq 1 ]; then
37669
- _pv_out="$(python3 -E "$verifier" --human "$pj" "${TARGET_DIR:-.}")" || _pv_rc=$?
37723
+ _pv_out="$(python3 -E "$verifier" --human "$pj" "$_pv_root")" || _pv_rc=$?
37670
37724
  else
37671
- _pv_out="$(python3 -E "$verifier" "$pj" "${TARGET_DIR:-.}")" || _pv_rc=$?
37725
+ _pv_out="$(python3 -E "$verifier" "$pj" "$_pv_root")" || _pv_rc=$?
37672
37726
  fi
37673
37727
  _pv_base_rc="$_pv_rc"
37674
37728
 
package/autonomy/run.sh CHANGED
@@ -22698,16 +22698,24 @@ build_prompt() {
22698
22698
  # configuration there), or LOKI_GOAL_SCORING=0. Advisory only: it never
22699
22699
  # blocks a build and never rewrites the user's goal.
22700
22700
  local goal_sharpening_instruction=""
22701
- if [ -n "${COMPLETION_PROMISE:-}" ] \
22701
+ # Bash-native trim, matching (goal ?? "").trim() in goal_score.ts: a
22702
+ # whitespace-only promise is an absent goal, not an unmeasurable one.
22703
+ local _goal_trim="${COMPLETION_PROMISE:-}"
22704
+ _goal_trim="${_goal_trim#"${_goal_trim%%[![:space:]]*}"}"
22705
+ _goal_trim="${_goal_trim%"${_goal_trim##*[![:space:]]}"}"
22706
+ if [ -n "$_goal_trim" ] \
22702
22707
  && [ "${LOKI_GOAL_SCORING:-}" != "0" ] \
22703
22708
  && [ "${AUTONOMY_MODE:-}" != "perpetual" ] \
22704
22709
  && [ "${PERPETUAL_MODE:-}" != "true" ] && [ "${PERPETUAL_MODE:-}" != "1" ]; then
22705
22710
  local _goal_lc _goal_dims
22706
- _goal_lc="$(printf '%s' "$COMPLETION_PROMISE" | tr '[:upper:]' '[:lower:]')"
22711
+ _goal_lc="$(printf '%s' "$_goal_trim" | tr '[:upper:]' '[:lower:]')"
22707
22712
  _goal_dims=0
22708
22713
  # Mirrors the four DIMENSIONS regexes in goal_score.ts, in the same order.
22709
22714
  printf '%s' "$_goal_lc" | grep -qE '[0-9]+(\.[0-9]+)?[[:space:]]*(%|ms|s\b|sec|second|min|minute|hour|day|kb|mb|gb|rps|qps|req|x\b|users?|items?|rows?)' && _goal_dims=$((_goal_dims + 1))
22710
- printf '%s' "$_goal_lc" | grep -qE '\b(under|below|less than|no more than|at most|over|above|greater than|at least|within|between|<=?|>=?)\b' && _goal_dims=$((_goal_dims + 1))
22715
+ # The symbol alternatives (<, <=, >, >=) cannot use grep's \b: BSD and GNU
22716
+ # disagree on it next to a non-word character. TS \b(<=?)\b means a word
22717
+ # character on BOTH sides of the symbol, so spell that out explicitly.
22718
+ printf '%s' "$_goal_lc" | grep -qE -e '\b(under|below|less than|no more than|at most|over|above|greater than|at least|within|between)\b' -e '[A-Za-z0-9_](<=?|>=?)[A-Za-z0-9_]' && _goal_dims=$((_goal_dims + 1))
22711
22719
  printf '%s' "$_goal_lc" | grep -qE '\b(latency|throughput|p50|p95|p99|uptime|error rate|conversion|coverage|score|accuracy|precision|recall|bundle size|load time|response time|memory|cpu|cost)\b' && _goal_dims=$((_goal_dims + 1))
22712
22720
  printf '%s' "$_goal_lc" | grep -qE '\b(tests? pass|builds? clean|endpoint|returns? [0-9]{3}|exit code|schema|migration|deploys?|renders?|compiles?)\b' && _goal_dims=$((_goal_dims + 1))
22713
22721
  # Only a goal with ZERO measurable dimensions is flagged (score == 0),
package/bin/loki CHANGED
@@ -115,6 +115,14 @@ else
115
115
  exec "$BASH_CLI" "$@"
116
116
  fi
117
117
 
118
+ # FC-30: an install artifact must not outlive the install that made it. If a provider
119
+ # skill link Loki created is dangling or points at another Loki install, repoint it
120
+ # to this one. A few lstat/readlink calls, no network, silent when already correct.
121
+ if [ -f "$REPO_ROOT/autonomy/lib/skill-link-heal.sh" ]; then
122
+ # shellcheck source=../autonomy/lib/skill-link-heal.sh
123
+ source "$REPO_ROOT/autonomy/lib/skill-link-heal.sh" 2>/dev/null && loki_skill_link_heal "$REPO_ROOT" || true
124
+ fi
125
+
118
126
  # Shell completions install themselves: once per VERSION, on the first interactive
119
127
  # run after an install or upgrade (stamp: ~/.loki/completions-version). Never needed
120
128
  # by the user; skipped under CI, LOKI_NO_COMPLETIONS=1 and non-TTY stdout. Failure
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
- // npm postinstall: install shell completions. Must never fail the install.
2
+ // npm postinstall: repoint stale skill links (FC-30) and install shell completions. Must never fail the install.
3
3
  try {
4
4
  const { spawnSync } = require("node:child_process");
5
5
  const path = require("node:path");
6
+ const root = path.join(__dirname, "..");
7
+ spawnSync("bash", ["-c", '. "$1/autonomy/lib/skill-link-heal.sh" && loki_skill_link_heal "$1"', "_", root], { stdio: ["ignore", "ignore", "inherit"], timeout: 10000 });
6
8
  if (!process.env.CI && process.env.LOKI_NO_COMPLETIONS !== "1") {
7
9
  spawnSync(path.join(__dirname, "loki"), ["completion", "--postinstall"], { stdio: "ignore", timeout: 20000 });
8
10
  }
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "11.1.0"
10
+ __version__ = "11.2.1"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -1,6 +1,6 @@
1
1
  # CLI reference
2
2
 
3
- Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.1.0. Do not edit by hand.
3
+ Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.2.1. Do not edit by hand.
4
4
  Hidden and legacy-only commands are omitted; see `docs/v10/CLI-MODERN.md` for the full inventory.
5
5
 
6
6
  | Command | Description |
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v11.1.0
5
+ **Version:** v11.2.1
6
6
 
7
7
  **Engine note:** `loki "<task>"`, `loki owner/repo#N` and `loki quick` run the Loki 10 engine. `loki start` still routes to the legacy engine, which is being removed (planned work resumes 2026-10-07); prefer `loki owner/repo#N`. See [docs/v10/GUIDE.md](v10/GUIDE.md).
8
8
 
@@ -53,7 +53,7 @@ Measured on this machine, not asserted.
53
53
  | **1 Systems Thinking** | STRONG | 8 quality gates, RARV-C loop, council, Evidence Receipt, dual-route parity enforced by test |
54
54
  | **2 Speed** | MEASURED, UNOPTIMISED | agent call = **980s = 96%** of iteration; all gates together = 44s |
55
55
  | **3 Reliability** | STRONG, newly so | 73 mutation-proven trust invariants; four gates were shipping broken until v8.38.0 |
56
- | **4 Extensibility** | STRONG | 5 providers, agent type registry, MCP (39 tools), plugin marketplace |
56
+ | **4 Extensibility** | STRONG | 5 providers, agent type registry, MCP (47 tools), plugin marketplace |
57
57
  | **5 Feedback Loops / Evals** | **BROKEN** | `cost_usd == 0` on **3 of 5** efficiency records; no eval score for our own harness |
58
58
 
59
59
  **Principle 5 is the gap, and it is the one Wang weights highest.** He states the
@@ -729,3 +729,7 @@ PROGRESS.md (the FC-25 P9 entry) cites a D88 CI waiver that was never recorded i
729
729
  8. Implementation: docs/v11/ROUTER-1.md (slices R1-01 to R1-21). This slice is R1-04.
730
730
  9. Default-on only after parity: while slices are built, `LOKI_ROUTER` unset means off. R1-19 flips the default to on only when, on the release candidate SHA, the FireLater#17 gate is green (VERIFIED, ready PR, time within raw + 30s, cost <= raw x 1.2) and the B9 corpus shows correctness parity with raw `claude -p` on every shape not listed in the seeded router-shape-defaults.json. After the flip, `LOKI_ROUTER=0` is the opt-out and restores the exact pre-router model behavior, pins included (R1-21 golden).
731
731
  10. Accepted risk (CTO, 2026-10-07): B9 measures about 5 of the 14 or more Repo Shape Matrix shapes, so an unmeasured shape starts on Haiku without shape-level parity evidence; the backstops are the in-run escalation ladder (Haiku -> Sonnet 5.5 before STALLED, so a miss costs time and money, not correctness) and the H4 history floor, the receipt records shape parity as NOT PROVEN for any shape with no B9 row, and each matrix shape gains a B9 row as it becomes runnable.
732
+
733
+ Amendment to D89 (2026-10-07 21:40Z, CTO decision, refined by the founder at 21:45Z, CoS records). This amends only R1-19, the default-on flip that follows the CTO gate; 11.1.0 and 11.2.0 still ship the router OFF. (1) The default executor is Sonnet 5.5; Haiku 5.5 per work unit when Opus assigns it. (2) Roles: Opus 5.5 is the architect, router and advisor, plans and assigns each unit, and is consulted on hard calls. Sonnet is the senior engineer and the default for implementation and fixes. Haiku does tests, docs and small low-risk changes only when Opus assigns them in the plan. Haiku is never the blanket default. (3) L0 holds: the harness never routes by task type or path regex; it only executes, verifies and escalates. (4) The route schema is `units: [{id, kind, executor, reason}]` plus a run default of sonnet; a missing or invalid unit route goes to sonnet with NOT PROVEN. A haiku unit that fails a code-owned check is redone on sonnet, then escalates to opus; a Wall assigned to haiku gets an Opus review before implement. The receipt lists the executor, assigner and reason per unit. The per-unit schema lands in 11.2.0 or 11.2.1 depending on what is merged. (5) Escalation stays evidence-gated (haiku -> sonnet -> opus, and sonnet -> opus), and `LOKI_ROUTER=0` equals pre-router behavior exactly (the R1-21 golden). (6) The R1-16 shape-defaults semantics are inverted (a shape is listed as `haiku` once it has earned it; an absent listing means Sonnet), and R1-16 history is an input to Opus, not a harness rule. Rationale: a wrong PR costs the user far more than the token savings, B9 covers only about 5 of 14 shapes (item 10), and under L0 a model earns the cheaper tier with evidence. Items 3, 5, 7 and 9 above are read with this amendment; their text is kept as recorded. See docs/v11/ROUTER-1.md sections 4.1, 4.3 and 4.6.
734
+
735
+ Amendment 2 to D89 (2026-10-08 00:15Z, steering finding, CoS records; tightens a gate, so it binds until the CTO relaxes it). R1-19 gains one more gate line: on the trivial fixture (the steering Node off-by-one fixture), router-on cost must be <= router-off cost x 1.05, measured with n>=3 per arm and recorded per arm as cache-read input tokens, fresh input tokens, output tokens, dollars and wall time (R1-17 B9). Evidence that prompted it (n=1, may be variance): 11.2.0 smoke, router on cost +15% tokens, +27% dollars and +38s with zero advisor calls and no route change. If the advisor tool definition costs tokens on sessions that never consult it, the advisor is enabled only on the sessions Opus marks as needing it (plan and fix rounds), not on every stage. Measure before changing code.
@@ -331,6 +331,32 @@ L0 review rule (ENGINE-LAWS.md L0): a fix that adds an `if` or a regex about the
331
331
  - User saw: tests/test-proof-verify-jwks.sh passed 108/0 in 33s in a fresh worktree and hung until killed when run from the main checkout. The CI-style run was green and the founder's own run was red, so the suite read as flaky.
332
332
  - Mechanism (measured): `loki proof verify` and `loki_remote_verify_receipt` run `proof-verify.py "$pj" "${TARGET_DIR:-.}"`. With TARGET_DIR unset the verifier runs `compute_tree_digest` on the caller's cwd, whose filesystem walk visited 1,054,266 paths (13.3s just to enumerate, then it hashes them) because `.claude/worktrees` (190 worktrees, 32G) sits inside the checkout. A fresh worktree has a few thousand files.
333
333
  - Law: L2 (a verdict is about the tree it names, never an ambient one), and the test hermeticity rule: a test owns its fixture tree.
334
- - Siblings: swept every tests/*.sh spawn of `proof verify` and `loki_remote_verify_receipt`; only the jwks test lacked TARGET_DIR. Direct `proof-verify.py` runs pass the repo as an argument. Other `${TARGET_DIR:-.}` readers in autonomy/loki (HANDOFF.md, failover.json, .loki dir) are not swept yet.
334
+ - Siblings: swept every tests/*.sh spawn of `proof verify` and `loki_remote_verify_receipt`; only the jwks test lacked TARGET_DIR. Direct `proof-verify.py` runs pass the repo as an argument. Sibling sweep (11.2.0 lane 10): of every `${TARGET_DIR:-.}` reader in autonomy/, five spawn sites were unsafe: the Bun `loki proof verify` (loki-ts/src/commands/proof.ts, the default route for a plain `verify <id>`), the bash `loki proof verify` (human and JSON, reached by flags or LOKI_LEGACY_BASH), `loki_remote_verify_receipt` and `_deploy_receipt_verdict` all handed the verifier the caller's cwd (it digests the tree when cwd is a repo). Safe, each reads or writes fixed paths and walks nothing: cmd_deploy detectors (test -f on named files), cmd_failover (one failover.json), `loki handoff --md` (writes one HANDOFF.md), crash.sh (writes .loki/crash), spec-interrogation.sh (five named manifests). app-runner.sh and completion-council.sh run only inside a `loki start` run, where run.sh sets TARGET_DIR to the project.
335
335
  - Mechanism of the fix: every verifier spawn in the jwks test sets TARGET_DIR to its fixture; tests/test-verifier-spawn-sets-target-dir.sh fails any test that spawns the verifier without TARGET_DIR or a cd into an owned directory.
336
+ - Shared mechanism for the sweep: `loki_verify_root` in autonomy/loki and its port `resolveVerifyRoot` in loki-ts/src/commands/proof.ts are the one resolver (TARGET_DIR, parent of an absolute LOKI_DIR, cwd or repo top-level holding .loki, else a plain refusal and exit 2 NOT CHECKED; HOME and / are never roots, a trailing slash is normalized first). The first sweep fixed only the bash copy; the HIGH review found the Bun route still defaulting to ".".
337
+ - Sweep fixture: tests/test-verifier-root-not-ambient-cwd.sh (runs `proof verify` through bin/loki on both the default and legacy routes; source guards cover autonomy/loki and loki-ts/src) and loki-ts/tests/commands/proof_verify_root.test.ts (the TS resolver, red before the port). The bash source guard goes red when a spawn passes `${TARGET_DIR:-.}` again.
336
338
  - Fixture: tests/test-verifier-spawn-sets-target-dir.sh, mutated against the pre-fix jwks test (7 hits). tests/test-proof-verify-jwks.sh run with cwd = the main checkout.
339
+
340
+ ## FC-30 An install artifact outlives the install that made it
341
+ - User saw: after upgrading to 11.1.0, ~/.claude/skills/loki-mode was a symlink to ~/.bun/install/global/node_modules/loki-mode, which no longer existed. `loki doctor` FAILed and told the user to run `loki setup-skill`, an extra command the product should never ask for.
342
+ - Mechanism (measured): `cmd_setup_skill` creates the provider skill links once and only on request. Nothing re-checked them on install, upgrade or first run, so a package-manager move or reinstall left a dangling link (or one to the previous install). Raw cause is a one-shot writer with no owner for the link's lifetime.
343
+ - Law: L0 (the harness repairs what it can verify is its own instead of delegating to the user), L2 (the doctor verdict is about the host as it is, and the fix must make that true without a command).
344
+ - Siblings: swept every place Loki writes an install-time path. Provider skill links ~/.claude, ~/.codex, ~/.cline, ~/.aider skills/loki-mode (all four covered by the shared mechanism); `cmd_remote` ln -sf of the Claude skill link in autonomy/loki (only creates when SKILL.md is absent, covered by the same repair on the next run); shell completions via `completion --postinstall` and the shim (already self-healing, stamped by VERSION in ~/.loki/completions-version); no PATH shims or bin links are written by Loki (the package manager owns bin/loki). Real directories and foreign links are deliberately left to doctor.
345
+ - Mechanism of the fix: autonomy/lib/skill-link-heal.sh `loki_skill_link_heal` repoints a symlink only when its target is a Loki install path (node_modules/loki-mode or a Homebrew loki-mode dir) that is missing or is a different install than the running one, and only when the running root is itself an install path. Atomic temp link plus rename, one stderr line, silent no-op when correct, LOKI_NO_SKILL_LINK_HEAL=1 opts out. Called from bin/loki-postinstall.js (install and upgrade) and the bin/loki shim (first run).
346
+ - Fixture: tests/test-skill-link-heal.sh (dangling, other install, real dir, foreign link, correct link, opt-out, dev checkout), red before autonomy/lib/skill-link-heal.sh existed.
347
+
348
+ ## FC-31 A slice gate skips the suites that guard its own files, and main goes red in Tier B
349
+ - User saw: main 27447cf46 failed Tier B on tests/test-doctor-blocker-parity.sh (run 37697291383). The slice R1-20 (8ff988516) changed loki-ts/src/commands/doctor.ts and gated only on tsc and bun test. The local-ci fast tier had timed out (rc=124) on a loaded host, so no shell suite ran.
350
+ - Law: L2 (a verdict is about the tree it names; a gate that did not run the guarding suites says nothing about them) and the evidence rule (a timeout is not a pass).
351
+ - Cause (measured): scripts/select-tests.sh rule R4 handled loki-ts/src files with `continue` after selecting bun tests, so rule R3 (shell suites that reference the file) never ran for them. test-doctor-blocker-parity.sh runs `bin/loki doctor`, never the string doctor.ts, so no path or basename match could have found it anyway.
352
+ - Mechanism: one shared mapping, scripts/select-tests.sh. R4 now also selects shell suites that name the full path, and for loki-ts/src/commands/X.ts those that run `loki X`. One gate command, `bash scripts/local-ci.sh --impacted <base>` (scripts/impacted-gate.sh), runs only the selected shell and moat suites, each under `timeout -k`, prints per-suite rc, and exits non-zero on any failure or timeout.
353
+ - Siblings swept (file-to-suite mapping by source tree): loki-ts/src was the only tree whose rule skipped the shell grep (fixed). mcp/, providers/, autonomy/ (other than run.sh and loki), scripts/ get R3 path and basename grep. dashboard/ gets R3 plus the R5 pytest area. web-app/ gets R3 plus R5. Still thin: a loki-ts/src file that is not a command module (runner/, util/) maps to shell suites by full path only, not by the subcommand a suite drives; dashboard and web-app shell suites that drive an API route by URL are not mapped to the handler file; mcp/server.py tools are not mapped to suites that call the tool by name. Follow-up: a per-tree needle table once a second miss appears.
354
+ - Fixture: tests/test-select-tests.sh, "FC-31: doctor.ts selects doctor parity shell suite" (red before the fix: 39 passed, 1 failed).
355
+
356
+ ## FC-32 Hand-maintained file lists drift from the import graph
357
+ - User saw: Tier B run 37706008817 on main 6a01413bd failed two suites: "no hardcoded home-directory paths in tests" (and its cascade, "Structural checks catch planted defects (D44-C)") and "Loki control plane wiring (CP-04)". Both slices had passed tsc and the full bun test.
358
+ - Law: L2 (a verdict is about the tree it names; a gate that did not run the guarding suites says nothing about them).
359
+ - Cause (measured): faf1d0ace added tests/test-parity-goal-score.sh with hardcoded /opt/homebrew/bin and /usr/local/bin grep paths (flagged by tests/test-no-hardcoded-paths.sh). The R1 router slices (0f3e3d55a, c15b8157b, 1b9e479c1) added loki-ts/src/engine10/cost.ts, runner/router/flag.ts and runner/router/route_block.ts to the control-plane value-import closure, but Dockerfile.control-plane lists its COPY files by hand. RC-SLICE-SHELL-GATE: the FC-31 impacted gate selected neither shell structural guard, including for a touched tests/*.sh.
360
+ - Mechanism (this slice, minimal): the parity test discovers greps with `type -ap grep ggrep` (no absolute paths); the three files are added to Dockerfile.control-plane. 11.2.2 follow-up (root fix): replace the hand-maintained COPY list with a directory COPY or a generated list, make CP-04 build the image and import the bundle, and extend scripts/select-tests.sh so a touched tests/*.sh selects the hardcoded-path and structural-checks suites and a loki-ts/src file reachable from packages/control-plane selects CP-04.
361
+ - Siblings swept: tests/test-no-hardcoded-paths.sh scans every tests/ file (3 passed, 0 failed after the fix); the CP-04 closure guard covers every loki-ts/src value import (25 passed, 0 failed); no other hits.
362
+ - Fixture: the two existing guards, red on 6a01413bd and green here.
package/docs/v10/GUIDE.md CHANGED
@@ -233,6 +233,91 @@ NOT PROVEN line adds the literal entry `kill blocking not enforced`
233
233
  (`loki-ts/src/engine10/stages/seal.ts`). opencode is not one of engine10's
234
234
  provider names (`claude`, `codex`, `cline`, `aider`); it is not supported.
235
235
 
236
+ ## Model routing
237
+
238
+ Model routing picks which Claude model does each stage of a run. It is OFF
239
+ by default in 11.2.0. Set `LOKI_ROUTER=1` to opt in. `LOKI_ROUTER=0` is the
240
+ explicit opt-out and keeps the exact pre-router model behavior, pins
241
+ included (guarded by the opt-out golden test in
242
+ `loki-ts/tests/engine10/router_optout_golden.test.ts`). Explicit
243
+ `LOKI_MODEL_OVERRIDE` and `LOKI_CLAUDE_MODEL_DEVELOPMENT` still win and
244
+ bypass the router. The other knobs are `LOKI_ROUTER_EXECUTOR` (post-flip
245
+ default sonnet; haiku only by earned evidence), `LOKI_ROUTER_ESCALATE` (default sonnet) and `LOKI_ROUTER_ADVISOR`
246
+ (default opus; `off` disables the advisor). The router engine itself lands in
247
+ 11.2.0; the pieces below marked "shipped in 11.1.0" are already in the code.
248
+
249
+ ### Executor and advisor
250
+
251
+ When the router is on, the post-flip default executor is Sonnet 5.5;
252
+ Haiku 5.5 per work unit when Opus assigns it. Opus 5.5 is the architect,
253
+ router and advisor: it plans, assigns each work unit and is consulted on
254
+ hard calls. Sonnet is the senior engineer and the default for
255
+ implementation and fixes. Haiku does tests, docs and small low-risk changes
256
+ only when Opus assigns them in the plan, and is never the blanket default.
257
+ The harness never routes by task type or path regex; it only executes,
258
+ verifies and escalates. Per-repo history is an input to Opus, not a harness
259
+ rule. The per-unit route (`units: [{id, kind, executor, reason}]` plus a run
260
+ default of sonnet) lands in 11.2.0 or 11.2.1 depending on what is merged. A
261
+ missing or invalid unit route goes to Sonnet and is reported as NOT PROVEN.
262
+ The receipt lists the executor, assigner and reason per unit.
263
+ The advisor needs Claude Code 2.1.293 or later,
264
+ which the Agent SDK 0.3.293 bundles (shipped in 11.1.0). If the advisor is
265
+ unavailable, the executor is Sonnet 5.5 and the run is never failed for it.
266
+ The advisor is unavailable when the provider is not
267
+ claude, when `CLAUDE_CODE_USE_BEDROCK`, `CLAUDE_CODE_USE_VERTEX` or
268
+ `CLAUDE_CODE_USE_FOUNDRY` is set, when `ANTHROPIC_BASE_URL` points at a
269
+ non-Anthropic host, when Claude Code is older than 2.1.293, when
270
+ `LOKI_ROUTER_ADVISOR=off`, or after a session reports an advisor tool error
271
+ (remembered for the rest of the run). The probe is
272
+ `loki-ts/src/runner/router/advisor_probe.ts` (shipped in 11.1.0). The
273
+ default-on flip happens after the CTO gate; 11.1.0 and 11.2.0 still ship the
274
+ router off.
275
+
276
+ ### Escalation
277
+
278
+ Escalation is gated on evidence and only moves up: haiku to sonnet to opus,
279
+ and sonnet to opus. A haiku unit that fails a code-owned check is redone on
280
+ Sonnet, then escalates to Opus. A Wall assigned to Haiku gets an Opus review
281
+ before implement. A repeated failure on Sonnet gets a fix round on Opus
282
+ before the run can stall. A failure owned by the harness, the environment or
283
+ the provider causes no transition and is reported as NOT PROVEN.
284
+
285
+ ### Per-repo-shape outcome memory
286
+
287
+ Loki remembers how each kind of repo went. The shape key is the Project
288
+ Model `workspaceKind` plus the sorted runners, for example
289
+ `multi-root:pytest+vitest`; a package with no runner contributes `none`.
290
+ Outcomes are stored in the per-repo cache. The haiku floor moves a shape to
291
+ Sonnet after 2 code-owned losses in the last 3 Haiku runs. Only code-owned
292
+ FAILs and escalations count; harness, environment and provider errors and
293
+ NOT PROVEN outcomes never do. A missing or corrupt history file is a cold
294
+ read, never a crash (shipped in 11.1.0, `loki-ts/src/runner/router/history.ts`).
295
+ The shipped `loki-ts/data/router-shape-defaults.json` is empty in 11.1.0.
296
+ After the flip a shape is listed as `haiku` once it has earned it with a B9
297
+ row, and an absent listing means Sonnet. Shape history and the defaults file
298
+ are inputs to Opus, not harness routing rules.
299
+
300
+ ### Pricing
301
+
302
+ Haiku 5.5 is priced on its exact model id: $0.10 in and $0.50 out per MTok
303
+ for prompts up to 100K tokens, and $0.50 in and $2.50 out over 100K, with
304
+ the cache rates in `loki-ts/data/model-pricing.json`. The `haiku` family
305
+ alias row in that file is still the Haiku 4.5 price of $1 and $5, because
306
+ the exact-id key is matched first (`loki-ts/src/runner/budget.ts`).
307
+
308
+ ### What you see
309
+
310
+ In 11.2.0 the receipt carries a `route` block (initial decision, each
311
+ escalation with its trigger, advisor availability and the share of requests
312
+ above 100K), and `loki doctor` prints an advisor line. The executor and any
313
+ fallback reason are also printed on the start line.
314
+
315
+ ### Comparing models
316
+
317
+ `scripts/b9-scoreboard.sh` is the comparison harness for routed against raw
318
+ runs on the B9 corpus, and it can emit shape defaults from those runs
319
+ (lands in 11.2.0).
320
+
236
321
  ## After the flip
237
322
 
238
323
  D48 made v10 the default for `loki "<task>"`, `loki owner/repo#N` and
@@ -1,11 +1,11 @@
1
- # ROUTER-1: Haiku executor, Opus advisor, evidence-gated Sonnet escalation
1
+ # ROUTER-1: Sonnet executor, Opus advisor, evidence-earned Haiku, evidence-gated escalation
2
2
 
3
- Status: plan (Architect, 2026-10-07; default-on adjustment from steering the same day). Ship target 11.1.0, where Haiku 5.5 executor + Opus advisor is the DEFAULT. While slices are built they merge behind `LOKI_ROUTER` (unset = off). R1-19, the final slice, makes it default ON, gated on steering's FireLater#17 gate plus the B9 scoreboard on the release candidate. After the flip `LOKI_ROUTER=0` is the opt-out and restores the exact pre-router model behavior, pins included (R1-21 test).
3
+ Status: plan (Architect, 2026-10-07; default-on adjustment from steering the same day). CTO decision 2026-10-07 21:40Z, refined by the founder at 21:45Z (D89 amendment), governs R1-19 only: when the default-on flip lands after the CTO gate, the default executor is Sonnet 5.5; Haiku 5.5 per work unit when Opus assigns it. Opus 5.5 is the architect, router and advisor: it plans, assigns each work unit and is consulted on hard calls. Sonnet is the senior engineer and the default for implementation and fixes. Haiku does tests, docs and small low-risk changes only when Opus assigns them in the plan. Haiku is never the blanket default. The per-unit schema (section 4.1) lands in 11.2.0 or 11.2.1 depending on what is merged; the earlier whole-run schema below is superseded. 11.1.0 and 11.2.0 still ship the router OFF. Earlier text below that calls Haiku the default executor describes the superseded plan. While slices are built they merge behind `LOKI_ROUTER` (unset = off). R1-19, the final slice, makes it default ON, gated on steering's FireLater#17 gate plus the B9 scoreboard on the release candidate. After the flip `LOKI_ROUTER=0` is the opt-out and restores the exact pre-router model behavior, pins included (R1-21 test).
4
4
  Source: founder directive 2026-10-07 ("Opus is the advisor, Haiku does all engineering work; Sonnet is optional, routed by Opus when Haiku is not performing well enough; model decides").
5
5
  Scope: the engine10 path (`loki start <issue|text>`, `loki-ts/src/engine10`). The legacy RARV route (`autonomy/run.sh`, `runner/autonomous.ts`, `runner/capability_router.ts`) keeps its tiers; only its pricing table mirror changes (R1-02).
6
6
 
7
7
  ## 1. Goal
8
- Every engine10 session runs on Claude Haiku 5.5 as the executor with Opus attached through the native Claude Code advisor tool, so the model itself decides when it needs stronger judgment. Opus picks the executor at plan time with a recorded reason. Sonnet 5.5 takes over for the rest of a run only on execution evidence. The outcome (solve rate, time, cost) is never below raw `claude -p`, proven on the B9 scoreboard.
8
+ After the R1-19 flip, every engine10 session runs on Claude Sonnet 5.5 as the default executor with Opus 5.5 attached through the native Claude Code advisor tool, so the model itself decides when it needs stronger judgment. Opus picks the executor at plan time with a recorded reason. Opus may route a task to Haiku 5.5 only when the R1-16 history and a B9 row show Haiku at outcome parity on that repo shape; a shape with no evidence stays on Sonnet. Escalation stays evidence-gated (haiku -> sonnet -> opus, and sonnet -> opus). The outcome (solve rate, time, cost) is never below raw `claude -p`, proven on the B9 scoreboard.
9
9
 
10
10
  ## 2. What already exists (read before building; cite, do not rebuild)
11
11
  - Session model plumbing: `engine10/session.ts:25` resolveModel, `:31-60` childEnv (tier pins via `LOKI_CLAUDE_MODEL_<TIER>`, `LOKI_E10_MODEL_DEFAULT` omits `--model`).
@@ -32,15 +32,17 @@ Every engine10 session runs on Claude Haiku 5.5 as the executor with Opus attach
32
32
  ## 4. Architecture
33
33
  ### 4.1 Routing decision schema (written into plan-scope.json by the plan session; parsed by `runner/router/decision.ts`)
34
34
  ```
35
- route: { executor: "haiku" | "sonnet", reason: string (<= 200 chars), risk: "low" | "medium" | "high",
36
- source: "advisor" | "opus-plan" | "default" | "history" }
35
+ route: { default: "sonnet",
36
+ units: [ { id: string, kind: string, executor: "haiku" | "sonnet", reason: string (<= 200 chars) } ],
37
+ source: "opus-plan" | "advisor" | "default" }
37
38
  ```
38
- Invalid or missing JSON yields `{executor: "haiku", source: "default", reason: "no route returned"}` (Sonnet when the advisor is unavailable, section 4.4) plus a NOT PROVEN line (L2: fail open on work). Aliases resolve through `providers/model_catalog.json` (haiku -> claude-haiku-5-5, sonnet -> claude-sonnet-5-5, opus -> claude-opus-5-5).
39
+ Per-unit schema (founder refinement 2026-10-07 21:45Z; lands in 11.2.0 or 11.2.1 depending on what is merged). Opus writes the units into plan-scope.json at plan time, with a run default of sonnet. The harness never routes by task type or path regex (L0): it only executes, verifies and escalates. R1-16 history is an input to the Opus planning prompt, not a harness rule. The receipt lists the executor, the assigner and the reason per unit.
40
+ A missing or invalid unit route goes to sonnet (`{executor: "sonnet", source: "default", reason: "no route returned"}`; the superseded plan returned haiku here) plus a NOT PROVEN line (L2: fail open on work). Aliases resolve through `providers/model_catalog.json` (haiku -> claude-haiku-5-5, sonnet -> claude-sonnet-5-5, opus -> claude-opus-5-5).
39
41
 
40
42
  ### 4.2 Where the decision is made
41
- 1. Run start: advisor availability is probed (R1-06). Start line: `executor haiku-5.5, advisor opus` (or `executor sonnet-5.5, advisor unavailable: <reason>`).
43
+ 1. Run start: advisor availability is probed (R1-06). Start line: `executor sonnet-5.5, advisor opus` (or `executor haiku-5.5, advisor opus` for a shape with earned Haiku evidence; or `executor sonnet-5.5, advisor unavailable: <reason>`).
42
44
  2. Plan stage runs (normal tasks): the plan session runs on the executor with the Opus advisor attached; its brief asks it to consult the advisor and write `route` into plan-scope.json. With no advisor, the plan session itself is pinned to Opus (the single cheap Opus call) and writes the same field; a `haiku` route is then raised to Sonnet (section 4.4).
43
- 3. Plan skipped (small task, `plan.ts:55`): no extra call. Executor = history floor or shape default if one applies, else Haiku with the advisor (Sonnet with no advisor, section 4.4), `source: default`.
45
+ 3. Plan skipped (small task, `plan.ts:55`): no extra call. Executor = history floor or shape default if one applies, else Sonnet with the advisor (post-flip default; Haiku only for a shape listed `haiku` in section 4.6), `source: default`.
44
46
  4. Recorded: a `route` event (implement), run.started `route_initial`, receipt `route` block (initial decision, every escalation with trigger and evidence pointer, advisor availability, share of requests above 100K).
45
47
 
46
48
  ### 4.3 Escalation state machine (pure function in `runner/router/decision.ts`)
@@ -51,7 +53,7 @@ EXEC(haiku) --c: SPEC_CONFLICT exit | stall | implement limit kill------> EXEC(s
51
53
  EXEC(sonnet) --a or stall------------------------------------------------> fix round on opus (existing L1 path) -> STALLED
52
54
  any state --harness/env/provider-owned ERROR-----------------------------> no transition (NOT PROVEN path)
53
55
  ```
54
- At most one Haiku -> Sonnet transition per run. A SPEC_CONFLICT confirmed by Sonnet stays BLOCKED. Resume across the swap: the fix session resumes the prior session id with the new model when the SDK honors `model` on `resume` (verified in R1-12); otherwise it starts fresh with the full brief plus the prior diagnosis and records `fix_resume_why`.
56
+ Per-unit rule (founder refinement 2026-10-07 21:45Z): a haiku unit that fails a code-owned check is redone on sonnet, then escalates to opus; a Wall assigned to haiku gets an Opus review before implement. The state machine above is the whole-run form and is read per unit. At most one Haiku -> Sonnet transition per run (whole-run form). A SPEC_CONFLICT confirmed by Sonnet stays BLOCKED. Resume across the swap: the fix session resumes the prior session id with the new model when the SDK honors `model` on `resume` (verified in R1-12); otherwise it starts fresh with the full brief plus the prior diagnosis and records `fix_resume_why`.
55
57
 
56
58
  ### 4.4 Advisor wiring and fallback detection
57
59
  - Wiring: SDK route passes `settings: { advisorModel }` (in SDK 0.3.288 `advisorModel` is a field of `interface Settings`, `sdk.d.ts:6674,8987`, not a top-level Option; R1-07 confirms the 0.3.293 shape). CLI route merges `advisorModel` into the existing `--settings` JSON or uses `--advisor` when `claudeFlagSupported` says so.
@@ -62,13 +64,13 @@ At most one Haiku -> Sonnet transition per run. A SPEC_CONFLICT confirmed by Son
62
64
  Lean briefs and the cache-stable prefix already exist (`LOKI_E10_PREFIX=lean`, `providers.ts:524`). R1-07 adds `autoCompactWindow: 100000` (the SDK minimum, `sdk.d.ts` Settings) for routed sessions. R1-08 records per request `input+cache_read+cache_creation` and writes `requests_total`, `requests_over_100k` and advisor tokens into each cost event; the receipt shows the share.
63
65
 
64
66
  ### 4.6 Per-shape defaults (a shape that fails parity routes to Sonnet instead of holding the release)
65
- - Where it lives: `loki-ts/data/router-shape-defaults.json`, shipped in the package next to model-pricing.json. Schema `{"$schema_version":1, "_source":"<B9 run ids + RC version + date>", "shapes":{"<shape_key>":{"executor":"sonnet"|"prior-default","evidence":"<METRICS.md row ref>"}}}`. Legal values are `sonnet` and `prior-default` (the `LOKI_ROUTER=0` model for that stage). A shape is listed only to move it up; absent shape = Haiku default. Created empty by R1-16.
67
+ - Where it lives: `loki-ts/data/router-shape-defaults.json`, shipped in the package next to model-pricing.json. Schema `{"$schema_version":1, "_source":"<B9 run ids + RC version + date>", "shapes":{"<shape_key>":{"executor":"haiku"|"prior-default","evidence":"<METRICS.md row ref>"}}}`. Semantics inverted by the 2026-10-07 21:40Z CTO decision and refined by the founder at 21:45Z (D89 amendment): the default executor is Sonnet 5.5 and Haiku 5.5 is used per work unit when Opus assigns it. A shape is listed as `haiku` once it has earned it with a B9 row showing Haiku at outcome parity on that shape, and an absent listing means Sonnet 5.5. The shipped file and the R1-16 history are inputs to the Opus planning prompt, not harness routing rules; the harness never routes by task type or path regex (L0). `prior-default` (the `LOKI_ROUTER=0` model for that stage) stays legal for a shape where Sonnet also loses. The shipped file is empty (R1-16), so every shape starts on Sonnet.
66
68
  - Shape key: Project Model `workspaceKind` plus the sorted, lowercased, de-duplicated package `runner` labels from `.loki/project.json` (both model-derived, L4; the schema has no language field, `project_model/schema.ts:20-30`, decided 2026-10-07 by the CoS after R1-16 found the gap), for example `multi-root:pytest+vitest`. A package with no runner contributes `none`. The same function (`shapeKey()` in history.ts) computes it at runtime and in the receipt (R1-15 writes `route.shape_key`), so B9 rows and runtime agree by construction. No file-pattern regex (L0).
67
69
  - Seeding: `scripts/b9-scoreboard.sh --emit-shape-defaults <out.json>` (R1-17) reads each run's receipt `route.shape_key`, compares arm 2 (router) with arm 1 (raw) per shape on correctness, and lists every shape where router solve count < raw. A shape where Sonnet also loses on the confirming rerun is emitted as `prior-default`. R1-19 commits that output as the shipped file, citing the METRICS.md rows. Later B9 runs reseed it in a normal slice; removing a shape, or lowering it from `prior-default` to `sonnet`, needs a B9 row showing the lower executor at parity on it.
68
- - Read path: `decision.ts` resolves the executor as the strongest of: Opus route, shipped shape default, per-repo history floor (R1-16, local runs; Haiku lost 2 of the last 3 by code-owned FAIL or escalation -> Sonnet, cleared only by a B9 row showing Haiku at parity on that shape), the no-advisor floor (section 4.4), then Haiku. `prior-default` resolves to the exact `LOKI_ROUTER=0` model for that stage (R1-21 fixture semantics). Explicit `LOKI_MODEL_OVERRIDE`, `LOKI_CLAUDE_MODEL_DEVELOPMENT` or `LOKI_ROUTER_EXECUTOR` still win. Evidence rungs never lower a model: a shape default can only move the executor up. The shape key is a lookup into executed outcome evidence and may only move the executor up (ENGINE-LAWS H4); this is the documented exception to the L0 review rule. The receipt records `source: shape-default` with the evidence ref. Missing or corrupt file = empty map plus a NOT PROVEN line, never a crash.
70
+ - Read path: `decision.ts` resolves the executor as the strongest of: Opus route, shipped shape default, per-repo history floor (R1-16, local runs; under the inverted semantics Haiku is reachable only when the shape is listed `haiku` and the history does not show Haiku losing 2 of the last 3 by code-owned FAIL or escalation -> Sonnet, cleared only by a B9 row showing Haiku at parity on that shape), the no-advisor floor (section 4.4), then Sonnet unless the shape is listed `haiku`. `prior-default` resolves to the exact `LOKI_ROUTER=0` model for that stage (R1-21 fixture semantics). Explicit `LOKI_MODEL_OVERRIDE`, `LOKI_CLAUDE_MODEL_DEVELOPMENT` or `LOKI_ROUTER_EXECUTOR` still win. Evidence rungs never lower a model: a shape default can only move the executor up. The shape key is a lookup into executed outcome evidence and may only move the executor up (ENGINE-LAWS H4); this is the documented exception to the L0 review rule. The receipt records `source: shape-default` with the evidence ref. Missing or corrupt file = empty map plus a NOT PROVEN line, never a crash.
69
71
 
70
72
  ## 5. Knobs
71
- `LOKI_ROUTER` (unset = off while building; unset = on after R1-19; `0` = opt-out to pre-router behavior), `LOKI_ROUTER_EXECUTOR` (default haiku), `LOKI_ROUTER_ESCALATE` (default sonnet), `LOKI_ROUTER_ADVISOR` (default opus; `off` disables). `LOKI_MODEL_OVERRIDE` and explicit `LOKI_CLAUDE_MODEL_DEVELOPMENT` still win and bypass the router (recorded `source: override`).
73
+ `LOKI_ROUTER` (unset = off while building; unset = on after R1-19; `0` = opt-out to pre-router behavior), `LOKI_ROUTER_EXECUTOR` (post-flip default sonnet; haiku only by earned evidence), `LOKI_ROUTER_ESCALATE` (default sonnet), `LOKI_ROUTER_ADVISOR` (default opus; `off` disables). `LOKI_MODEL_OVERRIDE` and explicit `LOKI_CLAUDE_MODEL_DEVELOPMENT` still win and bypass the router (recorded `source: override`).
72
74
 
73
75
  ## 6. Slices
74
76
  Tier sets the review (D12): LOW/MEDIUM by Tech Leads, HIGH by opus reviewers, unanimous. Every TS slice rebuilds dist in the main checkout before its train (memory: symlinked node_modules), runs `bash scripts/local-ci.sh`, and keeps `LOKI_ROUTER` unset behavior byte-identical (existing suites green). File sets do not overlap; `runner/router/flag.ts` (created by R1-05) and `loki-ts/data/router-shape-defaults.json` (created empty by R1-16) are edited only by R1-19, after every other slice has merged, so no two slices touch them concurrently.
@@ -93,7 +95,7 @@ Tier sets the review (D12): LOW/MEDIUM by Tech Leads, HIGH by opus reviewers, un
93
95
  | R1-16 | Per-shape outcome history (H4) | loki-ts/src/runner/router/history.ts, loki-ts/src/e10ext/repomemory.ts, loki-ts/tests/runner/router_history.test.ts | MEDIUM | haiku | append/read in the per-repo cache dir; shape key from Project Model workspaceKind + sorted runner labels (model-derived, L4), never from file regex; floor: haiku lost 2 of last 3 on the shape (code-owned FAIL or escalation; harness, env and provider ERRORs do not count) -> sonnet, cleared only by a B9 row showing Haiku at parity on that shape; fixture: 3 harness-owned FAILs on haiku do NOT trigger the floor. Also creates the empty shipped `loki-ts/data/router-shape-defaults.json` and its reader `shapeDefault(key)` (section 4.6), which accepts `sonnet` and `prior-default` and rejects any other value (cold read + NOT PROVEN) | corrupt file is a cold read, never a crash | none |
94
96
  | R1-17 | B9 scoreboard harness | scripts/b9-scoreboard.sh, tests/test-b9-scoreboard.sh, tests/run-all-tests.sh (registration + shard row) | MEDIUM | sonnet | dry-run with a stub `claude` produces one METRICS row per arm with solve, wall seconds, usd, shape_key; `--emit-shape-defaults` on a fixture where the router loses one shape lists exactly that shape as `sonnet`; a fixture where the Sonnet confirming rerun also loses emits it as `prior-default` | shellcheck, `timeout -k`, runner registration guard (memory: new test file structural guards) | none |
95
97
  | R1-18 | Docs: GUIDE model-routing section, CHANGELOG | docs/v10/GUIDE.md, CHANGELOG.md | LOW | haiku | doc lint; GUIDE names every knob in section 5 and the fallback | Release Manager reads it against the receipt | R1-15 |
96
- | R1-19 | Final flip: LOKI_ROUTER default ON for 11.1.0, ship seeded shape defaults | loki-ts/src/runner/router/flag.ts, loki-ts/data/router-shape-defaults.json, loki-ts/tests/runner/router_flag_default.test.ts | HIGH | sonnet | red: unset env resolves off; green: unset resolves on, `0`/`off`/`false` resolve off; R1-21 opt-out golden still green with `LOKI_ROUTER=0` | GATE (all on the release candidate SHA): steering's FireLater#17 gate green (VERIFIED, ready PR, time within raw + 30s, cost <= raw x 1.2); B9 correctness parity with raw `claude -p` on every shape not listed in the seeded shape-defaults file; arm 4 correctness parity, or the Sonnet no-advisor fallback in place (R1-07, R1-09); every shape listed as `prior-default` runs the exact LOKI_ROUTER=0 models (R1-21 golden per listed shape); the seeded file committed with its METRICS.md refs | all |
98
+ | R1-19 | Final flip after the CTO gate: LOKI_ROUTER default ON with Sonnet 5.5 + Opus 5.5 advisor as the default executor (11.1.0 and 11.2.0 still ship OFF); Haiku 5.5 per work unit only when Opus assigns it in the plan, per-unit schema `units: [{id, kind, executor, reason}]` (11.2.0 or 11.2.1); ship seeded shape defaults (`haiku` only for earned shapes) | loki-ts/src/runner/router/flag.ts, loki-ts/data/router-shape-defaults.json, loki-ts/tests/runner/router_flag_default.test.ts | HIGH | sonnet | red: unset env resolves off; green: unset resolves on, `0`/`off`/`false` resolve off; R1-21 opt-out golden still green with `LOKI_ROUTER=0` | GATE (all on the release candidate SHA): steering's FireLater#17 gate green (VERIFIED, ready PR, time within raw + 30s, cost <= raw x 1.2); B9 correctness parity with raw `claude -p` on the Sonnet default and on every shape listed `haiku` (a shape with no evidence stays on Sonnet); arm 4 correctness parity, or the Sonnet no-advisor fallback in place (R1-07, R1-09); every shape listed as `prior-default` runs the exact LOKI_ROUTER=0 models (R1-21 golden per listed shape); the seeded file committed with its METRICS.md refs | all |
97
99
  | R1-21 | Opt-out golden: `LOKI_ROUTER=0` equals pre-router behavior exactly | loki-ts/tests/engine10/router_optout_golden.test.ts, loki-ts/tests/engine10/fixtures/pre_router_models.json | HIGH | sonnet | fixture captured on 11.0.3 code BEFORE any router slice merges: for an env matrix (default; LOKI_E10_CASCADE=1; LOKI_E10_WALL_TIER=haiku; LOKI_ALLOW_HAIKU=true; LOKI_MODEL_OVERRIDE=sonnet; LOKI_CLAUDE_MODEL_DEVELOPMENT=opus; LOKI_E10_PLAN=always) it records, per stage (plan, wall, already_done, implement, fix round 1 and escalated round 2), the model passed to sessions.run, the child-env LOKI_CLAUDE_MODEL_* values, the SDK query options model/fallbackModel/settings, the downgrade list, the start line and the run.started keys. The test replays the matrix through fake sessions with `LOKI_ROUTER=0` (and unset, until R1-19) and requires deep equality. Mutation: changing wallModel's default or adding `settings.advisorModel` under `0` turns it red | runs in the fast tier; must be green on every slice's train | none (lands first in W1) |
98
100
  | R1-20 | `loki doctor` shows advisor availability and bundled CC version | loki-ts/src/commands/doctor.ts, loki-ts/tests/commands/doctor_router.test.ts | LOW | haiku | doctor prints `advisor: available` or the reason from R1-06 | doctor suites | R1-06 |
99
101