silas

Gem CI License: MIT

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.