Skip to content

feat(providers): external-engine provider kind — never orchestrated, read-only health, surfaced - #386

Merged
kh0pper merged 11 commits into
mainfrom
feat/external-engine-provider
Sep 24, 2026
Merged

kh0pper merged 11 commits into
mainfrom
feat/external-engine-provider

Conversation

@kh0pper

@kh0pper kh0pper commented Sep 24, 2026

Copy link
Copy Markdown
Owner

Strix Halo track, sub-project 4. Crow now treats an externally operated engine as a first-class provider kind. The first case is halogen on raven (http://10.0.0.126:8030/v1), and gufo is next if pi-lab's evaluation passes.

  • Spec: docs/superpowers/specs/2026-09-23-external-engine-provider-design.md
  • Plan: docs/superpowers/plans/2026-09-23-external-engine-provider.md (two adversarial review rounds)

What changes

  • Marker. A row carrying gpu_policy.engine = {managed:"external", host, label} is an external engine. The marker replicates with the row, so every instance knows not to manage it.
  • Crow never orchestrates it. The orchestrator and lifecycle.js check the marker first:
    • acquire returns null, or throws ExternalEngineError;
    • warm resolution returns null, including when it would resolve to a marked sibling;
    • residency skips the row, isAlwaysResident is false, and the deferred set excludes it;
    • it is never in a mutex group or evicted as a sibling, and idle-revert never targets it.
  • Validation (transition-only). upsertProvider refuses writes that change engine, bundleId or runtime into a contradictory state (EXTERNAL_ENGINE_CONFLICT/INVALID).
    • Spread writes that re-send the stored value pass, even for contradictory or malformed rows replicated from peers.
    • syncProvidersFromModelsJson and repairProviderHosts isolate EXTERNAL_ENGINE_* errors per row and rethrow everything else.
    • The reconciler preserves the marker.
    • registerModel refuses a marked row before any state is written.
  • Read-only health. Each gateway runs GET <base_url>/models every 60 s (CROW_EXTERNAL_ENGINE_POLL_MS) and records the result in a new external map in provider-health.
    • The request carries no auth header and has a bounded 3 s timeout.
    • It runs only against config loaded from the DB.
    • It is off in the test suite.
  • Surfacing.
    • A separate nest signal, externalEngines, is info at most and never warns or pushes. External engines stop for hours during raven windows by design, and Crow is not their operator.
    • The resident providers signal is byte-identical, and a regression test covers the push path: with a shared id, the dedupe would have suppressed real resident pushes.
    • The Providers tab gets a reachability dot and an escaped "external · host" badge.
  • The health-notify loop is extracted into runHealthNotifyCycle without changing its behaviour.

Rollout

  • After deploy, only raven-flash-next gets marked, via upsertProvider on crow. It replicates through the sync outbox.
  • pi-lab cleared the read-only probe: "fine at any time, windows included".
  • raven-halogen-smoke points at a dead port (:8731) and stays unmarked, pending Kevin's call.

Verification

  • Every task passed a spec and quality review.
  • The final whole-branch review (Opus) found nothing Critical. It confirmed:
    • older-code peers (black-swan, grackle) neither strip the marker nor try to start the row;
    • nothing new writes the providers table or state.json on a timer.
  • The final-review fixes were applied and re-reviewed.
  • Full suite: 5094/5094 on Node 24. check-ports passes.

kh0pper added 11 commits September 23, 2026 14:21
…ition-only validation, info-only surfacing, reconciler keeps marker)
… id, spread-safe malformed policies, EXTERNAL_ENGINE_-only per-row catches, lifecycle seam)
…conciler keeps the marker and isolates rows
- registerModel now refuses immediately when the existing row at a target
  provider id is an external engine (EXTERNAL_ENGINE_CONFLICT), before
  allocatePortFn/the conversions snapshot/the registry entry/saveState run
  — closing a leak where a contradictory replicated marker+bundle row could
  slip past the collision guard and reserve a port before upsertProvider's
  own validation caught it.
- docs/architecture/models.md: correct the malformed-gpu_policy and
  unmark-then-register claims to match spec §2.2 and registerModel's actual
  behavior; note repairProviderHosts' per-row isolation.
- tests/health-notify-cycle.test.js: neutralize CROW_BACKUP_DIR and the
  tailscale reader like external-engines-signal.test.js does.
- Minor comment fixes: move providers-db.js's orphaned JSDoc onto
  assertExternalEngineWrite, note the belt-and-braces !p.disabled filter in
  external-engine-poll.js, document runHealthNotifyCycle in health-signals.js's
  header, and point the plan doc's operational check at
  /var/log/crow-inference/gateway.log instead of journalctl for crow-gateway.
@kh0pper
kh0pper merged commit e83448f into main Sep 24, 2026
3 checks passed
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