Silas is a Rails-native framework for durable AI agents. An agent's capabilities live as plain files in conventional locations inside the app you already run — easy to inspect, extend, and operate — and its tool effects land exactly once, even through a crash.
The filesystem is the authoring interface
A typical Silas agent:
app/agent/
instructions.md # the persona
agent.yml # model + limits — data only
tools/ # one file per tool; filename = identity, keywords = schema
skills/ # markdown playbooks, loaded on demand
schedules/ # cron frontmatter -> recurring turns
channels/ # slack.rb, email.rb — transports bound to the loop
connections/ # remote MCP servers as <name>.yml
Documentation lives at
danielstpaul.github.io/silas —
start with the tutorial. The
same docs ship inside the gem (bundle show silas), and the installer writes
a Claude Code skill so coding agents working in your app know the conventions.
Quick start
rails new my-agent -m https://raw.githubusercontent.com/danielstpaul/silas/main/templates/desk.rb
This creates a new agent app with Solid Queue wired, a starter refund-desk
agent installed, and a keyless demo — the first cd my-agent && bin/dev
needs no API key. More starting shapes live in
templates/ —
swap desk.rb for analyst.rb to start from a scheduled reporting agent
instead.
To add Silas to an existing app:
bundle add silas
bin/rails generate silas:install
bin/rails db:migrate
bin/rails silas:doctor verifies the whole setup.
A minimal example
Replace app/agent/instructions.md with your agent's persona:
You are the refund desk. Look orders up before promising anything, and never
quote an amount a tool didn't return.
Create a tool at app/agent/tools/issue_refund.rb — the keyword signature
is the schema the model sees:
class Agent::Tools::IssueRefund < Silas::Tool
description "Refund part or all of an order."
approval ->(session:, input:) { input[:amount_pence] > 2_500 ? :user_approval : :approved }
transactional! # DB effect + ledger commit atomically -> exactly-once
def call(number:, amount_pence:, reason:)
order = Order.find_by!(number: number)
refund = order.refunds.create!(amount_pence:, reason:)
{ refunded_pence: refund.amount_pence, order: order.number }
end
end
Restart, then talk to it:
bin/rails silas:chat
Refunds over £25 hold for a person — in the operator inbox the gem mounts at
/silas/inbox, in Slack, or over the JSON API — and approving resumes the
turn exactly where it stopped, with exactly one refund row in your database.
How that's guaranteed (and verified with a kill -9 chaos harness on every
release): guarantees.
Status
Early (0.5.x) and moving fast. Requires Rails >= 8.1 (Active Job Continuations) and Solid Queue >= 1.2; any model provider RubyLLM supports.
Community
Questions, ideas, and bug reports → issues.
Contributing
See CONTRIBUTING.md — including the short list of things Silas deliberately doesn't do, and the chaos gate that protects the durability contract.
Security
Please report vulnerabilities privately via GitHub's vulnerability reporting — see SECURITY.md.
License
MIT.