Edit this page


Ruby in the Age of AI β€” Ruby DSLs and metaprogramming bridging Rails and AI-native software

From Rails world to the age of AI. Ruby’s next opportunity may not be to become the language AI models are implemented in. It may be to become one of the best languages for creating the languages through which humans and AI describe, generate, and orchestrate software.

Ruby has been here before.

When Rails appeared, Ruby did not suddenly become closer to HTTP internals, SQL, sockets, or browser rendering. Something more interesting happened: Ruby made it possible to create a language closer to the Web domain.

resources :orders

class Customer < ApplicationRecord
  has_many :orders
end

has_many :orders is technically a Ruby method call. But that is not how a Rails developer experiences it. It reads like a statement about the domain.

Rails took infrastructure and transformed it into vocabulary.

That abilityβ€”supported by blocks, symbols, closures, introspection, open classes, and metaprogrammingβ€”became one of Ruby’s defining strengths.

And the age of AI may be creating another moment where that matters.

Contents

  1. Ruby already did this once
  2. AI currently exposes too much infrastructure
  3. From infrastructure to language
  4. Why Ruby DSLs are different
  5. Metaprogramming turns vocabulary into behavior
  6. What an AI-native Ruby DSL could look like
  7. The SDLC as a program
  8. Agents as first-class language concepts
  9. From DSL to a real system
  10. A tiny executable prototype
  11. Try it in 60 seconds
  12. Ruby as a semantic layer
  13. One declaration, many projections
  14. What comes next

Ruby already did this once

The current Rails Guides still describe Active Record associations as macro-style calls: concise declarations such as has_many :comments that generate or modify behavior at runtime.

class Author < ApplicationRecord
  has_many :books
end

Conceptually, Rails changes the level at which the developer works:

DOMAIN INTENT
Author has many Books
        ↓
RUBY / RAILS DSL
has_many :books
        ↓
FRAMEWORK
associations Β· queries Β· methods Β· persistence behavior

The DSL does not merely save keystrokes. It changes what the program says.

That leads to the question behind this post:

If Rails made Ruby feel native to the Web, could DSLs make Ruby feel native to AI?

Rails world β†’ AI-native Ruby

AI currently exposes too much infrastructure

Building AI-native software today quickly introduces a growing vocabulary:

Prompts Β· Context Β· Tools Β· MCP Β· RAG Β· Memory
Agents Β· Skills Β· Instructions Β· Guardrails
Handoffs Β· Workflows Β· Tracing Β· Evaluation

These are valuable primitives, but applications often have to assemble them explicitly. We are still close to the infrastructure layer.

Rails faced a related problem in another domain. Its answer was not to remove HTTP or SQL. It created a language closer to what developers were trying to express.

From Infrastructure to Language β€” Ruby DSL compressing AI infrastructure into higher-level intent
From Infrastructure to Language β€” a Ruby DSL turns AI building blocks into domain vocabulary.

From infrastructure to language

Imagine expressing the domain directly:

agent :support_expert do
  goal "Resolve customer issues"

  knows :product,
        :support_history

  uses :github,
       :ticketing,
       :knowledge_base

  remembers :conversation
  follows :support_policy

  hands_off_to :human_support,
               when: :confidence_is_low
end

The interesting part is not that the syntax is pleasant. The interesting part is what those words can mean.

knows :product could expand into retrieval, access policy, context assembly, observability, and provenance. uses :github could expand into tool registration, permission checks, schemas, and runtime bindings. hands_off_to could become orchestration behavior.

The language expresses semantic intention. The framework decides how that intention becomes machinery.

Why Ruby DSLs are different

We could represent an agent with YAML:

agent:
  name: discovery
  knowledge:
    - product
  tools:
    - github
  output:
    - discovery_report

That is structured and useful. Ruby can go further:

agent :discovery do
  knows :product
  uses :github
  produces :discovery_report

  when confidence < 0.70 do
    request_more_evidence
  end
end

The same artifact can be configuration, model, policy, specification, executable program, and extension point.

That matters because the AI domain is not stable. Its vocabulary is still evolving. Ruby lets the language evolve with the domain.

Metaprogramming turns vocabulary into behavior

Consider again:

has_many :orders

Rails can make this tiny declaration create methods, queries, relationships, and conventions around an association.

An AI-native DSL could do the same with a declaration such as:

agent :architect do
  knows :architecture
  produces :adr
end

A framework could expand it into:

  • an agent definition;
  • input and output contracts;
  • knowledge bindings;
  • retrieval configuration;
  • tool permissions;
  • prompts and context;
  • observability and tracing;
  • documentation;
  • a node in an orchestration graph.

That is the deeper role of metaprogramming here:

Turning domain vocabulary into executable behavior.

Metaprogramming as a Multiplier β€” a small Ruby declaration expanding into runtime, knowledge, policies, tracing, orchestration, documentation, and graph behavior
Metaprogramming as a Multiplier β€” a small declaration expands into executable system behavior.

The direction matters:

intent β†’ vocabulary β†’ Ruby DSL β†’ metaprogramming β†’ infrastructure

