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.
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.
rails new my-agent -m https://raw.githubusercontent.com/danielstpaul/silas/main/templates/desk.rbThis 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:migratebin/rails silas:doctor verifies the whole setup.
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
endRestart, then talk to it:
bin/rails silas:chatRefunds 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.
Early (0.6.x) and moving fast. Requires Rails >= 8.1 (Active Job Continuations) and Solid Queue >= 1.2; any model provider RubyLLM supports — Anthropic direct, OpenRouter, OpenAI-compatible gateways, local runtimes (providers guide).
Questions, ideas, and bug reports → issues.
See CONTRIBUTING.md — including the short list of things Silas deliberately doesn't do, and the chaos gate that protects the durability contract. Everyone interacting here is expected to follow the code of conduct.
Please report vulnerabilities privately via GitHub's vulnerability reporting — see SECURITY.md.
MIT.