Decision operation family evaluates application state against a closed set of typed questions. It is separate from chat inference: a decision returns NoulAnswer, ChoiceAnswer, and ScoreAnswer objects rather than generated text or an invented chat envelope.
TypeSafe is the first provider. The domain and runtime contracts live in Polyglot, so callers depend on the stable Decision API rather than a provider-specific SDK.
Use the rest of this section for the complete surface:
- Question design explains
Noul,Choice,Score, and text versus structured state. - Response handling covers typed answers, probability distributions, confidence, usage, and IDs.
- Configuration and runtime covers presets, explicit wiring, pending execution, retries, events, and custom drivers.
- Errors and testing covers provider failures, validation failures, test seams, and the opt-in live test.
Configure TypeSafe
SetTYPESAFE_API_KEY in the environment. The bundled typesafe preset selects https://api.typesafe.ai/v1/systemone and the jev-latest model alias.
config/sdm/default.yaml and config/sdm/presets/<name>.yaml. Aggregate and split Composer installs discover the bundled files from cognesy/instructor-php and cognesy/instructor-polyglot, respectively.
Ask typed questions
confidence(), probabilities(), and, for Score, legend().
Build dynamic options
Choice IDs are application-owned strings. Build them from runtime data, then keep the returned value inside that closed set.Use structured content
JSON input is optional. Passing a PHP string sends text state. UseJsonContent only when named fields or a list make the state or question definitions clearer. State, instructions, descriptions, and Score levels can all preserve JSON objects or lists.
JsonContent::text(), object(), and list() retain the root kind. Values are snapshotted, finite, and JSON-safe.
See Question design for guidance on selecting a primitive and defining useful criteria, options, and levels.
Use an explicit request and runtime
The facade and explicit runtime share the same execution path.DecisionResponse exposes answers(), model(), usage(), responseData(), and providerRequestId().
See Response handling for the complete typed result API.
Portable payload versus execution envelope
DecisionRequest::toArray() serializes the portable domain payload: input, questions, and an optional model. fromArray() reconstructs those typed questions.
Execution-only concerns intentionally stay outside that payload:
- API credentials and endpoint configuration belong to
DecisionConfig. - Retry policy belongs to the in-memory request/runtime composition.
- Request, execution, and attempt identities belong to the lifecycle.
- Telemetry correlation belongs to the execution envelope.
Retry ownership
Decision defaults to one attempt. Enable bounded retries explicitly:408, 429, 5xx, and 529 responses, honors bounded Retry-After, and memoizes both success and terminal failure. Do not add a second HTTP retry middleware for the same operation.
Events and telemetry
DecisionRuntime exposes onEvent() and wiretap() on its event root. The lifecycle events are:
DecisionStartedandDecisionCompletedorDecisionFailedDecisionAttemptStartedandDecisionAttemptSucceededorDecisionAttemptFailed
sdm.decision and child sdm.decision.attempt spans. Default event and telemetry payloads contain IDs, model/driver, primitive count, timing, retry decisions, status, and token counts. They omit state, instructions, criteria, provider bodies, credentials, and exception messages.
Live smoke test
Ordinary tests never call TypeSafe. To run the bounded mixed-primitive smoke explicitly:TYPESAFE_API_KEY through the existing environment loader. It fails when explicitly enabled without a key and prints only safe endpoint/model/count/usage evidence.
Deliberate limits
Decision has no stream API: an SDM response is one typed result. Generating question definitions through Instructor, Dynamic, or Agents is intentionally deferred until the low-level contract has settled. Those layers can later produceQuestions or portable request payloads without moving Decision ownership out of Polyglot.