Skip to content

Loki Mode v8.1.0

Choose a tag to compare

@github-actions github-actions released this 30 Jul 00:57
Immutable release. Only release title and notes can be modified.

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 9 with 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. Because emit.sh runs per invocation, those hangs accumulated.
  • The mkdir-mutex fallback, which is the path real users take because
    macOS ships no flock(1), checked staleness only after exhausting all
    500 attempts, forking a perl sleep 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.json pointed the non-Claude providers at Claude. Both
    aider and cline defaulted all three tiers to claude-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 set LOKI_MODEL_OVERRIDE. Both
    now default to openrouter/deepseek/deepseek-v3.2, and the catalog carries
    DeepSeek, GLM, MiniMax, and local Ollama entries. The claude provider 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 point ANTHROPIC_BASE_URL at OpenRouter. OpenRouter
    serves only the OpenAI-shaped /v1/chat/completions -- it has no
    Anthropic /v1/messages endpoint -- 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.txt reported Failed: 0 while COMPLETION.txt reported failed=1
    from the same queue/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 --json emitted a stale completion record unlabeled, so a
    script reading completion.branch or completion.pr_url after a crashed run
    would attribute a previous run's branch and PR to the current one. It now
    emits completion_is_stale and head_sha explicitly.

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.