Skip to content

Independent Ruby Consumer

Use published praxo-core==0.9.0 in your own application with Ruby 3.1 or newer. No PRAXO-CORE source checkout, API key, model service, or AxisVela product is required. Installation needs RubyGems access or a cached gem.

This is an extended-language starter. It demonstrates the allow/deny path but is not one of the four pilot languages that carry the governed negative matrix.

Choose the Boundary

This is a fit for an embedded model/tool loop with explicit tool scope and policy. Your application still owns model access, authentic identity, persistent approvals, external effects, and isolation. It is not a hosted agent platform, durable graph engine, or sandbox service. See when not to use Core.

Create the Application

Declare the exact dependency in a Gemfile:

source 'https://rubygems.org'

gem 'praxo-core', '0.8.0'

Then run bundle install. Do not use a path: or git: source, a vendored checkout, or a $LOAD_PATH entry that substitutes the published gem.

Application Source

Use the following as starter.rb, rendered from the tested source.

require 'praxo'

module Starter
  M = Praxo::Models

  class FixtureModel
    def initialize(target)
      @target = target
    end

    def generate(request)
      last = request.messages.last
      if last.role == M::TOOL
        M.response(message: M.message_assistant('recorded'))
      else
        M.response(message: M.message_assistant('', [
          M.tool_call(id: 'record-1', name: 'record', arguments: { 'target' => @target })
        ]))
      end
    end
  end

  class RecordTool
    attr_reader :calls

    def initialize
      @calls = 0
    end

    def call(_arguments)
      @calls += 1
      'recorded'
    end
  end

  def self.run_scenario(target)
    tool = RecordTool.new
    registry = Praxo::Agent::ToolRegistry.new
    registry.register(Praxo::Agent::FuncTool.new(
      'record', 'Record a target in an in-memory counter.',
      ->(arguments) { tool.call(arguments) }
    ))
    options = Praxo::Agent::AgentOptions.new(FixtureModel.new(target))
    options.tools = registry
    options.allowed_tools = ['record']
    options.policy = lambda do |action|
      case action.kind
      when 'model.generate'
        M.decision(allowed: true)
      when 'tool.invoke'
        M.decision(
          allowed: action.tool_name == 'record' && action.arguments['target'] == 'public',
          reason: 'only the public target is permitted'
        )
      else
        M.decision(allowed: false, reason: 'unsupported action')
      end
    end
    result = Praxo::Agent.run_agent(
      options, M.session(id: "consumer-#{target}"), 'Record the requested target.'
    )
    [result.output, tool.calls, nil]
  rescue StandardError => error
    ['', tool.calls, error]
  end

  def self.run(out = $stdout)
    output, calls, error = run_scenario('public')
    unless error.nil? && output == 'recorded' && calls == 1
      raise "allow verification failed: output=#{output.inspect} effects=#{calls} error=#{error}"
    end
    out.puts 'allow: recorded; effects=1'

    _, calls, error = run_scenario('private')
    unless error && error.message.include?('policy denied') && calls.zero?
      raise "deny verification failed: effects=#{calls} error=#{error}"
    end
    out.puts 'deny: policy rejected; effects=0'
  end
end

Starter.run if $PROGRAM_NAME == __FILE__

Acceptance Tests

Use the following as test_starter.rb, rendered from the tested source.

require_relative 'starter'

output, calls, error = Starter.run_scenario('public')
raise "allow: output=#{output.inspect} effects=#{calls} error=#{error}" unless error.nil? && output == 'recorded' && calls == 1

_, calls, error = Starter.run_scenario('private')
raise "deny: effects=#{calls} error=#{error}" unless error && error.message.include?('policy denied') && calls.zero?

puts 'ruby consumer tests passed'

Verify

bundle install
bundle exec ruby test_starter.rb

Expected program output:

allow: recorded; effects=1
deny: policy rejected; effects=0

The model is a deterministic fixture, not an LLM. allowed_tools limits the tool name; Policy also checks the target argument. A denied private target must never reach the in-memory effect counter. Keep these tests when replacing the fixture with a real adapter.

For a source checkout without Bundler, the same test runs with ruby -I language/ruby/lib test_starter.rb; that path is not independent consumer evidence.

Scope

This starter establishes only the stated gem and fixture behavior. It does not add the governed negative matrix, prove authentic identity, provide durable approvals, reconcile effects, or establish isolation. Continue with the Ruby implementation guide and the security model.