Skip to content

Releases: danielstpaul/silas

v0.6.3

Choose a tag to compare

@danielstpaul danielstpaul released this 30 Jul 20:53
9df3c84

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_mode and YourTool.approval_policy in 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

Choose a tag to compare

@danielstpaul danielstpaul released this 26 Jul 21:09
0f3af5d

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

Choose a tag to compare

@danielstpaul danielstpaul released this 26 Jul 20:19
7fa3ffb
  • 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

Choose a tag to compare

@danielstpaul danielstpaul released this 26 Jul 12:48
3c92c7a

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, so assert_answer_data works 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

Choose a tag to compare

@danielstpaul danielstpaul released this 25 Jul 23:44
2ba3b85

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_compactions row that MessageBuilder reads 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_question escape 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, and docs/channels.md with a worked WhatsApp example.
  • Per-agent schedulesapp/agents/<name>/schedules/ ticks start that agent, never the root.

Changed

  • The :ruby_llm adapter binds to the public Provider#complete instead of aborting Chat#complete's loop from the inside — Tool::Halt is gone from the codebase, removing the largest RubyLLM 2.0 exposure.

Removed

  • The broken demo/ cp-kits; examples/playground is 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

Choose a tag to compare

@danielstpaul danielstpaul released this 25 Jul 18:02
63905b5

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::, not Engines:: — and config.adapter, not config.engine. "Engine" meant two unrelated things in one namespace: the Rails engine at Silas::Engine, and the pluggable inference backend. Every comparable seam disambiguates — ActiveJob has QueueAdapters::, ActiveStorage Service::, RubyLLM Provider. Changed now because 1.0 freezes the public API and host apps subclass this seam. Nothing breaks today: Silas::Engines::Base, config.engine, and Silas.resolved_engine still resolve, warn through the new deprecator, and are removed in 2.0.
  • Notification names follow the Rails convention <event>.silas (like sql.active_record). The two existing events were backwards: silas.stepstep.silas, silas.deltadelta.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.silas times the tool's own execution and reports how it settled; resume.silas carries parked_for — how long the human actually took. Silas::LogSubscriber renders them at operator-filterable levels and stays quiet when the rescuer did nothing.
  • Silas.deprecator, registered in app.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 a rake zeitwerk:check job.
  • 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::Halt and changes the model-registry price accessors.
  • docs/conventions.md recording the deliberate deviations from Rails defaults and the audited posture: nothing mass-assigned, no raw/html_safe anywhere, Time.current throughout.

No migration. 336 specs on SQLite and Postgres.

v0.3.2 — the live trace actually streams in host apps; approval-aware timeouts

Choose a tag to compare

@danielstpaul danielstpaul released this 25 Jul 10:03
9284545

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

Choose a tag to compare

@danielstpaul danielstpaul released this 25 Jul 07:55
24efe28

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.rb initializer opts into use_new_acts_as, silencing RubyLLM's legacy-API deprecation warning on every boot — Silas never uses acts_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_refund is transactional! 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

Choose a tag to compare

@danielstpaul danielstpaul released this 25 Jul 00:03
0668e49

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=1 for the full transcript), turns (create — 409 while one is active — and cancel), and approvals (list/approve/decline, the exact same approve!/decline! as the inbox, Slack, and email, stamped with config.api_actor). Deny-by-default via config.api_auth. GET .../sessions/:id/stream is server-sent events at row granularity with Last-Event-ID resume — at-least-once, epoch-ms watermark ids, ?poll=1 for a curl-friendly backlog-and-close, self-closing after api_stream_max_duration.
  • Structured final answers — declare a JSON schema under final_answer: in agent.yml and read the parsed Hash from Turn#answer_data (API answer_data, inbox trace, REPL, and a new assert_answer_data eval assertion). Schema-less agents keep a byte-identical definitions digest, so turns parked across the upgrade never trip NondeterminismError.
  • Cost accounting prices itself from RubyLLM's model registry (1,100+ models) per (model, provider) — steps stamp the provider at persist time; config.model_prices is now an override map; unknown models stay unpriced, never a lying $0.00. Default model is now claude-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_sdk config shims are hard-removed (NoMethodError now); workflows on actions/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

Choose a tag to compare

@danielstpaul danielstpaul released this 24 Jul 23:08
3adcf7f

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:chat prints 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 running forever.
  • approval :once is now (tool, arguments)-scoped — approving a £5 refund no longer auto-approves a £5,000 one.
  • Removed the :agent_sdk engine — its subscription-auth rationale was structurally unreachable. config.engine = :agent_sdk raises clearly; agent_sdk_*/auth options 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/ci can 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.