Loki Mode v8.1.0
Minor release. The telemetry layer could hang forever and burn the CPU. For
a product whose claim is that it proves work actually happened, that is the
worst possible place for a defect.
Telemetry no longer outlives the run (P0)
Found on a real machine at 0% idle with the fans at full: 59 orphaned
events/emit.sh processes, the oldest alive 21 hours, orphaned to launchd.
emit.sh spawns on every CLI invocation and was never exiting.
Both of its lock paths could hang:
flock -x 9with no timeout (events/emit.sh:48). A holder killed
mid-write -- a reaped CI run, a Ctrl-C'd build -- blocked every later emit
forever. Becauseemit.shruns per invocation, those hangs accumulated.- The mkdir-mutex fallback, which is the path real users take because
macOS ships noflock(1), checked staleness only after exhausting all
500 attempts, forking aperlsleep helper on each one. Measured cost of a
single append against an already-stale lock: 10 seconds and ~500 forked
processes, returning failure.
Fixed: flock -w 5 with an unlocked-append fallback; staleness checked on
every iteration; attempts cut 500 to 100; read -t replaces the forked
sleep. Measured after: 10s to 2s, failure to success, no fork storm.
Observability must never block the thing it observes. A rare interleaved line
is strictly better than a hung CLI, so the append now degrades instead of
waiting.
The council no longer fabricates reviewer verdicts
Every provider arm in autonomy/council-v2.sh substituted a literal
{"verdict":"REJECT","reasoning":"review execution failed"} on any miss --
CLI absent, timeout, transient error, unparseable output -- and the tally
counted "anything not APPROVE" as a rejection. The engine therefore recorded a
reviewer vote the model never gave, and blocked the run on it. Twelve such
sites existed across five providers.
The devil's advocate had the same default, so a single transient parse failure
could silently overturn a unanimous approval.
This is the same defect class as an Evidence Receipt attesting to a diff stat
it never measured: the artifact asserts a fact nobody established.
It matters most on a cheap or weak model, where low format compliance became a
permanent BLOCK that reads as "Loki is broken" rather than "your model could
not produce a parseable verdict."
Now: an unobtained verdict is INCONCLUSIVE. It is counted separately, never
as a rejection, and reported to the user. A genuine REJECT still counts as
one, and an inconclusive devil's advocate leaves a unanimous approval
unchanged.
The receipt names the gate that stopped the run
A blocked run's Evidence Receipt gave only a bare outcome -- intervention.
That tells a reader something stopped them, not what, and not how close it
came to its threshold: the single most actionable fact about a blocked run.
The engine already had it. .loki/signals/GATE_ESCALATION.json is written on
escalation, and both COMPLETION.txt and PAUSED.md already surfaced the gate,
count, and threshold. The signed receipt was the one surface that stayed silent.
It now reports Blocked by: code_review (failed 3 times, threshold 3). These
are deterministic facts read verbatim from a file the engine wrote, so they live
in the facts block rather than the AI-assessment block. A missing or malformed
signal leaves the fields empty -- which reads as "no escalation recorded", never
as a false claim -- and never breaks receipt generation.
Cheap models are now reachable, and the docs stopped lying about how
Three defects that together made the "run any model" story unusable:
model_catalog.jsonpointed the non-Claude providers at Claude. Both
aiderandclinedefaulted all three tiers toclaude-opus-4-8/
claude-sonnet-5, and the catalog contained zero open-weight models. A
user who switched provider specifically to stop paying frontier prices kept
paying them, silently, unless they knew to setLOKI_MODEL_OVERRIDE. Both
now default toopenrouter/deepseek/deepseek-v3.2, and the catalog carries
DeepSeek, GLM, MiniMax, and local Ollama entries. Theclaudeprovider is
unchanged, and the env-override chain still wins.- The documented OpenRouter on-ramp could not work. Both the CLI help and
the README told users to pointANTHROPIC_BASE_URLat OpenRouter. OpenRouter
serves only the OpenAI-shaped/v1/chat/completions-- it has no
Anthropic/v1/messagesendpoint -- so that routes an Anthropic-shaped
client at an API it does not speak. The CLI example even named
anthropic/claude-sonnet-4.5, using OpenRouter to reach Claude again. - The README claimed OpenRouter speaks the Anthropic Messages API. It does
not.
Both surfaces now present two clearly separated routes: OpenAI-shaped endpoints
via aider/cline (which speak that API natively), and Anthropic-protocol
gateways via ANTHROPIC_BASE_URL with an explicit note that OpenRouter does not
qualify.
Status surfaces stop contradicting each other
STATUS.txtreportedFailed: 0whileCOMPLETION.txtreportedfailed=1
from the samequeue/failed.json. The cause was age, not source: the
status monitor refreshes every 2s but is not running once the engine blocks
in a pause. All three blocking pause sites now refresh before writing the
durable record.loki why --jsonemitted a stale completion record unlabeled, so a
script readingcompletion.branchorcompletion.pr_urlafter a crashed run
would attribute a previous run's branch and PR to the current one. It now
emitscompletion_is_staleandhead_shaexplicitly.
Tests
tests/test-emit-lock-no-hang.sh(8 assertions, registered). Non-vacuity
proven by reverting the fix: 3 go red, including the exact 10s regression.tests/test-status-surface-agrees.sh(10 assertions, registered). The
pause-site check is structural, so a future site added without the refresh is
caught. Non-vacuity proven three ways.