Skip to content

fix: P3 cleanup wave — legacy completions, alias listing, telemetry, terminal chunk, pool race, embedder gating#57

Merged
magicnight merged 1 commit into
mainfrom
fix/v0.5.3-p3-cleanup
Jul 8, 2026
Merged

fix: P3 cleanup wave — legacy completions, alias listing, telemetry, terminal chunk, pool race, embedder gating#57
magicnight merged 1 commit into
mainfrom
fix/v0.5.3-p3-cleanup

Conversation

@magicnight

Copy link
Copy Markdown
Owner

The P3 tail of the debug-round findings (follows #51 P0/P1 and #56 P2), plus the v0.5.2 embedder-gating follow-up. Eight small items:

  • Legacy /v1/completions — prompt-only bodies now work (fallback decode → user turn; stream honored; neither-shape still 400).
  • Ollama /api/tags — reports the configured alias, consistent with /v1/models. (/api/show is a static stub with no id field — verified nothing to alias.)
  • Anthropic message_start.usage.input_tokens — documented-only: prompt tokens only arrive on the engine's terminal chunk, so deferring message_start can't help; message_delta already carries real counts.
  • Streaming telemetrytokens_generated_total now accumulates the terminal chunk's completionTokens (was: SSE/NDJSON frame count), exactly once per request on all four streaming paths.
  • ModelPool unload-vs-load raceunload(id) during an in-flight load(id) no longer resurrects the model: tombstone honored by the completing load (fresh engine unloaded, byte reservation released, insertion skipped).
  • EngineCoordinator residency-miss — reconciles currentModel/status instead of leaving a stale .ready; guarded so a stale-id generation can't clobber a healthy newer model (review follow-up).
  • emitFinalChunk — always emits one terminal chunk on non-cancel stream ends (synthetic .stop + zero usage when no info), so reasoning splitters flush held-back partial tags; cancellation semantics untouched.
  • Embedder gating/v1/embeddings + /v1/rerank return 400 model_not_embedder for non-embedder targets (chat models no longer return meaningless vectors); missing models still 404.

Review + verification

Adversarially reviewed (APPROVE; six targeted suspicions adjudicated — fallback-decode safety, tombstone lifecycle incl. no-await invariant + reservation exactly-once, terminal-chunk double-emit, telemetry exactly-once on all paths, gate placement, stale-id race; the one MEDIUM finding — status write outside the stale-id guard — is fixed here). Independently verified under both CI-equivalent conditions: xcodebuild ** TEST SUCCEEDED ** (80 XCTest + 210 swift-testing, 0 failures; 7 new tests + all prior regressions + 11 DeepSeek parity) and bare swift test (210 passed, 0 MLX error, gated tests skip cleanly); app build ** BUILD SUCCEEDED **.

…listing, telemetry, terminal chunk, pool race, embedder gating

- /v1/completions (+ POST /, /v1) accept legacy {"prompt"} bodies:
  fallback decode wraps the prompt into a user turn (stream honored);
  neither-shape bodies still 400 with the original decode error
- Ollama /api/tags reports the configured alias, matching /v1/models
  (/api/show is a static stub with no id field — nothing to alias)
- Anthropic message_start.usage.input_tokens: documented-only — prompt
  tokens only arrive on the terminal engine chunk, so deferring
  message_start cannot help; message_delta carries the real counts
- Streaming telemetry counts real completion tokens (terminal
  usage.completionTokens) instead of SSE/NDJSON frames, on all four
  streaming paths, exactly once per request
- ModelPool: unload(id) during an in-flight load(id) is honored via a
  tombstone — the completing load releases its byte reservation,
  unloads the fresh engine, and skips insertion (no resurrection,
  no reservation leak)
- EngineCoordinator residency-miss reconciles currentModel/status —
  guarded by currentModelID == id so a stale-id generation can't
  clobber a healthy newer model's status (review follow-up)
- MLXSwiftEngine.emitFinalChunk always emits one terminal chunk on a
  non-cancel stream end (synthetic .stop + zero usage when the engine
  closed without info) so reasoning splitters get their finish signal;
  cancellation paths untouched
- /v1/embeddings + /v1/rerank return 400 model_not_embedder when the
  resolved model isn't ModelFormat.embedder (404-when-missing kept)

7 new tests incl. a discriminating unload-during-load probe (budget
math proves the reservation released) and a K≠N token-counter test.
@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@magicnight magicnight merged commit e28f40a into main Jul 8, 2026
3 checks passed
magicnight added a commit that referenced this pull request Jul 8, 2026
docs: CHANGELOG for PRs #55-#57 + roadmap refresh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant