Releases: danielstpaul/silas
Release list
v0.6.3
Two safety declarations were failing open, and one approval card could be spent twice.
- Tool declarations now inherit. Ruby does not inherit class-level instance variables, so a shared tool base class silently left subclasses with no database transaction and no human gate — both failing open, for sixteen versions. If your app has a tool base class, read
YourTool.effect_modeandYourTool.approval_policyin a console before deploying: that is what those tools have been running as. approve!/answer!/decline!are compare-and-swap. Two holders of one approval card could both settle it and run the turn twice. A verdict now claims the row or loses loudly, naming the approver who won.
Also: the installer prints the queue-adapter fix instead of failing its own doctor; channels route to named staff; named agents gain ask_question and connection tools (settle their parked turns before upgrading — digest change); the session page is an activity feed with handoff lineage; the trace schema is documented as a versioned public interface (docs/traces.md); docs/vs-eve.md rewritten against eve 0.27.8; six false published claims corrected.
Full details in CHANGELOG.md.
v0.6.2
Providers & gateways guide (OpenRouter one-key recipe, OpenAI-compatible gateways, Bedrock/Vertex/Azure, local runtimes) shipped in the gem and on the site; CODE_OF_CONDUCT.md; PR template mirroring the real contribution gates. Full notes in CHANGELOG.md.
v0.6.1 — live pill fix, security hardening, polished brand, real imagery
- Live pill fix: replace-broadcast partials now carry their own target ids — the park flips the turn to held in place, the cost line ticks per step, and a fresh worker's first broadcast no longer dies on Rails 8.1 lazy routes. Verified live in a template-generated app.
- Security: connections refuse credentials over plaintext http; the pre-release audit (manual + automated) was otherwise clean.
- Polished brand kit: shared r4.8 lamp geometry, a favicon that reads at 16px, the ink-cropped wordmark lockup re-outlined to font-independent paths.
- Docs: SEO/AEO plumbing (sitemap, per-page meta, llms-full.txt) and real product imagery — the park→approve→clear GIF, the held card, the signal board.
Full details in CHANGELOG.md.
🤖 Generated with Claude Code
v0.6.0 — the brand release: Signals inbox, docs surface, templates family
The brand release. Highlights:
- Signals in the operator inbox: dark-first tokens, the position-light mark, seven-state aspect ramp, hoisted approval cards, a Held/Working/Filed rail.
- The docs surface: framework-first README with the outlined wordmark lockup; six new capability guides, a tutorial, and a configuration reference — shipped in the gem and on the new docs site (danielstpaul.github.io/silas) with llms.txt.
- The templates family:
rails new my-agent -m .../templates/desk.rb(refund desk) or.../templates/analyst.rb(scheduled reporting agent with schema-checked answers) — single-file, CI-regenerated starters with keyless demos and deterministic evals. - Eval DSL:
on_step data: {...}scripts structured answers, soassert_answer_dataworks with scripted adapters. - Community files: CONTRIBUTING (with the deliberate scope no-list), SECURITY, issue forms.
Full details in CHANGELOG.md. Gates: 435 specs on SQLite and Postgres, RuboCop, Brakeman, both template apps regenerated and tested keyless in CI. No loop/ledger changes since 0.5.0.
🤖 Generated with Claude Code
v0.5.0 — replay-safe compaction, ask_question, channel generator, adapter rebind
Two new loop primitives, both built as exactly-once effects, plus the adapter rebound onto RubyLLM's public single-turn seam.
Added
- Replay-safe context compaction — long sessions no longer fail at the context window. Prior turns summarise into a CAS-claimed
silas_compactionsrow thatMessageBuilderreads deterministically: same rows, same bytes, compacted or not.config.compact_at(fraction of the model's registry window, absolute tokens for custom adapters, or nil to disable). ask_question— the agent parks at zero compute to ask the operator something; the free-text answer becomes the tool result it resumes with. Inbox question card,POST /silas/api/v1/approvals/:id/answer, TTL expiry,config.ask_questionescape hatch.⚠️ Adding a builtin changes the definitions digest — settle parked turns before upgrading (details in the CHANGELOG).rails g silas:channel <name>— scaffolds both halves of a transport (signature-verifying webhook controller + outbound Channel) with the security decisions pre-made.Silas::Webhook.verify_hmac,Silas::Channel.approval_url, anddocs/channels.mdwith a worked WhatsApp example.- Per-agent schedules —
app/agents/<name>/schedules/ticks start that agent, never the root.
Changed
- The
:ruby_llmadapter binds to the publicProvider#completeinstead of abortingChat#complete's loop from the inside —Tool::Haltis gone from the codebase, removing the largest RubyLLM 2.0 exposure.
Removed
- The broken
demo/cp-kits;examples/playgroundis the example.
Verification — 430 specs green on SQLite + Postgres (93.4% coverage); chaos matrix re-run in full: 295 kill/deploy cycles, zero duplicate side effects, byte-identical replay, including a new compact mode that kills mid-summarisation and asserts exactly-once compaction claims and byte-identical message rebuilds (chaos_host/RESULTS.md). Real-API smoke + playground evals green.
v0.4.0 — architecture lockdown, money-path coverage, instrumentation
The architecture-and-hardening release. Chaos-gated at 225 kill/deploy cycles — zero duplicate side effects, byte-identical replay, on SQLite and Postgres.
Fixed
The email approval channel had never worked. The approval email template and the confirmation page both called approval_url/approval_path, but the route is declared inside namespace :channels, so the real helpers are channels_approval_url/_path. Rendering raised — meaning ChannelMailer#approval blew up and the "your agent needs approval" email was never delivered, and the confirmation page 500'd. If you relied on email approvals, you were silently never told that a money-moving call was parked. Found by writing the first specs for these surfaces.
Changed (breaking, pre-1.0)
- The inference seam is
Adapters::, notEngines::— andconfig.adapter, notconfig.engine. "Engine" meant two unrelated things in one namespace: the Rails engine atSilas::Engine, and the pluggable inference backend. Every comparable seam disambiguates — ActiveJob hasQueueAdapters::, ActiveStorageService::, RubyLLMProvider. Changed now because 1.0 freezes the public API and host apps subclass this seam. Nothing breaks today:Silas::Engines::Base,config.engine, andSilas.resolved_enginestill resolve, warn through the new deprecator, and are removed in 2.0. - Notification names follow the Rails convention
<event>.silas(likesql.active_record). The two existing events were backwards:silas.step→step.silas,silas.delta→delta.silas.subscribe(/\.silas\z/)now catches everything.
Added
- Instrumentation for the durable loop. It was silent before — a turn could start, park for a human, be rescued after a
kill -9, breach a budget and finish without a single line. Ten documented events now.tool.silastimes the tool's own execution and reports how it settled;resume.silascarriesparked_for— how long the human actually took.Silas::LogSubscriberrenders them at operator-filterable levels and stays quiet when the rescuer did nothing. Silas.deprecator, registered inapp.deprecators[:silas], so hosts control Silas deprecations exactly as they control Rails'.- 36 specs on the four money-path surfaces that had zero coverage — Slack webhook signature verification end to end (unsigned, wrong-secret and replayed requests refused), the signed email approval links (tampered/expired/wrong-purpose tokens refused; GET never mutates, so a link preview cannot approve a refund), inbound-mail threading, and the approval mailer.
- Quality tooling in CI:
rubocop-rails-omakase(zero offenses), SimpleCov with a 90% floor that fails the build (actual 92.5%), Brakeman and bundler-audit, and arake zeitwerk:checkjob. - Ten dependency contract specs pinning the Solid Queue and RubyLLM internals Silas couples to, where a rename would break recovery silently — plus an allowed-to-fail canary against ruby_llm edge, which on its first run already flagged that RubyLLM 2.0 drops
Tool::Haltand changes the model-registry price accessors. docs/conventions.mdrecording the deliberate deviations from Rails defaults and the audited posture: nothing mass-assigned, noraw/html_safeanywhere,Time.currentthroughout.
No migration. 336 specs on SQLite and Postgres.
v0.3.2 — the live trace actually streams in host apps; approval-aware timeouts
Two fixes surfaced by the playground's new customer-facing chat page — both worth upgrading for.
The live trace now actually works in host apps. Turbo's broadcast jobs render the trace partials through the HOST's default renderer, where the engine-scoped helper and bare engine route helpers didn't exist — so every broadcast render raised inside Turbo's job and the inbox's live streaming never worked in a real host app (the gem's specs stubbed the dispatch seam and never rendered a partial). The helper is now registered host-wide, partials build routes context-free via silas_engine_path, and four host-renderer regression specs pin the real path. If you host the trace partials in your own pages, note relative_time is now silas_relative_time.
The timeout budget no longer counts time spent parked for approval. Any approval slower than limits.timeout previously made the approved resume instantly re-park on "timeout" — pathological for a gate whose whole point is waiting for a person. Approval resumes restart the wall clock; timeout bounds active stretches (hung providers, runaway loops); cost/token budgets stay cumulative; crash-rescue resumes keep the original clock.
Also in this release: examples/playground gains a customer-facing chat page that renders and live-streams the engine's own trace partials with zero custom streaming code, plus a scripted keyless demo mode — bin/setup && bin/dev with no API key, and the tools, ledger, approval park, and token streaming are all still real. Verified end to end in a browser: £15 auto-approved (auto-approved by policy), £48 parked → approved by operator → resumed → exactly one refund row.
No migration. 273 specs green on SQLite and Postgres.
v0.3.1 — eval isolation fix, auto-approval audit record
Two correctness fixes, both surfaced by building the new examples/playground reference app.
Fixed: only the first scenario in a silas:eval run was really tested. Silas.configure never invalidated the memoized resolved engine, so a second configure in the same process kept serving the first one — every eval scenario after the first silently ran the first scenario's script while still reporting PASS/FAIL as though it hadn't. If you gate deploys on bin/rails silas:eval with more than one scenario, you were getting false green on 0.3.0 and earlier. Reconfiguring now re-resolves both the engine and the sandbox. The gem's own specs missed this because the spec helper resets config between examples; a real eval suite does not.
Automatic approvals are now recorded. A graded gate that cleared a call — a lambda under its threshold, or an :once rule already satisfied — recorded nothing, leaving the audit trail unable to distinguish "gate ran and passed automatically" from "no gate at all" on a money-moving tool. It now sets approval_state: "approved" with no approved_by; that absence is what marks it automatic, and the inbox renders it as auto-approved by policy. Ungated calls still read nil.
Also:
- The generated
ruby_llm.rbinitializer opts intouse_new_acts_as, silencing RubyLLM's legacy-API deprecation warning on every boot — Silas never usesacts_as_*. Existing initializers are still never touched. examples/playground— the first genuinely runnable example: a real Rails 8.1 app (Tinker & Co) whose support agent looks up orders and issues refunds.issue_refundistransactional!behind a graded approval lambda (under £20 immediate, over £20 parks for a human), with four evals pinning the flow including exactly-once execution across a park-and-approve.
No migration. 268 specs green on SQLite and Postgres.
v0.3.0 — HTTP+SSE API, structured answers, registry pricing, silas:doctor
The second half of the pre-1.0 push. Chaos-gated: 235 kill/deploy cycles across worker/supervisor/SIGTERM/parked on SQLite and Postgres — 100% completion, byte-identical replay, zero duplicate side effects.
- A public HTTP + SSE session API at
/silas/api/v1— sessions (create/show,?trace=1for the full transcript), turns (create — 409 while one is active — and cancel), and approvals (list/approve/decline, the exact sameapprove!/decline!as the inbox, Slack, and email, stamped withconfig.api_actor). Deny-by-default viaconfig.api_auth.GET .../sessions/:id/streamis server-sent events at row granularity withLast-Event-IDresume — at-least-once, epoch-ms watermark ids,?poll=1for a curl-friendly backlog-and-close, self-closing afterapi_stream_max_duration. - Structured final answers — declare a JSON schema under
final_answer:in agent.yml and read the parsed Hash fromTurn#answer_data(APIanswer_data, inbox trace, REPL, and a newassert_answer_dataeval assertion). Schema-less agents keep a byte-identical definitions digest, so turns parked across the upgrade never tripNondeterminismError. - Cost accounting prices itself from RubyLLM's model registry (1,100+ models) per (model, provider) — steps stamp the provider at persist time;
config.model_pricesis now an override map; unknown models stayunpriced, never a lying $0.00. Default model is nowclaude-sonnet-4-5. - Inbox at scale — the budget "raise & resume" top-up card the 0.1.5 changelog promised, keyset pagination (
?before=), and a 2-query session index. bin/rails silas:doctor— provider key, queue adapter, model resolution, migrations, tool validation, rescuer entry, cable adapter, auth wiring. Exits non-zero on failures.- The 0.2
agent_sdkconfig shims are hard-removed (NoMethodErrornow); workflows onactions/checkout@v5.
Upgrade: bin/rails silas:install:migrations db:migrate (adds silas_steps.provider, drops the write-only silas_turns.cost_microcents). Full details in CHANGELOG.md.
v0.2.0 — streaming, web chat, resilience; agent_sdk cut
The "you can actually use it" release. Chaos-gated: 235 kill/deploy cycles across worker/supervisor/SIGTERM/parked on SQLite and Postgres — 100% completion, byte-identical replay, zero duplicate side effects.
- Token streaming, end to end — the engine streams;
silas:chatprints tokens as they arrive; the inbox trace renders them live over Turbo (~10Hz coalescing). Deltas are decoration over the durable rows: never persisted, never re-emitted on replay. - Web chat in the inbox — start a session or reply from the browser; same durable loop, write-gated like approvals.
- Audit trail — every tool call's arguments and result/error, who approved, who declined and why; cancel button on active turns; the awaiting-approval badge filters to what needs you.
- Model-call resilience — transient provider errors (429/529/timeouts) retry with backoff and resume from the last completed step; exhaustion and permanent rejections fail the turn loudly with approvals expired; the rescuer sweeps stranded turns. A turn can never sit in
runningforever. approval :onceis now (tool, arguments)-scoped — approving a £5 refund no longer auto-approves a £5,000 one.- Removed the
:agent_sdkengine — its subscription-auth rationale was structurally unreachable.config.engine = :agent_sdkraises clearly;agent_sdk_*/authoptions are warning no-ops for this release. The in-process MCP server survives as the seam for a future "mount your tools as MCP" feature. - Onboarding — generated
bin/cican actually fail; complete initializer template (Sonnet default); provider-key and async-adapter guards raise at boot in production; idempotent, environment-aware rescuer recurring entry.
Upgrade: bin/rails silas:install:migrations db:migrate (drops two unused agent_sdk columns). Full details in CHANGELOG.md.