From effect-questions

September 17, 2026 ยท View on GitHub

This is a semantic redesign, not a source-compatible drop-in replacement.

Effect versionNative Questions
Questions.about(state) with provided Layerconst questions = Questions.create({ model }); questions.about(state)
yield* q.is(...)await q.is(...)
Effect-valued live contextquestions.about(({ signal }) => readCurrentContext(signal))
A reusable EffectA function returning a fresh Promise
Effect.catchTag("UncertainDecision", ...)try/catch and error instanceof UncertainDecision
Question.choice / Question.score / Question.booleanSame concepts; normalized immutable definitions, no Effect Schema codecs
Stream.fromIterable(items)Streams.from(items)
Stream.mapEffect(f, { concurrency }).map(f, { concurrency }); callback receives { signal, index }
Stream.filterEffect(predicate).filter(predicate); sequential, or concurrent .map followed by .filter
Stream.scan(seed, reducer).scan(() => seed, reducer); no seed emission
Stream.mapAccumEffect(seed, step).mapAccum(() => seed, step)
Stream.takeUntil(predicate).takeUntil(predicate), inclusive
Stream.runForEach(effect).forEach(callback, { signal })
Effect timeout / interruptionProvider timeoutMs, or an AbortSignal passed to a terminal/operation
Curried dual Answer/Decision helpersOrdinary data-first functions
Effect services / LayersExplicit QuestionModel injection

each is still one request for the collection, not parallel per-item calls. choose still returns an original candidate, and branch still runs only one lazy handler. Add explicit fallback candidates and handle confidence rejection at the application boundary.

Validation is intentionally stricter: probability mass, chosen maximum, weighted score and rubric legend must agree. Inconsistent provider responses fail instead of being silently projected. Numeric options reject NaN, Infinity and invalid ranges. Do not confuse score confidence, boolean separation and P(true).

The native implementation has no built-in Effect metrics/tracing service. Decorate QuestionModel.evaluate or inject a traced fetch. Observe one evaluation rather than incrementing metrics for every projected batch field. Propagate cancellation through decorators and avoid logging context, credentials or sensitive error causes by default.

Original inspiration is acknowledged, but the runtime and validation code are independently implemented. No Effect code, runtime dependency, schemas or internal abstractions are bundled.

Adopt Zod incrementally (alpha.2)

Existing Question.boolean, Question.choice, Question.score, plain batches and their inferred results remain supported. For schema-driven results, pass a z.object(...) (not a raw object of Zod fields) to ask. Attach instructions with .describe(), standard .meta() fields, or typed .register(Schema.registry, ...) annotations. Explicitly annotate numbers as probability or score. The result is the parsed z.output<typeof schema>, including transforms.

Install the Zod 4 peer alongside the package. Existing clients importing the root now resolve that peer even when using only the question-batch API. Applications using only streams or the standalone Jev provider can keep their independent subpath imports. No Effect schema adapter or free-form extraction is implied; see the schema guide.

Multiple providers (alpha.3)

Existing Jev.create({ apiKey, baseUrl }) calls remain valid. New code can use TypeSafe.create with baseURL, a configured System One host, or the optional Vercel integration. Passing both URL spellings to Jev is rejected.

Type change: usage counters are now optional, because SDK providers may omit them. Missing tokens are unknown, not zero; account for that explicitly. Choice/score evidence now optionally records confidenceSource; native TypeSafe reports provider, SDK defaults to margin, and custom policies report custom. Thresholds may need recalibration across providers. New optional envelope fields preserve rounding, warnings and provider metadata. Missing SDK distributions are rejected.

The root package still needs only Zod. The Vercel subpath adds an optional @ai-sdk/gateway peer and requires Zod >=4.1.8 for the SDK, while native schema support retains its Zod 4.0.0 minimum. See provider setup and contracts.

HTTP and replay (alpha.4)

No changes are required for ask, evidence, existing Jev/System One retry objects or streams. ofetch 1.5.0 is a new runtime dependency, installed with the package; it is not bundled into the independent streams or structural AI SDK bridge. The Gateway preset now accepts the same explicit retry/hook policy as the native providers. Arbitrary supplied SDK models still own their transport policy.

Use run only when typed output plus evidence/replay are needed, and prepare when context must be captured before inference or rerun after an error. HTTP hooks run per attempt; Zod transforms run per explicit inference, never per HTTP retry. Live replay may incur charges and is not an idempotency or offline playback feature. See the full guide.

Adopt semantic DX and duration strings (alpha.5)

Existing clients and numeric timeoutMs/initialDelayMs/maxDelayMs/delayMs settings remain valid. New code can use timeout: "15 seconds" and retry: { maxRetries: 2, initialDelay: "200 ms", maxDelay: "3 s" }. Do not supply both spellings. Bare numbers stay milliseconds; strings require a known unit, and widened config strings use Duration.parse(). ms@2.1.3 is a runtime dependency; its ambient types are not exposed to consumers.

Set client defaults and semantic hooks with Questions.create({ model, defaults, hooks }); derive clients with extend(). These are separate from provider timeout and HTTP hooks. The total operation budget includes live context, validation, hooks and branch handler waiting. See semantic DX for hook inheritance, error handling and cancellation semantics. Each Execution now exposes operationId and diagnostics; compiled schemas expose fields and diagnose. Confidence errors on schema-backed decisions include a path and questionId, while SchemaValidationError retains Zod issues plus field evidence. These descriptors refer to schema inputs, not arbitrary transformed output paths.

Add language models (alpha.6)

Existing native providers and the Evaluation V4 adapter remain compatible. Additive ProbabilitySource metadata is preserved on answers and boolean/coarsening helpers; missing metadata remains unknown. Generative.create({ model, evidence: "estimated" }) is an opt-in subpath accepting a configured AI SDK LanguageModelV4. The new ai and @ai-sdk/provider peers are optional; native users do not install them. No thresholds are automatically recalibrated, no arbitrary JSON generation is enabled, and SDK retries default to zero. See generative providers.