not the other way around.

What an AI-native Ruby DSL could look like

A first vocabulary might be surprisingly small:

AI.define do
  agent :discovery do
    goal "Understand the problem before proposing solutions"

    observes :customer_interviews,
             :support_tickets,
             :roadmap

    knows :product,
          :architecture

    distinguishes :evidence,
                  :inference,
                  :unknowns

    uses :github,
         :rag

    produces :discovery_report
    hands_off_to :planning
  end
end

Notice what is missing: embedding dimensions, vector-database details, HTTP schemas, token budgets, and low-level runtime configuration.

Those concerns do not disappear. They move below the semantic layer, just as SQL does not disappear when Rails says has_many :orders.

The SDLC as a program

This becomes more interesting when applied to the whole software lifecycle.

The previous AI Across the SDLC work already gives us the domain vocabulary. We can now make that lifecycle explicit:

sdlc "CaseFlow" do
  discovery do
    sources :customer_interviews,
            :support_tickets,
            :roadmap,
            :architecture_docs

    distinguish :evidence,
                :inference,
                :unknowns

    produce :discovery_report
  end

  planning do
    consume :discovery_report
    produce :prioritized_backlog,
            :acceptance_criteria
  end

  architecture do
    consume :discovery_report
    produce :adr,
            :component_map,
            :api_contract
  end

  development do
    implement :approved_architecture
  end

  testing do
    verify :business_rules,
           :integrations,
           :critical_flows
  end

  review do
    verify :quality,
           :security,
           :compliance
  end

  release do
    require_all :passing_tests,
                :approved_review
    deploy :production
  end

  production do
    observe :metrics,
            :logs,
            :traces,
            :events
  end

  feedback do
    learn_from :customers,
               :usage,
               :incidents
    feed_into :discovery
  end
end

Now the lifecycle is no longer only documentation. It is a model the machine can inspect, validate, transform, visualize, and potentially execute.

The SDLC as a Program β€” Discovery through Feedback expressed and orchestrated in Ruby
The SDLC as a Program β€” describing, generating, and orchestrating the lifecycle in Ruby.

The DSL can express more than a sequence. It can encode conditions, concurrency, gates, and organizational policy.

after :development do
  parallel do
    run :testing
    run :security_review
    run :documentation
  end

  converge_at :release_readiness
end
policy :production_change do
  require :tests
  require :security_review
  require_human_approval
end

Agents as first-class language concepts

An agent can become a native concept rather than a pile of configuration:

agent :discovery do
  phase :discovery
  knows :product, :customer_context
  skills :product_discovery
  uses :github
  produces :discovery_report
  hands_off_to :planning
end

GitHub Copilot already supports custom agents as Markdown-based agent profiles that can specify prompts, tools, and MCP servers. Agent Skills package instructions, scripts, and other resources in reusable skill directories that Copilot can load for specialized tasks.

That makes Copilot a useful target platform, but the Ruby DSL should sit above any one provider.

A declaration such as:

agent :discovery do
  skills :product_discovery
  tools :github
  knowledge :product_docs
  produces :discovery_report
end

could be compiled into platform-specific artifacts:

.github/
β”œβ”€β”€ agents/
β”‚   └── discovery.agent.md
β”œβ”€β”€ skills/
β”‚   └── product-discovery/
β”‚       └── SKILL.md
└── instructions/
    └── discovery.instructions.md

And agents can form a graph:

A useful language vocabulary starts to emerge:

πŸ€– Agent       who acts
⚑ Skill       what it can do
πŸ”§ Tool        what it can use
πŸ“š Knowledge   what it can know
🧠 Memory      what it can retain
πŸ›‘οΈ Policy      what it may do
πŸ“¦ Artifact    what it produces
πŸ”” Event       what triggers it
πŸ”€ Flow        how work moves

From DSL to a real system

The DSL becomes interesting when a declaration creates multiple projections of the same intent.

agent :operations do
  observes :metrics,
           :logs,
           :traces

  knows :runbooks
  uses :observability_platform

  on :incident do
    investigate
    correlate_signals
    suggest_remediation
  end
end

The same semantic model could produce agent definitions, skills, tool schemas, policies, knowledge configuration, diagrams, Markdown documentation, JSON representations, and runtime configuration.

From DSL to Real System β€” agents, knowledge, tools, policies, diagrams, documentation and runtime generated from Ruby
From DSL to Real System β€” one expressive language can project into agents, knowledge, tools, policies, documentation, diagrams, and runtime.

Ruby is no longer merely the runtime language. It becomes a semantic authoring layer.

One declaration, many projections

The clearest way to feel the potential is to look at one declaration and several outputs.

agent :discovery do
  goal "Understand the problem"

  knows :product
  uses :github

  produces :discovery_report

  policy do
    distinguish :evidence, :inference, :unknowns
  end
end

From that single semantic source, a framework can project into multiple artifacts:

β†’ JSON model
β†’ Mermaid diagram
β†’ Markdown documentation
β†’ Agent profile
β†’ Skill package
β†’ Runtime configuration

