agentic examples

The One-File API

The One-File API: an endpoint is a contract wearing HTTP. Declare the capability once and the rest is derived - the 422s (with relation rules explained), the 201, and the machine-readable schema your client generator reads. No serializer classes, no validator classes, no docs pipeline. One declaration, three doors.

Data & Pipelines Round 10 DHH exit 0

source on github

bundle exec ruby examples/one_file_api.rb

a real captured run

THE ONE-FILE API (quotes v3.0.0)

  GET /quotes/schema
    -> 200 {"$schema":"http://json-schema.org/draft-07/schema#","title":"quotes inputs","type":"object","requir... (687 bytes)

  POST /quotes {"mode":"teleport","weight":9000}
    -> 422 {"errors":[{"field":"mode","errors":["must be one of: air, sea, road"]},{"field":"weight","errors":[... (140 bytes)

  POST /quotes {"mode":"air","weight":4000,"volume":3000}
    -> 422 {"errors":[{"rule":"fits","fields":["weight","volume"],"error":"weight + volume must total at most 6000"}]}

  POST /quotes {"mode":"air","weight":100,"express":true}
    -> 422 {"errors":[{"rule":"customs","fields":["express","customs_code"],"error":"express requires customs_code"}]}

  POST /quotes {"mode":"air","weight":100,"express":true,"customs_code":"HS-42"}
    -> 201 {"price_cents":1800}

  count what you didn't write: the 422 renderer never mentions a
  field name, the schema endpoint is one method call, and the
  relation rules flow to BOTH doors - the 422 explains
  "express requires customs_code" to humans, while the schema's
  dependencies clause ({"express":["customs_code"]}) tells
  client generators the same law in draft-07. one declaration,
  and the API layer is just... reading it. the best code in your
  app is the code that isn't there.

source

# frozen_string_literal: true

# The One-File API: an endpoint is a contract wearing HTTP. Declare
# the capability once and the rest is derived - the 422s (with
# relation rules explained), the 201, and the machine-readable schema
# your client generator reads. No serializer classes, no validator
# classes, no docs pipeline. One declaration, three doors.
#
#   bundle exec ruby examples/one_file_api.rb
#
# Runs offline; requests are simulated, responses are real.

require class="s">"bundler/setup"
require class="s">"agentic"
require class="s">"json"

QUOTES = Agentic:class="y">:CapabilitySpecification.new(
  name: class="s">"quotes", description: class="s">"Quote a shipment", version: class="s">"3.0.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"}
  },
  outputs: {price_cents: {type: class="s">"number", required: true}},
  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]}
  }
)

RATES = {class="s">"air" => 9, class="s">"sea" => 2, class="s">"road" => 4}.freeze

# The entire app. Everything else in this file is derived from QUOTES.
def create_quote(params)
  {price_cents: (params[class="y">:weight] * RATES[params[class="y">:mode]] * (params[class="y">:express] ? 2 : 1)).round}
end

# --- the derived API layer -----------------------------------------------------
def handle(method, path, body = nil)
  case [method, path]
  in [class="s">"GET", class="s">"/quotes/schema"]
    [200, QUOTES.to_json_schema]
  in [class="s">"POST", class="s">"/quotes"]
    validator = Agentic:class="y">:CapabilityValidator.new(QUOTES)
    begin
      params = body.transform_keys(&class="y">:to_sym)
      validator.validate_inputs!(params)
      output = create_quote(params)
      validator.validate_outputs!(output) # the contract guards BOTH doors
      [201, output]
    rescue Agentic:class="y">:Errors:class="y">:ValidationError => e
      errors = e.violations.except(class="y">:base).map { |field, messages| {field: field, errors: messages} }
      errors += e.rule_violations.map { |v| {rule: v[class="y">:rule], fields: v[class="y">:fields], error: v[class="y">:message]} }
      [422, {errors: errors}]
    end
  else
    [404, {error: class="s">"no such route"}]
  end
end

REQUESTS = [
  [class="s">"GET", class="s">"/quotes/schema", nil],
  [class="s">"POST", class="s">"/quotes", {class="s">"mode" => class="s">"teleport", class="s">"weight" => 9_000}],
  [class="s">"POST", class="s">"/quotes", {class="s">"mode" => class="s">"air", class="s">"weight" => 4_000, class="s">"volume" => 3_000}],
  [class="s">"POST", class="s">"/quotes", {class="s">"mode" => class="s">"air", class="s">"weight" => 100, class="s">"express" => true}],
  [class="s">"POST", class="s">"/quotes", {class="s">"mode" => class="s">"air", class="s">"weight" => 100, class="s">"express" => true, class="s">"customs_code" => class="s">"HS-42"}]
].freeze

puts class="s">"THE ONE-FILE API (#{QUOTES.name} v#{QUOTES.version})"
puts
REQUESTS.each do |method, path, body|
  status, response = handle(method, path, body)
  puts class="s">"  #{method} #{path}#{body ? " #{JSON.generate(body)}class="s">" : "class="s">"}"
  rendered = JSON.generate(response)
  rendered = class="s">"#{rendered[0, 100]}... (#{rendered.size} bytes)" if rendered.size > 110
  puts class="s">"    -> #{status} #{rendered}"
  puts
end

schema = QUOTES.to_json_schema
puts class="s">"  count what you didn't write: the 422 renderer never mentions a"
puts class="s">"  field name, the schema endpoint is one method call, and the"
puts class="s">"  relation rules flow to BOTH doors - the 422 explains"
puts class="s">"  \"#{QUOTES.rules[class="y">:customs][class="y">:fields].first} requires #{QUOTES.rules[class="y">:customs][class="y">:fields].last}\class="s">" to humans, while the schema's"
puts class="s">"  dependencies clause (#{JSON.generate(schema["dependenciesclass="s">"])}) tells"
puts class="s">"  client generators the same law in draft-07. one declaration,"
puts class="s">"  and the API layer is just... reading it. the best code in your"
puts class="s">"  app is the code that isn't there."