omakase-agents 0.0.1.alpha → 0.0.2.alpha

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: c1cd37ac842736548d6501feab0ad139df3bad22437902f19ead73ed5eae819c
4
- data.tar.gz: 733def6d93d950e7bd1507240877d90447dd7359255ba4e3c5bc3d93ce3794c6
3
+ metadata.gz: 96b04f8669c6e6bf4f176d19b779ca495fb1b2fde034952c48175eb2999647b3
4
+ data.tar.gz: 32d2d58018dd35cf63590c889f6c9e5a8bca214e1ccfcd9989c0f90c157bec70
5
5
  SHA512:
6
- metadata.gz: 78f54588fa331091f7e604bab6637a31a9f7f14298b0f513df78e243f851079f9181c2c71c627f0a33d5069ec541711adee81e17516916077990b5e1495a4533
7
- data.tar.gz: 4c97ef7304cce538f6dd4b9d95d6c7053bc6fff9ee6cab88a02b9d44ccdc38e78edfcf95d669a3a9a20fd9f94557d28e05b714eeac3405957d47e1e615f89592
6
+ metadata.gz: e596c18452218540f3a92c7f2542cbf1ea7e5b4fe0314c359e784d14fe331defdaf40f642cd4679667ed0e6ca5c8e42bd171cb940b2b2bd6b3fd47faba41bf97
7
+ data.tar.gz: 18e626683d93029d7d29da409b9f456800cf4b0579e9e478636d4b8ff2902f628ef34460cf3dd935b164e76d057bc3b01bb22c3a4b58fe9c15c354befbdc9cf9
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # Omakase
2
2
 
3
- A light agent framework — about 600 lines of library. *Omakase* (お任せ): you name what you want,
3
+ A light agent framework — about 700 lines of library. *Omakase* (お任せ): you name what you want,
4
4
  the rest is left to the chef.
5
5
 
