Endpoint and provider selection

export TYPESAFE_API_KEY="provider-key"
export TYPESAFE_BASE_URL="https://your-typesafe-host.example/deployment"
export TYPESAFE_DEFAULT_MODEL="provider-model-id"
client = TypeSafeSDK.new_client(
  api_key: key,
  base_url: "https://your-typesafe-host.example/deployment",
  model: "provider-model-id"
)

The base URL is the root before /v1/models and /v1/systemone; path prefixes are retained. Use the selected provider's key/model and TypeSafe operation contract. A generic OpenAI-compatible endpoint is not automatically TypeSafe-compatible.

Client and questions

Strict semantic API

client = TypeSafeSDK.new_client()
questions = [
  urgent: TypeSafeSDK.noul("Urgent?"),
  team: TypeSafeSDK.choice("Team?", billing: "Payments", technical: "Product"),
  severity: TypeSafeSDK.score("Severity?", ["Low", "High"])
]
prepared = TypeSafeSDK.prepare!(questions)
{:ok, response} = TypeSafeSDK.evaluate(client, state, prepared)

Tuple constructors

TypeSafeSDK.Question.Noul.new("Urgent?", true: "Time-sensitive")
TypeSafeSDK.Question.Choice.new("Team?", [billing: nil, technical: nil])
TypeSafeSDK.Question.Score.new("Severity?", ["Low", "High"])

Each also has new!. Legacy TypeSafeSDK.Noul/Choice/Score.new still returns legacy structs; system_one still uses string response keys.

Answers and policy

team = TypeSafeSDK.Response.fetch!(response, :team)
TypeSafeSDK.Answer.Choice.ranked(team)
TypeSafeSDK.Answer.Choice.margin(team)
TypeSafeSDK.Answer.confidence(team)
TypeSafeSDK.Answer.gate(team, act: 0.9, review: 0.7)
TypeSafeSDK.Answer.yes?(response.answers.urgent, 0.85)
TypeSafeSDK.Answer.Score.expected_level(response.answers.severity)
TypeSafeSDK.Answer.Score.max_level(response.answers.severity)
TypeSafeSDK.Answer.Score.normalized(response.answers.severity)
TypeSafeSDK.Response.values(response)
# %{urgent: 0.91, team: :billing, severity: 1.7}

Thresholds are explicit application policy, not safety guarantees. Expected and modal Score levels differ; Noul confidence is derived as max(p, 1-p).

Batch, errors and tests

TypeSafeSDK.evaluate_many(client, states, prepared,
  max_concurrency: 8, max_pending: 32, ordered: true, on_error: :collect,
  task_timeout_ms: 40_000, attempt_timeout_ms: 5_000)
TypeSafeSDK.evaluate_stream(client, states, prepared, ordered: false)
TypeSafeSDK.Error.retryable?(error)
TypeSafeSDK.Error.retry_after(error)

client = TypeSafeSDK.Test.client()
client = TypeSafeSDK.Test.stub(client, urgent: {:noul, 0.9})
# Run application code, then verify before the creating process exits.
TypeSafeSDK.Test.verify!(client)
TypeSafeSDK.Test.close(client)

Unordered results have batch_index; errors use details.batch_index. Request timeout and retry policy values still use seconds. Retries can repeat processing.

Contract and operations tools

mix typesafe.schema.export
mix typesafe.schema.verify
mix typesafe.capabilities
mix typesafe.record --output tmp/live --base-url "$TYPESAFE_BASE_URL" --model "$TYPESAFE_DEFAULT_MODEL"
mix reach.check --arch --smells

Recording needs a real API key; no live data is fabricated. Unadvertised transport bounds remain unverified. Semantic telemetry excludes automatic prompt/state data.

0.3 runtime controls

cancel = Pristine.Cancellation.new()
{:ok, response} = TypeSafeSDK.evaluate(client, state, prepared,
  cancellation: cancel,
  max_request_bytes: 262_144,
  response_contract: [on_unknown_answer: :error])

TypeSafeSDK.Prepared.keys(prepared)
TypeSafeSDK.Prepared.fingerprint(prepared)
TypeSafeSDK.Response.metadata(response)
TypeSafeSDK.RuntimeCapabilities.report(client)

Per-call retry keys inherit omitted client retry fields; retry: false disables retry for that call. Cancellation uses Pristine directly and cannot prove that a remote service did not begin processing a request.

0.4 OTP and answer telemetry

# Successful evaluate/4 calls emit one event per known answer:
[:typesafe_sdk, :answer]
# measurements: confidence, top_probability, distribution_margin
# metadata: answer_type, question_index, model/request ID, Prepared fingerprint

children = [
  {Task.Supervisor, name: MyApp.TypeSafeTasks},
  {MyApp.Triage,
   client: client,
   task_supervisor: MyApp.TypeSafeTasks,
   max_in_flight: 32}
]

MyApp.Triage uses TypeSafeSDK.OTP.Server and returns {:evaluate, ...} from callbacks. The SDK starts no global task supervisor. See guides/otp-server.md and guides/recursive-decisions.md.