Typesafe S1 model Jev implementation as a native/primitive Ruby feature.
Ruby
1
1 commits
updated Sep 18, 2026
Ruby primitives for System One (S1) models
Your code asks a question; it gets back a bool, float, a choice, or a level it can branch on directly.
Illustrative code:
# TypeSafe.configure { |c| c.primitives = true } # enables the bare noul? / ask below
def handle_chat(chat)
TypeSafe::S1.about(chat) do
triage = ask do |q|
q.noul :escalate, "Is the customer asking for a human agent?"
q.noul :new_matter, "Is this a new matter?"
q.score :severity, "How severe is the injury?", "None", "Minor", "Serious", "Catastrophic"
end
return escalate_to_human if triage.true?(:escalate)
create_case(triage[:severity].level) if triage.true?(:new_matter) && triage[:severity].index >= 1
end
end
chat = { messages: [] }
inbox.each do |message| # whatever feeds you messages: a queue, a webhook, a socket
chat[:messages] << message
handle_chat(chat) # one call per message; every question answered fresh
end
state = TypeSafe::S1::State.new("I have asked three times now. Can I just talk to a real person?")
if state.noul?("Is the customer asking for a human agent?")
escalate_to_human
end
Or one-liners:
escalate_to_human if TypeSafe::S1::State.new(chat).noul?("is the customer asking for a human agent?")
Or even simpler, if installed as primitives:
chat = { messages: [ "How may I help you?" ... ]}
escalate_to_human if chat.noul? "is the customer asking for a human agent?"
Or even, with no receiver at all:
TypeSafe::S1.about(chat) do
escalate_to_human if noul? "is the customer asking for a human agent?"
triage = ask do |q|
q.score :severity, "How severe is the injury?", "None", "Minor", "Serious", "Catastrophic"
q.noul :new_matter, "Is this a new matter?"
end
create_case(triage[:severity].level) if triage.true?(:new_matter) && triage.true?(:severity) >= 0.5
end
gem "typesafe-ruby"
# config/initializers/typesafe.rb (or anywhere at boot)
TypeSafe.configure do |c|
c.api_key = ENV["TYPESAFE_API_KEY"] # default: read from the environment
c.provider = :jev # default; a name under Providers, or an instance
c.model = "jev-latest"
c.base_url = "https://api.typesafe.ai"
c.timeout = 30 # seconds per request
c.max_retries = 2 # transient failures before raising
c.threshold = 0.5 # a noul at or above this reads as true
c.logger = Rails.logger # optional; debug lines per request, warns on retry
c.primitives = false # see below
end
Every value has a default; an initializer is only needed to change one. Ruby ≥ 3.2. No runtime dependencies.
Off by default. Turn it on and String, Hash and Array answer questions about
themselves; a bare noul? / choice / score / ask acts on the ambient subject.
TypeSafe.configure { |c| c.primitives = true } # or a subset: [String]
require "typesafe/primitives" # equivalent, require-style
"Can I talk to a real person?".noul?("Is the customer asking for a human agent?")
chat.choice("Which team?", returns: "Refunds", billing: "Charges")
chat.ask { |q| q.noul :escalate, "..." }
chat.to_s1(threshold: 0.9).noul?("...") # the State, for per-call options
TypeSafe::S1.about(chat) { noul?("...") } # block-scoped subject, restored after
TypeSafe::S1.subject = chat # console / long-lived scope
Every question is one of three shapes. Pick by what the answer is.
noul — "Is this true?" Returns a probability, 0 to 1. The probability is the signal.
state.noul("Is the customer asking for a human agent?") # => #<TypeSafe::S1::Answer::Noul 0.98> compares like a number
state.noul?("Is the customer asking for a human agent?") # => true (at or above the threshold)
# Optional clarification of what counts as yes / no:
state.noul("Has the customer contacted support about this before?",
true: "mentions a prior attempt, ticket, or having asked before",
false: "no sign of any previous contact")
choice — "Which of these options?" An unordered set; you get the pick plus a distribution over all options.
dept = state.choice("Which team should handle this?",
returns: "Exchanges, refunds, wrong or damaged items",
shipping: "Delivery status, delays, lost packages",
billing: "Charges, invoices, payment problems")
dept.to_sym # => :returns
dept[:shipping] # => 0.0
dept.confidence # => 1.0
score — "Which level?" An ordered spectrum, worst → best. You get the most likely level, and the probability-weighted position.
sev = state.score("How severe is the reported issue?",
"Cosmetic; no impact to functionality",
"Broken or degraded feature, but a workaround exists",
"Blocking issue; no workaround exists")
sev.level # => "Blocking issue; no workaround exists"
sev.index # => 2
sev.to_f # => 1.68 (weighted position across the levels)
Each single-question method above is one request. When several questions share a state, batch them — one round trip, and the model answers each independently (one answer is never hidden context for another).
result = state.ask do |q|
q.noul :escalate, "Is the customer asking for a human agent?"
q.noul :repeat, "Has the customer contacted support about this before?",
true: "mentions a prior attempt", false: "no sign of one"
q.choice :department, "Which team should handle this?", returns: "Refunds", shipping: "Delays", billing: "Charges"
q.score :severity, "How severe is the issue?", "Cosmetic", "Degraded, workaround exists", "Blocking"
end
result[:escalate].true? # => true
result[:department].to_sym # => :returns
result[:severity].level # => "Blocking"
result.usage # => { input_tokens: 490, output_tokens: 86 }
result.duration_ms # => 398
Ask speculatively: include questions whose answers you only need conditionally, then let your code decide which to use. That keeps it to one call.
r = state.ask do |q|
q.noul :is_lead, "Is there a potential new case or matter?"
q.noul :qualified, "Is this a qualified lead, based on `firm.criteria`?"
q.noul :prior_rep, "Does the lead already have an attorney?" # asked regardless,
end # used only when relevant
if r[:is_lead].true? && r[:qualified] >= 0.85
flag_conflict if r[:prior_rep].true?
end
State can be a string, or a hash/array (braced — bare keywords to State.new are options,
not state). With a hash, instructions can point at fields with backticked paths:
state = TypeSafe::S1::State.new({
transcript: utterances,
case_details: { date_of_incident: "2026-08-15", sol_deadline: "2027-08-15" }
})
state.noul?("Judging from `transcript` and `case_details.sol_deadline`, is the claim still within the statute of limitations?")
Instructions can be structured too — the verification pattern:
state = TypeSafe::S1::State.new({ source_text: "Invoice #4471 issued March 3, 2026 to Beaver Dam Logistics for $12,840.00, net 30." })
state.noul?({ field: { name: "invoice_number", type: "string", description: "The identifier printed on the invoice." },
extracted_value: "4471",
question: "Does `extracted_value` match the `field` as it appears in `source_text`?" })
All answers carry probabilities and confident?(threshold); use it to route — act automatically
when confident, escalate to a person or a reasoning model when not. Choice and score answers
also carry the provider's confidence; a noul's confidence is how far its probability sits from
the fence, so confident? only discriminates when the threshold is above 0.5.
| answer | reads as |
|---|---|
Answer::Noul | to_f, true? / false?, compares to numbers (a >= 0.85) |
Answer::Choice | to_sym, to_s, [option], == :returns |
Answer::Score | level, index, to_f, levels |
answer = result[:department]
if answer.confident?
route_to(answer.to_sym)
else
hold_for_review(answer.probabilities)
end
Rescue by intent, not by HTTP code:
begin
result = state.ask { |q| ... }
rescue TypeSafe::TransientError => e # RateLimitError, ServerError, ConnectionError, TimeoutError — retry later
retry_later(e)
rescue TypeSafe::PermanentError => e # AuthenticationError, InvalidRequestError, ValidationError — fix the request
raise
end
Transient failures already retry inside the provider (max_retries, honoring Retry-After) before surfacing.
Providers::Stub answers without the network. Give it the answers that matter; everything
else gets a neutral default (noul 0.5, the first option, the first level).
TypeSafe.configure do |c|
c.provider = TypeSafe::S1::Providers::Stub.new(escalate: 0.9, department: :billing, severity: 2)
end
Single-question calls (noul?, choice, score) are keyed by their own name:
Stub.new(noul: 0.9, choice: :billing, score: 2).
A block form receives the request when an answer should depend on the state:
TypeSafe::S1::Providers::Stub.new { |req| { escalate: req.state.include?("real person") ? 0.95 : 0.1 } }
Hook every completed call for telemetry or a cost ledger. Extra keyword arguments to
State.new ride along on the request, so you can attribute a call to its owner:
TypeSafe::S1.on_result do |result, request|
Ledger.record(owner: request.options[:owner], model: result.model, **result.usage)
end
TypeSafe::S1::State.new(transcript, owner: phone_call).noul?("...")
A provider is any object responding to call(request) → Result. Configure by name — :jev
resolves to TypeSafe::S1::Providers::Jev — or pass an instance. Providers::Base gives a new
provider the normalized Answer constructors, so consumers never see a vendor's wire keys.
TypeSafe.configure { |c| c.provider = :jev }
TypeSafe::S1::State.new(text, provider: MyProvider.new) # per-state override
bin/setup, then bundle exec rake runs the specs and rubocop. bin/console opens IRB with the
gem loaded. TYPESAFE_LIVE=1 TYPESAFE_API_KEY=… bundle exec rspec spec/typesafe/live_spec.rb
hits the real API.
An LLM writes text for a person to read. A System One model makes a judgment for code to consume: typed question in, calibrated answer out, decision composed in Ruby you can read and test. Decompose a decision into atomic questions, ask them together, combine the answers with plain logic — and hand off to a person or a reasoning model only where the confidence says you should.
MIT.
1 commits
Ruby
99.9%
Typesafe S1 model Jev implementation as a native/primitive Ruby feature.
Ruby
1
1 commits
updated Sep 18, 2026
Ruby primitives for System One (S1) models
Your code asks a question; it gets back a bool, float, a choice, or a level it can branch on directly.
Illustrative code:
# TypeSafe.configure { |c| c.primitives = true } # enables the bare noul? / ask below
def handle_chat(chat)
TypeSafe::S1.about(chat) do
triage = ask do |q|
q.noul :escalate, "Is the customer asking for a human agent?"
q.noul :new_matter, "Is this a new matter?"
q.score :severity, "How severe is the injury?", "None", "Minor", "Serious", "Catastrophic"
end
return escalate_to_human if triage.true?(:escalate)
create_case(triage[:severity].level) if triage.true?(:new_matter) && triage[:severity].index >= 1
end
end
chat = { messages: [] }
inbox.each do |message| # whatever feeds you messages: a queue, a webhook, a socket
chat[:messages] << message
handle_chat(chat) # one call per message; every question answered fresh
end
state = TypeSafe::S1::State.new("I have asked three times now. Can I just talk to a real person?")
if state.noul?("Is the customer asking for a human agent?")
escalate_to_human
end
Or one-liners:
escalate_to_human if TypeSafe::S1::State.new(chat).noul?("is the customer asking for a human agent?")
Or even simpler, if installed as primitives:
chat = { messages: [ "How may I help you?" ... ]}
escalate_to_human if chat.noul? "is the customer asking for a human agent?"
Or even, with no receiver at all:
TypeSafe::S1.about(chat) do
escalate_to_human if noul? "is the customer asking for a human agent?"
triage = ask do |q|
q.score :severity, "How severe is the injury?", "None", "Minor", "Serious", "Catastrophic"
q.noul :new_matter, "Is this a new matter?"
end
create_case(triage[:severity].level) if triage.true?(:new_matter) && triage.true?(:severity) >= 0.5
end
gem "typesafe-ruby"
# config/initializers/typesafe.rb (or anywhere at boot)
TypeSafe.configure do |c|
c.api_key = ENV["TYPESAFE_API_KEY"] # default: read from the environment
c.provider = :jev # default; a name under Providers, or an instance
c.model = "jev-latest"
c.base_url = "https://api.typesafe.ai"
c.timeout = 30 # seconds per request
c.max_retries = 2 # transient failures before raising
c.threshold = 0.5 # a noul at or above this reads as true
c.logger = Rails.logger # optional; debug lines per request, warns on retry
c.primitives = false # see below
end
Every value has a default; an initializer is only needed to change one. Ruby ≥ 3.2. No runtime dependencies.
Off by default. Turn it on and String, Hash and Array answer questions about
themselves; a bare noul? / choice / score / ask acts on the ambient subject.
TypeSafe.configure { |c| c.primitives = true } # or a subset: [String]
require "typesafe/primitives" # equivalent, require-style
"Can I talk to a real person?".noul?("Is the customer asking for a human agent?")
chat.choice("Which team?", returns: "Refunds", billing: "Charges")
chat.ask { |q| q.noul :escalate, "..." }
chat.to_s1(threshold: 0.9).noul?("...") # the State, for per-call options
TypeSafe::S1.about(chat) { noul?("...") } # block-scoped subject, restored after
TypeSafe::S1.subject = chat # console / long-lived scope
Every question is one of three shapes. Pick by what the answer is.
noul — "Is this true?" Returns a probability, 0 to 1. The probability is the signal.
state.noul("Is the customer asking for a human agent?") # => #<TypeSafe::S1::Answer::Noul 0.98> compares like a number
state.noul?("Is the customer asking for a human agent?") # => true (at or above the threshold)
# Optional clarification of what counts as yes / no:
state.noul("Has the customer contacted support about this before?",
true: "mentions a prior attempt, ticket, or having asked before",
false: "no sign of any previous contact")
choice — "Which of these options?" An unordered set; you get the pick plus a distribution over all options.
dept = state.choice("Which team should handle this?",
returns: "Exchanges, refunds, wrong or damaged items",
shipping: "Delivery status, delays, lost packages",
billing: "Charges, invoices, payment problems")
dept.to_sym # => :returns
dept[:shipping] # => 0.0
dept.confidence # => 1.0
score — "Which level?" An ordered spectrum, worst → best. You get the most likely level, and the probability-weighted position.
sev = state.score("How severe is the reported issue?",
"Cosmetic; no impact to functionality",
"Broken or degraded feature, but a workaround exists",
"Blocking issue; no workaround exists")
sev.level # => "Blocking issue; no workaround exists"
sev.index # => 2
sev.to_f # => 1.68 (weighted position across the levels)
Each single-question method above is one request. When several questions share a state, batch them — one round trip, and the model answers each independently (one answer is never hidden context for another).
result = state.ask do |q|
q.noul :escalate, "Is the customer asking for a human agent?"
q.noul :repeat, "Has the customer contacted support about this before?",
true: "mentions a prior attempt", false: "no sign of one"
q.choice :department, "Which team should handle this?", returns: "Refunds", shipping: "Delays", billing: "Charges"
q.score :severity, "How severe is the issue?", "Cosmetic", "Degraded, workaround exists", "Blocking"
end
result[:escalate].true? # => true
result[:department].to_sym # => :returns
result[:severity].level # => "Blocking"
result.usage # => { input_tokens: 490, output_tokens: 86 }
result.duration_ms # => 398
Ask speculatively: include questions whose answers you only need conditionally, then let your code decide which to use. That keeps it to one call.
r = state.ask do |q|
q.noul :is_lead, "Is there a potential new case or matter?"
q.noul :qualified, "Is this a qualified lead, based on `firm.criteria`?"
q.noul :prior_rep, "Does the lead already have an attorney?" # asked regardless,
end # used only when relevant
if r[:is_lead].true? && r[:qualified] >= 0.85
flag_conflict if r[:prior_rep].true?
end
State can be a string, or a hash/array (braced — bare keywords to State.new are options,
not state). With a hash, instructions can point at fields with backticked paths:
state = TypeSafe::S1::State.new({
transcript: utterances,
case_details: { date_of_incident: "2026-08-15", sol_deadline: "2027-08-15" }
})
state.noul?("Judging from `transcript` and `case_details.sol_deadline`, is the claim still within the statute of limitations?")
Instructions can be structured too — the verification pattern:
state = TypeSafe::S1::State.new({ source_text: "Invoice #4471 issued March 3, 2026 to Beaver Dam Logistics for $12,840.00, net 30." })
state.noul?({ field: { name: "invoice_number", type: "string", description: "The identifier printed on the invoice." },
extracted_value: "4471",
question: "Does `extracted_value` match the `field` as it appears in `source_text`?" })
All answers carry probabilities and confident?(threshold); use it to route — act automatically
when confident, escalate to a person or a reasoning model when not. Choice and score answers
also carry the provider's confidence; a noul's confidence is how far its probability sits from
the fence, so confident? only discriminates when the threshold is above 0.5.
| answer | reads as |
|---|---|
Answer::Noul | to_f, true? / false?, compares to numbers (a >= 0.85) |
Answer::Choice | to_sym, to_s, [option], == :returns |
Answer::Score | level, index, to_f, levels |
answer = result[:department]
if answer.confident?
route_to(answer.to_sym)
else
hold_for_review(answer.probabilities)
end
Rescue by intent, not by HTTP code:
begin
result = state.ask { |q| ... }
rescue TypeSafe::TransientError => e # RateLimitError, ServerError, ConnectionError, TimeoutError — retry later
retry_later(e)
rescue TypeSafe::PermanentError => e # AuthenticationError, InvalidRequestError, ValidationError — fix the request
raise
end
Transient failures already retry inside the provider (max_retries, honoring Retry-After) before surfacing.
Providers::Stub answers without the network. Give it the answers that matter; everything
else gets a neutral default (noul 0.5, the first option, the first level).
TypeSafe.configure do |c|
c.provider = TypeSafe::S1::Providers::Stub.new(escalate: 0.9, department: :billing, severity: 2)
end
Single-question calls (noul?, choice, score) are keyed by their own name:
Stub.new(noul: 0.9, choice: :billing, score: 2).
A block form receives the request when an answer should depend on the state:
TypeSafe::S1::Providers::Stub.new { |req| { escalate: req.state.include?("real person") ? 0.95 : 0.1 } }
Hook every completed call for telemetry or a cost ledger. Extra keyword arguments to
State.new ride along on the request, so you can attribute a call to its owner:
TypeSafe::S1.on_result do |result, request|
Ledger.record(owner: request.options[:owner], model: result.model, **result.usage)
end
TypeSafe::S1::State.new(transcript, owner: phone_call).noul?("...")
A provider is any object responding to call(request) → Result. Configure by name — :jev
resolves to TypeSafe::S1::Providers::Jev — or pass an instance. Providers::Base gives a new
provider the normalized Answer constructors, so consumers never see a vendor's wire keys.
TypeSafe.configure { |c| c.provider = :jev }
TypeSafe::S1::State.new(text, provider: MyProvider.new) # per-state override
bin/setup, then bundle exec rake runs the specs and rubocop. bin/console opens IRB with the
gem loaded. TYPESAFE_LIVE=1 TYPESAFE_API_KEY=… bundle exec rspec spec/typesafe/live_spec.rb
hits the real API.
An LLM writes text for a person to read. A System One model makes a judgment for code to consume: typed question in, calibrated answer out, decision composed in Ruby you can read and test. Decompose a decision into atomic questions, ask them together, combine the answers with plain logic — and hand off to a person or a reasoning model only where the confidence says you should.
MIT.
1 commits
Ruby
99.9%