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.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).
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. Everyone interacting here is expected to follow the code of conduct.
Security
Please report vulnerabilities privately via GitHub's vulnerability reporting — see SECURITY.md.
License
MIT.