Orchestrator / tensor — corrected final state

Canonical target architecture: [[jobcache-control-plane]] (architecture/CONTROL-PLANE.md, locked 2026-06-08) — the THIN orchestrator: tensor in RAM + a NEVER-ENDING priority queue built from the DB; hands every device its tensor + lease in one call; one-row-per-URL last-writer-wins; NO device is ever cut off — trust only WEIGHTS queue priority (recency, observer trust, page volatility, …), never a gate; a low-trust result just raises that target's re-check priority so another actor corroborates; Render demoted to pure frontend. This file is STATUS toward that target.

Principles live in sporewright (docs/ROUTING-MODEL.md, MODEL.md). This file is the status, not the philosophy.

Lease engine — BUILDING LIVE (2026-06-08), brick by brick toward CONTROL-PLANE.md

  • Orchestrator owns its priority queuequeue.rs builds it straight from CrateDB (HTTP :4200/_sql via curl), ranked staleness × trust-deficit; excludes targets leased within LEASE_TTL (600s). The M2 budget loop still runs alongside.
  • RabbitMQ work queue (enqueue) — a refill loop (QUEUE_REFILL_SECS=120) purges + publishes the top-50 due targets to cv.work with bucketed priority. RabbitMQ = the lease-delivery engine, PRIVATE to the orchestrator (lapin, plain AMQP 5672, co-located on alwaysdata; devices NEVER hold AMQP creds — untrusted fleet).
  • Device lease endpoint (consume)POST /lease: pop highest-priority target (atomic dequeue) → stamp leased_at → mint primary Corbet + B2 overflow deposit URLs → return { lease, deposit_url, deposit_overflow_url, deposit_key, tensor } — ONE call. Devices only ever see HTTP.
  • Device deposits to object storage; orchestrator folds into CrateDB — the device PUTs its one observation to the orchestrator-minted presigned URL (deposits/<ad_id>/<token>.json); the URL is object-scoped, so it IS the lease write key (no DB/queue/store credential in the device). Corbet S3 is primary; B2 accepts the same logical deposit key only after a primary failure. ingest.rs is the ONLY corpus writer and tries both inboxes before charging a poison attempt. object_store.rs is pure-Rust SigV4 (PUT/GET/LIST/DELETE). /observe + the DB lease-key write-through REMOVED (CrateDB has no per-lease RLS, so the presigned URL is the credential instead). lease_key column deprecated (kept nullable).
  • Axiom ops tapaxiom.rs: fire-and-forget lease/queue_refill/ingest/planner_tick telemetry to jobcache_telemetry (EU edge); never read back for a control decision. Proven live.
  • Device client — TS, orchestrator-backedorchestrator-device-client.ts: a JobcacheDeviceClient that leases from the orchestrator (/lease), resolves the adapter from the url HOST (adapterMetaForUrl — the orchestrator stays dumb), and on submit RS03-seals fields['raw'] to the master pubkey, /archive-grant + PUTs it to raw/<hash>, then PUTs the structured value (raw excluded) + the archive pointer to the presigned deposit url. The EXISTING cycle + adapters + method-chain + method-learning are reused UNCHANGED behind the client seam (zero change to the broker path or the scraping/learning code). runConfigured­Orchestrator­DeviceCycle wires it. Four focused tests + typecheck clean.
  • Corbet-primary/B2-overflow store-and-forward — Rust + TS devices try each primary object-scoped PUT once, then the paired B2 grant. Deposit notifications identify the successful inbox as a latency hint; the ledger sweep still tries both. Raw B2 objects use overflow/raw/; raw_overflow.rs streams their encrypted bytes to canonical raw/, reads the Corbet object back, verifies byte length + SHA-256, and only then deletes the overflow copy.
  • Corpus archive pointerad_observation_state.raw_object_key + raw_wrapped_dk; the CrateDB self-ingest staging + upsert carry the pointer from the deposit into the row, so the corpus links to its RS03-encrypted job ad (serve / re-extraction via a raw_grant).
  • Rust RS03 encoder — PROVEN (crates/jobcache-device/src/rs03.rs): the Rust device seals the raw job ad to the master pubkey (X25519 sealed box + AES-GCM + brotli), all pure-Rust / musl-clean (x25519-dalek, brotli, hkdf, getrandom). Cross-language interop proven with the PRODUCTION keypair: Rust seals → TS decryptRs03 decrypts → exact match. brotli is the ONE fleet codec (pure-Rust encoder exists; Node has brotli built-in; beats zstd on HTML/text).
  • Rust orchestrator transport (mechanical remainder) — mirror the TS orchestrator client in the Rust cycle (main.rs): an orchestrator.rs (curl lease / archive-grant / deposit, like broker.rs), adapter_for_url (host→adapter), and the deposit path (extract fields['raw']encrypt_rs03 → archive-grant → PUT → deposit structured value + pointer). The crypto + the flow are proven; this is the same flow in Rust. Then the fleet runs the orchestrator client.
  • Generic model hoisted to sporewright — priority, buckets, lease lifecycle, budget band live in sporewright::schedule (public); the server is thin I/O (CrateDB SQL, RabbitMQ, B2, HTTP).