That is where the Ruby DSL stops being merely expressive and starts becoming architecturally useful.

A tiny executable prototype

This post includes a small, dependency-free Ruby experiment. It deliberately avoids pretending to be a production framework. Its purpose is to test the central hypothesis: can a Ruby DSL become a semantic model that generates multiple useful artifacts?

The core declaration looks like this:

world = RubyAIDSL.define do
  agent :discovery do
    goal "Understand the problem before proposing solutions"
    observes :customer_interviews, :support_tickets, :roadmap
    knows :product, :architecture
    distinguishes :evidence, :inference, :unknowns
    skills :product_discovery
    uses :github, :rag
    produces :discovery_report
    hands_off_to :planning
  end
end

The same library can model a complete lifecycle:

world = RubyAIDSL.define do
  sdlc :caseflow do
    discovery { produce :discovery_report }
    planning { consume :discovery_report; produce :prioritized_backlog }
    architecture { produce :adr, :component_map }
    development { implement :approved_architecture }
    testing { verify :critical_flows }
    review { verify :quality, :security }
    release { require_all :passing_tests; deploy :production }
    production { observe :metrics, :logs, :traces }
    feedback { learn_from :production; feed_into :discovery }
  end
end

Run it with ordinary Ruby:

ruby examples/agents.rb
ruby examples/sdlc.rb
ruby examples/generate.rb

The generator creates JSON, Markdown, and Mermaid projections from the same model.

Download the Ruby AI DSL support package

The prototype is intentionally small because the important thing at this stage is not the API surface. It is the direction:

human / AI intent
       ↓
Ruby DSL
       ↓
semantic model / AST
       ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
GitHub Copilot β”‚ Agent runtimeβ”‚ Documentationβ”‚
Mermaid        β”‚ JSON         β”‚ Other targetsβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Try it in 60 seconds

The quickest way to evaluate the idea is to run the support project and inspect what one Ruby definition can generate.

unzip ruby-ai-dsl-support.zip
cd ruby-ai-dsl-support
ruby examples/generate.rb

The generator writes multiple projections from the same semantic model:

generated/
β”œβ”€β”€ caseflow.json
β”œβ”€β”€ caseflow.md
└── caseflow.mmd

The exact syntax is still experimental. The important part is that the same Ruby declaration becomes machine-readable data, human-readable documentation, and a visual model.

Ruby as a semantic layer

Large models do not need Ruby in order to reason. That is not the claim.

The opportunity is that natural language is expressive but ambiguous, while machine configuration is precise but often semantically poor. Ruby DSLs occupy an interesting middle ground:

Natural language      Ruby DSL            Machine config
────────────────────────────────────────────────────────
expressive             expressive          precise
ambiguous              structured          rigid
not inherently         executable          executable
executable
human-first            human + AI          machine-first

An AI can generate the DSL. A human can read and review it. Ruby can execute it. A compiler can transform it. Another agent can reason about its structure.

That combination is valuable in an AI-native development environment.

What comes next

Python dominates much of today’s AI implementation ecosystem. That does not mean every layer of an AI-native application must use the same language.

Ruby has a different strength: it is unusually good at creating languages inside the language.

Rails demonstrated it for Web development. RSpec did it for testing. Rake did it for tasks. Bundler did it for dependency declaration.

An AI-native generation of Ruby libraries could do the same for:

Agents Β· Knowledge Β· Tools Β· Skills Β· Policies
Workflows Β· Evaluation Β· Governance Β· Software delivery

So perhaps the interesting question is not:

Will AI bring Ruby back?

It is:

What kinds of languages become valuable when AI writes more of the implementation?

This post is not a proposal for a finished framework. It is an experiment around a more useful question:

Can Ruby become a semantic language for AI-native software?

The next practical step is to push the prototype beyond JSON, Markdown, and Mermaid and generate real AI assetsβ€”agent profiles, skills, tool bindings, policies, and runtime configurationβ€”from the same source language.

A natural follow-up is:

Building the First AI-Native DSL in Ruby

Abstraction tends to move upward:

Assembly
   ↑
Programming languages
   ↑
Frameworks
   ↑
DSLs
   ↑
AI
   ↑
Human intent

If AI increasingly handles implementation, the valuable artifact may become the thing that expresses the intent, constraints, semantics, and structure of the system.

Ruby can help make that artifact executable.

Conclusion

Rails showed that programming becomes dramatically more powerful when the language moves closer to the problem domain.

AI may now be creating another such domain.

Agents, skills, tools, context, knowledge, policies, memory, evaluation, and orchestration are becoming important primitives of software engineering.

Ruby already possesses one of the most powerful mechanisms for turning domain concepts into executable language: its tradition of DSLs, backed by metaprogramming.

If Rails made Ruby feel native to the Web, perhaps DSLs can make Ruby feel native to AI.

And perhaps the next important Ruby framework will not primarily help us write more implementation.

It may help humans and AI express what software should become.

intent do
  humans :define_meaning
  ai     :reason_and_create
  ruby   :make_it_a_language
end

References

Mermaid diagram