airb 0.0.1 → 0.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0bea345cb31d92c865148aefe0699c0f83cdc2d6ba27ad85e9aee5f609b14bf9
4
- data.tar.gz: 90e5d25be0ebb4c61975646bb17f399895b308d6d733e0a976da114d2613e6d4
3
+ metadata.gz: 6265896136e1ee23d92dacfc00bd7245a1256af1bb20b1a4a76af33af46dfd02
4
+ data.tar.gz: 67f21a5fb4456a45f3b7c22bcb1fdd429ec84fe0e4234b4cee931b1a5b857118
5
5
  SHA512:
6
- metadata.gz: da1cf07726a440047c5a1ed5217f0b8d532bd71e7f0f73057c7e65b602e6438180a47cde49125869f3dbc96f7a682d7580f5e1208420dcbb4f5644d312cdf405
7
- data.tar.gz: e9f60f7b30f30d9471bcaa8c6395d40891ff4d9fa66f2d3f6fe6b0d9ce0f23d98b42370ae00c1067113acaaa1206ce5cd1874fd9eabf3e2ebdecc3ef9d7e7e10
6
+ metadata.gz: 4758d0cae83f02b112640a096225b1ecd1722419ddcfbbef96509b4698343e0366ad7ab44a084f375e4ebfcb4930d234ad68d9568c560e47f946c61562171462
7
+ data.tar.gz: 1bbf93a6fbc3a6d94b4fc8f5105df79173fe0dbf699af12f27d244e1a06cf9156250da175c9287ac3c6f291b94217800d28ef6a3d1b9f8f20472ab79194a5efd
@@ -0,0 +1,13 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(rake spec:*)",
5
+ "Bash(bundle install:*)",
6
+ "Bash(bundle exec:*)",
7
+ "Bash(gem install:*)",
8
+ "Bash(bundle show:*)"
9
+ ],
10
+ "deny": [],
11
+ "ask": []
12
+ }
13
+ }
data/CLAUDE.md ADDED
@@ -0,0 +1,54 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ This is a Ruby gem called "airb" - an AI-powered programming assistant that runs in a terminal chat interface. It uses the VSM (Viable System Model) architecture to coordinate between different systems including intelligence, governance, coordination, identity, and monitoring.
8
+
9
+ ## Development Commands
10
+
11
+ - `bin/setup` - Install dependencies
12
+ - `rake spec` - Run tests
13
+ - `bin/console` - Interactive console for experimentation
14
+ - `bundle exec rake install` - Install gem locally
15
+ - `rake` or `rake default` - Run both tests and RuboCop
16
+ - `rake rubocop` - Run RuboCop linter
17
+ - `exe/airb` - Run the airb CLI directly
18
+
19
+ ## Architecture
20
+
21
+ The codebase follows a modular VSM-based architecture:
22
+
23
+ ### Core Components
24
+ - `Airb::CLI` - Entry point that builds the organism and starts the runtime
25
+ - `Airb::Organism` - Factory that assembles all systems and tools using VSM DSL
26
+ - `Airb::Ports::ChatTTY` - Terminal interface for user interaction
27
+
28
+ ### Systems (VSM Components)
29
+ - `Intelligence` - Handles LLM interactions with support for OpenAI, Anthropic, and Gemini providers
30
+ - `Governance` - Security layer that validates file operations and prompts for risky actions
31
+ - `Coordination` - Manages async message flow and turn-taking
32
+ - `Identity` - System identification
33
+ - `Monitoring` - VSM monitoring system
34
+
35
+ ### Tools
36
+ File system tools in `lib/airb/tools/fs/`:
37
+ - `ListFiles` - Directory listing
38
+ - `ReadFile` - File reading with path safety
39
+ - `EditFile` - File editing with governance approval
40
+
41
+ ## Configuration
42
+
43
+ - `AIRB_PROVIDER` - LLM provider (openai, anthropic, gemini)
44
+ - `AIRB_MODEL` - Specific model to use
45
+ - `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY` - API keys
46
+ - `VSM_LENS=1` - Enable VSM visualization dashboard
47
+ - `VSM_LENS_PORT`, `VSM_LENS_TOKEN` - Lens configuration
48
+
49
+ ## Code Style
50
+
51
+ - Uses RuboCop with double quotes for strings
52
+ - Ruby 3.4+ required
53
+ - Frozen string literals enabled throughout
54
+ - VSM framework patterns for system composition
data/README.md CHANGED
@@ -1,39 +1,297 @@
1
- # Airb
1
+ # airb
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
3
+ An open-source, CLI-based programming agent for Rubyists.
4
4
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/airb`. To experiment with that code, run `bin/console` for an interactive prompt.
5
+ ## 🌌 Why use airb?
6
6
 
7
- ## Installation
7
+ airb is an open‑source, CLI‑based programming agent for Rubyists. We built it to explore a clean, composable agent architecture grounded in cybernetics—specifically Stafford Beer's Viable System Model (VSM)—and to make a practical tool you can run in your terminal to read, list, and edit files with the help of modern LLMs.
8
8
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
9
+ **In short:**
10
10
 
11
- Install the gem and add to the application's Gemfile by executing:
11
+ - **A new spine for agents:** Operations, Coordination, Intelligence, Governance, Identity—recursive, inspectable, testable.
12
+ - **A minimal, useful CLI** that streams responses and uses structured tool calls (no fragile "JSON from text" parsing).
13
+ - **A foundation to learn from and extend:** add tools, swap models (OpenAI/Anthropic/Gemini), plug in UI/observability, grow sub‑agents.
14
+
15
+ If you like small objects, clear seams, and UNIXy ergonomics, airb is for you.
16
+
17
+ ## 🌌 Who benefits from airb?
18
+
19
+ - **Ruby developers** who want a capable, hackable terminal agent that can actually work on a codebase.
20
+ - **Framework/tool authors** exploring agent design (capsules, tools, sub‑agents) with high cohesion & low coupling.
21
+ - **Educators and teams** who need a clear, auditable loop to reason about tool calling, streaming, and safety.
22
+ - **Researchers** playing with agent recursion (sub‑agents, tool‑as‑capsule), budget homeostasis, and observability.
23
+
24
+ ## 🌌 What does airb do?
25
+
26
+ A CLI programming agent that:
27
+
28
+ - **Streams assistant output** to your terminal as it thinks.
29
+
30
+ - **Uses native, structured tool calling** across providers:
31
+ - OpenAI "tools" (functions) — streaming + parallel tool calls
32
+ - Anthropic tool_use / tool_result — streaming (input_json_delta)
33
+ - Gemini function calling — non‑streaming MVP (streaming later)
34
+
35
+ - **Ships with core programming tools** (as capsules):
36
+ - `list_files(path?)` — directory listing (dirs end with /)
37
+ - `read_file(path)` — read UTF‑8 text files
38
+ - `edit_file(path, old_str, new_str)` — replace/create with confirmation
39
+
40
+ - **Runs on a VSM engine** (via the vsm gem):
41
+ - Operations — dispatches tool calls; concurrency per tool
42
+ - Coordination — session floor control & turn lifecycle
43
+ - Intelligence — LLM driver + conversation + streaming
44
+ - Governance — workspace sandbox, confirmations, budgets
45
+ - Identity — purpose/invariants & escalation hooks
46
+
47
+ - **Provides observability from day one:**
48
+ - JSONL event ledger (`.vsm.log.jsonl`)
49
+ - Optional web Lens (local SSE app) for live timelines
50
+
51
+ ### High‑level architecture
52
+
53
+ ```
54
+ airb (top capsule)
55
+ ├─ Identity – name, invariants
56
+ ├─ Governance – sandbox, confirms, budgets
57
+ ├─ Coordination – floor control, turn end
58
+ ├─ Intelligence – driver(OpenAI/Anthropic/Gemini), streaming, tool loop
59
+ ├─ Operations – dispatch tools as child capsules (parallel)
60
+ │ ├─ list_files (tool capsule)
61
+ │ ├─ read_file (tool capsule)
62
+ │ └─ edit_file (tool capsule)
63
+ └─ Ports – ChatTTY (CLI), Lens (web)
64
+ ```
65
+
66
+ ## 🌌 How to use it
67
+
68
+ ### Install
69
+
70
+ Requires Ruby 3.4+.
71
+
72
+ Add to your app or install globally:
12
73
 
13
74
  ```bash
