Repository navigation
Conversation
… query path Two gaps showed up on a CPU embed backend (Qwen3-Embedding-8B, the documented ~23 tok/s) with a ~10k-token block (issue damienmoon/ctx#1): 1. backfillOneEmbedding was called only at the top of runDreamLoop, so with dream.enabled=false (the default) or Dream switched off at runtime nothing embedded pending blocks in the background. runEmbedBackfillLoop is now its own goroutine, started regardless of Dream: a successful pick loops at once, an empty pick waits embed_backfill.interval (default 15 s), 0 = arm off, polled for a hot re-enable. The dream loop keeps its inline call; the tx-wrapped FOR UPDATE SKIP LOCKED pick makes two pickers safe (Welle-49). 2. backfillPending had a count cap (sync_cap) but no time budget: one slow block consumed the caller's whole deadline (120 s on the CLI client), the question's own embed was never admitted and the query answered 500, and the failure was memoized as class wire although the backend was only slow for this caller. embed_backfill.sync_budget (seconds, default 30, 0 = unbounded) bounds the whole pre-search loop per request; each wire call runs under what is left. A block that overruns it, or dies with the request context, is memoized with the new class caller_timeout: next_attempt_at = now(), the query path never picks it again (EmbedFailureQueryPathExcludedPredicate), the background arm picks it at once, and a later wire failure there reclassifies it as wire with the ordinary backoff. Also: the "german detected" log line is now "non-english heuristic matched" (the heuristic fires on Swedish, Danish and Finnish too; the translation was right, the label was not). Tests (Rot/Grün through the production functions): - handler: TestBackfillPending_SyncBudget_Integration — SyncBudget=0 holds the request for the backend's full delay (red); SyncBudget=300ms returns inside the budget with the parent context intact, the backend sees the cancel, the memo is caller_timeout and due now, a second backfillPending makes no wire call, the background predicate still sees the block; a request cancel mid-embed classifies caller_timeout, not wire. - events: TestEmbedBackfillArm_RunsWithoutDream_Integration — the arm embeds a pending block with DreamEnabled=false, picks a caller_timeout block at once, and Interval=0 turns it off. - compose and description gates: TestComposeDeclaresEveryRegistryKey and TestKeyDescriptionsComplete pass with the two new keys. Run on 2026-10-09 in golang:1.27-alpine with CTX_TEST_PG_IMAGE=localhost/ ctx-db:38fdb74f: go build ./..., go vet -tags=integration, go test -short for config/store/handler/events (one pre-existing failure, TestRebuildViaWorker_ TimeoutKillsHangingChild, fails identically on the unpatched checkout in this container), and the backfill integration suites of handler, events and store all green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The comments and the operations paragraph said "issue damienmoon/ctx#1", which on GitHub reads as a reference into a repository that does not exist there. The issue lives on the author's Forgejo, a read-only mirror of the GitHub fork: https://force-push.me/damienmoon/ctx/issues/1. Comments only; no code changes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
embed_backfill.sync_budget and embed_backfill.interval are two new typDuration keys. TestValidateRejectsNegativeOnEveryDurationKey pins the count as a drift guard (45 before this branch) and failed in CI. Both keys are covered by the generic V17 sign walk, which the test exercises per key; the pin moves to 47 in the commit that explains why, as the comment above it asks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ation 155 A fourth wire protocol, "anthropic", for the native Anthropic Messages API (POST /v1/messages). This commit adds the constant and the row shape the wire needs; the wire itself follows in go/internal/llm. Validation (422 on create and update, protocol-bound so openai/ollama rows keep their freedoms): api_key_ref required (the key travels as x-api-key), locality external, num_ctx > 0 (no window discovery on this wire — synthesis would fail closed on promptguard.ErrUndeclaredWindow), provider_class generic, no embed/dream-embed/rerank roles (the Messages API has no such endpoints; embed.go would silently dispatch them onto the Ollama path), effort in extra_body or model_map params limited to low|medium|high|xhigh|max, and the /v1 root guard extended (the wire appends /v1/messages itself). Migration 155 widens the CHECK on context_backends.protocol. The baseline declared it inline and unnamed, so the DO block resolves the name from pg_constraint by table and definition (the pattern 113 and 114 already use) before re-adding it as context_backends_protocol_check. Manifest: the schema contract records column types, indexes, functions, triggers, rules and hypertables — no CHECK clauses — so a regeneration after 155 changes only generated_against.migration_max (155) and migration_count (43). Those two fields are bumped by hand here because the generator needs Docker (testcontainers), which the authoring seat does not have; the integration job's contract check verifies the manifest against the live chain. Tests: TestValidateAnthropicHappyPath, TestValidateAnthropicRules (one mutation per rule, plus the valid effort set and the protocol-boundedness check). docs/architecture.md names the protocol. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
chatWithFormat gains a third leg: ProtocolAnthropic dispatches to
chatAnthropic (anthropic.go), POST {base_url}/v1/messages with x-api-key
and anthropic-version headers (core headers set last, after
extra_headers, as setChatHeaders does). The request carries the system
prompt as a top-level text block with cache_control ephemeral, the user
prompt as the single message, max_tokens from NumPredict (16000 when the
caller sets none — the field is required on this wire), and
output_config.effort resolved from the role's model_map params
(Options.Extra) over the row's extra_body over the explicit default
medium. No sampling parameter is ever marshalled: the Claude 5 models
answer 400 to temperature/top_p/top_k, and the OpenAI-compatible layer ctx
used until now needed a stripping sidecar for exactly that. The think
toggle is ignored (thinking cannot be disabled on Opus 5.5; effort is the
depth control), logprobs are never requested (the decide primitive falls
back through its existing ErrNoLogprobs memo), and ChatJSON is a
documented no-op (output_config.format needs a JSON schema ctx does not
have; the pipelines' parsers stay the validator, the CTX_DREAM_JSON_MODE=off
contract). Other extra_body keys merge top-level, row over params.
Response: text blocks joined, thinking blocks skipped; PromptTokens is the
TOTAL prompt (input_tokens + cache_read + cache_creation — Anthropic's
input_tokens is the uncached remainder) so llmlog's prompt_tokens stays
comparable across wires; the two cache counters travel in new
ChatResponse fields and reach llmlog metadata through
ApplyProviderTelemetry (cache_read_input_tokens /
cache_creation_input_tokens, written only when non-zero so other wires'
rows stay byte-identical); the message id lands in provider_request_id;
cost stays NULL. stop_reason is mapped onto the vocabulary the consumers
read (end_turn→stop, max_tokens→length, tool_use→tool_calls,
refusal→content_filter, unknown raw, absent ""), because dream's cap
detection compares against "length" and the chat harness against
"tool_calls" — a raw pass-through would hide both. Non-2xx answers wrap
httpx.StatusError as on every wire (429 → RateLimit with Retry-After,
529 overloaded → Gateway failover, 401 → Auth, 400 → BadRequest, 500 →
ServerFault) with the provider's error type folded into the message.
Tests (httptest fakes, no live key): request shape and headers, effort
precedence, extra_body merge, JSON no-op, max_tokens fallback, empty
system omitted, response decode incl. cache counters and telemetry,
stop-reason table, error classification; anthropic rows in the wire-path
and finish-reason tables; the MW6 dispatch invariant gains the seventh
wire site (llm/anthropic.go pinned, Σ acquires == Σ wire contacts).
docs/api.md: the Anthropic paragraph under Backend pool.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ChatStreamBackend is the new protocol dispatch of the streaming path: it
takes the Backend (ChatStream took host/key/model loosely and never read
b.Protocol) and routes ProtocolAnthropic to chatStreamAnthropic
(anthropic_stream.go), everything else to ChatStream as before — ollama
rows included, which always streamed over /v1/chat/completions. The chat
harness (chat/engine.go runStream) calls it; ChatStream stays exported as
the openai implementation its tests drive.
The anthropic leg feeds the same StreamEvent/StreamResult contract: a
dedicated SSE parser keys on the data frame's own "type" (message_start →
prompt + cache counters; content_block_start tool_use → pending call and
the ToolCallName event; text_delta → ContentDelta; input_json_delta →
arguments fragments, assembled into the object form ToolCall carries;
message_delta → mapped stop reason and output_tokens; message_stop →
done; error → *StreamError with the provider's error type; ping,
thinking_delta and signature_delta ignored). Lenient ending as on the
openai parser: EOF after message_delta is a complete turn, EOF before it
is a hard error.
Message mapping: system rows lift into the cached top-level system block,
an assistant row with tool calls becomes [text?, tool_use…] (unparseable
arguments replay as {} — the executor already answered them ok:false), a
tool row becomes a user turn with a tool_result block, and consecutive
same-role turns fold into one message (the API requires alternation; a
parallel-call turn yields several tool rows). ToolDef becomes
{name, description, input_schema}; no tool_choice (a 400 on Claude Opus
5.5; auto is what the openai wire sends too).
StreamResult gains the cache counters and the message id; recordLLM
writes them into the web-chat llmlog row (metadata.cache_read_input_tokens
/ cache_creation_input_tokens / provider_request_id) through the shared
ApplyCacheTelemetry.
Tests: three hand-written fixtures from the Messages API event reference
(content with thinking + ping, two tool calls with fragmented
partial_json and an empty-input call, mid-stream overloaded_error);
lenient and hard endings; 529 → ClassGateway via errors.As; callback
abort; the full tool round-trip request shape (headers, cached system,
folded tool results, input_schema, effort, forbidden keys absent); the
dispatch across openai/ollama/anthropic rows; message edge cases. The
MW6 dispatch invariant drives the eighth wire site (llm/anthropic_stream.go
pinned, Σ acquires == Σ wire contacts holds at 8/8 and 10/10).
docs/api.md: the web-chat harness paragraph names the per-protocol stream.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The operator surfaces of the new wire protocol.
backend-test: probeBackendURL branches on the protocol before the
provider class — an anthropic row probes GET {base_url}/v1/models with
x-api-key + anthropic-version (no Bearer header); the generic branch
would have hit the bare root with a Bearer header, which the API answers
404 (reported as unreachable) or 401. The 1-token chat probe runs through
llm.Chat as before and so takes the anthropic wire automatically.
ctx backends seed: the chat leg accepts protocol anthropic and is
validated on the spec, before any secret is sealed — the embed leg
rejects it (the Messages API serves no embeddings), a keyless leg and a
leg without num_ctx (no window discovery on this wire) fail with the
reason. The same rules the server enforces as 422s, one layer earlier.
ctx init: the protocol prompt names anthropic; choosing it turns the
single-host wizard into a two-host one — it asks the chat model's
context window (default 100000), a separate embedding host and its
protocol (ollama default), and goes straight to the no-echo key prompt
instead of the "does the host need a key?" detour.
Web UI: 'anthropic' in the PROTOCOLS enum of the backend dialog.
Tests: seed error table (anthropic on embed / keyless / no num_ctx) and
the mixed-protocol happy path; the wizard flow with the scripted ask
(window default, separate embed host, key sealed once, not echoed, not
attached to the embed row); the handler probe against an httptest fake
(path, headers, base_url/auth ok).
Docs: operations.md (Backends — the two-host SeedSpec example and the
spec-level rules; Models & hardware; the CTX_DREAM_JSON_MODE row's
anthropic clause; the Migration 155 section with the downgrade note),
README's engine-agnostic sentence.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
chatOptions builds the stream call's Options by hand (the harness owns NumPredict and the think toggle, so it never runs applyModelParams) and copied only temperature and top_p from the chat role's params. The anthropic wire resolves output_config.effort from Options.Extra["effort"] first — the slot applyModelParams fills on the non-stream path — so a row's model_map.chat.params.effort was honoured by synthesis and dream but silently ignored by web chat, which sent extra_body.effort or the default medium instead. docs/api.md promised the params-over-extra_body precedence for both. Found by the adversarial review; the existing tests injected Options.Extra by hand and never went through chatOptions. chatOptions now lifts params.effort into Options.Extra. Test: TestChatOptionsCarriesModelMapEffort (and the nil slot without the param). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…the tail Follow-ups from the three-way review of the anthropic wire against the claude-api reference, the tests and the docs. Each one is a request the Messages API would have answered 400 to, or a cache write paid for nothing. Thinking blocks ride along. The Claude 5 models think on every answer and require the FINAL assistant turn of a tool loop to start with its thinking block (empty text under the default display, the signature carries it). The parser now re-assembles thinking (thinking_delta + signature_delta, sealed on content_block_stop) and redacted_thinking blocks into StreamResult.ProviderBlocks; the harness puts them on the assistant ChatMsg it appends for the next iteration (ChatMsg.ProviderBlocks, json:"-" so the OpenAI wire never sees it, not persisted — later turns replay without them, which the API accepts for non-final turns), and anthropicMessages emits them first in the assistant turn. Without this the second iteration of every tool-using turn was a 400. Orphaned tool_use is repaired. A turn interrupted between the model's tool call and the tool's result leaves an assistant tool_use without a tool_result in the persisted history, and the API rejects that on every later turn of the session. anthropicMessages now answers every unanswered tool_use with a synthetic is_error tool_result before the next user text (and at the end of the history). Tool calls without ids — an openai-wire row earlier in the session; llama.cpp streams calls without one — get synthetic ids, matched in order to the tool rows that follow. Prompt cache actually hits. Every pipeline ends its system prompt with promptguard.Rule(nonce), fresh per call; one cached block carrying it would be written on every call and read never (cache_creation > 0, cache_read == 0, a pure 1.25x surcharge). anthropicSystem splits the rule into a second, unmarked block, so the stable prefix is the breakpoint. The stream path additionally sends the top-level automatic cache_control, so the growing history is read from cache across iterations and turns. Smaller: message_delta usage copies each input counter only when it is itself present, so a delta carrying input_tokens alone cannot zero the cache counters; a nameless tool_use block (proxy artefact) is dropped instead of dispatched as an empty call; StreamError exposes its provider error type and backends.Classify maps the anthropic types inside a mid-stream error event onto the classes the equivalent status codes get (overloaded_error → gateway failover, rate_limit_error → rate limit, authentication_error → auth, invalid_request_error → bad request, api_error → stop) — OpenRouter's numeric codes keep the stop default; handleFrame split into per-event methods (cyclop); the package doc names the model floor (effort is always sent) and why raw net/http over the Go SDK. Tests: thinking/redacted blocks carried and placed first (fixture now opens with the thinking block Opus 5.5 always sends), orphan and empty-id repair, per-field usage, nameless tool_use, top-level cache_control on the stream path and its absence on the non-stream path, the promptguard split (byte-identical prefix across nonces), StreamError classification. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…mbed key
ctx init, anthropic branch: a non-numeric context window ("100k") no
longer falls back to 100000 silently — the step aborts with the reason
before any secret is sealed or any row written (a typo'd window would
have seeded a tenth of the prompt budget the operator meant). The
separately asked embedding host gets the same "does it need an API key?"
question the single-host flow has, so a keyed openai embedding service
can be seeded next to the Anthropic chat row.
probeBackendURL: the comment claimed the chat probe never runs after a
401 on /v1/models; it does (the row counts as reachable) and 401s the
same way, billing nothing — the comment now says so.
Tests: TestInitBackendsStepAnthropicRejectsBadWindow; the wizard flow
test answers the new embed-key question.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…re and UI help docs/api.md: the complete stop-reason mapping (stop_sequence, pause_turn, model_context_window_exceeded were applied but not listed), the 16000 max_tokens fallback, the model floor of the always-sent effort field, the mid-stream error classification, the refusal-after-partial-text behaviour, the three session repairs and the two cache breakpoints of the stream path, and the OpenAI-compatible-layer claims marked as what they are — observed on the ku-heimdall install, not reference facts. docs/operations.md: the first-run step names the anthropic wizard questions; the Migration 155 downgrade note describes both old fallthroughs (Ollama path on the pipelines, OpenAI SSE on web chat) and that the pool loads the row unvalidated. docs/architecture.md: the transport-retry enumeration names the anthropic wire. go/internal/config/validate.go: the V4 comment lists the four protocols. Web: ModelMapEditor's help text names the Anthropic wire (and the effort key's destination); settings.ts' dormant protocol mirror and its test carry 'anthropic' so the declared mirror is true again. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
DamieMoon
marked this pull request as draft
October 11, 2026 10:32
…hain The Lint job fails on every run since 2026-10-10 (nightly on root 38019738113 and 38107728729, this PR's 38132148410), all other jobs green: GO_VERSION "1.27" now resolves to go1.27.2 on the runner, and the pinned v2.13.2 cannot read its export data — "export data version 5 is greater than maximum supported version 4", reported as a typecheck error on the first `import "fmt"` (internal/clustersql/clustersql.go:28). v2.14.0 (2026-09-24) reads it; locally against go1.27.2 with the same .golangci.yml it reports 0 issues over ./..., and the deadcode gate is green. Deliberate pin bump, as the comment in the workflow asks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…hropic.com Skipped under -short and without CTX_ANTHROPIC_SMOKE_KEY, so CI never spends tokens. By hand it proves what the httptest fakes cannot: the request shape is accepted by the real endpoint, the prompt cache hits on the second call (stable prefix above 512 tokens, fresh promptguard nonce per call), the thinking block replays through a streamed tool round trip (and what the API says about the same round trip without it), effort low and high are accepted, the 1-token backend-test probe's behaviour on a thinking model, and a bad key classifying as auth. Every probe logs the usage it saw (tokens, cache counters, message id). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Integration-tagged: the full migration chain on the prod-parity image, then the constraint context_backends_protocol_check exists by name, admits 'anthropic' next to openai/ollama/rerank, is the only CHECK on the table that mentions the column (the DO block's catalog lookup stays unambiguous), an anthropic row inserts, and an unknown protocol fails with SQLSTATE 23514 on that named constraint. Ran locally against testcontainers (colima) — PASS in 2.2 s; the schema-manifest generator run on the same image reproduced the hand-bumped manifest.json byte for byte. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n observed 400 The adversarial review of the last three commits caught an overstatement: docs/api.md, the anthropic_stream.go comments and a test comment said the API "requires" the final assistant turn of a tool loop to start with its thinking block. The claude-api reference asks that a turn whose tool_use still awaits its result KEEP its thinking block and rejects an altered one; live on 2026-10-11 (claude-opus-5-5) a short tool turn came back with no thinking block at all — even with thinking.display summarized and effort high — and the round trip was accepted. The replay mechanism stays (it honours the reference's rule whenever a block is produced, and the fixtures cover that path); the wording now says exactly that. The live smoke test reports when the replay path was not exercised instead of logging an "accepted without the block" line that compared two identical requests, asks for visible reasoning on the tool turn, and its header says that the integration job (which runs without -short) relies on the absent key alone. The Migration 155 integration test also pins that the column's DEFAULT 'openai' survives the constraint swap. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ay commit
"accepted either way" said two variants were sent when the live run sent
one (no block was produced); the stale header line said thinking deltas
are ignored while the parser accumulates them into the replay block; the
live test's stripped-block log no longer calls a rejection "expected"
(the reference only asks to keep the block). In the live test the second
and the stripped third round-trip call now send the same effort ("high")
as the first — an effort change invalidates the messages cache by itself
and would mask the prefix question — and the second call's NumPredict
rises 1024 → 2048 to match the first, headroom for the high-effort turn.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
DamieMoon
marked this pull request as ready for review
October 11, 2026 11:14
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on #46 (
fix/embed-backfill-budget): the first three commits here are that PR's; the rest are this one. A deploy of this branch carries both.Summary
ctx learns a third wire protocol,
anthropic: the native Anthropic Messages API (POST {base_url}/v1/messages,x-api-key+anthropic-version). Until now a hosted-Claude install had to reach Anthropic through its OpenAI-compatible layer with theopenaiprotocol, which rejects every sampling parameter on the Claude 5 models (ctx always sendstemperature), and — as observed on my install — dropsresponse_formatsilently and offers neither prompt caching nor effort control, so a stripping sidecar sat in front of it. With this PR the chat-class roles (synthesis, translate, dream, digest, chat) talk toapi.anthropic.comdirectly; the embed/rerank roles stay on a local backend, since the Messages API has no such endpoints.What the wire does:
thinkingfield, notool_choice— the request carriesmodel,max_tokens(from the pipeline'snum_predict), the system prompt as a cached top-level block (cache_control: ephemeral), the messages, andoutput_config.effort.model_mapparams.effort> rowextra_body.effort>medium(the Claude Opus 5.5 default), validated tolow|medium|high|xhigh|max. The chat harness carriesparams.efforttoo (it builds its options by hand and initially dropped it — caught in review).promptguardnonce rule, so that rule goes into a second, uncached block and the stable prefix is the breakpoint; the web-chat stream path adds the top-level automaticcache_controlfor the growing history.cache_read_input_tokens/cache_creation_input_tokensland incontext_llm_log.metadata;prompt_tokensis the total prompt (Anthropic'sinput_tokensis the uncached remainder only).ChatStreamBackend, the new protocol dispatch of the stream path): the Messages API SSE events map onto the existingStreamEvent/StreamResultcontract;ToolDef→input_schema, assistant tool calls →tool_useblocks, tool rows →tool_resultblocks folded into one user turn. Three repairs keep a session alive: the answer'sthinkingblocks (when the model emits any) ride along in memory to the next iteration of the same turn and are replayed verbatim (the reference asks to keep them while atool_useawaits its result), an unansweredtool_usefrom an interrupted turn gets a syntheticis_errorresult, and id-less tool calls from an openai-wire row earlier in the session get synthetic ids.end_turn→stop,max_tokens→length,tool_use→tool_calls,refusal→content_filter); errors classify like every other wire (429 → rate-limit cooldown withRetry-After, 529 → gateway failover, 401 → auth, 400 → bad request), the same for the error types inside a mid-streamerrorevent.output_config.formatneeds a JSON schema ctx does not carry, soChatJSONsends the same body asChatand the pipelines' parsers stay the validator (run Dream withCTX_DREAM_JSON_MODE=off, as the OpenAI-compatible layer already forced).Row shape (422 on create/update):
api_key_refrequired,locality: external,num_ctx > 0(no window discovery on this wire),provider_class: generic, no embed/dream-embed/rerank roles,/v1-root guard. Migration 155 widens theCHECKoncontext_backends.protocol(the baseline's unnamed constraint is resolved frompg_constraint); no rows change.backend-testprobesGET /v1/modelswith the key;ctx backends seedacceptsanthropicon the chat leg (two-host spec),ctx initasks the extra questions; the UI enum and docs (api.md,operations.md,architecture.md, README) carry the protocol.Also in this PR: ci(lint) bumps the golangci-lint pin to v2.14.0 — the runner's
GO_VERSION: "1.27"resolves to go1.27.2 since 2026-10-10 and v2.13.2 cannot read its export data (Lint was red on the root nightlies of 10 and 11 October with everything else green).How it was verified
Unit (fake upstream, httptest, no key): request shape and headers (non-stream and stream, forbidden sampling keys absent), effort precedence,
extra_bodymerge, JSON no-op,max_tokensfallback, empty-system omission, response decode incl. cache counters and llmlog telemetry, the stop-reason table, error → failover class (429 withRetry-After, 529, 401, 400, 500), SSE fixtures (text with thinking + ping, two tool calls with fragmentedpartial_json, mid-streamoverloaded_error), lenient/hard stream endings, callback abort, thinking/redacted blocks carried and replayed first, orphan and empty-id repair, per-field usage merge, namelesstool_usedropped, the promptguard split (byte-identical prefix across nonces), the stream/non-streamcache_controlplacement, protocol dispatch across openai/ollama/anthropic rows, validation rules one by one, seed/wizard flows, the handler probe, the chat harness's effort pass-through, and the MW6 dispatch invariant (eighth wire site, Σ acquires == Σ wire contacts).-raceclean on the touched packages.Integration (testcontainers, prod-parity
pgvector-timescaledb:pg18built fromdb-image/): the full migration chain through 155, thenTestMigration155ProtocolCheckAdmitsAnthropicpins the named constraint, that it admitsanthropic, is the onlyCHECKnaming the column, keeps the column'sDEFAULT 'openai', inserts an anthropic row and rejects an unknown protocol with SQLSTATE 23514. The schema-manifest generator (-tags=genmanifest) ran on the same image and reproducedmanifest.jsonbyte for byte (the header had been bumped by hand first). The whole integration suite (-tags=integration -p 1 ./...) ran locally as well: 60 packages green, 10 tests in 6 packages red on this macOS/arm64 seat (armsweep×3,rrf×2,handler×2,llmlogexport,dream,events) — each of them either fails identically on the base commit218d5f84(float last-digit parity,/tmpsymlink semantics, a byte-identical response anchor) or passes in isolation at HEAD (two timing-sensitive tests under the serial run); none touches this branch's diff, and CI's six Linux shards are green.Web:
bun install --frozen-lockfile,bun run test(100 files, 995 tests) andbun run check(svelte-check, tsc, lint:css — 0 errors).Live (
claude-opus-5-5, 2026-10-11, env-gatedTestAnthropicLive*, skipped in CI): the request shape is accepted; the prompt cache hits on the second call (cache_creation 3033→cache_read 3033with a fresh promptguard nonce per call); JSON-mode no-op still yields JSON; effortlowandhighaccepted; the 1-tokenbackend-testprobe answers 200 withstop_reason: max_tokensand empty text (so the probe reports ok); a streamed tool round trip completes (tool_use→tool_result→ final answer); a bad key → 401 →auth. Not exercised live: the thinking-block replay — Opus 5.5 emitted no thinking block on the short tool turn, even withthinking.display: summarizedand efforthigh, and the block-less round trip was accepted; the replay path is covered by fixtures and follows the reference's rule.Three independent adversarial review passes (wire vs the Claude API reference, tests vs code, docs vs code; and a pre-push pass on every later commit) produced the follow-up commits on the branch; the remaining review notes are recorded in the code comments.
Operator notes
protocol: "anthropic",base_url: "https://api.anthropic.com"(no/v1), keepapi_key_refandnum_ctx; the sidecar/shim can go.max_tokens; if a role's rows showmetadata.finish_reason = length, raise that role'smodel_mapparams.max_tokens.anthropicrow in the pool dispatches it onto the wrong wire — disable or delete such rows first (operations.md, Migration 155).🤖 Generated with Claude Code