The model (the bit I kept getting wrong)

  • The option axis is the candidates chosen among — API providers / services / execution archetypes / P2P peers offering a service. NEVER a roster of individual devices.
  • The device LEVEL is learning at the device — the finest context tier where one device applies a LOCAL DELTA to the global consensus for its own conditions (the Australian device for whom the European mean isn't best). Not a list of devices.
  • Weights flow DOWN, measurements flow UP. Orchestrator/workspace seed gates (hard gate = no key / missing capability) and meddle with weights (policy); devices mostly measure + report.
  • Knowing which devices are connected is the OPS story (telemetry → Axiom → dashboard; cockpit /devices already does it), NOT a routing input.
  • CareerVector is where the tensor earns its keep (the many LLMs competing on the option axis). JobCache is trivial: a device declares its capability and gets the one kind of work it can do — a hard capability gate — which the broker's selectReadyTasks lease already does.

What's DONE (the bulk — most of it already runs)

  • Tensor + budget substrate — clock-free Σ value·weight, gates, normalizer, per-pool shadow-price λ + Budget::tick + reduce_sum + TokenBucket. Rust+TS, golden-pinned (sporewright).
  • CareerVector options routing — the LLM cascade resolves the real option axis (resolveCascadeTensor, live; gates on missing key, orders by quality/financial/route).
  • JobCache device → work — trivial capability gate; the broker leases capability-matched work (selectReadyTasks, live). The orchestrator owns it outright; nothing to build here.
  • The bargain (JobCache) — device declares capabilities (upsertCapability) → gets fitting work. Live.
  • The trust loop, tactically — pairwise agree→+ / disagree→− (updatePairTrust, devices at 0.96), verify-dispatch on disagreement, freshness/time-mediation. Live in the broker.
  • SOFT trust principle recorded — trust is a soft modulation + verify pressure, never a hard gate (sporewright §3C/Story 6). The +∞ min_trust gate in router.rs is superseded.
  • Device observability — cockpit /devices (online/offline, trust, capabilities) + ingestion-health + source-matrix. The "who's connected / what's going on" story.
  • Planner heartbeat — the orchestrator slow tick drives POST /api/planner/run hourly.

JobCache coverage-budget loop — BUILT 2026-06-08 (the M2 fold)

The orchestrator's documented job: "it decides when and with what budget to ask" (tick.rs). The budget DECISION lives in the orchestrator (alwaysdata), NOT the broker.

  • Broker exposes RAW coverageGET /api/coverage (per-source pending/failed/done24h + totals). Measurement only; control-gated; on Render because that is where the DB is.
  • Orchestrator folds coverage → budgettick.rs::derive_budget: pending BACKLOG is the lever — idle queue ⇒ INVEST (max_tasks 100, refetch 40), saturated ⇒ BACK OFF (10, 10), linear between. Each tick GETs /api/coverage, derives, drives POST /api/planner/run. Coverage-read hiccup ⇒ conservative fixed fallback (heartbeat never stops). 4 fold unit tests.
  • Deploy path is NOT blockedinfra/jobcache/redeploy-orchestrator.sh (drüben build on build-brain k3s → deploy-orchestrator.sh ships the static ELF to alwaysdata over KEYED ssh → restart user_program 1047719 via the alwaysdata API → verify /healthz). The old fleet-build "BLOCKED (needs human) / 502" note was STALE — orchestrator is LIVE (healthz 200).
  • ⚠️ WRONG TURN avoided — first cut put the budget BALANCE in the broker's planner (Render) to dodge the alwaysdata deploy. Reverted: balancing belongs in the orchestrator. The planner keeps only TACTICAL per-source distribution (staleness/fairness) next to the DB.

Genuine remainder — small + DEFERRED (not blocking; sensibly waits)

  • F2 (CareerVector). The full λ price-loop — per-pool usage → shadow-price λ (Lagrangian dual ascent) → publish; resolve auto-reshards off saturated channels. Primitives BUILT (budget.rs/budget.ts). The JobCache loop above is the simpler backlog-envelope cousin; the full λ loop is a CareerVector LLM-quota concern — defer until real quota pressure exists.
  • Device-level local deltas — a device re-scores its chain from its Δ vs the global mean (geo/connection). A refinement; only matters once there's a non-trivial option set per device.
  • Rich direct device↔orchestrator channel (the bijective wire) — only needed when a product grows a real options competition the broker's lease doesn't already cover. Not now.

Bottom line

The orchestrator/tensor is largely real: the tensor + budget substrate is built, CareerVector routes its LLM options over it, JobCache's device→work is the trivial capability gate the broker already serves, device connectivity is the cockpit's ops view, and the trust loop runs. The misframing (devices-as-options) made this look like a big build; corrected, the genuine remainder is the budget price-loop wiring (deferred until real quota pressure) + a couple of refinements. This thread is closed — ready to advance.

Superseded — do NOT build

Devices on the option axis (they're not options). Hard +∞ trust gate (→ soft cost + verify). Borda seed-consensus (→ declared-presence quorum). Matryoshka stack (→ one sparse tensor + tier weight).

Source: wiki/content/working-memory/orchestrator-checklist.md