14
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
75
+ # Using Bundler in a project
76
+ bundle add airb
77
+
78
+ # Or install gem globally
79
+ gem install airb
80
+ ```
81
+
82
+ airb depends on the vsm gem (the agent runtime & drivers).
83
+
84
+ ### Configure a provider
85
+
86
+ Pick one provider and set env vars:
87
+
88
+ ```bash
89
+ # OpenAI (streaming + tools)
90
+ export AIRB_PROVIDER=openai
91
+ export OPENAI_API_KEY=sk-...
92
+ export AIRB_MODEL=gpt-5-nano # default if unset
93
+
94
+ # Anthropic (streaming + tool_use)
95
+ # export AIRB_PROVIDER=anthropic
96
+ # export ANTHROPIC_API_KEY=...
97
+ # export AIRB_MODEL=claude-sonnet-4-0 # default if unset
98
+
99
+ # Gemini (MVP: non-streaming tool calls)
100
+ # export AIRB_PROVIDER=gemini
101
+ # export GEMINI_API_KEY=...
102
+ # export AIRB_MODEL=gemini-2.5-flash # default if unset
103
+ ```
104
+
105
+ ### Quickstart
106
+
107
+ From the root of a Git repo:
108
+
109
+ ```bash
110
+ airb
111
+ ```
112
+
113
+ Sample session:
114
+
115
+ ```
116
+ airb — chat (Ctrl-C to exit)
117
+ You: what's in this directory?
118
+ <streams…>
119
+ airb: README.md
120
+ lib/
121
+ spec/
122
+ tmp/
123
+ You: open README.md
124
+ <streams…>
125
+ airb: (prints file contents)
126
+ You: replace the title with "Airb Demo"
127
+ <streams…>
128
+ confirm? Write to README.md? [y/N] y
129
+ <streams…>
130
+ airb: OK. Title updated.
15
131
  ```
16
132
 
17
- If bundler is not being used to manage dependencies, install the gem by executing:
133
+ ### Live visualizer (optional)
134
+
135
+ Start the local Lens web app (SSE):
18
136
 
19
137
  ```bash
20
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
138
+ VSM_LENS=1 airb
139
+ # Lens: http://127.0.0.1:9292
140
+ ```
141
+
142
+ See live timeline & sessions: user messages, assistant deltas, tool calls/results, confirms, audits.
143
+
144
+ ### Configuration reference
145
+
146
+ | Variable | Meaning | Default |
147
+ |----------|---------|---------|
148
+ | `AIRB_PROVIDER` | openai \| anthropic \| gemini | openai |
149
+ | `AIRB_MODEL` | Model name for chosen provider | see examples above |
150
+ | `OPENAI_API_KEY` | OpenAI auth | — |
151
+ | `ANTHROPIC_API_KEY` | Anthropic auth | — |
152
+ | `GEMINI_API_KEY` | Gemini auth | — |
153
+ | `VSM_LENS` | 1 to enable web Lens | off |
154
+ | `VSM_LENS_PORT` | Lens port | 9292 |
155
+ | `VSM_LENS_TOKEN` | Optional access token (append ?token=...) | none |
156
+
157
+ **Workspace:** airb auto‑detects repo root (git rev-parse). If not a repo, it uses `Dir.pwd`.
158
+
159
+ ### What happens on each turn?
160
+
161
+ 1. You type text.
162
+ 2. Intelligence appends it to the conversation and calls the provider driver.
163
+ 3. The driver streams assistant text (assistant_delta).
164
+ 4. If the model needs a tool, the driver emits tool_calls → Operations routes to the proper capsule.
165
+ 5. The tool runs (in parallel, if multiple) and returns tool_result which is fed back to Intelligence.
166
+ 6. The model produces a final assistant message; Coordination marks the turn complete.
167
+ 7. Everything is emitted on the bus and logged; the Lens renders it live.
168
+
169
+ ### Provider behavior (at a glance)
170
+
171
+ | Capability | OpenAI | Anthropic | Gemini (MVP) |
172
+ |------------|--------|-----------|--------------|
173
+ | Streaming text | ✅ SSE | ✅ SSE (text_delta) | ➖ (planned) |
174
+ | Structured tool calls | ✅ tools/tool_calls | ✅ tool_use/tool_result | ✅ functionCall/Response |
175
+ | Parallel tool calls | ✅ supported | ✅ supported | ✅ supported |
176
+ | System prompt handling | in messages | header param (system) | in content / safety opts |
177
+
178
+ airb normalizes these differences so your CLI experience is the same.
179
+
180
+ ## Advanced Usage
181
+
182
+ ### Add your own tool (as a capsule)
183
+
184
+ Create a class that inherits `VSM::ToolCapsule`, describe its schema, implement `#run`.
185
+
186
+ ```ruby
187
+ # lib/airb/tools/search_repo.rb
188
+ class SearchRepo < VSM::ToolCapsule
189
+ tool_name "search_repo"
190
+ tool_description "Search files for a regex under optional path"
191
+ tool_schema({
192
+ type: "object",
193
+ properties: { path: {type:"string"}, pattern:{type:"string"} },
194
+ required: ["pattern"]
195
+ })
196
+
197
+ # Optional: choose how it executes (fiber/thread/ractor/subprocess)
198
+ def execution_mode = :thread
199
+
200
+ def run(args)
201
+ root = governance.send(:safe_path, args["path"] || ".")
202
+ rx = Regexp.new(args["pattern"])
203
+ matches = Dir.glob("#{root}/**/*", File::FNM_DOTMATCH).
204
+ select { |p| File.file?(p) }.
205
+ filter_map do |file|
206
+ lines = File.readlines(file, chomp:true, encoding:"UTF-8") rescue []
207
+ hits = lines.each_with_index.filter_map { |line,i| "#{file}:#{i+1}:#{line}" if rx.match?(line) }
208
+ hits unless hits.empty?
209
+ end
210
+ matches.flatten.join("\n")
211
+ end
212
+ end
213
+ ```
214
+
215
+ Register it in your organism under Operations:
216
+
217
+ ```ruby
218
+ operations do
219
+ capsule :search_repo, klass: SearchRepo
220
+ end
21
221
  ```
