release: v1.0.0#23
Merged
Merged
Conversation
runed never dials the index engine, but inserts need a plaintext IVF cluster assignment computed next to the embedding. The centroid set is relayed runespace -> vault -> rune-mcp -> here: - SetCentroids (client-stream): header + id-ordered batches, validated against the daemon's embedding dim, installed atomically, persisted to $RUNED_HOME/cache (gob, atomic rename) so restarts skip the re-push. - Embed/EmbedBatch with_route: responses carry cluster_id and the centroid_set_version the assignment was routed against; requests fail fast with FAILED_PRECONDITION until a set is loaded. - Info exposes centroid_set_version so callers can detect staleness. - internal/route: max-inner-product assignment (lowest-id ties), mirroring runespace-go-sdk's insert routing exactly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Embed/EmbedBatch return FAILED_PRECONDITION for two opposite conditions:
the model is still loading (wait and retry the same call) vs no centroid
set is loaded (push one via SetCentroids, then retry — §9.2 C4). Clients
could only tell them apart by parsing the human message, and rune-mcp
misdiagnosed the bootstrap window as the centroid case: it fired a
pointless resync and surfaced a non-retryable error for a condition that
heals itself in ~30s (first capture after a machine reboot hits this).
Attach an ErrorInfo{reason, domain} detail — BOOTSTRAPPING or
NO_CENTROID_SET — the same pattern runespace uses for its error reasons.
The status code and message are unchanged, so the transport-level
non-retry decision (see TestServer_EmbedFailsBeforeBackendSet) and old
clients are unaffected; new clients branch on the reason.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port the engine's exact version recipe (sha256 over dim LE, nlist LE, preset+NUL, float32 bits LE in id order — runespace/internal/cluster/ centroid.go computeVersion) as route.ComputeVersion, and add VerifyVersion to recompute it against the relayed Version tag. Verification runs at exactly two entry points, never per-Embed: C2 SetCentroids receipt -> InvalidArgument on mismatch C5 gob cache Load -> 'route: cache verify' error on bitrot Requires the new 'string preset = 4' header field (the missing hash ingredient). Empty preset = legacy sender -> verification skipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- verify_test: TestComputeVersionRecipe now hashes the hand-assembled bytes and compares to ComputeVersion, so recipe drift (dim/nlist order, endianness, preset NUL) actually fails the test instead of the golden buffer being dead - centroids_test: TestEmbedWithRouteRequiresCentroids wires a backend so the bootstrap gate passes, then asserts the NO_CENTROID_SET reason — previously it passed via the bootstrap error and never exercised the routing branch (would pass even with with_route=false) - centroids_test: add a content-hash-mismatch case (preset present, tag wrong) to cover the SetCentroids VerifyVersion gate the no-preset cases skip - add doc comments describing what each test verifies Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- add installMu guarding the in-memory Store + disk Persist so two concurrent pushes of different sets can't leave memory holding one set while the cache holds another (a restart would then silently serve the stale one); guards only the tail, not the Recv loop - comment that header nlist is only a preallocation capacity hint — the authoritative count is len(cs.Vectors) and the content hash already pins the exact vectors, so header nlist isn't enforced Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Under sustained embedding load (20 concurrent capture workers), EnsureStarted entered a kill/restart loop: the single-slot llama-server starves its /health endpoint while inference saturates it, the per-RPC 500ms probe missed, and one missed probe was treated as death. Worse, the restart path called stopLocked directly — skipping the inflightMu drain that Stop performs — so every queued embed was severed with a connection EOF. The restarted child saturated again immediately and the loop repeated every ~6s (observed: dozens of restarts over 3 minutes, 244 embeds cut, 240/1000 captures failed). Three defenses, in escalation order: 1. Proof-of-life tracking: every completed embed (doJSON success) and every successful health probe stamps lastAlive. A running child with a proof of life within aliveGrace (15s) that misses the quick probe is busy, not dead — EnsureStarted leaves it alone. Under saturation embeds complete continuously, so this short-circuits almost every false alarm. 2. Drain-then-reprobe (restartIfDead): with no life signs, take the inflightMu writer lock — which by construction waits for every in-flight embed, so nothing is ever severed — then re-probe with a generous budget. A merely-saturated child empties its queue during the drain and answers; the restart is skipped. restartMu single-flights bursts of suspicious RPCs. Lock order restartMu → inflightMu → lifecycleMu (Stop keeps its inflightMu → lifecycleMu suffix). 3. Only a child that stays unresponsive with zero in-flight work is killed and relaunched. Serving() gets the same busy-vs-dead awareness so health dashboards stop reporting DEGRADED during every heavy burst. Regression tests (verdict_test.go, -race): a busy child with fresh life signs is left alone; a child that recovers after the drain is not restarted and the in-flight embed completes un-severed; a truly dead child is replaced. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… test Review findings on the busy-vs-dead verdict: 1. restartIfDead did not re-check proof of life after acquiring restartMu. Followers queued behind a leader (whose drain let embeds complete and stamp lastAlive) would each run their own 2s reprobe and — under renewed saturation — their own full drain cycle in series. Re-check recentlyAlive first; the leader's drain or a restart's startup health poll has usually stamped it, so followers exit immediately. 2. TestEnsureStartedDrainRecoverySkipsRestart synchronized on a 100ms sleep to assume the in-flight embed held its RLock; on a slow runner the drain could acquire instantly while /health still reported busy, restarting a healthy child and flaking the test. Replace the sleep with the same received-channel handshake inflight_test.go uses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…recovery doJSON used http.DefaultClient (no timeout) with the caller's ctx, which carries no deadline on the embed path (the server handler forwards the gRPC ctx unchanged). A llama-server that accepts the TCP connection but never responds — a hang, not a crash, since a crash resets the connection — would block the call forever. That call holds inflightMu.RLock, so it would also wedge restartIfDead's inflightMu.Lock() drain: the recovery path that exists to replace a stuck server would itself hang on the stuck server. Wrap each embed request in context.WithTimeout(ctx, embedRequestTimeout=120s) (WithTimeout takes the earlier of the caller's deadline and the ceiling). The ceiling is far above any legitimate embed, including the ~10s deep-queue waits seen under heavy concurrency, so it only trips on a genuinely wedged backend; the request then errors, releases the RLock, and the next EnsureStarted restarts the child. TestStuckServerDoesNotWedgeDrain: a fake server that never responds — the embed returns on the timeout and a subsequent inflightMu.Lock drains, proving the recovery path no longer hangs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… (review) Per review: probeHealthy is now a pure "does /health answer 200?" query with no side effect, and IsHealthy records proof of life on success. The direct probeHealthy callers that only decide whether to restart (EnsureStarted's quick probe, restartIfDead's verdict probe) no longer reset the liveness clock as a side effect of asking. Measured trade-off (documented on IsHealthy, TestQuickProbeStampEdge): dropping the quick-probe stamp reopens one narrow edge — idle >aliveGrace, a slow first embed, then a concurrent second request whose quick probe is starved — where that second request's EnsureStarted escalates into restartIfDead and pays the verdict probe (~1.7s vs ~0.5s before). It never false-restarts, never blocks on the drain, and self-corrects on the next embed completion, so at most one request in a rare window sees the extra latency — acceptable for the cleaner separation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
No Python file has ever existed in this repo's history; the scripts/.venv/ and __pycache__/ entries came along with the initial public import. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…E is deep A UNIX domain socket path must fit sockaddr_un.sun_path — 104 bytes on macOS, 108 on Linux. When $RUNED_HOME sits under a deep directory (long scratchpad HOMEs, sandboxes), $RUNED_HOME/embedding.sock exceeds that and BOTH sides of the IPC failed: the daemon's bind died with the cryptic "bind: invalid argument", and the client's connect() hit the same limit, so fixing either side alone was useless. Introduce ipc.ResolveSocketPath: paths under 104 bytes pass through unchanged; a path at/over the limit maps to /tmp/runed-<hash>.sock, where <hash> is the first 8 bytes of sha256(cleaned canonical path) in hex. The alias is deterministic — daemon and client each resolve the same canonical path and converge on the identical short alias with no handshake — and collision-safe across users/homes because the hash covers the full path. /tmp is joined literally (not os.TempDir(), which on macOS is itself a long /var/folders/... path). The resolver is idempotent, so defensive re-resolution at any bind/dial site is harmless. Applied at every bind/dial surface: - ipc.Listen resolves before binding (stale-socket cleanup, 0700 chmod, and ownership tracking all operate on the alias). - cmd/runed resolves sockPath up front so the reachability probe, stale removal, and self-eviction watchdog match what Listen binds. - client dials the resolved alias but keeps the canonical path for spawn.EnsureDaemon, which derives the daemon's RUNED_HOME from it. - spawn.probeDaemon resolves before stat/dial; the spawn.lock and log paths stay under the canonical home. Windows named pipes are unaffected; the resolver is pure and untagged, so GOOS=windows compiles as before. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
runed redistributes llama-server (llama.cpp, MIT) and the Qwen3-Embedding GGUF weights (Apache-2.0) via self-bootstrap, but carried no license text at all — not even its own (README said "LICENSE file forthcoming"). MIT requires the copyright + permission notice to accompany all copies, and Apache-2.0 §4 requires a copy of the license with redistribution, so this was a live compliance gap on every machine that ran the bootstrap. - LICENSE: runed's own MIT text (canonical wording; copyright line "Copyright (c) 2026 CryptoLab, Inc." — entity/year subject to legal review). - THIRD_PARTY_LICENSES/: byte-exact upstream texts, fetched from source — llama.cpp LICENSE (ggml authors, MIT) and the canonical Apache License 2.0 (apache.org, sha256 cfc7749b…) for Qwen3-Embedding-0.6B, plus a factual attribution index. The Qwen HF repos declare license:apache-2.0 and ship no LICENSE/NOTICE of their own, so no NOTICE contents exist to reproduce (§4(d) applies only when the work includes one) — none were invented. - licenses.go embeds the texts into the daemon binary; bootstrap now installs them to $RUNED_HOME/licenses/ on every EnsureAll (idempotent, non-fatal on write failure), so the artifacts and their licenses always travel together. - README's License section now points at the real files. Out of scope (release-pipeline work): baking the texts into the re-hosted llama-server tarball itself, and legal sign-off on the attribution wording. Verified: go build/vet clean, all 9 packages pass, new tests prove the installed copies are byte-identical to the embedded texts and self-heal on rerun; upstream texts diff-verified against their sources at fetch time. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rywhere the LICENSE goes - LICENSE: byte-exact apache.org LICENSE-2.0.txt (sha256 cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30) - NOTICE: Apache-2.0 §4(d) attribution (runed / Copyright 2026 CryptoLab, Inc.) - NOTICE rides with the LICENSE at every redistribution point: go:embed LicenseFS → $RUNED_HOME/licenses/ install, release tarball contents, and the release workflow's tarball verification list - README + THIRD_PARTY_LICENSES/README attribution index updated (third-party texts unchanged: llama.cpp stays MIT, Qwen3 GGUF stays Apache-2.0) Verified: go build ./... + full test suite (installLicenses now asserts the NOTICE lands verbatim), LICENSE checksum matches the canonical text. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
esifea
approved these changes
Jul 20, 2026
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.
Summary
Merge
release/v1.0.0intomain.SetCentroidsRUNED_HOMEis deep; isolate long-path socket aliases per userTest
go test ./...(new coverage: route/verify, server/centroids, backend verdict/timeout, ipc/resolve, bootstrap/licenses)