loki-mode 7.31.0 → 7.32.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 +2 -2
- package/VERSION +1 -1
- package/autonomy/loki +367 -48
- package/autonomy/mcp-launch.sh +27 -16
- package/autonomy/run.sh +20 -3
- package/bin/loki +52 -0
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +128 -28
- package/docs/INSTALLATION.md +1 -1
- package/loki-ts/dist/loki.js +248 -247
- package/mcp/__init__.py +1 -1
- package/mcp/server.py +21 -0
- package/package.json +1 -1
- package/skills/model-selection.md +7 -3
package/autonomy/mcp-launch.sh
CHANGED
|
@@ -113,15 +113,22 @@ _ml_python() {
|
|
|
113
113
|
# `--check-sdk` probe, which runs the exact loader the server uses and exits 0
|
|
114
114
|
# only when FastMCP loaded.
|
|
115
115
|
#
|
|
116
|
-
# Critical: we
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
#
|
|
116
|
+
# Critical: we probe with the SAME FILE-EXEC form the launch uses
|
|
117
|
+
# (`"$root/mcp/server.py"`, NOT `-m mcp.server`), with PYTHONPATH set to the
|
|
118
|
+
# install root and WITHOUT cd-ing into it, so the probe exercises byte-identical
|
|
119
|
+
# module resolution to the real launch (which preserves the user's cwd). This
|
|
120
|
+
# matters because `-m mcp.server` puts the user's cwd at sys.path[0] AHEAD of
|
|
121
|
+
# PYTHONPATH=$root, so a cwd that happens to contain a regular `mcp/` python
|
|
122
|
+
# package would shadow Loki's server during the probe (false SDK-missing) while
|
|
123
|
+
# the file-exec launch -- immune to cwd shadowing -- would succeed. Probing by
|
|
124
|
+
# file path keeps probe and launch resolving the IDENTICAL module. The redirect
|
|
125
|
+
# of stdin from /dev/null is insurance: if the pip SDK's own `mcp.server` were
|
|
126
|
+
# ever reached, its stub starts a stdio receive loop; the EOF makes it exit
|
|
127
|
+
# instead of hanging.
|
|
121
128
|
_ml_sdk_importable() {
|
|
122
129
|
local py="$1" root="$2"
|
|
123
130
|
PYTHONPATH="$root${PYTHONPATH:+:$PYTHONPATH}" \
|
|
124
|
-
"$py"
|
|
131
|
+
"$py" "$root/mcp/server.py" --check-sdk </dev/null >/dev/null 2>&1
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
# _ml_print_manual <root> <venv>: print the honest manual install commands.
|
|
@@ -135,7 +142,7 @@ _ml_print_manual() {
|
|
|
135
142
|
printf 'Install the MCP server dependencies manually:\n' >&2
|
|
136
143
|
printf " python3 -m venv '%s'\n" "$venv" >&2
|
|
137
144
|
printf " '%s/bin/pip' install -r '%s/mcp/requirements.txt'\n" "$venv" "$root" >&2
|
|
138
|
-
printf " PYTHONPATH='%s' '%s/bin/python'
|
|
145
|
+
printf " PYTHONPATH='%s' '%s/bin/python' '%s/mcp/server.py'\n" "$root" "$venv" "$root" >&2
|
|
139
146
|
}
|
|
140
147
|
|
|
141
148
|
_ml_help() {
|
|
@@ -189,7 +196,8 @@ EOF
|
|
|
189
196
|
# mcp_launch_main: dispatcher invoked by cmd_mcp() (autonomy/loki) or directly.
|
|
190
197
|
mcp_launch_main() {
|
|
191
198
|
# Split argv into launcher-owned flags (consumed here) and server argv
|
|
192
|
-
# (forwarded verbatim to `python
|
|
199
|
+
# (forwarded verbatim to the file-exec launch `python "$root/mcp/server.py"`).
|
|
200
|
+
# The server's argparse only
|
|
193
201
|
# accepts --transport/--port/--check-sdk; forwarding a launcher flag like
|
|
194
202
|
# --yes would make it abort with exit 2, so launcher flags MUST be stripped.
|
|
195
203
|
# A bare `--` ends launcher parsing: everything after it is forwarded as-is
|
|
@@ -255,20 +263,23 @@ mcp_launch_main() {
|
|
|
255
263
|
fi
|
|
256
264
|
|
|
257
265
|
# 3. If the venv already has the SDK, use it directly. The server is launched
|
|
258
|
-
#
|
|
266
|
+
# by FILE PATH ($root/mcp/server.py) rather than `-m mcp.server`, with
|
|
267
|
+
# PYTHONPATH=$root (NOT by cd-ing) so the user's cwd is preserved for
|
|
259
268
|
# .loki resolution; see _ml_sdk_importable for why.
|
|
260
|
-
#
|
|
261
|
-
#
|
|
262
|
-
#
|
|
263
|
-
#
|
|
269
|
+
# Running the file directly avoids the runpy RuntimeWarning that `-m`
|
|
270
|
+
# emits (the local mcp/ package is imported during SDK-namespace setup
|
|
271
|
+
# before runpy executes mcp.server). server.py uses only absolute imports
|
|
272
|
+
# (e.g. `from mcp._sdk_loader import ...`), which resolve via PYTHONPATH=$root
|
|
273
|
+
# under file execution. File-exec also removes the old narrow cwd-shadowing
|
|
274
|
+
# residual: an explicit path can never be shadowed by a cwd `mcp/` package.
|
|
264
275
|
if [ -x "$venv_py" ] && _ml_sdk_importable "$venv_py" "$root"; then
|
|
265
|
-
exec env PYTHONPATH="$root${PYTHONPATH:+:$PYTHONPATH}" "$venv_py"
|
|
276
|
+
exec env PYTHONPATH="$root${PYTHONPATH:+:$PYTHONPATH}" "$venv_py" "$root/mcp/server.py" "$@"
|
|
266
277
|
fi
|
|
267
278
|
|
|
268
279
|
# 4. If the BASE python already has the SDK (e.g. user pip-installed it),
|
|
269
280
|
# use it -- no venv needed.
|
|
270
281
|
if _ml_sdk_importable "$base_py" "$root"; then
|
|
271
|
-
exec env PYTHONPATH="$root${PYTHONPATH:+:$PYTHONPATH}" "$base_py"
|
|
282
|
+
exec env PYTHONPATH="$root${PYTHONPATH:+:$PYTHONPATH}" "$base_py" "$root/mcp/server.py" "$@"
|
|
272
283
|
fi
|
|
273
284
|
|
|
274
285
|
# 5. SDK missing. Decide whether we may bootstrap.
|
|
@@ -374,7 +385,7 @@ mcp_launch_main() {
|
|
|
374
385
|
return 2
|
|
375
386
|
fi
|
|
376
387
|
printf "%sMCP dependencies ready. Launching server over stdio ...%s\n" "$_ML_BOLD" "$_ML_NC" >&2
|
|
377
|
-
exec env PYTHONPATH="$root${PYTHONPATH:+:$PYTHONPATH}" "$venv_py"
|
|
388
|
+
exec env PYTHONPATH="$root${PYTHONPATH:+:$PYTHONPATH}" "$venv_py" "$root/mcp/server.py" "$@"
|
|
378
389
|
}
|
|
379
390
|
|
|
380
391
|
# Executed directly (tests, manual): run the dispatcher.
|
package/autonomy/run.sh
CHANGED
|
@@ -12328,13 +12328,30 @@ except Exception as exc:
|
|
|
12328
12328
|
# helpers (which expect tier names) resolve correctly. Unknown
|
|
12329
12329
|
# model strings are passed through as-is; provider loaders fall
|
|
12330
12330
|
# back to a sane default.
|
|
12331
|
-
|
|
12331
|
+
#
|
|
12332
|
+
# Normalize case + surrounding whitespace BEFORE the match so
|
|
12333
|
+
# 'OPUS' and ' opus ' resolve identically to 'opus'. We do NOT use
|
|
12334
|
+
# loki_normalize_model_alias here: that helper is the narrow
|
|
12335
|
+
# OVERRIDE-file allowlist (haiku|sonnet|opus|fable) and would strip
|
|
12336
|
+
# the documented tier-name pins (planning|development|fast) to
|
|
12337
|
+
# empty, collapsing them onto the default tier. The session pin
|
|
12338
|
+
# legitimately accepts tier names (skills/model-selection.md), and
|
|
12339
|
+
# the estimator + dashboard mirror this exact tier route, so the
|
|
12340
|
+
# canonical session-pin rule is trim+lowercase WITHOUT the alias
|
|
12341
|
+
# allowlist. Interior whitespace is preserved (so 'fab le' stays a
|
|
12342
|
+
# junk value that falls through the '*' default arm), matching the
|
|
12343
|
+
# estimator/dashboard ports.
|
|
12344
|
+
local _session_pin="${LOKI_SESSION_MODEL:-sonnet}"
|
|
12345
|
+
_session_pin="${_session_pin#"${_session_pin%%[![:space:]]*}"}"
|
|
12346
|
+
_session_pin="${_session_pin%"${_session_pin##*[![:space:]]}"}"
|
|
12347
|
+
_session_pin="$(printf '%s' "$_session_pin" | tr '[:upper:]' '[:lower:]')"
|
|
12348
|
+
case "$_session_pin" in
|
|
12332
12349
|
opus) CURRENT_TIER="planning" ;;
|
|
12333
12350
|
sonnet) CURRENT_TIER="development" ;;
|
|
12334
12351
|
haiku) CURRENT_TIER="fast" ;;
|
|
12335
12352
|
fable) CURRENT_TIER="fable" ;;
|
|
12336
|
-
planning|development|fast) CURRENT_TIER="$
|
|
12337
|
-
*) CURRENT_TIER="$
|
|
12353
|
+
planning|development|fast) CURRENT_TIER="$_session_pin" ;;
|
|
12354
|
+
*) CURRENT_TIER="$_session_pin" ;;
|
|
12338
12355
|
esac
|
|
12339
12356
|
fi
|
|
12340
12357
|
# Architect opt-in (LOKI_FABLE_ARCHITECT=1): route ONLY the first
|
package/bin/loki
CHANGED
|
@@ -65,6 +65,21 @@ elif [ "${BUN_FROM_SOURCE:-0}" = "1" ] || [ "${BUN_FROM_SOURCE:-}" = "true" ]; t
|
|
|
65
65
|
fi
|
|
66
66
|
elif [ -f "$REPO_ROOT/loki-ts/dist/loki.js" ]; then
|
|
67
67
|
BUN_CLI="$REPO_ROOT/loki-ts/dist/loki.js"
|
|
68
|
+
# Stale-dist freshness guard. dist/loki.js is gitignored (loki-ts/.gitignore)
|
|
69
|
+
# and rebuilt by package.json's prepack and the release Docker build, so on
|
|
70
|
+
# released channels (npm/Docker/brew) src/cli.ts is absent and this guard is
|
|
71
|
+
# a no-op -- dist is always current there. On a DEV machine / worktree the
|
|
72
|
+
# gitignored dist can predate the current dispatcher: e.g. a new shim->Bun
|
|
73
|
+
# route (`report kpis`) added in src that the old dist bundle does not know,
|
|
74
|
+
# which would make the canonical form fail with "Unknown command" while a
|
|
75
|
+
# deprecated alias the old dist still knows silently works -- the exact
|
|
76
|
+
# deprecation inversion the report-kpis route exists to prevent. So when src
|
|
77
|
+
# exists AND is newer than dist (`-nt`, available on bash 3.2), prefer the src
|
|
78
|
+
# form so dev runs never execute a stale dispatcher. Released channels have no
|
|
79
|
+
# src, so they keep using dist unchanged.
|
|
80
|
+
if [ -f "$REPO_ROOT/loki-ts/src/cli.ts" ] && [ "$REPO_ROOT/loki-ts/src/cli.ts" -nt "$REPO_ROOT/loki-ts/dist/loki.js" ]; then
|
|
81
|
+
BUN_CLI="$REPO_ROOT/loki-ts/src/cli.ts"
|
|
82
|
+
fi
|
|
68
83
|
elif [ -f "$REPO_ROOT/loki-ts/src/cli.ts" ]; then
|
|
69
84
|
BUN_CLI="$REPO_ROOT/loki-ts/src/cli.ts"
|
|
70
85
|
else
|
|
@@ -131,6 +146,43 @@ if [ "${1:-}" = "trust" ]; then
|
|
|
131
146
|
done
|
|
132
147
|
fi
|
|
133
148
|
|
|
149
|
+
# CLI consolidation (Phase B): `report kpis` is the canonical form of the
|
|
150
|
+
# Bun-only KPI snapshot. The `report` noun is otherwise bash-owned (every other
|
|
151
|
+
# subcommand -- session/metrics/cost/export/share/dogfood -- routes to bash
|
|
152
|
+
# cmd_report), so it is NOT in the Bun allowlist below. But kpis has no bash
|
|
153
|
+
# implementation (it reuses the canonical cost arithmetic in runner/budget.ts),
|
|
154
|
+
# so the canonical `report kpis` must reach the Bun handler -- otherwise the
|
|
155
|
+
# canonical form would print the honest "requires Bun" message on a Bun machine
|
|
156
|
+
# while the deprecated `kpis` alias actually worked, inverting the deprecation.
|
|
157
|
+
# Route `report kpis` to Bun when `kpis` is the report SUBCOMMAND, i.e. the FIRST
|
|
158
|
+
# non-flag token after `report`. This satisfies `report kpis`, `report kpis
|
|
159
|
+
# --json`, and `report --json kpis` (the flag-anywhere orderings the trust-detail
|
|
160
|
+
# precedent established), while NOT hijacking `kpis` when it appears as a
|
|
161
|
+
# positional VALUE of a different report subcommand. `report export json kpis`
|
|
162
|
+
# (kpis = the export output filename) must keep working exactly as on main
|
|
163
|
+
# (v7.31.0): exit 0, file `kpis` created. So we scan past leading flags, take the
|
|
164
|
+
# first real token, and only route to Bun if it is literally `kpis`. Fire the
|
|
165
|
+
# same cli_command telemetry the Bun case-arm below fires (command=report), so a
|
|
166
|
+
# Bun-routed `report kpis` is not invisible to usage analytics (the v7.8.2 parity
|
|
167
|
+
# the case-arm comment documents). Backgrounded, FD-detached, opt-out honored by
|
|
168
|
+
# loki_telemetry itself.
|
|
169
|
+
if [ "${1:-}" = "report" ]; then
|
|
170
|
+
_report_first_sub=""
|
|
171
|
+
for _report_arg in "${@:2}"; do
|
|
172
|
+
case "$_report_arg" in
|
|
173
|
+
-*) continue ;;
|
|
174
|
+
*) _report_first_sub="$_report_arg"; break ;;
|
|
175
|
+
esac
|
|
176
|
+
done
|
|
177
|
+
if [ "$_report_first_sub" = "kpis" ]; then
|
|
178
|
+
if command -v curl &>/dev/null && [ -f "$REPO_ROOT/autonomy/telemetry.sh" ]; then
|
|
179
|
+
( SCRIPT_DIR="$REPO_ROOT/autonomy"; source "$SCRIPT_DIR/telemetry.sh" 2>/dev/null && loki_telemetry "cli_command" "command=${1:-}" 2>/dev/null ) >/dev/null 2>&1 </dev/null &
|
|
180
|
+
disown 2>/dev/null || true
|
|
181
|
+
fi
|
|
182
|
+
exec bun "$BUN_CLI" "$@"
|
|
183
|
+
fi
|
|
184
|
+
fi
|
|
185
|
+
|
|
134
186
|
# Commands ported in Phase 2 -- route to Bun. Everything else goes to bash.
|
|
135
187
|
# Two-token routes (provider show/list, memory list/index) match on the first
|
|
136
188
|
# token only; the Bun dispatcher handles subcommand routing internally.
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -2233,6 +2233,29 @@ def _normalize_session_model(raw: str | None) -> str:
|
|
|
2233
2233
|
return val if val in _SESSION_MODEL_ALLOWLIST else ""
|
|
2234
2234
|
|
|
2235
2235
|
|
|
2236
|
+
# Session-pin allowlist is BROADER than the override-file allowlist above.
|
|
2237
|
+
# run.sh's session-pin case (run.sh:12331) accepts the four model aliases AND
|
|
2238
|
+
# the three raw tier names (planning|development|fast) -- documented at
|
|
2239
|
+
# skills/model-selection.md:8. The OVERRIDE file / POST path keeps the narrow
|
|
2240
|
+
# _SESSION_MODEL_ALLOWLIST because that value is fed straight to `claude
|
|
2241
|
+
# --model`, where tier names are not valid. The session pin is a tier route, so
|
|
2242
|
+
# tier names ARE valid pins.
|
|
2243
|
+
_SESSION_PIN_ALLOWLIST = _SESSION_MODEL_ALLOWLIST + ("planning", "development", "fast")
|
|
2244
|
+
|
|
2245
|
+
|
|
2246
|
+
def _normalize_session_pin(raw: str | None) -> str:
|
|
2247
|
+
"""Normalize a LOKI_SESSION_MODEL pin value (aliases + raw tier names).
|
|
2248
|
+
|
|
2249
|
+
Mirrors run.sh's session-pin case: trim + lowercase, accept the four model
|
|
2250
|
+
aliases and the three tier names. Interior whitespace is preserved (so
|
|
2251
|
+
"fab le" stays junk and falls through to the default tier, exactly like the
|
|
2252
|
+
runner's "*" arm). Use this for the session-pin (no-override) derivation;
|
|
2253
|
+
use _normalize_session_model for the override-file / POST path.
|
|
2254
|
+
"""
|
|
2255
|
+
val = (raw or "").strip().lower()
|
|
2256
|
+
return val if val in _SESSION_PIN_ALLOWLIST else ""
|
|
2257
|
+
|
|
2258
|
+
|
|
2236
2259
|
# Provider-config model resolution mirror.
|
|
2237
2260
|
#
|
|
2238
2261
|
# SYNC: This is a byte-faithful python port of the claude provider's tier->model
|
|
@@ -2268,6 +2291,17 @@ def _provider_model_development() -> str:
|
|
|
2268
2291
|
)
|
|
2269
2292
|
|
|
2270
2293
|
|
|
2294
|
+
def _provider_model_planning() -> str:
|
|
2295
|
+
# claude.sh:65 -> LOKI_CLAUDE_MODEL_PLANNING > LOKI_MODEL_PLANNING > opus.
|
|
2296
|
+
# CLAUDE_DEFAULT_PLANNING is always opus (LOKI_ALLOW_HAIKU lowers only the
|
|
2297
|
+
# development and fast defaults, not planning).
|
|
2298
|
+
return (
|
|
2299
|
+
os.environ.get("LOKI_CLAUDE_MODEL_PLANNING")
|
|
2300
|
+
or os.environ.get("LOKI_MODEL_PLANNING")
|
|
2301
|
+
or "opus"
|
|
2302
|
+
)
|
|
2303
|
+
|
|
2304
|
+
|
|
2271
2305
|
def _clamp_to_max_tier(alias: str) -> str:
|
|
2272
2306
|
"""Apply the operator LOKI_MAX_TIER ceiling to a model alias.
|
|
2273
2307
|
|
|
@@ -2297,44 +2331,102 @@ def _clamp_to_max_tier(alias: str) -> str:
|
|
|
2297
2331
|
return alias
|
|
2298
2332
|
|
|
2299
2333
|
|
|
2334
|
+
def _resolve_session_pin(alias: str) -> str:
|
|
2335
|
+
"""Resolve a session-pin alias the way the runner's NO-OVERRIDE path does.
|
|
2336
|
+
|
|
2337
|
+
The runner does NOT feed a session pin straight to --model. It maps the alias
|
|
2338
|
+
to an abstract TIER (run.sh:12331 -- opus->planning, sonnet->development,
|
|
2339
|
+
haiku->fast, fable->fable) and resolves that tier through
|
|
2340
|
+
resolve_model_for_tier (claude.sh:353), then applies
|
|
2341
|
+
loki_apply_max_tier_clamp(model, REAL_tier). This DIFFERS from
|
|
2342
|
+
_clamp_to_max_tier (the override-path clamp): a 'sonnet' SESSION pin
|
|
2343
|
+
dispatches OPUS (development tier -> PROVIDER_MODEL_DEVELOPMENT=opus on stock
|
|
2344
|
+
config), whereas a 'sonnet' OVERRIDE file dispatches sonnet (fed straight to
|
|
2345
|
+
--model). Use this for the no-override `default`/`effective` derivation so the
|
|
2346
|
+
dashboard reports the model the run actually dispatches on the default path.
|
|
2347
|
+
|
|
2348
|
+
SYNC: byte-faithful with run.sh's session-pin case + claude.sh
|
|
2349
|
+
resolve_model_for_tier + loki_apply_max_tier_clamp, and with the estimator's
|
|
2350
|
+
_resolve_session_pin in autonomy/loki. Locked by the session-pin parity matrix
|
|
2351
|
+
in tests/test-model-override.sh.
|
|
2352
|
+
"""
|
|
2353
|
+
pin_tier = {
|
|
2354
|
+
"opus": "planning",
|
|
2355
|
+
"sonnet": "development",
|
|
2356
|
+
"haiku": "fast",
|
|
2357
|
+
"fable": "fable",
|
|
2358
|
+
# Raw tier-name pins (run.sh:12336 passthrough arm) map to their own
|
|
2359
|
+
# tier, NOT through the alias table. pin=fast -> fast tier ->
|
|
2360
|
+
# PROVIDER_MODEL_FAST, matching the runner's dispatch instead of
|
|
2361
|
+
# collapsing onto development.
|
|
2362
|
+
"planning": "planning",
|
|
2363
|
+
"development": "development",
|
|
2364
|
+
"fast": "fast",
|
|
2365
|
+
}.get((alias or "").strip().lower(), "development")
|
|
2366
|
+
if pin_tier == "planning":
|
|
2367
|
+
model = _provider_model_planning()
|
|
2368
|
+
elif pin_tier == "fast":
|
|
2369
|
+
model = _provider_model_fast()
|
|
2370
|
+
elif pin_tier == "fable":
|
|
2371
|
+
model = "fable"
|
|
2372
|
+
else: # development (and the unknown-alias '*' fallthrough)
|
|
2373
|
+
model = _provider_model_development()
|
|
2374
|
+
max_tier = (os.environ.get("LOKI_MAX_TIER") or "").strip().lower()
|
|
2375
|
+
if not max_tier:
|
|
2376
|
+
return model
|
|
2377
|
+
if max_tier == "haiku":
|
|
2378
|
+
return _provider_model_fast()
|
|
2379
|
+
if max_tier == "sonnet":
|
|
2380
|
+
# claude.sh sonnet-cap downgrades planning/fable tiers (or a fable model)
|
|
2381
|
+
# to PROVIDER_MODEL_DEVELOPMENT; development/fast pass through.
|
|
2382
|
+
if pin_tier in ("planning", "fable") or model == "fable":
|
|
2383
|
+
return _provider_model_development()
|
|
2384
|
+
return model
|
|
2385
|
+
if max_tier == "opus":
|
|
2386
|
+
return "opus" if model == "fable" else model
|
|
2387
|
+
return model
|
|
2388
|
+
|
|
2389
|
+
|
|
2300
2390
|
@app.get("/api/session/model", dependencies=[Depends(auth.require_scope("read"))])
|
|
2301
2391
|
async def get_session_model():
|
|
2302
2392
|
"""Report the live run's model override and the effective default.
|
|
2303
2393
|
|
|
2304
2394
|
`override` is the alias currently written to .loki/state/model-override
|
|
2305
|
-
(None when no override is active). `default` is the session
|
|
2306
|
-
falls back to when there is no override (LOKI_SESSION_MODEL or
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
reports a model the run
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2395
|
+
(None when no override is active). `default` is the session pin alias the run
|
|
2396
|
+
falls back to when there is no override (LOKI_SESSION_MODEL or "sonnet").
|
|
2397
|
+
`effective` is the model the next iteration will actually DISPATCH, resolved
|
|
2398
|
+
on the SAME route the runner uses for the active case, so the dashboard never
|
|
2399
|
+
reports a model that differs from what the run runs:
|
|
2400
|
+
|
|
2401
|
+
- OVERRIDE active: the runner feeds the alias straight to --model via
|
|
2402
|
+
loki_apply_max_tier_clamp(alias, alias). `effective` = _clamp_to_max_tier
|
|
2403
|
+
(the override-path clamp). A "sonnet" override dispatches sonnet.
|
|
2404
|
+
- NO override (session pin): the runner maps the pin through a tier
|
|
2405
|
+
(opus->planning, sonnet->development, haiku->fast) and resolves the tier
|
|
2406
|
+
through PROVIDER_MODEL_* (then the cost-ceiling clamp). `effective` =
|
|
2407
|
+
_resolve_session_pin. A "sonnet" pin dispatches OPUS (development tier ->
|
|
2408
|
+
PROVIDER_MODEL_DEVELOPMENT=opus on stock config).
|
|
2409
|
+
|
|
2410
|
+
Both routes resolve through the SAME provider config the runner uses
|
|
2411
|
+
(LOKI_ALLOW_HAIKU plus the LOKI_CLAUDE_MODEL_PLANNING/FAST/DEVELOPMENT and
|
|
2412
|
+
LOKI_MODEL_* overrides) and the SAME LOKI_MAX_TIER ceiling, mirroring
|
|
2413
|
+
providers/claude.sh byte-for-byte. The agreement (estimator == dashboard ==
|
|
2414
|
+
runner) on BOTH routes -- including the no-override stock path -- is locked by
|
|
2415
|
+
the cross-route cases and the session-pin parity matrix in
|
|
2416
|
+
tests/test-model-override.sh. (Before task 568 the no-override path applied the
|
|
2417
|
+
override-path clamp to the pin, so a stock "sonnet" pin reported "sonnet" while
|
|
2418
|
+
the run dispatched opus; that gap is now closed.)
|
|
2319
2419
|
|
|
2320
2420
|
KNOWN LIMITATION (cross-process env divergence): the resolution reads
|
|
2321
2421
|
LOKI_MAX_TIER, LOKI_ALLOW_HAIKU, LOKI_SESSION_MODEL and the model-override env
|
|
2322
2422
|
vars from the DASHBOARD process's environment, which is usually a different
|
|
2323
2423
|
process than the live run. So if the run was launched with a different
|
|
2324
2424
|
environment than the dashboard, the no-override `default`/`effective` may not
|
|
2325
|
-
reflect the run's real pinned tier or ceiling (e.g. a run
|
|
2326
|
-
|
|
2327
|
-
|
|
2425
|
+
reflect the run's real pinned tier or ceiling (e.g. a run launched with
|
|
2426
|
+
LOKI_SESSION_MODEL=opus while the dashboard's env has no pin still reads the
|
|
2427
|
+
default here). The override case reads the run's own state file, so its alias
|
|
2428
|
+
is always accurate and the resolution is exact whenever the dashboard shares
|
|
2328
2429
|
the run's environment.
|
|
2329
|
-
|
|
2330
|
-
SCOPE NOTE (no-override default path): when there is no override, `effective`
|
|
2331
|
-
applies the override-path clamp to the session default. The runner's
|
|
2332
|
-
no-override route instead maps a session pin through a tier
|
|
2333
|
-
(resolve_model_for_tier: opus->planning, sonnet->development), which can differ
|
|
2334
|
-
from the override-path clamp in one cell (e.g. an opus pin under sonnet cap +
|
|
2335
|
-
LOKI_ALLOW_HAIKU: the tier route yields sonnet, the override-path clamp yields
|
|
2336
|
-
opus). That session-pin modeling gap is pre-existing and out of scope here;
|
|
2337
|
-
the override case this endpoint serves is exact.
|
|
2338
2430
|
"""
|
|
2339
2431
|
override = None
|
|
2340
2432
|
try:
|
|
@@ -2343,8 +2435,16 @@ async def get_session_model():
|
|
|
2343
2435
|
override = _normalize_session_model(p.read_text()) or None
|
|
2344
2436
|
except OSError:
|
|
2345
2437
|
override = None
|
|
2346
|
-
|
|
2347
|
-
|
|
2438
|
+
# Session pin accepts tier names too (run.sh:12336), so use the broader
|
|
2439
|
+
# session-pin normalizer here (NOT the narrow override allowlist).
|
|
2440
|
+
default = _normalize_session_pin(os.environ.get("LOKI_SESSION_MODEL")) or "sonnet"
|
|
2441
|
+
# Resolve on the route the runner will actually take: override-path clamp when
|
|
2442
|
+
# an override file is present, session-pin tier route otherwise. This closes
|
|
2443
|
+
# the task-568 stock-path gap (a "sonnet" pin dispatches opus).
|
|
2444
|
+
if override is not None:
|
|
2445
|
+
effective = _clamp_to_max_tier(override)
|
|
2446
|
+
else:
|
|
2447
|
+
effective = _resolve_session_pin(default)
|
|
2348
2448
|
return {
|
|
2349
2449
|
"override": override,
|
|
2350
2450
|
"default": default,
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -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:** v7.
|
|
5
|
+
**Version:** v7.32.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|