6
+ **[esshka.github.io/omakase](https://esshka.github.io/omakase/)** · [rubygems](https://rubygems.org/gems/omakase-agents) ![gem](https://img.shields.io/gem/v/omakase-agents?include_prereleases&color=c8452e&label=)
7
+
6
8
  The whole philosophy: **an agent is an object**. Its fields are state, its methods are what the
7
9
  model can call, and the methods it *declares without a body* are written by the model at runtime —
8
10
  the method name and prompt are the specification, the schema is the contract.
@@ -40,13 +42,11 @@ Ruby 3.2+.
40
42
  gem "omakase-agents" # the library is `Omakase`
41
43
  ```
42
44
 
43
- Alpha (`0.0.1.alpha`) and not on RubyGems yet, so until it is, build it from a checkout:
45
+ From the command line the flag is needed — `0.0.2.alpha` is a prerelease, and RubyGems skips those
46
+ unless asked. Bundler resolves it without one, since no stable version exists yet.
44
47
 
45
48
  ```bash
46
- git clone https://github.com/esshka/omakase
47
- cd omakase
48
- gem build omakase-agents.gemspec
49
- gem install ./omakase-agents-*.gem
49
+ gem install omakase-agents --pre
50
50
  ```
51
51
 
52
52
  ## Usage
@@ -131,8 +131,45 @@ Omakase.configure do |config|
131
131
  end
132
132
  ```
133
133
 
134
+ ### MCP tools
135
+
136
+ An MCP server's tools become methods on the agent, listed among its capabilities like any other —
137
+ so generated code calls a remote tool and the agent's own methods in the same expression. Add the
138
+ `ruby_llm-mcp` gem; options are passed to it verbatim.
139
+
140
+ ```ruby
141
+ class DocsAgent < ApplicationAgent
142
+ mcp :files,
143
+ transport_type: :stdio,
144
+ config: {command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", Rails.root.to_s]}
145
+
146
+ generates :changelog, "Summarise what changed in the last release."
147
+ end
148
+ ```
149
+
150
+ The connection opens when the class is defined and the tools are read from the server then, so a
151
+ tool's arguments reach the model as documentation. A failed call raises, which the model sees and
152
+ can correct. Only text comes back: an image or audio result is dropped.
134
153
  ### Testing
135
154
 
155
+ ### Skills
156
+
157
+ A skill is a directory with a `SKILL.md` — the same YAML front matter Claude Code and friends use.
158
+ `skill` reads it and defines one method: the front matter's `description` joins the agent's
159
+ capabilities, and the body is what the method returns.
160
+
161
+ ```ruby
162
+ class CommitAgent < ApplicationAgent
163
+ skill "skills/commit-style" # description: "How this project writes commit subjects…"
164
+
165
+ generates :subject_for, "Write the commit subject for this change.", returns: :string
166
+ end
167
+ ```
168
+
169
+ That is the whole of “loaded on demand”: the one-line description is in the prompt, the body only
170
+ reaches the model if the generated code calls `commit_style`. Anything else the skill ships —
171
+ scripts, templates — sits in the same directory, and the body ends with its path, so generated Ruby
172
+ can read or run it.
136
173
  `Omakase::Agent.new(chat:)` takes any object that quacks like a `RubyLLM::Chat`, and one ships with
137
174
  the library, so agents are tested without a network:
138
175
 
@@ -278,6 +315,8 @@ generates :plan, strategy: CriticStrategy
278
315
  lib/omakase/doc.rb what an unfamiliar object offers, for generated code
279
316
  lib/omakase/executor.rb runs generated Ruby against the agent
280
317
  lib/omakase/tools/ruby.rb that executor, as a RubyLLM tool, with a call budget
318
+ lib/omakase/mcp.rb an MCP server’s tools, as methods on the agent
319
+ lib/omakase/skills.rb a SKILL.md directory, as one described method
281
320
  lib/omakase/fake_chat.rb the stand-in chat for tests
282
321
  lib/omakase/strategies/ code_act, predict
283
322
 
@@ -293,6 +332,8 @@ Copy `.env.example` to `.env` and fill in a key; `MODEL` and `PROVIDER` there pi
293
332
  | [`support_agent.rb`](examples/support_agent.rb) | plain Ruby orchestrating generated methods |
294
333
  | [`support_job.rb`](examples/support_job.rb) | generation off the request thread, via ActiveJob |
295
334
  | [`rails_app.rb`](examples/rails_app.rb) | a whole Rails app in one file: initializer, agent, controller |
335
+ | [`mcp_agent.rb`](examples/mcp_agent.rb) | an MCP server's tools as methods on the agent |
336
+ | [`skill_agent.rb`](examples/skill_agent.rb) | a SKILL.md directory the model loads when it needs it |
296
337
 
297
338
  ```bash
298
339
  bundle exec rake # tests, no network
@@ -327,10 +368,10 @@ What is not here yet, roughly in the order it would earn its place:
327
368
  - [ ] **Conversation history** — the chat is fresh per call. Keeping one per agent would let a
328
369
  method continue where the last one left off, at the cost of deciding what to keep.
329
370
  - [ ] **Session storage** — persist that history and the agent state so a run can be resumed.
330
- - [ ] **MCP tools** — external tools over the Model Context Protocol, via `ruby_llm-mcp`. Cheap to
331
- add, since generated code can call anything the agent exposes.
332
- - [ ] **Skills** — capabilities as markdown files with front matter, loaded on demand rather than
333
- all sitting in the system prompt.
371
+ - [x] **MCP tools** — external tools over the Model Context Protocol, via `ruby_llm-mcp`. Done:
372
+ `mcp :files, …` puts the server's tools on the agent, and generated code calls them.
373
+ - [x] **Skills** — capabilities as markdown files with front matter, loaded on demand rather than
374
+ all sitting in the system prompt. Done: `skill "path/to/dir"`, one described method.
334
375
  - [ ] **Memory** — recall that survives across sessions, backed by vector search.
335
376
  - [x] **Concurrency** — parallel generation calls. Done: output is buffered per thread instead of
336
377
  through `$stdout`, so threads no longer collide.
data/lib/omakase/agent.rb CHANGED
@@ -27,6 +27,16 @@ module Omakase
27
27
  @strategy = name
28
28
  end
29
29
 
30
+ # An MCP server's tools, as methods on the agent. Options are passed to
31
+ # `ruby_llm-mcp` verbatim: `mcp :files, transport_type: :stdio, config: {command: "npx", …}`.
32
+ def mcp(name, **options)
33
+ require "ruby_llm/mcp"
34
+ MCP.attach(self, RubyLLM::MCP.add_client(name: name.to_s, **options))
35
+ end
36
+
37
+ # A skill directory — a SKILL.md with YAML front matter. Its description
38
+ # joins the agent's capabilities; its body arrives when the model asks.
39
+ def skill(path) = Skills.attach(self, path)
30
40
  # Documents the method defined next — the docstring Ruby does not have.
31
41
  def describe(text)
32
42
  @pending_description = text
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omakase
4
+ # An MCP server's tools, as methods on the agent — so generated code calls a
5
+ # remote tool the same way it calls anything else the agent exposes.
6
+ module MCP
7
+ module_function
8
+
9
+ def attach(agent_class, client)
10
+ client.tools.each do |tool|
11
+ name = method_name(tool)
12
+ # A remote tool list must not quietly shadow a capability the agent already has.
13
+ raise Error, "#{agent_class} already has ##{name}" if Capabilities.names(agent_class).include?(name)
14
+
15
+ agent_class.describe(description(tool))
16
+ # nil is how a model leaves an argument out; MCP servers reject it.
17
+ agent_class.define_method(name) { |**arguments| MCP.result(tool.execute(**arguments.compact)) }
18
+ end
19
+ client
20
+ end
21
+
22
+ # Tool names may hold characters a Ruby method name cannot.
23
+ def method_name(tool) = tool.name.tr("-", "_").to_sym
24
+
25
+ # ponytail: text only — an image or audio result is dropped.
26
+ def result(value)
27
+ raise Error, value[:error] if value.is_a?(Hash) && value[:error]
28
+
29
+ value.to_s
30
+ end
31
+
32
+ # The signature is `**arguments`, so what those arguments are goes here.
33
+ def description(tool)
34
+ schema = tool.params_schema || {}
35
+ required = schema["required"] || []
36
+ arguments = (schema["properties"] || {}).map do |name, property|
37
+ "#{name}: #{property["type"]}#{" (required)" if required.include?(name)}"
38
+ end
39
+ text = tool.description.to_s.gsub(/\s+/, " ").strip
40
+ [text, ("Arguments — #{arguments.join(", ")}" if arguments.any?)].compact.join(" ")
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omakase
4
+ # A skill is a directory with a SKILL.md: YAML front matter says what it is,
5
+ # the body is the guidance. The description is listed with the agent's other
6
+ # capabilities; the body only arrives when the model calls the method — which
7
+ # is all "loaded on demand" has to mean.
8
+ module Skills
9
+ module_function
10
+
11
+ def attach(agent_class, path)
12
+ directory = File.expand_path(path)
13
+ front_matter, body = parse(File.read(File.join(directory, "SKILL.md")))
14
+ name = (front_matter["name"] || File.basename(directory)).tr("-", "_").to_sym
15
+ raise Error, "#{agent_class} already has ##{name}" if Capabilities.names(agent_class).include?(name)
16
+
17
+ agent_class.describe(front_matter["description"].to_s)
18
+ agent_class.define_method(name) { "#{body}\n\nFiles for this skill are in #{directory}." }
19
+ name
20
+ end
21
+
22
+ # The front matter every SKILL.md in the wild is written with.
23
+ def parse(text)
24
+ match = text.match(/\A---\n(.*?)\n---\n(.*)\z/m)
25
+ return [{}, text.strip] unless match
26
+
27
+ [YAML.safe_load(match[1]), match[2].strip]
28
+ end
29
+ end
30
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Omakase
4
- VERSION = "0.0.1.alpha"
4
+ VERSION = "0.0.2.alpha"
5
5
  end
data/lib/omakase.rb CHANGED
@@ -8,9 +8,11 @@ require "schematist"
8
8
  require "stringio"
9
9
  require "timeout"
10
10
  require "zeitwerk"
11
+ require "yaml"
11
12
 
12
13
  loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
13
14
  loader.ignore("#{__dir__}/omakase-agents.rb")
15
+ loader.inflector.inflect("mcp" => "MCP")
14
16
  loader.setup
15
17
 
16
18
  module Omakase
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omakase-agents
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.alpha
4
+ version: 0.0.2.alpha
5
5
  platform: ruby
6
6
  authors:
7
7
  - eugeny
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-12 00:00:00.000000000 Z
11
+ date: 2026-08-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby_llm
@@ -73,8 +73,10 @@ files:
73
73
  - lib/omakase/executor.rb
74
74
  - lib/omakase/fake_chat.rb
75
75
  - lib/omakase/generation.rb
76
+ - lib/omakase/mcp.rb
76
77
  - lib/omakase/request.rb
77
78
  - lib/omakase/schema.rb
79
+ - lib/omakase/skills.rb
78
80
  - lib/omakase/strategies.rb
79
81
  - lib/omakase/strategies/code_act.rb
80
82
  - lib/omakase/strategies/predict.rb