22
222
 
23
- ## Usage
223
+ The Intelligence system automatically advertises it to the model as a structured tool (OpenAI/Anthropic/Gemini shapes).
224
+
225
+ ### Create a sub‑agent (recursive capsule)
226
+
227
+ When a "tool" needs multiple steps (plan → read → patch → verify), make it a full capsule with its own 5 systems (Operations/Coordination/Intelligence/Governance/Identity). Expose it as a tool by including `VSM::ActsAsTool` and implementing `#run(args)` that orchestrates internally, then returns a summary.
228
+
229
+ This keeps the parent simple while the sub‑agent stays cohesive and testable.
230
+
231
+ ### Concurrency & performance
24
232
 
25
- TODO: Write usage instructions here
233
+ - The runtime uses async fibers for orchestration and streaming.
234
+ - Each tool call runs in its own task; set `execution_mode` to `:thread` for CPU‑heavier work.
235
+ - Governance can add timeouts and semaphores to limit concurrent tool calls.
26
236
 
27
- ## Development
237
+ ### Safety & governance
28
238
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
239
+ - airb runs in a workspace sandbox (repo root or CWD).
240
+ - `edit_file` prompts for confirmation before writing.
241
+ - You can extend Governance to show diffs, enforce allowlists, or budget tokens/time.
30
242
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
243
+ ## Troubleshooting
244
+
245
+ - **"No streaming"** — Gemini driver is MVP (non‑streaming). Use OpenAI/Anthropic for streaming.
246
+ - **"Path escapes workspace"** — Governance blocked a write; run airb from the repo root or adjust logic.
247
+ - **"No tool calls"** — Ensure your provider key & model are set; some models require enabling tools.
248
+ - **Lens shows nothing** — Start with `VSM_LENS=1`, then open http://127.0.0.1:9292.
249
+
250
+ ## Table of Contents
251
+
252
+ - [Why use airb?](#-why-use-airb)
253
+ - [Who benefits from airb?](#-who-benefits-from-airb)
254
+ - [What does airb do?](#-what-does-airb-do)
255
+ - [How to use it](#-how-to-use-it)
256
+ - [Install](#install)
257
+ - [Configure a provider](#configure-a-provider)
258
+ - [Quickstart](#quickstart)
259
+ - [Live visualizer](#live-visualizer-optional)
260
+ - [Configuration reference](#configuration-reference)
261
+ - [Provider behavior](#provider-behavior-at-a-glance)
262
+ - [Advanced Usage](#advanced-usage)
263
+ - [Add your own tool](#add-your-own-tool-as-a-capsule)
264
+ - [Create a sub-agent](#create-a-sub-agent-recursive-capsule)
265
+ - [Concurrency & performance](#concurrency--performance)
266
+ - [Safety & governance](#safety--governance)
267
+ - [Troubleshooting](#troubleshooting)
268
+ - [Roadmap](#roadmap)
269
+ - [Contributing](#contributing)
270
+ - [License](#license)
271
+ - [Acknowledgements](#acknowledgements)
272
+
273
+ ## Roadmap
274
+
275
+ - Streaming for Gemini driver
276
+ - Diff previews & undo for `edit_file`
277
+ - MCP client/server ports (tool ecosystem)
278
+ - "Command mode" (`airb -e "…"`) for one‑shot automation
279
+ - Rich Lens (search, replay, swimlanes, token counters)
280
+ - Additional built‑in capsules (planner, tester, editor)
32
281
 
33
282
  ## Contributing
34
283
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/airb.
284
+ Bug reports, ideas, and PRs welcome!
285
+
286
+ - Please keep code SRP‑friendly, name things clearly, and favor composition over inheritance.
287
+ - Tests for drivers should include small fixture streams → expected events.
36
288
 
37
289
  ## License
38
290
 
39
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
291
+ MIT (same as vsm), unless noted otherwise in subdirectories.
292
+
293
+ ## Acknowledgements
294
+
295
+ - Inspired by Stafford Beer's Viable System Model and the broader cybernetics community.
296
+ - Thanks to the Ruby OSS ecosystem for gems like async that make structured concurrency practical.
297
+ - Early discussions about good agent loops, tool use, and safety shaped this project.
data/Rakefile CHANGED
@@ -5,4 +5,4 @@ require "rspec/core/rake_task"
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
- task default: :spec
8
+ task default: %i[spec]
data/exe/airb CHANGED
@@ -1,3 +1,15 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "airb"
4
+ # --- Keep this CLI independent of any project's Bundler context ----------------
5
+ # If someone runs `airb` inside a Bundler-managed project, remove Bundler hooks
6
+ # so we resolve *this gem’s* dependencies, not the app’s Gemfile.
7
+ ENV.delete('BUNDLE_GEMFILE')
8
+ ENV.delete('BUNDLE_BIN_PATH')
9
+ if (rubyopt = ENV['RUBYOPT'])
10
+ ENV['RUBYOPT'] = rubyopt.split.reject { |x| x.include?('bundler/setup') }.join(' ')
11
+ end
12
+ ENV.delete('RUBYGEMS_GEMDEPS') # avoid implicit Gemfile activation
13
+
14
+ require 'airb'
15
+ Airb::CLI.start
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+ require_relative "systems/intelligence"
3
+ require_relative "systems/governance"
4
+ require_relative "systems/coordination"
5
+ require_relative "systems/identity"
6
+ require_relative "systems/monitoring"
7
+ require_relative "tools/fs/list_files"
8
+ require_relative "tools/fs/read_file"
9
+ require_relative "tools/fs/edit_file"
10
+
11
+ module Airb
12
+ module Organism
13
+ def self.build
14
+ workspace_root = `git rev-parse --show-toplevel`.strip
15
+ workspace_root = Dir.pwd if workspace_root.empty?
16
+
17
+ provider = (ENV["AIRB_PROVIDER"] || "openai").downcase
18
+
19
+ driver =
20
+ case provider
21
+ when "anthropic"
22
+ VSM::Drivers::Anthropic::AsyncDriver.new(
23
+ api_key: ENV.fetch("ANTHROPIC_API_KEY"),
24
+ model: ENV["AIRB_MODEL"] || "claude-sonnet-4-0"
25
+ )
26
+ when "gemini"
27
+ VSM::Drivers::Gemini::AsyncDriver.new(
28
+ api_key: ENV.fetch("GEMINI_API_KEY"),
29
+ model: ENV["AIRB_MODEL"] || "gemini-2.5-flash"
30
+ )
31
+ else
32
+ VSM::Drivers::OpenAI::AsyncDriver.new(
33
+ api_key: ENV.fetch("OPENAI_API_KEY"),
34
+ model: ENV["AIRB_MODEL"] || "gpt-5-nano"
35
+ )
36
+ end
37
+
38
+ VSM::DSL.define(:airb) do
39
+ identity klass: Airb::Systems::Identity, args: { name: "airb", invariants: [] }
40
+ governance klass: Airb::Systems::Governance, args: { workspace_root: workspace_root }
41
+ coordination klass: Airb::Systems::Coordination
42
+ intelligence klass: Airb::Systems::Intelligence, args: { driver: driver }
43
+ monitoring klass: VSM::Monitoring
44
+
45
+ operations do
46
+ capsule :list_files, klass: Airb::Tools::FS::ListFiles
47
+ capsule :read_file, klass: Airb::Tools::FS::ReadFile
48
+ capsule :edit_file, klass: Airb::Tools::FS::EditFile
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+ require "json"
3
+ require "securerandom"
4
+ module Airb
5
+ module Ports
6
+ class ChatTTY < VSM::Port
7
+ def should_render?(message)
8
+ [:assistant_delta, :assistant, :tool_call, :tool_result, :confirm_request].include?(message.kind)
9
+ end
10
+
11
+ def loop
12
+ session_id = SecureRandom.uuid
13
+ @capsule.roles[:coordination].grant_floor!(session_id)
14
+ @streaming_active = false
15
+ display_banner
16
+ print "\e[94mYou\e[0m: "
17
+
18
+ while (line = $stdin.gets&.chomp)
19
+ @capsule.bus.emit VSM::Message.new(kind: :user, payload: line, meta: { session_id: session_id }, path: [:airb])
20
+ @capsule.roles[:coordination].wait_for_turn_end(session_id)
21
+ print "\e[94mYou\e[0m: "
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def display_banner
28
+ puts <<~BANNER
29
+ \e[91m
30
+ ██████ ██ ██████ ██████
31
+ ██ ██ ██ ██ ██ ██ ██
32
+ ████████ ██ ██████ ██████
33
+ ██ ██ ██ ██ ██ ██ ██
34
+ ██ ██ ██ ██ ██ ██████
35
+ \e[0m
36
+ \e[96mAI-powered Ruby assistant\e[0m (Ctrl-C to exit)
37
+ BANNER
38
+ end
39
+
40
+ def render_out(message)
41
+ case message.kind
42
+ when :assistant_delta
43
+ @streaming_active = true
44
+ $stdout.print(message.payload)
45
+ $stdout.flush
46
+ when :assistant
47
+ if @streaming_active
48
+ # We already streamed the content; just end the line cleanly.
49
+ puts
50
+ else
51
+ puts
52
+ puts "\e[93mairb\e[0m: #{message.payload}"
53
+ end
54
+ @streaming_active = false
55
+ # Prompt is printed by the input loop after turn end
56
+ when :tool_call
57
+ tool = message.payload[:tool]
58
+ puts
59
+ puts "\e[90m→ tool\e[0m #{tool}"
60
+ when :tool_result
61
+ # Suppress tool result output; we only announce that the tool was called.
62
+ # Intentionally no-op for cleaner UI.
63
+ when :confirm_request
64
+ print "\n\e[95mconfirm?\e[0m #{message.payload} [y/N] "
65
+ ans = ($stdin.gets || "").strip.downcase.start_with?("y")
66
+ @capsule.bus.emit VSM::Message.new(
67
+ kind: :confirm_response,
68
+ payload: { accepted: ans },
69
+ meta: message.meta, # preserve corr_id/session_id
70
+ path: [:airb, :governance]
71
+ )
72
+ when :audit, :policy, :progress
73
+ # optional: print to stderr or keep quiet for now
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ module Airb
3
+ module Systems
4
+ class Coordination < VSM::Coordination
5
+ # Inherit the async-aware queue, floor & waiters
6
+ end
7
+ end
8
+ end
9
+
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+ module Airb
3
+ module Systems
4
+ class Governance < VSM::Governance
5
+ def initialize(workspace_root:)
6
+ @workspace_root = File.expand_path(workspace_root)
7
+ @pending = {} # corr_id => original :tool_call message
8
+ end
9
+
10
+ # Capture bus reference for emitting confirm requests, etc.
11
+ def observe(bus) = (@bus = bus)
12
+
13
+ def enforce(message, &pass)
14
+ case message.kind
15
+ when :tool_call
16
+ if risky?(message)
17
+ ensure_corr_id!(message)
18
+ @pending[message.corr_id] = message
19
+ @bus.emit VSM::Message.new(
20
+ kind: :confirm_request,
21
+ payload: confirm_text(message),
22
+ meta: message.meta.merge({ corr_id: message.corr_id }),
23
+ path: [:airb, :governance]
24
+ )
25
+ return true # swallow for now; will resume on confirm_response
26
+ else
27
+ check_paths!(message)
28
+ end
29
+ when :confirm_response
30
+ corr = message.meta&.fetch(:corr_id, nil)
31
+ if corr && (orig = @pending.delete(corr))
32
+ if message.payload[:accepted]
33
+ # proceed with original tool_call
34
+ return pass.call(orig)
35
+ else
36
+ # inform user and drop
37
+ @bus.emit VSM::Message.new(
38
+ kind: :assistant,
39
+ payload: "Cancelled.",
40
+ meta: orig.meta,
41
+ path: [:airb, :governance]
42
+ )
43
+ return true
44
+ end
45
+ end
46
+ end
47
+
48
+ pass.call(message)
49
+ end
50
+
51
+ private
52
+
53
+ def risky?(message)
54
+ message.payload[:tool].to_s == "edit_file"
55
+ end
56
+
57
+ def confirm_text(message)
58
+ args = message.payload[:args] || {}
59
+ path = args["path"] || "(no path)"
60
+ "Write to #{path}? (shows diff in a future version)"
61
+ end
62
+
63
+ def ensure_corr_id!(message)
64
+ message.corr_id ||= SecureRandom.uuid
65
+ end
66
+
67
+ def check_paths!(message)
68
+ args = message.payload[:args] || {}
69
+ if message.payload[:tool].to_s == "edit_file"
70
+ safe_path(args.fetch("path"))
71
+ elsif message.payload[:tool].to_s == "read_file"
72
+ safe_path(args.fetch("path"))
73
+ elsif message.payload[:tool].to_s == "list_files"
74
+ path = args["path"]
75
+ safe_path(path) if path && !path.empty?
76
+ end
77
+ end
78
+
79
+ def safe_path(rel)
80
+ full = File.expand_path(File.join(@workspace_root, rel.to_s))
81
+ raise "Path escapes workspace" unless full.start_with?(@workspace_root)
82
+ full
83
+ end
84
+ end
85
+ end
86
+ end
87
+
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ module Airb
3
+ module Systems
4
+ class Identity < VSM::Identity
5
+ def initialize(name:, invariants: [])
6
+ super(identity: name, invariants: invariants)
7
+ end
8
+ end
9
+ end
10
+ end
11
+
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ module Airb
3
+ module Systems
4
+ class Intelligence < VSM::Intelligence
5
+ SYSTEM_PROMPT = <<~PROMPT
6
+ You are "airb", a careful coding assistant inside a git workspace.
7
+ Use tools when needed. Prefer minimal, reversible edits and concise explanations.
8
+ PROMPT
9
+
10
+ def initialize(driver:)
11
+ super(driver: driver, system_prompt: SYSTEM_PROMPT)
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ module Airb
3
+ module Systems
4
+ class Monitoring < VSM::Monitoring
5
+ # Use VSM default JSONL monitor; extend later as needed.
6
+ end
7
+ end
8
+ end
9
+
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ require "fileutils"
3
+ module Airb
4
+ module Tools
5
+ module FS
6
+ class EditFile < VSM::ToolCapsule
7
+ tool_name "edit_file"
8
+ tool_description "Replace old_str with new_str in file (create if old_str is empty). Returns 'OK'."
9
+ tool_schema({
10
+ type: "object",
11
+ properties: {
12
+ path: { type: "string" },
13
+ old_str: { type: "string" },
14
+ new_str: { type: "string" }
15
+ },
16
+ required: ["path","old_str","new_str"]
17
+ })
18
+
19
+ def execution_mode = :fiber # change to :thread if you do heavy CPU work
20
+
21
+ def run(args)
22
+ path = governance.send(:safe_path, args.fetch("path"))
23
+ old = args.fetch("old_str")
24
+ newv = args.fetch("new_str")
25
+
26
+ if !File.exist?(path) && old.to_s.empty?
27
+ FileUtils.mkdir_p(File.dirname(path))
28
+ File.write(path, newv)
29
+ return "OK"
30
+ end
31
+
32
+ content = File.read(path, mode: "r:UTF-8")
33
+ replaced = content.gsub(old, newv)
34
+ raise "old_str not found" if replaced == content && !old.empty?
35
+ File.write(path, replaced)
36
+ "OK"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ module Airb
3
+ module Tools
4
+ module FS
5
+ class ListFiles < VSM::ToolCapsule
6
+ tool_name "list_files"
7
+ tool_description "List files/directories under a path (default: .). Directories end with '/'."
8
+ tool_schema({
9
+ type: "object",
10
+ properties: { path: { type: "string" } },
11
+ required: []
12
+ })
13
+
14
+ def run(args)
15
+ path = args["path"].to_s.empty? ? "." : args["path"]
16
+ root = governance.send(:safe_path, path) rescue Dir.pwd
17
+ entries = Dir.children(root).sort.map do |e|
18
+ full = File.join(root, e)
19
+ File.directory?(full) ? "#{e}/" : e
20
+ end
21
+ entries.join("\n")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ module Airb
3
+ module Tools
4
+ module FS
5
+ class ReadFile < VSM::ToolCapsule
6
+ tool_name "read_file"
7
+ tool_description "Read a UTF-8 text file at relative path."
8
+ tool_schema({
9
+ type: "object",
10
+ properties: { path: { type: "string" } },
11
+ required: ["path"]
12
+ })
13
+
14
+ def run(args)
15
+ path = governance.send(:safe_path, args.fetch("path"))
16
+ File.read(path, mode: "r:UTF-8")
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
data/lib/airb/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Airb
4
- VERSION = "0.0.1"
4
+ VERSION = "0.1.2"
5
5
  end
data/lib/airb.rb CHANGED
@@ -1,8 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "airb/version"
3
+ require "vsm"
4
+ require_relative "airb/organism"
5
+ require_relative "airb/ports/chat_tty"
4
6
 
5
7
  module Airb
6
- class Error < StandardError; end
7
- # Your code goes here...
8
+ class CLI
9
+ def self.start
10
+ $stdout.sync = true
11
+ $stderr.sync = true
12
+ capsule = Airb::Organism.build
13
+ hub = nil
14
+
15
+ # Optional: live visualizer (Lens) from VSM
16
+ if ENV["VSM_LENS"] == "1"
17
+ hub = VSM::Lens.attach!(
18
+ capsule,
19
+ host: "127.0.0.1",
20
+ port: (ENV["VSM_LENS_PORT"] || 9292).to_i,
21
+ token: ENV["VSM_LENS_TOKEN"]
22
+ )
23
+ puts "Lens: http://127.0.0.1:#{ENV['VSM_LENS_PORT'] || 9292}"
24
+ end
25
+
26
+ port = Airb::Ports::ChatTTY.new(capsule:)
27
+ VSM::Runtime.start(capsule, ports: [port]) # async reactor + port loop
28
+ end
29
+ end
8
30
  end
31
+
data/llms.txt ADDED
@@ -0,0 +1,306 @@
1
+
2
+ airb / VSM — llms.txt
3
+ =====================
4
+
5
+ Purpose of this file
6
+ --------------------
7
+ Give any LLM or code-editing agent **everything it needs** to:
8
+ 1) understand what `airb` is and how it works,
9
+ 2) make **safe, minimal edits**,
10
+ 3) add or modify tools and sub-agents (capsules),
11
+ 4) run and validate the project locally,
12
+ 5) keep behavior consistent across OpenAI / Anthropic / Gemini providers,
13
+ 6) use the built-in observability (Lens) to reason about live behavior.
14
+
15
+ This file summarizes architecture, invariants, directories, message semantics, configuration, and editing protocols.
16
+
17
+
18
+ High-level mission
19
+ ------------------
20
+ - **Why**: Explore a clean, recursive architecture for AI agents grounded in cybernetics (Stafford Beer’s Viable System Model), and ship a practical **open-source CLI programming agent** for Rubyists.
21
+ - **Who**: Ruby developers, agent researchers, educators, and tool authors.
22
+ - **What**: A terminal chatbot that streams answers and uses **structured tool calls** to list/read/edit files in a workspace. Built on the `vsm` runtime.
23
+
24
+
25
+ Project at a glance
26
+ -------------------
27
+ - Language: Ruby 3.4+
28
+ - Runtime: `vsm` gem (async reactor, Capsules, VSM systems, tools-as-capsules)
29
+ - Providers (from `vsm`): OpenAI (`AsyncDriver`), Anthropic (`AsyncDriver`), Gemini (`AsyncDriver`)
30
+ - Core tools (capsules): `list_files`, `read_file`, `edit_file`
31
+ - Observability: JSONL ledger + optional **Lens** (local SSE web app)
32
+ - Terminal UI: `ChatTTY` (streams deltas, handles confirmations)
33
+ - Concurrency: async fibers; tools choose execution mode (`:fiber` / `:thread`)
34
+
35
+
36
+ Directory layout (airb)
37
+ -----------------------
38
+ ```
39
+ exe/airb # executable
40
+ lib/airb.rb # CLI entry; starts capsule + ports + optional Lens
41
+ lib/airb/organism.rb # builds the top-level capsule via VSM::DSL
42
+
43
+ lib/airb/ports/chat_tty.rb # terminal port (streaming, confirmations)
44
+
45
+ lib/airb/systems/coordination.rb
46
+ lib/airb/systems/governance.rb
47
+ lib/airb/systems/identity.rb
48
+ lib/airb/systems/intelligence.rb
49
+ lib/airb/systems/monitoring.rb # thin wrapper over VSM::Monitoring
50
+
51
+ lib/airb/tools/fs/list_files.rb
52
+ lib/airb/tools/fs/read_file.rb
53
+ lib/airb/tools/fs/edit_file.rb
54
+
55
+ spec/integration_spec.rb # example integration (fake driver)
56
+ ```
57
+
58
+ Do not remove these files; they define the public behavior expected by users and tests.
59
+
60
+
61
+ Core concepts (VSM + Capsules)
62
+ ------------------------------
63
+ - **Capsule**: a self-contained agent unit with 5 named systems (roles):
64
+ - **Identity**: name/invariants; escalation hooks.
65
+ - **Governance**: **safety** (sandbox, confirms, budgets, policies).
66
+ - **Coordination**: **scheduling/floor** control, turn lifecycle.
67
+ - **Intelligence**: connects to LLM driver, manages conversation, streams output, triggers tools.
68
+ - **Operations**: dispatches **tools** (child capsules); parallel execution.
69
+
70
+ - **Tools as Capsules**:
71
+ - Implement class inheriting `VSM::ToolCapsule`.
72
+ - Declare `tool_name`, `tool_description`, `tool_schema` (JSON Schema).
73
+ - Implement `#run(args)` and optional `#execution_mode` (`:fiber` or `:thread`).
74
+ - Tools automatically expose cross-provider descriptors via `Descriptor#to_openai_tool / #to_anthropic_tool / #to_gemini_tool`.
75
+
76
+ - **Ports**:
77
+ - External adapters (CLI, Lens). Ports subscribe to the bus and render messages.
78
+
79
+ - **Recursion**:
80
+ - Any capsule can itself contain sub-capsules (planning, editor, tester). Sub-agents can also be exposed as a **single tool** to the parent via `ActsAsTool`.
81
+
82
+
83
+ Message bus semantics
84
+ ---------------------
85
+ All activity flows through the async bus as `VSM::Message` structs:
86
+
87
+ - `kind` (enum used by systems & ports):
88
+ - `:user` — user input from CLI.
89
+ - `:assistant_delta` — streaming text tokens to terminal.
90
+ - `:assistant` — final turn text.
91
+ - `:tool_call` — Intelligence wants Operations to run a tool.
92
+ - `:tool_result` — result string from a tool (routed back to Intelligence).
93
+ - `:confirm_request` / `:confirm_response` — Governance confirmation loop.
94
+ - `:policy`, `:audit`, `:progress` — optional advisories/logs.
95
+ - `meta`:
96
+ - `session_id` (required for multi-turn coherence + floor control).
97
+ - (optional) `tool`, `elapsed_ms`, `args_size`, etc.
98
+ - `corr_id`:
99
+ - Correlates `:tool_call` with its `:tool_result`.
100
+ - `path`:
101
+ - Lane like `[:airb, :operations, :read_file]` (Lens uses this for swimlanes).
102
+
103
+ **Turn lifecycle**:
104
+ 1) Port emits `:user`.
105
+ 2) Intelligence calls provider driver → streams `:assistant_delta` → may emit `:tool_calls`.
106
+ 3) Operations runs requested tools concurrently → `:tool_result` messages.
107
+ 4) Intelligence continues with results → emits final `:assistant`.
108
+ 5) Coordination marks turn end (so Port can unblock and prompt again).
109
+
110
+
111
+ Provider abstraction (drivers live in vsm)
112
+ ------------------------------------------
113
+ airb is provider-agnostic; drivers normalize to three events:
114
+
115
+ - `:assistant_delta` (streaming text)
116
+ - `:assistant_final` (final text)
117
+ - `:tool_calls` (array of `{ id:, name:, arguments: Hash }`)
118
+
119
+ Differences:
120
+ - **OpenAI**: messages include a `system` message; `tools` with `function` schema; SSE streaming.
121
+ - **Anthropic**: `system` provided in request header/body; `tool_use` streamed via `input_json_delta` fragments; we buffer & emit a single `:tool_calls` event per block.
122
+ - **Gemini**: `functionDeclarations` + `functionCall/functionResponse`; MVP driver is non-streaming; you can add streaming later.
123
+
124
+
125
+ Governance rules (safety)
126
+ -------------------------
127
+ - airb operates in a **workspace sandbox** (Git root or CWD). Governance validates file paths.
128
+ - `edit_file` requires **interactive confirmation** via `:confirm_request/response`.
129
+ - Future: token/time budgets, rate limits, diff previews, undo.
130
+
131
+ **LLM editing constraint**: Do not weaken path checks, confirms, or invariants without an explicit instruction in a change request.
132
+
133
+
134
+ CLI behavior (ChatTTY)
135
+ ----------------------
136
+ - Reads stdin lines, emits `:user` with a unique `session_id`.
137
+ - Streams `:assistant_delta` inline; prints `:assistant` on completion.
138
+ - Prompts for confirmation on `:confirm_request`, echoes decision as `:confirm_response`.
139
+ - Hands off turn control to Coordination (waits for final `:assistant` before re-prompting).
140
+
141
+
142
+ Observability (Monitoring + Lens)
143
+ ---------------------------------
144
+ - JSONL ledger: `.vsm.log.jsonl` (one line per message).
145
+ - Lens: local SSE web app (bundled in `vsm`).
146
+ - Enable with `VSM_LENS=1` when running airb.
147
+ - URL: `http://127.0.0.1:9292`
148
+ - Shows timeline, sessions, tool calls/results, lanes by `path`.
149
+
150
+ Make events useful: include `meta.session_id`, `path`, and `meta.tool` (for calls/results).
151
+
152
+
153
+ Configuration (env vars)
154
+ ------------------------
155
+ ```
156
+ AIRB_PROVIDER = openai | anthropic | gemini (default: openai)
157
+ AIRB_MODEL = provider-specific model name
158
+ OPENAI_API_KEY = ...
159
+ ANTHROPIC_API_KEY = ...
160
+ GEMINI_API_KEY = ...
161
+ VSM_LENS = 1 to enable web visualizer (default off)
162
+ VSM_LENS_PORT = 9292 (default)
163
+ VSM_LENS_TOKEN = optional access token for Lens (append ?token=...)
164
+ ```
165
+
166
+ Workspace: if `git rev-parse --show-toplevel` fails, airb uses `Dir.pwd`.
167
+
168
+
169
+ How to add a new tool (capsule)
170
+ -------------------------------
171
+ **Goal**: minimal diff, high cohesion, governance-aware.
172
+
173
+ 1) Create a file, e.g. `lib/airb/tools/search_repo.rb`:
174
+
175
+ ```ruby
176
+ class SearchRepo < VSM::ToolCapsule
177
+ tool_name "search_repo"
178
+ tool_description "Search files for a regex under optional path"
179
+ tool_schema({
180
+ type:"object",
181
+ properties:{ path:{type:"string"}, pattern:{type:"string"} },
182
+ required:["pattern"]
183
+ })
184
+
185
+ def execution_mode = :thread # CPU-ish scan; allow parallelism
186
+
187
+ def run(args)
188
+ root = governance.send(:safe_path, args["path"] || ".")
189
+ rx = Regexp.new(args["pattern"])
190
+ matches = Dir.glob("#{root}/**/*", File::FNM_DOTMATCH)
191
+ .select { |p| File.file?(p) }
192
+ .flat_map do |file|
193
+ lines = File.readlines(file, chomp:true, encoding:"UTF-8") rescue []
194
+ lines.each_with_index.filter_map { |line,i| "#{file}:#{i+1}:#{line}" if rx.match?(line) }
195
+ end
196
+ matches.join("\n")
197
+ end
198
+ end
199
+ ```
200
+
201
+ 2) Register it in `lib/airb/organism.rb` under `operations`:
202
+
203
+ ```ruby
204
+ operations do
205
+ capsule :search_repo, klass: SearchRepo
206
+ end
207
+ ```
208
+
209
+ 3) (Optional) Add a spec covering one happy path.
210
+
211
+ No changes required in `Intelligence`—it auto-advertises all tool descriptors to the provider driver.
212
+
213
+
214
+ How to add a sub-agent (recursive capsule)
215
+ ------------------------------------------
216
+ Use this when a feature needs planning/verification steps (e.g., “Editor” agent).
217
+
218
+ 1) Create a capsule with its own systems (ops/coord/intel/gov/identity) under `lib/airb/capsules/editor/...`.
219
+ 2) If you want to expose it to the parent as a single tool, `include VSM::ActsAsTool` and implement `#run(args)` that orchestrates the inner loop and returns a result string.
220
+ 3) Register the capsule under `operations`. The parent will see it as a tool; the Lens will show its lane via the `path` hierarchy.
221
+
222
+
223
+ Testing guidance
224
+ ----------------
225
+ - **Unit**: pure Ruby classes (tools, governance checks) should be tested without any network.
226
+ - **Integration**: use a **fake driver** that emits `:tool_calls` then `:assistant_final`. See `spec/integration_spec.rb`.
227
+ - **Contract**: if you change message kinds/meta/path, update both the Port and tests.
228
+ - **Style**: run `rubocop` if present; keep methods small; be explicit with names (POODR/SOLID).
229
+
230
+
231
+ Troubleshooting
232
+ ---------------
233
+ - No streaming: Gemini driver is MVP non-streaming; use OpenAI/Anthropic for streaming.
234
+ - “Path escapes workspace”: Governance blocked unsafe path; run in correct repo or update the rule intentionally.
235
+ - No tool calls: Ensure provider/model supports tools; keys set correctly.
236
+ - Lens empty: Start with `VSM_LENS=1` and open `http://127.0.0.1:9292`.
237
+
238
+
239
+ Roadmap (safe to implement)
240
+ ---------------------------
241
+ - Diff preview & undo for `edit_file` confirmations.
242
+ - Concurrency limits (Async semaphores) per tool family.
243
+ - Gemini streaming endpoint / Live API support.
244
+ - MCP client/server ports (map `ActsAsTool` to MCP tool specs).
245
+ - Replay mode in Lens (read JSONL and scrub timeline).
246
+ - “Command mode”: `airb -e "message"` for one-shot runs.
247
+
248
+
249
+ LLM Edit Protocol (LEP)
250
+ -----------------------
251
+ **Use this when applying automated changes.**
252
+
253
+ 1) **Understand the goal**: restate the change request; identify files to touch.
254
+ 2) **Plan minimal diffs**: prefer small, reversible edits; keep public method signatures stable unless requested.
255
+ 3) **Safety first**:
256
+ - Never weaken Governance sandbox/confirm flows without explicit instruction.
257
+ - Keep `session_id`, `corr_id`, `path` and message kinds consistent.
258
+ 4) **Make the change**:
259
+ - Edit only necessary files.
260
+ - For new tools, add a capsule file + register in `organism.rb`.
261
+ - For provider changes, modify `organism.rb` env switch or the `vsm` drivers (not here).
262
+ 5) **Update docs/tests**:
263
+ - Adjust README if behavior changes.
264
+ - Add/modify a spec (happy path).
265
+ 6) **Self-check**:
266
+ - Static scan for obvious errors (names, requires).
267
+ - Ensure `airb` still runs with `OPENAI_API_KEY=...` in a repo.
268
+ 7) **Commit guidance**:
269
+ - Conventional message: `feat(tool): add search_repo capsule` / `fix(intel): avoid duplicate tool_calls`.
270
+ - Include a brief rationale.
271
+
272
+ If conflicted, prefer **safer behavior** (ask for confirmation, maintain invariants) over convenience.
273
+
274
+
275
+ Quick reference: critical files
276
+ -------------------------------
277
+ - Entry: `exe/airb`, `lib/airb.rb`
278
+ - Organism wiring: `lib/airb/organism.rb`
279
+ - CLI Port: `lib/airb/ports/chat_tty.rb`
280
+ - Intelligence: `lib/airb/systems/intelligence.rb`
281
+ - Governance: `lib/airb/systems/governance.rb`
282
+ - Coordination: `lib/airb/systems/coordination.rb`
283
+ - Tools: `lib/airb/tools/**.rb`
284
+ - Tests: `spec/**`
285
+
286
+
287
+ Example prompts to drive changes (for humans)
288
+ ---------------------------------------------
289
+ - “Add a tool to search the repo for `TODO` and return matches with file:line.”
290
+ - “Require confirmation for files larger than 1 MB when reading them.”
291
+ - “Expose an `editor` sub-agent capsule that plans a small patch then applies it via `edit_file`.”
292
+
293
+
294
+ Glossary
295
+ --------
296
+ - **Capsule**: a composable agent component with the five VSM systems.
297
+ - **Tool**: a capsule exposed to the LLM via a JSON schema descriptor.
298
+ - **Turn**: a user→assistant exchange, possibly with tool calls.
299
+ - **Floor**: turn ownership; Coordination keeps output ordered per session.
300
+ - **Lens**: local web visualizer (SSE) for live events.
301
+ - **Descriptor**: the object that maps a tool to provider-specific shapes.
302
+
303
+
304
+ End
305
+ ---
306
+ If you (the LLM) need to modify behavior, follow **LLM Edit Protocol**, keep deltas small, and preserve the message semantics and Governance guarantees.
metadata CHANGED
@@ -1,21 +1,62 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: airb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Werner
8
8
  bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
