The Polite Form
The Polite Form: a contract usually speaks AFTER you fail - a 422, a stack of violations. This assistant makes it speak FIRST, turning every declaration into a question: required keys become requests, bounds become gentle corrections, and relation rules become the follow-ups a good clerk asks ("express? then I'll need a customs code"). Zero errors are ever shown; the contract is the script.
Developer Experience
Round 10
Matz
exit 0
bundle exec ruby examples/polite_form.rb
a real captured run
THE POLITE FORM (quote_shipping v2.1.0)
user: here's what I have: {"express":true,"api_key":"k-123","oauth_token":"t-456","volume":2500}
assistant: may I have your mode? (air, sea, road)
user: "air"
assistant: may I have your weight? (number)
user: 6000
assistant: ah - weight must be less than or equal to 5000. shall we adjust it?
user: 4500
assistant: together weight and volume come to 7000, and 6000 is our limit - could we lower the volume?
user: 1500
assistant: since you chose express, I'll also need your customs_code - what is it?
user: "HS-42"
assistant: you've given me api_key and oauth_token - I only need one; which shall we keep?
user: :api_key
assistant: all set. here's your form: {"express":true,"api_key":"k-123","volume":1500,"mode":"air","weight":4500,"customs_code":"HS-42"}
the same contract that would have stacked up 422s asked six
questions instead. nothing here was written twice: the
requests came from required:, the correction from max:, and
the follow-ups from the relations - requires became "then I'll
also need", sum_lte became "could we lower it", and
mutually_exclusive became "which shall we keep?". an error
message is just a question you asked too late.
source
# frozen_string_literal: true # The Polite Form: a contract usually speaks AFTER you fail - a 422, # a stack of violations. This assistant makes it speak FIRST, turning # every declaration into a question: required keys become requests, # bounds become gentle corrections, and relation rules become the # follow-ups a good clerk asks ("express? then I'll need a customs # code"). Zero errors are ever shown; the contract is the script. # # bundle exec ruby examples/polite_form.rb # # Runs offline; the "user" answers from a queue. require class="s">"bundler/setup" require class="s">"agentic" require class="s">"json" SPEC = Agentic:class="y">:CapabilitySpecification.new( name: class="s">"quote_shipping", description: class="s">"Quote a shipment", version: class="s">"2.1.0", inputs: { mode: {type: class="s">"string", required: true, enum: %w[air sea road]}, weight: {type: class="s">"number", required: true, min: 1, max: 5_000}, volume: {type: class="s">"number", min: 0}, express: {type: class="s">"boolean"}, customs_code: {type: class="s">"string"}, api_key: {type: class="s">"string"}, oauth_token: {type: class="s">"string"} }, rules: { fits: {relation: class="y">:sum_lte, fields: [class="y">:weight, class="y">:volume], limit: 6_000}, customs: {relation: class="y">:requires, fields: [class="y">:express, class="y">:customs_code]}, one_auth: {relation: class="y">:mutually_exclusive, fields: [class="y">:api_key, class="y">:oauth_token]} } ) # The half-filled form the user pasted in answers = {express: true, api_key: class="s">"k-123", oauth_token: class="s">"t-456", volume: 2_500} # What the user will say when asked (a queue per field) REPLIES = { mode: [class="s">"air"], weight: [6_000, 4_500], # first too heavy, then adjusted volume: [1_500], # reduced when the total is too much customs_code: [class="s">"HS-42"], keep: [class="y">:api_key] }.transform_values(&class="y">:dup) def say(role, line) puts format(class="s">" %-10s %s", class="s">"#{role}:", line) end def ask(field, question, answers) say(class="s">"assistant", question) reply = REPLIES.fetch(field).shift say(class="s">"user", reply.inspect) answers[field] = reply end validator = Agentic:class="y">:CapabilityValidator.new(SPEC) puts class="s">"THE POLITE FORM (#{SPEC.name} v#{SPEC.version})" puts say(class="s">"user", class="s">"here's what I have: #{JSON.generate(answers)}") 10.times do validator.validate_inputs!(answers) break rescue Agentic:class="y">:Errors:class="y">:ValidationError => e if e.rule_violations.any? violation = e.rule_violations.first rule = SPEC.rules[violation[class="y">:rule]] case rule[class="y">:relation] when class="y">:requires needed = rule[class="y">:fields].drop(1).find { |f| answers[f].nil? } ask(needed, class="s">"since you chose #{rule[class="y">:fields].first}, I'll also need your #{needed} - what is it?", answers) when class="y">:sum_lte total = rule[class="y">:fields].sum { |f| answers[f] || 0 } target = rule[class="y">:fields].last ask(target, class="s">"together #{rule[class="y">:fields].join(" and class="s">")} come to #{total}, and #{rule[class="y">:limit]} is our limit - could we lower the #{target}?", answers) when class="y">:mutually_exclusive say(class="s">"assistant", class="s">"you've given me #{violation[class="y">:fields].join(" and class="s">")} - I only need one; which shall we keep?") keep = REPLIES.fetch(class="y">:keep).shift say(class="s">"user", keep.inspect) (violation[class="y">:fields] - [keep]).each { |f| answers.delete(f) } end else field, messages = e.violations.first if messages.first.include?(class="s">"missing") ask(field, class="s">"may I have your #{field}? (#{SPEC.inputs[field][class="y">:enum]&.join(", class="s">") || SPEC.inputs[field][class="y">:type]})", answers) else ask(field, class="s">"ah - #{field} #{messages.first}. shall we adjust it?", answers) end end end puts say(class="s">"assistant", class="s">"all set. here's your form: #{JSON.generate(answers)}") validator.validate_inputs!(answers) # the countersignature puts puts class="s">" the same contract that would have stacked up 422s asked six" puts class="s">" questions instead. nothing here was written twice: the" puts class="s">" requests came from required:, the correction from max:, and" puts class="s">" the follow-ups from the relations - requires became \"then I'll" puts class="s">" also need\", sum_lte became \class="s">"could we lower it\", and" puts class="s">" mutually_exclusive became \"which shall we keep?\class="s">". an error" puts class="s">" message is just a question you asked too late."