Skip to content

v0.2.0: a cut stream stops passing for a finished answer

Choose a tag to compare

@Fanfulla Fanfulla released this 07 Aug 10:35
· 68 commits to main since this release

Added

  • editRetryHint, the first behavioural adapter (ADR-45, opt-in). Edits are
    applied by exact match, and a model that returns the right content with the
    wrong bytes (re-indented, tabs for spaces, trailing newline dropped) gets its
    edit refused, then often resends the same old_string for several turns. With
    this quirk enabled on a profile, the turn right after a rejected edit carries
    one extra system block naming the exact-match rule and telling the model to
    copy the bytes rather than retype them. It repairs nothing on the model's
    behalf: the proxy never rewrites old_string, because it has neither the file
    nor a way to know which occurrence was meant, and a wrong guess would corrupt
    a source file. Off by default, never applied to count_tokens, and the report
    now says on how many turns it fired, because an adapter nobody can see cannot
    be judged. Enable it knowing that it is unproven: the first live
    measurement (2026-08-07) confirmed it fires exactly when it should, and did
    not show it helping, on a model that turned out not to have the defect it
    targets. The numbers and what they do not license are in ROADMAP M5.

Changed

  • The Rust sidecar shows the same request markers as lupin top. It was
    reading four of them and dropping three, so retryAfterMs and dialect were
    documented but invisible there, and the new editHint would have been too. All
    three now print, in the wording the Node side has always used (waited:1500ms,
    dialect:a+b, editHint), with a test on each side: one log line reads the
    same whichever front end is watching it. An empty dialect list prints
    nothing rather than a marker with no name.
  • lupin doctor now names the quirks active on the profile. The report
    printed the dialect normalizations that fired but never the quirks configured
    on the profile, which the --submit body has always carried: the person
    reading their own terminal knew less than a stranger reading the scoreboard.
    The new line carries no warning glyph, because a configured quirk is true on
    every run and an alarm that always fires stops being read. It is printed
    because some request quirks (noParallelToolCalls, singleSystemMessage,
    identityHint) change what the model was asked, so a score earned under them
    is not comparable with a bare one. --json carries the same information as a
    quirks array next to dialects, always present, so a machine consumer of
    the payload alone can tell the two apart without reading the config.

Fixed

  • lupin doctor no longer scores a session that died on the transport
    (ADR-23).
    The headless result was still read starting from subtype, whose
    name promises a verdict and only reports how Claude Code's own loop exited. A
    run whose terminal_reason is api_error or auth_error is now voided on
    that field alone, without waiting for is_error to agree, so a provider that
    never answered can no longer earn the "session completed" point and a score
    that reads as a judgement on the model.

  • A truncated stream is no longer reported as a finished turn (ADR-44, issue
    #1).
    A provider that answered 200, streamed part of the answer and then
    dropped reached Claude Code as a short but well formed turn with
    stop_reason: end_turn. On the translated lanes the proxy now ends that
    stream with an error event instead of a synthesized clean close, and a tool
    call rescued from the cut text is no longer delivered. In passthrough the
    bytes are still forwarded untouched, but the request log gains
    streamError: truncated and the provider no longer earns the success that
    clears its failover cooldown. A stream that did send its stop_reason is
    unaffected, even if the connection ends before message_stop.