12
- description: airb is a command-line AI agent that lives in your Ruby REPL. It combines
13
- the flexibility of Ruby’s dynamic runtime with model-agnostic AI calls (powered
14
- by Sublayer) to create a self-modifying assistant that can learn new abilities on
15
- the fly. Drawing inspiration from Stafford Beer’s Viable System Model, airb organizes
16
- itself into adaptive subsystems, coordinates its own tools, and continuously improves
17
- through interactive learning — all without leaving your terminal. Perfect for developers
18
- who want a conversational, hackable, and deeply Ruby-native AI companion.
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: vsm
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.13'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.13'
40
+ - !ruby/object:Gem::Dependency
41
+ name: async-rspec
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.17'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.17'
54
+ description: " airb is an open-source CLI programming agent that helps developers
55
+ build software\n using modern LLMs (OpenAI, Anthropic, Gemini). Built on a clean,
56
+ composable architecture \n inspired by Stafford Beer's Viable System Model, it
57
+ features streaming responses, structured \n tool calling, built-in file operations,
58
+ and optional web-based observability. Designed for \n hackability with small
59
+ objects, clear seams, and UNIXy ergonomics.\n"
19
60
  email:
20
61
  - scott@sublayer.com
21
62
  executables:
@@ -23,13 +64,26 @@ executables:
23
64
  extensions: []
