arbiter-gem 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -4
- data/README.md +71 -62
- data/lib/arbiter/client.rb +78 -5
- data/lib/arbiter/version.rb +1 -1
- metadata +12 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c96f3c93f0987c1c51c5a7d9cd4b11606e41669e34bd2b9c35cc14511ddbc394
|
|
4
|
+
data.tar.gz: cec5e2049ddcec4bb643742e013cdd8652b49c4f74d053a56c4e06641720462e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: da81138d05eea4fee1fee34b9c30aff833d3083b9d63bd25bd69ce71152ca201056abc8ca2f15d7869de4fd4fac9c029f797f2fa804763b79d003eba30ce87aa
|
|
7
|
+
data.tar.gz: 220b705b197ea2521b37d572facff43e95938c3de8d1ad2480be5c29e07dea4191fb8fe1815fba14d64493afbb892687fc315f719266a9fed2136df18a5415c0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
4
|
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
3
|
+
## 0.1.1 — 2026-10-12
|
|
4
|
+
|
|
5
|
+
- Route calls without a key to the public ARBITER endpoint, with its published daily free allowance.
|
|
6
|
+
- Send configured API keys using `X-API-Key`, matching the authenticated GRIP gateway.
|
|
7
|
+
- Document all five methods, authorization at execution, and HTTP 402 handling.
|
|
8
|
+
- Add RubyGems homepage, source, documentation, changelog and issues metadata.
|
|
9
|
+
|
|
10
|
+
## 0.1.0 — 2026-10-12
|
|
11
|
+
|
|
12
|
+
- Initial Ruby compare, embed, rank, decide and dispatch APIs.
|
data/README.md
CHANGED
|
@@ -1,109 +1,118 @@
|
|
|
1
|
-
# ARBITER
|
|
1
|
+
# ARBITER GEM
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
**Ruby module:** `Arbiter`
|
|
5
|
-
**Concept:** The Last Conditional — measure a finite permitted action field instead of extending contextual branching logic.
|
|
6
|
-
|
|
7
|
-
Deterministic remote ARBITER measurement in ordinary Ruby applications. No native engine, CLI installation, model weights, or Regolith required.
|
|
8
|
-
|
|
9
|
-
## Install
|
|
3
|
+
Deterministic ARBITER measurement in Ruby applications, using the remote ARBITER engine. No local ARBITER binary or model downloads.
|
|
10
4
|
|
|
11
5
|
```bash
|
|
12
6
|
gem install arbiter-gem
|
|
13
7
|
```
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
```ruby
|
|
18
|
-
gem "arbiter-gem"
|
|
19
|
-
```
|
|
9
|
+
## Quickstart — 10 free comparisons per day
|
|
20
10
|
|
|
21
|
-
|
|
11
|
+
The public ARBITER endpoint advertises **10 successful measurements per day without a key**. That allowance is enforced by the service, not the gem. Free requests use `https://arbiter.grip.fyi/v1/compare`.
|
|
22
12
|
|
|
23
13
|
```ruby
|
|
24
14
|
require "arbiter"
|
|
25
15
|
|
|
26
|
-
|
|
16
|
+
Arbiter.configure(api_key: nil, base_url: "https://arbiter.grip.fyi")
|
|
17
|
+
results = Arbiter.rank(
|
|
27
18
|
query: "Resolve a failed payment",
|
|
28
|
-
candidates: ["Retry", "Escalate", "Notify"]
|
|
29
|
-
top_k: 3
|
|
19
|
+
candidates: ["Retry the payment", "Escalate to billing", "Notify the customer"]
|
|
30
20
|
)
|
|
31
|
-
|
|
21
|
+
results.each { |row| puts "#{row[:candidate]}: #{row[:score]}" }
|
|
32
22
|
```
|
|
33
23
|
|
|
34
|
-
|
|
24
|
+
For a direct HTTP trial (no Ruby required):
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
26
|
+
```bash
|
|
27
|
+
curl -sS https://arbiter.grip.fyi/v1/compare \
|
|
28
|
+
-H 'Content-Type: application/json' \
|
|
29
|
+
-d '{"query":"Resolve a failed payment","candidates":["Retry the payment","Escalate to billing","Notify the customer"]}'
|
|
40
30
|
```
|
|
41
31
|
|
|
42
|
-
|
|
32
|
+
**Free-allowance verification:** This route and policy originate from ARBITER's published CLI offering; if the live gateway returns 401, 403 or 402, check its current status before advertising the free tier as operational. No API key is needed for the published free tier.
|
|
43
33
|
|
|
44
|
-
|
|
45
|
-
permitted = ->(name, _action) { name != :retry || payment.retry_allowed? }
|
|
34
|
+
## Funded API-key credits
|
|
46
35
|
|
|
47
|
-
|
|
48
|
-
retry: { description: "Retry the payment", run: -> { retry_payment } },
|
|
49
|
-
escalate: { description: "Escalate to billing", run: -> { escalate_payment } },
|
|
50
|
-
notify: { description: "Notify customer", run: -> { notify_customer } }
|
|
51
|
-
}
|
|
36
|
+
For paid usage, start at https://grip.fyi and request a GRIP-issued funded API key. **A self-service key issuance or credit-pack checkout URL has not been independently verified.** Do not assume purchasing has completed until credits and a key are issued.
|
|
52
37
|
|
|
53
|
-
|
|
54
|
-
choice = Arbiter.decide(intent: "Resolve the payment", actions: actions, permitted: permitted)
|
|
55
|
-
puts choice[:action]
|
|
38
|
+
The working GRIP owner-access transport uses this header:
|
|
56
39
|
|
|
57
|
-
|
|
58
|
-
|
|
40
|
+
```http
|
|
41
|
+
X-API-Key: YOUR_GRIP_API_KEY
|
|
59
42
|
```
|
|
60
43
|
|
|
61
|
-
**Authority is external to measurement.** Filter prohibited actions first. Hard policy rules, authorization, cryptographic checks and transactional safety still belong to the application. `dispatch` never executes unknown actions returned by the API. Both the input field and the selected action are checked.
|
|
62
|
-
|
|
63
|
-
## Configuration
|
|
64
|
-
|
|
65
44
|
```bash
|
|
66
|
-
export
|
|
45
|
+
export GRIP_API_KEY="<the-key-issued-for-GRIP-credits>"
|
|
67
46
|
```
|
|
68
47
|
|
|
69
|
-
|
|
48
|
+
```ruby
|
|
49
|
+
require "arbiter"
|
|
50
|
+
Arbiter.configure(api_key: ENV.fetch("GRIP_API_KEY"), base_url: "https://api.grip.fyi")
|
|
51
|
+
puts Arbiter.compare(query: "Route incident", candidates: ["Escalate", "Restart service"])
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Rate:** advertised metered x402 charge of **$0.001 USDC per compare/embed call**. Credit-pack denominations and checkout are not confirmed in public documentation. RubyGems credentials are unrelated to API keys. The gem never signs wallet payments or authorizes spending automatically.
|
|
55
|
+
|
|
56
|
+
## API
|
|
57
|
+
|
|
58
|
+
All methods belong to the `Arbiter` module. `Arbiter.compare` and `Arbiter.embed` return decoded JSON; normalized rankings are obtained using `rank`.
|
|
70
59
|
|
|
71
60
|
```ruby
|
|
72
|
-
Arbiter.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
)
|
|
61
|
+
Arbiter.compare(query: "Current intent", candidates: ["A", "B"], top_k: 2)
|
|
62
|
+
Arbiter.embed(texts: ["A", "B"])
|
|
63
|
+
Arbiter.rank(query: "Current intent", candidates: ["A", "B"], top_k: 2)
|
|
76
64
|
```
|
|
77
65
|
|
|
78
|
-
|
|
66
|
+
`embed` is typically used with funded API access; verify public availability before relying on it without a key.
|
|
67
|
+
|
|
68
|
+
Actions require a `description` and callable `run`:
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
actions = {
|
|
72
|
+
retry: { description: "Retry the payment", run: -> { :retried } },
|
|
73
|
+
escalate: { description: "Escalate to billing", run: -> { :escalated } },
|
|
74
|
+
notify: { description: "Notify the customer", run: -> { :notified } }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
allowed = ->(name, _action) { name != :retry }
|
|
79
78
|
|
|
80
|
-
|
|
79
|
+
# Measures only; no action is executed:
|
|
80
|
+
choice = Arbiter.decide(intent: "Resolve failed payment", actions: actions, permitted: allowed)
|
|
81
|
+
puts choice[:action]
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
# Explicitly executes a permitted callable, checking permission again before execution:
|
|
84
|
+
executed = Arbiter.dispatch(intent: "Resolve failed payment", actions: actions, permitted: allowed)
|
|
85
|
+
puts executed[:value]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`decide` and `dispatch` require **at least two permitted actions**. The application owns authorization; ranking cannot override it. `dispatch` sends another comparison request; it does not execute the earlier decision object.
|
|
89
|
+
|
|
90
|
+
## Error handling
|
|
83
91
|
|
|
84
92
|
```ruby
|
|
85
93
|
begin
|
|
86
|
-
Arbiter.compare(query: "
|
|
94
|
+
Arbiter.compare(query: "Resolve failed payment", candidates: ["Retry", "Escalate"])
|
|
87
95
|
rescue Arbiter::PaymentRequired => e
|
|
88
|
-
|
|
89
|
-
# e.challenge contains payment-related response headers, if supplied
|
|
96
|
+
warn "HTTP #{e.status} — obtain credits or handle the x402 challenge explicitly"
|
|
90
97
|
end
|
|
91
98
|
```
|
|
92
99
|
|
|
93
|
-
|
|
100
|
+
### Environment variables
|
|
94
101
|
|
|
95
|
-
|
|
102
|
+
- `ARBITER_API_KEY` or `GRIP_API_KEY`: optional API key, sent as `X-API-Key`.
|
|
103
|
+
- `ARBITER_BASE_URL`: optional override. Otherwise keyless calls use the public endpoint and authenticated calls use the GRIP API.
|
|
96
104
|
|
|
97
|
-
|
|
98
|
-
ruby -Ilib:test test/test_arbiter.rb
|
|
99
|
-
```
|
|
105
|
+
## Links
|
|
100
106
|
|
|
101
|
-
|
|
107
|
+
- Docs: https://arbiter-gem.grip.fyi
|
|
108
|
+
- RubyGems: https://rubygems.org/gems/arbiter-gem
|
|
109
|
+
- Source: https://github.com/ziolndr/arbiter-gem (requires public repository publication)
|
|
110
|
+
- GRIP: https://grip.fyi
|
|
102
111
|
|
|
103
|
-
|
|
112
|
+
## Tests
|
|
104
113
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
114
|
+
```bash
|
|
115
|
+
ruby -Ilib:test test/test_arbiter.rb
|
|
116
|
+
```
|
|
108
117
|
|
|
109
118
|
MIT © 2026 Actual General Intelligence, Inc.
|
data/lib/arbiter/client.rb
CHANGED
|
@@ -2,20 +2,30 @@
|
|
|
2
2
|
require "json"
|
|
3
3
|
require "net/http"
|
|
4
4
|
require "uri"
|
|
5
|
+
require "open3"
|
|
6
|
+
require "tempfile"
|
|
5
7
|
|
|
6
8
|
module Arbiter
|
|
7
9
|
class Client
|
|
8
|
-
|
|
10
|
+
PAID_BASE_URL = "https://api.grip.fyi".freeze
|
|
11
|
+
PUBLIC_BASE_URL = "https://arbiter.grip.fyi".freeze
|
|
12
|
+
DEFAULT_BASE_URL = PAID_BASE_URL
|
|
9
13
|
attr_reader :base_url
|
|
10
14
|
|
|
11
15
|
def initialize(api_key: ENV["ARBITER_API_KEY"] || ENV["GRIP_API_KEY"],
|
|
12
|
-
base_url: ENV
|
|
16
|
+
base_url: ENV["ARBITER_BASE_URL"],
|
|
13
17
|
open_timeout: 5, read_timeout: 30, transport: nil)
|
|
14
18
|
@api_key = api_key
|
|
19
|
+
# Keyless compare uses the public ARBITER free-allowance route.
|
|
20
|
+
# Funded/owner API keys use the GRIP gateway.
|
|
21
|
+
base_url ||= (@api_key && !@api_key.empty? ? PAID_BASE_URL : PUBLIC_BASE_URL)
|
|
15
22
|
@base_url = base_url.to_s.sub(%r{/+$}, "")
|
|
16
23
|
@open_timeout = open_timeout
|
|
17
24
|
@read_timeout = read_timeout
|
|
18
|
-
|
|
25
|
+
unless transport.nil? || transport == :auto || transport == :net_http || transport == :curl || transport.respond_to?(:call)
|
|
26
|
+
raise InvalidInput, "transport must be :auto, :curl, :net_http, or a callable"
|
|
27
|
+
end
|
|
28
|
+
@transport = transport || :auto
|
|
19
29
|
validate_url!
|
|
20
30
|
end
|
|
21
31
|
|
|
@@ -62,10 +72,12 @@ module Arbiter
|
|
|
62
72
|
def request(path, payload)
|
|
63
73
|
uri = URI("#{@base_url}#{path}")
|
|
64
74
|
headers = { "content-type" => "application/json", "accept" => "application/json" }
|
|
65
|
-
headers["
|
|
75
|
+
headers["X-API-Key"] = @api_key if @api_key && !@api_key.empty?
|
|
66
76
|
body = JSON.generate(payload)
|
|
67
|
-
response = if @transport
|
|
77
|
+
response = if @transport.respond_to?(:call)
|
|
68
78
|
@transport.call(uri, headers, body)
|
|
79
|
+
elsif @transport == :curl || (@transport == :auto && curl_available? && known_grip_host?(uri))
|
|
80
|
+
curl_post(uri, headers, body)
|
|
69
81
|
else
|
|
70
82
|
http_post(uri, headers, body)
|
|
71
83
|
end
|
|
@@ -90,6 +102,67 @@ module Arbiter
|
|
|
90
102
|
raise HTTPError.new(0, "ARBITER transport error (#{e.class})")
|
|
91
103
|
end
|
|
92
104
|
|
|
105
|
+
|
|
106
|
+
# The hosted GRIP gateway has rejected some default language HTTP clients
|
|
107
|
+
# at Cloudflare's browser-signature layer. Prefer curl for GRIP hosts when
|
|
108
|
+
# installed; never expose the API key in process argv, files, or exception
|
|
109
|
+
# text. Caller-supplied endpoints continue to use Net::HTTP by default.
|
|
110
|
+
def known_grip_host?(uri)
|
|
111
|
+
uri.scheme == "https" && ["api.grip.fyi", "arbiter.grip.fyi"].include?(uri.host)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def curl_available?
|
|
115
|
+
ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).any? do |part|
|
|
116
|
+
File.executable?(File.join(part, "curl"))
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
CurlResponse = Struct.new(:code, :body, :headers) do
|
|
121
|
+
def each_header(&block)
|
|
122
|
+
headers.each(&block)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def curl_post(uri, headers, body)
|
|
127
|
+
raise HTTPError.new(0, "curl not available") unless curl_available?
|
|
128
|
+
Tempfile.create("arbiter-curl-payload") do |payload_file|
|
|
129
|
+
Tempfile.create("arbiter-curl-headers") do |header_file|
|
|
130
|
+
Tempfile.create("arbiter-curl-output") do |output_file|
|
|
131
|
+
payload_file.write(body)
|
|
132
|
+
payload_file.flush
|
|
133
|
+
# X-API-Key is passed via STDIN, not command line arguments.
|
|
134
|
+
headers.each_value do |value|
|
|
135
|
+
raise InvalidInput, "header contains a line break" if value.match?(/[\r\n]/)
|
|
136
|
+
end
|
|
137
|
+
config = headers.map { |key, value| %(header = "#{curl_escape(key + ': ' + value)}") }.join("\n") + "\n"
|
|
138
|
+
args = ["curl", "--config", "-", "--silent", "--show-error",
|
|
139
|
+
"--connect-timeout", @open_timeout.to_s,
|
|
140
|
+
"--max-time", (@open_timeout + @read_timeout).to_s,
|
|
141
|
+
"--request", "POST", "--url", uri.to_s,
|
|
142
|
+
"--data-binary", "@#{payload_file.path}",
|
|
143
|
+
"--dump-header", header_file.path,
|
|
144
|
+
"--output", output_file.path,
|
|
145
|
+
"--write-out", "%{http_code}"]
|
|
146
|
+
stdout, _stderr, status = Open3.capture3(*args, stdin_data: config)
|
|
147
|
+
raise HTTPError.new(0, "ARBITER transport error (curl)") unless status.success?
|
|
148
|
+
code = stdout.strip
|
|
149
|
+
raise InvalidResponse, "curl returned no HTTP status" unless code.match?(/\A[1-5][0-9]{2}\z/)
|
|
150
|
+
lines = header_file.read.split(/\r?\n/)
|
|
151
|
+
response_headers = lines.filter_map do |line|
|
|
152
|
+
next if line.start_with?("HTTP/") || !line.include?(":")
|
|
153
|
+
key, value = line.split(":", 2)
|
|
154
|
+
[key.downcase.strip, value.to_s.strip]
|
|
155
|
+
end.to_h
|
|
156
|
+
CurlResponse.new(code, output_file.read, response_headers)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def curl_escape(value)
|
|
163
|
+
value.to_s.gsub("\\", "\\\\").gsub('"', '\\"')
|
|
164
|
+
end
|
|
165
|
+
|
|
93
166
|
def http_post(uri, headers, body)
|
|
94
167
|
req = Net::HTTP::Post.new(uri)
|
|
95
168
|
headers.each { |key, value| req[key] = value }
|
data/lib/arbiter/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: arbiter-gem
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Actual General Intelligence, Inc.
|
|
@@ -9,8 +9,9 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
|
-
description:
|
|
13
|
-
|
|
12
|
+
description: Measure and rank candidate actions with the ARBITER API from Ruby. Includes
|
|
13
|
+
compare, embed, rank, decide and dispatch; MCP interoperability and x402 payment
|
|
14
|
+
challenges. No native engine.
|
|
14
15
|
executables: []
|
|
15
16
|
extensions: []
|
|
16
17
|
extra_rdoc_files: []
|
|
@@ -24,11 +25,15 @@ files:
|
|
|
24
25
|
- lib/arbiter/flow.rb
|
|
25
26
|
- lib/arbiter/ranking.rb
|
|
26
27
|
- lib/arbiter/version.rb
|
|
27
|
-
homepage: https://arbiter.grip.fyi
|
|
28
|
+
homepage: https://arbiter-gem.grip.fyi
|
|
28
29
|
licenses:
|
|
29
30
|
- MIT
|
|
30
31
|
metadata:
|
|
31
|
-
homepage_uri: https://arbiter.grip.fyi
|
|
32
|
+
homepage_uri: https://arbiter-gem.grip.fyi
|
|
33
|
+
source_code_uri: https://github.com/ziolndr/arbiter-gem
|
|
34
|
+
documentation_uri: https://arbiter-gem.grip.fyi/#methods
|
|
35
|
+
changelog_uri: https://github.com/ziolndr/arbiter-gem/blob/main/CHANGELOG.md
|
|
36
|
+
bug_tracker_uri: https://github.com/ziolndr/arbiter-gem/issues
|
|
32
37
|
rdoc_options: []
|
|
33
38
|
require_paths:
|
|
34
39
|
- lib
|
|
@@ -45,5 +50,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
45
50
|
requirements: []
|
|
46
51
|
rubygems_version: 4.0.3
|
|
47
52
|
specification_version: 4
|
|
48
|
-
summary:
|
|
53
|
+
summary: Deterministic decision, ranking and action selection in Ruby; ARBITER API,
|
|
54
|
+
MCP and x402
|
|
49
55
|
test_files: []
|