Skip to content

Atlas step 6: golden-parity gate (Gate B) + cutover #5356

Closed

Description

@dezren39

Step 6 of the Atlas binary+service epic (#5350). Design of record: doc/2026-11-04T08-07-13Z_design_atlas_binary_service/ (§5 gates B/C + migration phases, §7 Q2/Q3).

Scope

  • Gate B run: the golden 311 (golden-queries-v3) through the binary vs the Python production path, both against the production corpus (107,639 chunks; the reference is a same-day Python run on the same corpus, not the historical v3-corpus 189/0.3894). Run through the eval3.py harness shape adapted to call the binary, so per-question rank diffs are inspectable. Tolerances (design §5): hits@10 within ±2 questions, overall MRR within ±0.005, no category (exact-fact / paraphrase / dictated-garbled / cross-session) regressing > 0.01 MRR. Configuration identical both sides, with the Gate C (A/B-winning) vector backend as the default.
  • Cutover phases (design §5, additive-first; each flip is Drew's per-surface call):
    1. CLI opt-in: binary installed as atlas-search from its own profile position; the Python wrapper renamed — not removed — to atlas-search-py.
    2. Service opt-in: unit enabled on amd; the §3 v1 client tokens issued (with Drew; Q2 decides caps above personal); --via-service live from laptop + sandbox CLIs; standalone remains default.
    3. Default flip per surface (laptop first, sandbox after a soak; Q3 recommends the sandbox default to via-service at this phase).
  • Rollback at every phase is repointing the name/flag — there is no data migration anywhere in this program. The Python remains installed as fallback and permanent eval reference after cutover.

Dependencies: step 5; Gate A green (step 1); the A/B verdict (Gate C). Drew's answers to design Q2/Q3 gate phases 2–3 details, not the Gate B run.

Verification gate (exit criteria)

  • Gate B results recorded on this issue with the paired per-category table; a tolerance miss stops the cutover (the binary does not flip defaults on a failed gate — the miss gets its own issue and this step stays open).
  • After each flip: the deployed atlas-search --version-equivalent and one golden spot query verified on that surface, and the Python fallback demonstrated still working.
  • Epic Epic: Atlas as a Rust binary + multi-client search service (b+c) #5350 closes when phase 3 has run on both surfaces or Drew rules a surface stays on the Python.

Out of scope: decommissioning the Python (explicitly never — it is the eval reference), new clients beyond §3 v1, publish-pipeline changes.

Acceptance criteria

  • Gate B run completed: golden-311 through the binary vs a same-day Python reference run, both against the production corpus, via the eval3.py-shaped harness; per-category paired table recorded on this issue.
  • Tolerances met (design §5): hits@10 within ±2 questions, overall MRR within ±0.005, no category regressing >0.01 MRR — a miss stops the cutover and spawns its own issue.
  • Cutover phases (CLI opt-in → service opt-in → per-surface default flip, laptop first) each land only on Drew's per-surface call, with the deployed binary version + a golden spot query verified per surface and the Python fallback demonstrated working.
  • Epic Epic: Atlas as a Rust binary + multi-client search service (b+c) #5350 closes when phase 3 has run on both surfaces or Drew rules a surface stays on the Python.

Activity

  1. added
    source/humanFiled directly by a human
    priority/normalOrdinary priority (the default)
    effort/lLarge: multi-day or open-ended; expect staged check-ins
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Nov 4, 2026
  2. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    Intake review — fleet monitor, 2026-11-04.

    Related: epic #5350 (Atlas binary+service); step 5 (#5355, packaging/deploy) and Gate A (step 1) + the Gate C A/B verdict are prerequisites. Design of record: doc/2026-11-04T08-07-13Z_design_atlas_binary_service/.

    Notes: the issue's own Verification gate is the substance of the acceptance criteria appended above. The same-day Python reference run on the production corpus is the easy part to get wrong — the historical v3-corpus numbers (189 / 0.3894) are explicitly not the reference.

    Risks: phases 2–3 details wait on Drew's answers to design Q2/Q3 — that gates the cutover details, not the Gate B run. The Gate B run itself is unblocked once step 5 lands.

    Suggested approach: run Gate B as soon as the step-5 package exists; record the paired table here before any flip conversation.

  3. added
    queue/queuedHas a queued task in the Supabase agent queue
    task/triageQueue packet kind: verify, reproduce, and route this item
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed on Nov 4, 2026
  4. 43 remaining items

  5. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  6. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 4, 2026
  7. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    Step 6 — Gate B verdict + cutover evidence

    Gate B: PASS. The service is live and the cutover is complete (one deferred decision, recorded at the end — it was never part of this gate).

    Gate B — paired golden-311, unfiltered, scan backend, production corpus

    Rust = the packaged binary (step-5 output); Python = the deployed reference CLI, run fresh back-to-back per question (the 05:31 banked Python numbers are shown only for drift context — the fresh pairing is the measurement).

    arm hits@10 MRR errors
    Python (fresh, paired) 184 0.3755 1
    Rust (packaged) 184 0.3771 1
    Python (banked 2026-11-04 05:31, context) 185 0.3787 0

    Paired deltas (Rust − Python): Δhits@10 = 0 (gate ±2), ΔMRR = +0.0016 (gate ±0.005). Per-category MRR deltas: exact-fact 0.0000, dictated-garbled 0.0000, paraphrase −0.0032, cross-session +0.0175 — no category regression beyond 0.01 (the only negative is −0.0032; the cross-session delta is an improvement).

    Stronger than the bands require: for all 309 questions where both arms returned results, the top-10 lists are byte-identical (309/309). The only two rank disagreements in 311 are one-sided transient Turso errors, and they cancel: v3q058 — the Rust call hit a Turso global timeout at 325.8s (Python ranked the gold 2nd); v3q276 — the Python arm exited SystemExit(2) (Rust ranked the gold 1st). The fresh Python reference also drifted slightly against its own banked run (185→184, −0.0032 MRR) under the concurrent row rewrite, which is why the gate was run paired rather than against the banked numbers.

    ORT adjudication: the nix closure's ONNX Runtime 1.27.1 vs the Python pipeline's 1.31.1 produces no measurable ranking drift — consistent with step 1's bit-identity gate. No adjudication action needed.

    The filtered pairing was skipped per the step plan (the unfiltered set is the decision set; filtered would have doubled a ~3.5h run). Raw records: ~/atlas/v3/swap-2026-11-03/gateb-results.jsonl (driver gateb_eval.py alongside). One process death at pair 182 (clean exit, unattributed) was absorbed by the resumable JSONL with zero loss.

    Default backend, as ruled: scan. The default is chosen on unfiltered numbers across backends; the index arms have no numbers until the row rewrite completes, so the cutover default is the incumbent scan (banked 185 / 0.3787), and the index-backend decision is explicitly deferred to the A/B completion owned by the rewrite chain (hatch-personal#20).

    Activation

    • atlas-search init was run before any enablement could deploy: ~/.local/state/atlas-search/service.json (0600) holds the v1 table as ruled in Q2 — drew-cli capped secret-bearing; openchamber / fork-server / sandbox-scripts capped personal.
    • Enablement: PR home(atlas-search): enable the atlas-search user unit — step 6 cutover (#5356) #5471 (Install.WantedBy) merged 3b3df1be3. It deployed via the fleet switch chain's successful switch (2026-11-04 10:19 CST, SWITCH_EXIT=0, generation /nix/store/w768inbg2z…-nixos-system-amd-26.11.20261102.f9887a8) — two earlier attempts that morning failed inside the chain's own scope (07:04 libvirtd systemd-credential decryption; 09:29 home-manager refusing to clobber the hand-installed broker wants symlinks) and are recorded here only because they gated this step's timing.
    • Live state: unit enabled + active; the running ExecStart is the deployed generation's own build of pkgs/atlas-search (/nix/store/dfs864kg…-atlas-search-0.1.0); /health → {"status":"ok","embedder_state":"ready"}; NRestarts was 0 from first start (09:39, during the chain's activation sequence) until the deliberate battery restart below.
    • Exposure: tailscale serve --tcp 18760 → 127.0.0.1:18760 (tailnet only), i.e. http://amd-6.raptor-pumpkinseed.ts.net:18760. Deviation, recorded: the --https form was created first and verified from the laptop, but the sandbox's egress proxy cannot complete TLS to it; the tcp form is the fleet's door pattern and is what the sandbox's tunnel proxy reaches.

    Client distribution (Q2/Q3 as ruled)

    • Laptop: ~/.config/atlas-search/client.json = drew-cli. The Python wrapper was renamed to ~/.local/bin/atlas-search-py (verified working); atlas-search now resolves to the Rust binary via an interim symlink to the step-5 store output. PR home(atlas-search): ship the atlas-search package in home.packages — step 6 cutover (#5356) #5479 (merged 49d11b5b7, atlasSearch in home.packages) is not in the current generation — when a later switch deploys it, the profile binary becomes the durable path and the interim symlink should be removed.
    • Sandbox: the packaged closure was imported into the sandbox nix store (binary runs: atlas-search 0.1.0); the model was provisioned into the state dir by copying the laptop's verified copy — both files re-hashed on arrival and matching the pins (tokenizer f1cc44ad…, model c0c53d7f…; fetch-model itself is blocked on the sandbox by its proxy's MITM CA, which the binary's rustls stack rightly rejects). ~/.config/atlas-search/client.json = sandbox-scripts (personal). ~/tools/bin/atlas-search is now a wrapper that forwards loopback:18760 over the shared laptop-ssh control connection and defaults to via-service — necessary because the via-service client is proxy-blind (follow-up filed: atlas-search: via-service client should honor proxy env (ureq Proxy::try_from_env) #5597); its loud standalone fallback was verified live in exactly that failure mode. The Python wrapper was renamed to ~/tools/bin/atlas-search-py (verified working).

    Verification battery (all against the live service)

    • via-service ≡ standalone: identical top-10 id lists for the same query (service 10.5s vs standalone 67.8s under load).
    • Dedupe: a fresh query repeated via-service — 13.44s → 0.02s, identical results.
    • Ceiling canary: secret-bearing chunk 537 — drew-cli (requesting secret-bearing) gets it at rank 1 with max_sensitivity: secret-bearing applied; sandbox-scripts (requesting secret-bearing) is clamped to personal in the applied filters and chunk 537 is absent.
    • Restart: a job submitted pre-restart returns HTTP 404 job_expired_or_unknown after systemctl --user restart; health returns ok and the embedder returns to ready after a warm query.
    • Python reference: atlas-search-py verified working on both machines after the renames.
    • Sandbox via-service end-to-end through the wrapper: correct results in ~2–60s (vs ~9.5 min for a standalone hybrid search over the sandbox's Turso path); the service's dedupe makes repeats ~instant.

    Deferred (not part of this gate)

    The index-backend decision (scan vs index-fill vs index-fixed as the default) waits on the row rewrite + the parked index-arm A/Bs — owned by the rewrite chain on hatch-personal#20. When those numbers land, the default flips by config if an index arm wins; nothing in this cutover needs to be redone.

  8. added
    queue/claimedQueue worker lifecycle state
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Nov 4, 2026
  9. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

effort/lLarge: multi-day or open-ended; expect staged check-inskind/enhancementmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)source/humanFiled directly by a humantask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions