Use Decision and DecisionModel from effect/ai to evaluate schema-defined input.
The model returns evidence; application code owns thresholds, routing, and side effects.
Start with the decision guide or the
complete example.
Decision.make({ input, decisions }) pairs an input Schema with named decisions.
DecisionModel.decide(definition, { input }) encodes that input using Schema.toCodecJson
and answers all decisions in one provider call. Encoding services remain visible in the Effect’s
requirements. Include only data the provider should receive.
Constructor
Required options besides string instructions
Answer
Decision.classify
criteria: at least two labels mapped to string descriptions
label, probabilities, optional confidence
Decision.rate
criteria: at least two distinct ordered string levels
rating, label, probabilities, optional confidence
Decision.probability
None; optional criteria describes both false and true
probability
Classification labels and rating levels infer literal unions. Ratings can be fractional,
from zero to the last level’s index; their probability keys are the level strings. The rating
label is the most probable level, choosing the first on ties. Probability answers are in [0, 1].
Probability criteria are optional; when supplied, describe both outcomes. Code inspecting a
Decision.Probability must check whether criteria is present before reading it.
Constructors throw for empty decision sets or invalid criteria counts; define valid static
assessments before executing them.
import{
importSchema
Schema }from"effect";
import{
importDecision
Decision,
importDecisionModel
DecisionModel }from"effect/ai";
const
constUrgency:Decision.Definition<Schema.Struct<{
readonlymessage:Schema.String;
}>,{
urgent:Decision.Probability;
}>
Urgency=
importDecision
Decision.
constmake:<Schema.Struct<{
readonlymessage:Schema.String;
}>,{
urgent:Decision.Probability;
}>(options:{
readonlyinput:Schema.Struct<{
readonlymessage:Schema.String;
}>;
readonlydecisions:{
urgent:Decision.Probability;
};
})=>Decision.Definition<Schema.Struct<{
readonlymessage:Schema.String;
}>,{
urgent:Decision.Probability;
}>
Creates a decision definition from an input schema and named decisions.
DecisionModel.decide encodes the input with Schema.toCodecJson before
calling the provider. Answer keys and types are inferred from the decisions.
Throws if decisions is empty.
Example (Defining ticket triage)
import{ Schema }from"effect"
import{ Decision }from"effect/ai"
constTicket=Schema.Struct({
subject:Schema.String,
body:Schema.String
})
constTicketTriage=Decision.make({
input:Ticket,
decisions:{
department:Decision.classify({
instructions:"Which team should handle this",
criteria:{billing:"payments",technical:"bugs"}
}),
urgent:Decision.probability({
instructions:"The message is time-sensitive",
criteria:{false:"No time pressure",true:"Needs action now"}
})
}
})
@see ― Definition for the returned shape
@stability ― unstable
@category ― constructors
@since ― 4.0.0
make({
input:Schema.Struct<{
readonlymessage:Schema.String;
}>
input:
importSchema
Schema.
functionStruct<{
readonlymessage:Schema.String;
}>(fields:{
readonlymessage:Schema.String;
}):Schema.Struct<{
readonlymessage:Schema.String;
}>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Declared fields may be inherited and are copied to own properties in the
output. The __proto__ field is accepted only when it is an own property.
Parsing does not guarantee that output keys retain their input order.
Schema for string values. Validates that the input is typeof"string".
@category ― models
@since ― 4.0.0
@category ― schemas
@since ― 4.0.0
String}),
decisions:{
urgent:Decision.Probability;
}
decisions:{
urgent:Decision.Probability
urgent:
importDecision
Decision.
constprobability:(options:{
readonlyinstructions:string;
readonlycriteria?:{
readonlyfalse:string;
readonlytrue:string;
}|undefined;
})=>Decision.Probability
Creates a probability decision from instructions and optional outcome descriptions.
When criteria is supplied, descriptions for both false and true are required.
Example (Estimating urgency)
import{ Decision }from"effect/ai"
consturgent=Decision.probability({
instructions:"The message is time-sensitive"
})
Example (Providing outcome descriptions)
import{ Decision }from"effect/ai"
consturgent=Decision.probability({
instructions:"The message is time-sensitive",
criteria:{
false:"The message can wait",
true:"The message needs immediate attention"
}
})
@see ― classify for more than two outcomes
@stability ― unstable
@category ― constructors
@since ― 4.0.0
probability({
instructions:string
instructions:"Does this need immediate attention?",
Answers a decision definition using the current DecisionModel service.
Encodes the input with Schema.toCodecJson, requiring the schema's encoding services.
Explicit undefined fields become null; absent fields stay absent.
Custom declarations need a JSON codec annotation or encoding fails.
Returned answers and probability dictionaries have null prototypes.
Example (Triaging a ticket)
import{ Effect,Schema }from"effect"
import{ Decision,DecisionModel }from"effect/ai"
constTicketTriage=Decision.make({
input:Schema.String,
decisions:{
urgent:Decision.probability({
instructions:"The message is time-sensitive",
criteria:{false:"No time pressure",true:"Needs action now"}
Results contain answers keyed by decision name and usage.inputTokens / usage.outputTokens.
Unreported token counts are undefined. Usage is separate from an agent Run’s language-model
budgets. Classification and rating confidence is optional, provider-defined evidence, not a
correctness guarantee. Provider and resolved model identifiers are not part of the response.
Native DecisionModel validates required answers, kinds, labels, finite probabilities in [0, 1],
and, by default, distribution sums within 1e-6 of 1. Classification may return a label that does not have the
highest probability. Ratings must lie within the scale; the core derives their labels from the
distribution. Application acceptance policies remain explicit.
Providers can opt into probabilityPrecision to accept rounding drift and rescale distributions.
TypeSafe sets this to two decimal places, so rounded totals such as 0.99 or 1.01 can be accepted.
The language model adapter keeps strict validation; invalid sums fail with
AiError.InvalidOutputError.
Creates a decision definition from an input schema and named decisions.
DecisionModel.decide encodes the input with Schema.toCodecJson before
calling the provider. Answer keys and types are inferred from the decisions.
Throws if decisions is empty.
Example (Defining ticket triage)
import{ Schema }from"effect"
import{ Decision }from"effect/ai"
constTicket=Schema.Struct({
subject:Schema.String,
body:Schema.String
})
constTicketTriage=Decision.make({
input:Ticket,
decisions:{
department:Decision.classify({
instructions:"Which team should handle this",
criteria:{billing:"payments",technical:"bugs"}
}),
urgent:Decision.probability({
instructions:"The message is time-sensitive",
criteria:{false:"No time pressure",true:"Needs action now"}
})
}
})
@see ― Definition for the returned shape
@stability ― unstable
@category ― constructors
@since ― 4.0.0
make({
input:Schema.String
input:
importSchema
Schema.
constString:Schema.String
Type-level representation of
String
.
Schema for string values. Validates that the input is typeof"string".
@category ― models
@since ― 4.0.0
@category ― schemas
@since ― 4.0.0
String,
decisions:{
tone:Decision.Classify<"positive"|"negative">;
}
decisions:{
tone:Decision.Classify<"positive"|"negative">
tone:
importDecision
Decision.
constclassify:<"positive"|"negative">(options:{
readonlyinstructions:string;
readonlycriteria:{
readonlypositive:string;
readonlynegative:string;
};
})=>Decision.Classify<"positive"|"negative">
Creates a classification decision from labelled criteria.
Throws if fewer than two labels are supplied.
Example (Choosing a department)
import{ Decision }from"effect/ai"
constdepartment=Decision.classify({
instructions:"Which team should handle this",
criteria:{
billing:"payments",
technical:"bugs",
sales:"pricing"
}
})
@see ― rate for an ordered scale
@see ― probability for a single yes or no likelihood
Adapts the supplied LanguageModel to native Effect decisions using structured output.
Requires a model supporting generateObject. All decisions share one request; native
DecisionModel validation rejects invalid distributions without normalization or retry.
Probabilities are LLM estimates, not calibrated confidence scores. Provider errors,
defects, and interruption propagate; callers own retry, timeout, and fallback policies.
Feeds the output services of the dependency layer into the requirements of
this layer, returning a layer that only provides the services from this layer.
When to use
Use when you need to hide an implementation dependency layer from callers.
Details
In serviceLayer.pipe(Layer.provide(dependencyLayer)), the dependency layer is
built first and is used to satisfy the requirements of serviceLayer.
Feeds the output services of the dependency layer into the requirements of
this layer, returning a layer that only provides the services from this layer.
When to use
Use when you need to hide an implementation dependency layer from callers.
Details
In serviceLayer.pipe(Layer.provide(dependencyLayer)), the dependency layer is
built first and is used to satisfy the requirements of serviceLayer.
Feeds the output services of the dependency layer into the requirements of
this layer, returning a layer that only provides the services from this layer.
When to use
Use when you need to hide an implementation dependency layer from callers.
Details
In serviceLayer.pipe(Layer.provide(dependencyLayer)), the dependency layer is
built first and is used to satisfy the requirements of serviceLayer.
Effect.runSync(program)// => { id: "123", name: "DB: SELECT * FROM users WHERE id = 123" }
logs// => ["[LOG] Looking up user 123"]
@see ― provideMerge for retaining the dependency services
@category ― providing services
@since ― 2.0.0
provide(
importFetchHttpClient
FetchHttpClient.
constlayer:Layer.Layer<HttpClient,never,never>
Layer that provides an HttpClient implementation backed by the configured
Fetch function.
When to use
Use when an Effect program should execute HttpClient requests through the
platform fetch implementation, especially in browser, edge, or Node.js
runtimes with globalThis.fetch.
Details
The layer uses the current Fetch reference and optional RequestInit
service for each request. Request-specific method, headers, body, and abort
signal are supplied by the client and override matching RequestInit fields.
Gotchas
Fetch behavior comes from the runtime's implementation, so CORS, cookies,
redirects, abort handling, and streaming support can vary by platform. Stream
request bodies are sent as Web streams with duplex: "half", and any
content-length header is removed before calling fetch.
@see ― Fetch for supplying the fetch implementation used by this layer
@see ― RequestInit for default RequestInit options applied before request-specific fields
Answers a decision definition using the current DecisionModel service.
Encodes the input with Schema.toCodecJson, requiring the schema's encoding services.
Explicit undefined fields become null; absent fields stay absent.
Custom declarations need a JSON codec annotation or encoding fails.
Returned answers and probability dictionaries have null prototypes.
Example (Triaging a ticket)
import{ Effect,Schema }from"effect"
import{ Decision,DecisionModel }from"effect/ai"
constTicketTriage=Decision.make({
input:Schema.String,
decisions:{
urgent:Decision.probability({
instructions:"The message is time-sensitive",
criteria:{false:"No time pressure",true:"Needs action now"}
Transforms the value inside an effect by applying a function to it.
When to use
Use to transform an effect's success value with a function that returns a
plain value, producing a new effect without changing the original effect's
typed error or context requirements.
Details
map takes a function and applies it to the value contained within an
effect, creating a new effect with the transformed value.
It's important to note that effects are immutable, meaning that the original
effect is not modified. Instead, a new effect is returned with the updated
value.
Provides dependencies to an effect using layers or a context. Use options.local
to build the layer every time; by default, layers are shared between provide
calls.
The adapter answers all classification, rating, and probability decisions in one generateObject
call. It derives the response schema from the decisions, puts decision instructions in the system
message, and sends schema-encoded input as untrusted user data. Prompt separation does not make
model decisions an authorization boundary. Input is still sent to the selected provider.
Probabilities are LLM estimates, not calibrated confidence scores. Native DecisionModel
validation applies unchanged: invalid distributions fail with InvalidOutputError without
normalization. JSON or schema decoding failures retain StructuredOutputError. Token usage is
forwarded; the adapter does not invent confidence values. Provider errors, defects, and interruption
propagate. Configure model options on the supplied Layer and compose retry, timeout, or failover
policies explicitly; this adapter does not automatically retry another provider.
The Layer requires LanguageModel and provides DecisionModel. Import it from the package root
or @yielded/agent-ai-decision/language-model-decision-model. The
provider-neutral example
uses the same decision API without selecting a provider.
AutoModel is a native model Layer: provide it to satisfy an agent’s model requirement.
The runtime selects before each thread’s first model call and keeps that choice for later turns
and follow-up runs. Each new subagent selects independently from its delegated task.
Describe at least two approved native models and evaluate them through DecisionModel.
Supply Jev with TypeSafeDecisionModel.model("jev-latest") from
@effect ―
/ai-typesafe.
Increment version whenever a profile's model, effort, or other settings change;
retain old catalogs while their threads remain active.
State should describe the whole task, relevant context, constraints, and tools;
only include information the decision provider may receive. The application
must filter candidates for authorization and required capabilities first.
Provide this Model with Effect.provide (or Layer.provide for subagent handlers).
The runtime resolves it on each Thread's first Turn. Supply DecisionModel, native
provider clients, and a shared SelectionStore. Direct LanguageModel calls lack
thread context and fail with InvalidRequestError; select/restore/resolve remain
available for hosts that own selection at admission instead.
Persist record before generation and reuse selection.model for all turns and later runs.
Restore rejects a different thread, catalog version, or missing profile; it
never silently reselects. Store ownership, authorization, atomic creation, and
recovery belong to the host's SelectionStore.
There are no implicit retries, deadlines, fallbacks, confidence thresholds,
or mid-thread switches. Defects and interruption propagate. The
AutoModel.select span records only the selected profile ID, not task content.
description:"Higher cost. Difficult reasoning, subtle bugs, and ambiguous requirements.",
},
},
});
Import AutoModel from @yielded/agent-ai-decision. At least two profiles are required.
Each profile pairs a native Effect model Layer with a description. Configure reasoning effort
and provider options on that Layer; describe capability, cost, and appropriate tasks in the
catalog. Supply Jev through TypeSafeDecisionModel, or use another
DecisionModel implementation.
Provides dependencies to an effect using layers or a context. Use options.local
to build the layer every time; by default, layers are shared between provide
calls.
Build the Toolkit handler Layer using the provided model requirement, or pass
a native model / explicit child Binding as an override. Without an override,
provide the model with Layer.provide; the handler captures it at construction.
AutoModel resolves each new child's own Thread ID and projected first task.
Share its selection store across parent Runs and child handler Layers.
Construction requirements carry the child Binding's full runtime needs and
both projections; they are captured once via Effect.context so the
per-call handler requirements stay exactly the Tool's declared engine
dependencies. The handler dispatches on the engine-provided per-batch
SubagentDurability service mode:
ephemeral (the explicit engine default when no durable coordinator
supplied RunOptions.subagent): the S1 path unchanged — preflight, an
in-process scoped child Run (SUB-011/012), stable lifecycle events,
total-mapped expected child failures, and Scope-finalizer reservation
settlement on every exit path.
durable (S2): the same fail-closed preflight and input projection,
then idempotent establishment through the coordinator (spec §12 steps
2-9) with construction-fixed child Binding digests, encoded grant, and
encoded allocation; the engine-owned waiting signal while the attached
child is nonterminal; and, on re-entry with a settled child, output
decoding, projectResult, and ONE atomic settlement join carrying the
conservative accounting summary. Failed children join as the bounded
SubagentExecutionFailure; no in-process child fiber ever starts.
Feeds the output services of the dependency layer into the requirements of
this layer, returning a layer that only provides the services from this layer.
When to use
Use when you need to hide an implementation dependency layer from callers.
Details
In serviceLayer.pipe(Layer.provide(dependencyLayer)), the dependency layer is
built first and is used to satisfy the requirements of serviceLayer.
Effect.runSync(program)// => { id: "123", name: "DB: SELECT * FROM users WHERE id = 123" }
logs// => ["[LOG] Looking up user 123"]
@see ― provideMerge for retaining the dependency services
@category ― providing services
@since ― 2.0.0
provide(
constThreadModels:AutoModel<OpenAiClient>
ThreadModels));
Provide one AutoModel.layerMemory() alongside InMemory.layer, outside the parent program
and child handler Layers. The shared store keys choices by thread ID, so siblings use the same
catalog without sharing a selection. The selected native model retains its provider identity,
client requirements, streaming, tools, and structured output. Building the AutoModel Layer
captures dependencies without selecting or acquiring any candidate. It requires an agent thread;
for direct LanguageModel calls, explicitly resolve a thread and provide the returned model.
The runtime supplies the rendered prompt and eligible tool descriptions to the resolver after
input validation and inputPrompt projection, before context preparation. Raw input fields
excluded by that projection are excluded from selection too. Selection is inside the run’s
deadline and interruption scope. Context hooks may prepare prompts but cannot replace the
selected model through modelCall; doing so fails with AiError.InvalidRequestError.
AutoModel.SelectionStore.getOrCreate(threadId, select) owns atomic creation and retention.
It returns the committed winning SelectionRecord before generation starts. Concurrent
resolutions of the same thread share that choice; independent threads can select concurrently.
Failed or interrupted selections may retry. A crash before commitment can repeat a selector
request. Generative failure after commitment does not change the chosen model.
layerMemory({ capacity? }) retains choices for one Layer lifetime. Capacity defaults to 10,000
distinct attempted thread IDs; entries never expire or evict. New threads fail at capacity while
existing choices remain available. Rebuilding the Layer loses selections. Durable hosts must
provide a SelectionStore backed by application-owned thread storage, preserving records across
restarts. Keep model capacity and pricing configuration aligned with the selected profiles.
Stable model settings avoid implicit changes that can reduce prompt-cache reuse; cache hits
still depend on provider behavior and prompt prefixes.
Native Model Layer over approved profiles; construction performs no selection
version
Required nonempty version; change when model, effort, or settings change
instructions
Optional string classification policy; defaults to the least expensive capable profile for the whole task
resolve({ threadId, state })
Explicit resolution using SelectionStore, DecisionModel, and native client services
layerMemory({ capacity? })
Bounded shared selection storage for ephemeral hosts
SelectionStore
Host storage port; durable implementations must atomically retain the winning record
select({ threadId, state })
Explicit one-shot classification for hosts that own admission; re-execution selects again
restore(threadId, record)
Validates stored data and returns the native model without decision-provider I/O
SelectionRecord
Schema for format version, thread ID, catalog version, profile ID, and decision evidence
Use Schema.encodeEffect(AutoModel.SelectionRecord) at storage boundaries; never serialize
model Layers. Retain previous catalogs for active threads. Wrong-thread, malformed, missing-profile,
or catalog-version mismatches fail with AiError.InvalidRequestError rather than reselecting.
New selections use record version 2: decision.answers.model contains label,
probabilities, and optional confidence; decision.usage contains optional token counts.
Version 1 records are rejected without mutation or reselection. Hosts with active version 1
threads must retain their previous runtime and catalog until those threads finish, or implement
an explicit, data-preserving upgrade in their storage adapter.
The host must restrict candidates to compatible, authorized profiles and send only task context
the decision provider may receive. Selection has no implicit retries, fallbacks, or confidence
thresholds. Selector usage and confidence live in record.decision, separately from generative run
accounting. AutoModel.select tracing records the profile ID without adding task-body logging.
Install @effect/ai-typesafe at the same version as Effect. Its TypeSafeDecisionModel.model(model)
provides the native DecisionModel and provider/model identity. It does not provide a
LanguageModel.
Feeds the output services of the dependency layer into the requirements of
this layer, returning a layer that only provides the services from this layer.
When to use
Use when you need to hide an implementation dependency layer from callers.
Details
In serviceLayer.pipe(Layer.provide(dependencyLayer)), the dependency layer is
built first and is used to satisfy the requirements of serviceLayer.
Feeds the output services of the dependency layer into the requirements of
this layer, returning a layer that only provides the services from this layer.
When to use
Use when you need to hide an implementation dependency layer from callers.
Details
In serviceLayer.pipe(Layer.provide(dependencyLayer)), the dependency layer is
built first and is used to satisfy the requirements of serviceLayer.
Effect.runSync(program)// => { id: "123", name: "DB: SELECT * FROM users WHERE id = 123" }
logs// => ["[LOG] Looking up user 123"]
@see ― provideMerge for retaining the dependency services
@category ― providing services
@since ― 2.0.0
provide(
importFetchHttpClient
FetchHttpClient.
constlayer:Layer.Layer<HttpClient,never,never>
Layer that provides an HttpClient implementation backed by the configured
Fetch function.
When to use
Use when an Effect program should execute HttpClient requests through the
platform fetch implementation, especially in browser, edge, or Node.js
runtimes with globalThis.fetch.
Details
The layer uses the current Fetch reference and optional RequestInit
service for each request. Request-specific method, headers, body, and abort
signal are supplied by the client and override matching RequestInit fields.
Gotchas
Fetch behavior comes from the runtime's implementation, so CORS, cookies,
redirects, abort handling, and streaming support can vary by platform. Stream
request bodies are sent as Web streams with duplex: "half", and any
content-length header is removed before calling fetch.
@see ― Fetch for supplying the fetch implementation used by this layer
@see ― RequestInit for default RequestInit options applied before request-specific fields
@stability ― unstable
@category ― layers
@since ― 4.0.0
layer),
);
TypeSafeClient.layerConfig() reads TYPESAFE_API_KEY. To read a custom base URL, pass
{ apiUrl: Config.string("TYPESAFE_API_URL") }; the default is https://api.typesafe.ai/v1.
Use TypeSafeClient.layer({ apiKey, apiUrl, transformClient }) for explicit configuration.
The API key is a Redacted<string>. Construction performs no requests.
For low-level access, obtain TypeSafeClient.TypeSafeClient and call
client.systemOne({ model, state, questions }) or client.listModels().
TypeSafeSchema exposes their wire schemas. System One uses choice, score, and noul
questions. Use DecisionModel.decide for request-derived answer types and validated
probability distributions; systemOne returns the provider’s wire answer union.
The direct example
shows bounded retries and a timeout.
Native decision input encoding failures use AiError.InvalidUserInputError; invalid answers
use AiError.InvalidOutputError. TypeSafe maps HTTP failures to typed AiError reasons,
including authentication, rate limiting, and provider failures. Configuration can fail with
ConfigError. Defects and interruption propagate.
There are no automatic retries or deadlines. Compose Effect.retry and Effect.timeout,
or configure HTTP policies through the client’s transformClient option or
TypeSafeConfig.withClientTransform. Rate-limit errors retain retry delays when available.
HTTP error text may include submitted content; the host controls tracing and logging.