SwapAI

Legacy compatibility

Migrate from the v0.3 init API without keeping automatic training semantics.

init() remains available for applications built against SwapAI v0.3. It preserves classification and collection compatibility while supported schemas migrate to the current database format.

import { init } from "@swapai/core";

const legacy = init({
  name: "existing-classifier",
  result: { type: "boolean" },
  retrainOnCount: 50,
  acceptableError: "10%",
  retestInterval: 100,
  retestRevertOn: 3,
  model: "needle2",
  maxTrainingSet: 10_000,
  automaticTraining: false,
});

Important differences

  • Set automaticTraining: false. New deployments must not start local training from an application request or deploy hook.
  • On the first configured open, never-trained historical rows are assigned deterministically across all four purposes. If any training exposure evidence exists, the old train/held-out split becomes training/validation only and never representative or coverage evidence. See Data policy.
  • Existing promoted models remain subject to artifact and runtime-version checks.
  • The modern candidate, provider and inspection workflow is available through createClassifier().
  • erase() performs the awaited data-and-artifact erasure path. clearTrainingData() remains for source compatibility with older queued-clear code.
  1. Upgrade and initialize against a backup copy of the data directory.
  2. Confirm the schema migration and classifier inspection.
  3. Replace init() with createClassifier() and provide the reference function.
  4. Add product decision boundaries and low-cardinality facets.
  5. Keep the reference authoritative while protected datasets become ready.
  6. Configure an explicit provider, request one candidate, review it and promote it manually.

retrainOnCount is a compatibility option, not the modern trigger for paid or remote training.

Explicit held-out migration

A failed legacy local attempt can leave every old held_out row labelled as validation even when the trainer never received or evaluated those rows. SwapAI cannot infer that history. Use this migration only when a named operator can independently establish that the held-out examples were never used for model selection.

Stop every process using the classifier first. The inspection is read-only, but the apply step requires an exclusive database transaction.

Inspect the plan

Choose exact targets, then inspect without changing data. This production-shaped example allocates 270 eligible legacy held-out examples as 135 validation, 100 representative-test and 35 coverage-test examples:

import {
  inspectLegacyHeldOutMigration,
  migrateLegacyHeldOutExamples,
} from "@swapai/core";

const options = {
  dataDirectory: ".swapai",
  classifierName: "accountant-relevance",
  targetExamplesByPurpose: {
    validation: 135,
    representative_test: 100,
    coverage_test: 35,
  },
} as const;

const plan = inspectLegacyHeldOutMigration(options);

if (plan.status !== "ready") {
  throw new Error(JSON.stringify(plan.blockers));
}

Eligibility comes from the durable legacy_dataset_purpose_adoptions.adopted_at timestamp. Targets apply only to pre-adoption held_out/validation rows where created_at < adopted_at. They must total that exact eligible count and satisfy the classifier's stored aggregate and per-result-bin minimums. They do not include protected rows collected after adoption.

The inspector reports the training rows that will be preserved, eligible held-out count, attempt counters, result-bin/facet groups, blockers, target counts and planSha256. Do not copy these example counts to a classifier with different data or requirements.

For the production-shaped dataset, 1,064 training rows are already present. Five post-adoption protected rows—three validation, one representative-test and one coverage-test—are preserved in addition to the 270 legacy targets. The final totals are therefore:

const expectedFinalExamplesByPurpose = {
  training: 1_064,
  validation: 138,
  representative_test: 101,
  coverage_test: 36,
};

migration.examplesByPurpose reports only the 135/100/35 migrated legacy targets. Read classifier.inspect().examplesByPurpose for final totals including the preserved fresh rows.

Review blockers

status: "blocked" includes stable blocker codes:

  • active_runtime: a classifier process has a current heartbeat;
  • candidate_evaluation: completed candidate, training or shadow evaluation evidence exists;
  • candidate_artifact: a candidate model, LoRA or sidecar exists on disk;
  • trained_generation: a trained generation or indexed model artifact exists;
  • previous_generation: another generation may have used the held-out rows;
  • training_run: a provider training run exists;
  • invalid_legacy_dataset: adoption provenance is missing, a held-out row has the exact adoption timestamp, or eligible pre-adoption rows are no longer all legacy validation rows;
  • target_count_mismatch: targets do not assign every eligible held-out row exactly once;
  • target_below_configured_minimum: targets are below the stored dataset requirements;
  • result_bin_lacks_protected_examples: a result bin cannot meet its validation and coverage minimums.

Observed local training-attempt and examples-used counters are included in the plan and durable evidence, but do not establish what the trainer saw. A failed attempt's training.jsonl and shared base checkpoint do not prove that held-out evaluation happened. Equally, their presence does not prove the migration is automatically safe. The operator must verify the history outside SwapAI.

Apply the reviewed plan

Apply the exact plan hash with an explicit attestation:

const migration = migrateLegacyHeldOutExamples({
  ...options,
  expectedPlanSha256: plan.planSha256,
  attestation: {
    heldOutExamplesWereNeverUsedForModelSelection: true,
    operator: "deployment-owner",
    reason: "Verified no candidate model or evaluation was produced",
  },
});

operator and reason must be non-empty. The apply call opens an exclusive transaction, rebuilds the plan and refuses to continue if expectedPlanSha256 no longer matches. A fresh row added after inspection invalidates the plan and requires another inspection.

The migration preserves every original training row and every post-adoption protected row. It changes only original pre-adoption held_out rows that still have the legacy validation purpose. A missing adoption timestamp blocks the migration. A row with an equal adoption timestamp (created_at === adopted_at) is ambiguous and also blocks rather than guessing which side of the cutoff it belongs to. Assignment is deterministic across result-bin and facet groups.

The durable migration evidence stores the operator, reason, observed attempt counters, targets, resulting counts, migration time and plan hash. Repeating the operation returns already_migrated and the prior evidence instead of reallocating rows.