Problem
When someone asks a coding agent (Claude Code, Copilot, Cursor, ...) to "set up RBS and Steep in this project", the agent very often ends up with a Steepfile that lists dependencies by hand:
target :lib do
signature "sig"
check "lib"
library "pathname"
library "json"
library "rainbow" # a gem, listed one by one
end
rbs collection exists exactly so that nobody has to maintain that list: it resolves RBSs from Gemfile.lock, and both rbs and Steep load the installed RBS files automatically. Agents rarely reach that setup on their own.
Agents are becoming a common entry point to RBS, so "what an agent does by default" is effectively part of our onboarding UX. Today the default is the worse path.
For reference, this is the setup we want an agent to produce:
- Gems in
Gemfile.lock are resolved by rbs collection install; nothing to list.
- Standard libraries are covered either by declaring them in the
Gemfile so that they appear in Gemfile.lock, or by listing them under gems: in rbs_collection.yaml.
library in a Steepfile is for setups that don't use rbs collection, or can't.
Why agents miss it
Three concrete places where the tools we ship point away from rbs collection:
-
steep init template. The only hint about how to get types for dependencies is:
# # library "pathname" # Standard libraries
# # library "strong_json" # Gems
rbs collection is not mentioned at all, so hand-written library lines read as the way to load RBSs for dependencies. An agent that reads the generated Steepfile learns exactly that. (This lives in soutaro/steep, so it needs an upstream PR; tracked here because the motivation is ours.)
-
rbs collection init output. It prints one line:
$ rbs collection init
created: rbs_collection.yaml
No next step, and no statement of the one fact that would stop an agent from enumerating gems: the gem list comes from Gemfile.lock. An agent that runs init and reads the output still doesn't know it should run install, nor that library lines for gems are unnecessary.
-
The generated rbs_collection.yaml. Its comments explain sources, path and gems[].ignore, i.e. the knobs, but never the model: "your dependencies are read from Gemfile.lock; you don't list them here". The file is read by the agent right after it is written, so it is a high-value place to say this. The commented-out gems: example only shows ignore: true, so the other half — adding an entry to cover something that isn't in Gemfile.lock, a standard library for instance — stays invisible.
Bonus, related: rbs collection install without rbs_collection.yaml dies with a raw Errno::ENOENT backtrace (lib/rbs/cli.rb never rescues it). An agent that tries install first sees a crash and may conclude collection is broken and fall back to library.
Proposal
1. rbs collection init stdout
Something along these lines (wording to be discussed):
$ rbs collection init
created: rbs_collection.yaml
rbs collection installs RBS files for the gems in your Gemfile.lock.
You don't need to list your dependencies anywhere.
Next steps:
$ echo "/.gem_rbs_collection/" >> .gitignore
$ rbs collection install
init is run once per project, so a few extra lines cost nothing, but they should stay short and actionable.
2. Comments in the generated rbs_collection.yaml
Add a header that states the model before the knobs:
# rbs collection installs RBS files for the gems in your Gemfile.lock.
# Run `rbs collection install` to resolve them into rbs_collection.lock.yaml and install them.
# Your dependencies are not listed in this file; they are read from Gemfile.lock.
And show both sides of the gems: section, not only ignore:
# gems:
# # RBS for a library that doesn't appear in Gemfile.lock, such as a standard library.
# - name: pathname
#
# # If you want to avoid installing rbs files for gems, you can specify them here.
# - name: GEM_NAME
# ignore: true
3. steep init template (upstream: soutaro/steep)
Mention collection where library is explained, and say what library is actually for:
# # RBSs for the gems in Gemfile.lock are loaded from rbs collection automatically.
# # Set it up with `rbs collection init` and `rbs collection install`.
# #
# # library "pathname" # Load an RBS without rbs collection
4. Friendly error for rbs collection install without a config
Replace the raw backtrace with a message that names the fix (rbs collection init), in the same style as RBS::Collection::Config::CollectionNotAvailable.
5. Documentation
docs/collection.md opens with "Requirements" and a full walkthrough. A short quickstart block at the top ("new project? run these two commands") gives agents and humans that fetch the doc the answer in the first screen.
Problem
When someone asks a coding agent (Claude Code, Copilot, Cursor, ...) to "set up RBS and Steep in this project", the agent very often ends up with a
Steepfilethat lists dependencies by hand:rbs collectionexists exactly so that nobody has to maintain that list: it resolves RBSs fromGemfile.lock, and bothrbsand Steep load the installed RBS files automatically. Agents rarely reach that setup on their own.Agents are becoming a common entry point to RBS, so "what an agent does by default" is effectively part of our onboarding UX. Today the default is the worse path.
For reference, this is the setup we want an agent to produce:
Gemfile.lockare resolved byrbs collection install; nothing to list.Gemfileso that they appear inGemfile.lock, or by listing them undergems:inrbs_collection.yaml.libraryin aSteepfileis for setups that don't userbs collection, or can't.Why agents miss it
Three concrete places where the tools we ship point away from
rbs collection:steep inittemplate. The only hint about how to get types for dependencies is:rbs collectionis not mentioned at all, so hand-writtenlibrarylines read as the way to load RBSs for dependencies. An agent that reads the generatedSteepfilelearns exactly that. (This lives in soutaro/steep, so it needs an upstream PR; tracked here because the motivation is ours.)rbs collection initoutput. It prints one line:No next step, and no statement of the one fact that would stop an agent from enumerating gems: the gem list comes from
Gemfile.lock. An agent that runsinitand reads the output still doesn't know it should runinstall, nor thatlibrarylines for gems are unnecessary.The generated
rbs_collection.yaml. Its comments explainsources,pathandgems[].ignore, i.e. the knobs, but never the model: "your dependencies are read fromGemfile.lock; you don't list them here". The file is read by the agent right after it is written, so it is a high-value place to say this. The commented-outgems:example only showsignore: true, so the other half — adding an entry to cover something that isn't inGemfile.lock, a standard library for instance — stays invisible.Bonus, related:
rbs collection installwithoutrbs_collection.yamldies with a rawErrno::ENOENTbacktrace (lib/rbs/cli.rbnever rescues it). An agent that triesinstallfirst sees a crash and may conclude collection is broken and fall back tolibrary.Proposal
1.
rbs collection initstdoutSomething along these lines (wording to be discussed):
initis run once per project, so a few extra lines cost nothing, but they should stay short and actionable.2. Comments in the generated
rbs_collection.yamlAdd a header that states the model before the knobs:
And show both sides of the
gems:section, not onlyignore:3.
steep inittemplate (upstream: soutaro/steep)Mention collection where
libraryis explained, and say whatlibraryis actually for:4. Friendly error for
rbs collection installwithout a configReplace the raw backtrace with a message that names the fix (
rbs collection init), in the same style asRBS::Collection::Config::CollectionNotAvailable.5. Documentation
docs/collection.mdopens with "Requirements" and a full walkthrough. A short quickstart block at the top ("new project? run these two commands") gives agents and humans that fetch the doc the answer in the first screen.