24
65
  extra_rdoc_files: []
25
66
  files:
67
+ - ".claude/settings.local.json"
26
68
  - ".rspec"
69
+ - CLAUDE.md
27
70
  - LICENSE.txt
28
71
  - README.md
29
72
  - Rakefile
30
73
  - exe/airb
31
74
  - lib/airb.rb
75
+ - lib/airb/organism.rb
76
+ - lib/airb/ports/chat_tty.rb
77
+ - lib/airb/systems/coordination.rb
78
+ - lib/airb/systems/governance.rb
79
+ - lib/airb/systems/identity.rb
80
+ - lib/airb/systems/intelligence.rb
81
+ - lib/airb/systems/monitoring.rb
82
+ - lib/airb/tools/fs/edit_file.rb
83
+ - lib/airb/tools/fs/list_files.rb
84
+ - lib/airb/tools/fs/read_file.rb
32
85
  - lib/airb/version.rb
86
+ - llms.txt
33
87
  - sig/airb.rbs
34
88
  homepage: https://github.com/sublayerapp/airb
35
89
  licenses:
@@ -38,7 +92,6 @@ metadata:
38
92
  homepage_uri: https://github.com/sublayerapp/airb
39
93
  source_code_uri: https://github.com/sublayerapp/airb
40
94
  changelog_uri: https://github.com/sublayerapp/airb
41
- rubygems_mfa_required: 'true'
42
95
  rdoc_options: []
43
96
  require_paths:
44
97
  - lib
@@ -46,7 +99,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
46
99
  requirements:
47
100
  - - ">="
48
101
  - !ruby/object:Gem::Version
49
- version: 3.1.0
102
+ version: '3.4'
50
103
  required_rubygems_version: !ruby/object:Gem::Requirement
51
104
  requirements:
52
105
  - - ">="
@@ -55,6 +108,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
55
108
  requirements: []
56
109
  rubygems_version: 3.6.9
57
110
  specification_version: 4
58
- summary: REPL-driven AI agent for Ruby self-modifying, model-agnostic, and inspired
59
- by Stafford Beer’s Viable System Model.
111
+ summary: CLI-based programming agent for Ruby with VSM architecture
60
112
  test_files: []