Skip to main content
import { DriftDetector } from "smithers-orchestrator";

Props

PropTypeDefaultDescription
idstring"drift"ID prefix for generated task ids ({id}-capture, {id}-compare).
captureAgentAgentLike(required)Agent that captures the current state snapshot.
compareAgentAgentLike(required)Agent that compares current state against the baseline.
captureOutputOutputTarget(required)Output schema for the captured state.
compareOutputOutputTarget(required)Output schema for comparison. Should include drifted: boolean and significance: string.
baselineunknown(required)Static baseline data (object, string, etc.) to compare against.
alertIf(comparison) => booleanundefinedCustom condition for firing the alert. If omitted, the drifted field from the comparison output is used.
alertReactElementundefinedElement to render when drift is detected (e.g. a <Task> that sends a notification).
poll{ intervalMs: number, maxPolls?: number }undefinedIf set, wraps the detector in a <Loop> for periodic polling. maxPolls defaults to 100 when poll is provided but maxPolls is omitted.
skipIfbooleanfalseSkip the entire component. Returns null.

What it builds

<DriftDetector> composes primitives into the following tree:
Sequence
  ├─ Task (capture current state)
  ├─ Task (compare against baseline)
  └─ Branch (if drifted → alert element)
When poll is provided, the entire Sequence is wrapped in a Loop.

Basic usage

import { DriftDetector, Task, Workflow } from "smithers-orchestrator";
import { z } from "zod";

const captureSchema = z.object({
  endpoints: z.array(z.string()),
  schemaHash: z.string(),
});

const compareSchema = z.object({
  drifted: z.boolean(),
  significance: z.string(),
  changes: z.array(z.string()),
});

<Workflow name="api-drift-check">
  <DriftDetector
    captureAgent={snapshotAgent}
    compareAgent={diffAgent}
    captureOutput={outputs.capture}
    compareOutput={outputs.compare}
    baseline={{ endpoints: ["/users", "/orders"], schemaHash: "abc123" }}
    alert={
      <Task id="notify" output={outputs.notify} agent={slackAgent}>
        API drift detected — notify the team.
      </Task>
    }
  />
</Workflow>

Poll mode

Poll periodically to detect drift over time:
<DriftDetector
  id="config-drift"
  captureAgent={configReader}
  compareAgent={configDiffer}
  captureOutput={outputs.configSnapshot}
  compareOutput={outputs.configDiff}
  baseline={knownGoodConfig}
  poll={{ intervalMs: 60_000, maxPolls: 24 }}
  alert={
    <Task id="alert" output={outputs.alert} agent={pagerAgent}>
      Configuration drift detected — page on-call.
    </Task>
  }
/>
This runs every 60 seconds, up to 24 times.

Custom alert condition

Use alertIf to override the default drifted check:
<DriftDetector
  captureAgent={snapshotAgent}
  compareAgent={diffAgent}
  captureOutput={outputs.capture}
  compareOutput={outputs.compare}
  baseline={previousRelease}
  alertIf={(comparison) => comparison.significance === "breaking"}
  alert={
    <Task id="block-deploy" output={outputs.block}>
      Block the deployment — breaking changes detected.
    </Task>
  }
/>

Generated task ids

With the default id prefix of "drift":
TaskID
Capturedrift-capture
Comparedrift-compare
Poll loopdrift-poll
Override with the id prop:
<DriftDetector id="schema" ... />
// → schema-capture, schema-compare, schema-poll

Notes

  • <DriftDetector> is a composite component. It renders a tree of <Sequence>, <Task>, <Branch>, and optionally <Loop>.
  • The compareOutput schema should include drifted: boolean so the default alert condition works. If you use alertIf, any schema shape is fine.
  • Without alert, the component captures and compares but takes no action on drift.
  • Without poll, the component runs once. Use poll for continuous monitoring.