Skip to main content

layered-assistant-rails (v0.5.0)

An open source Rails 8+ engine built on `layered-ui-rails` that provides a multi-provider AI assistant with streaming responses and a full conversation UI.

Features

Multi-provider and model support

Use cloud (Anthropic, OpenAI, Gemini, Mistral, Groq, OpenRouter) or local (Ollama or LM Studio) providers with configurable models.

Streaming responses

Real-time streaming via Turbo Streams for a responsive chat experience with dynamic token fade-in and chunked markdown formatting.

Conversation UI

Full conversation interface with message history, model selection, and a slide-out panel mode.

Encrypted secrets

API keys are encrypted at rest using Active Record Encryption.

Requirements

Agent skill

An agent skill is included so AI coding agents can work with layered-assistant-rails in your project. Once installed, the agent can handle the full setup - just ask it to add layered-assistant-rails to your app and it will install the gem, run the generator, and configure your layout.

Project install - scoped to a single repo, available to all contributors:

bin/rails generate layered:assistant:install_agent_skill

Global install - available across all your projects:

./install-skill.sh
# or install remotely without cloning the repo:
curl -fsSL https://raw.githubusercontent.com/layered-ai-public/layered-assistant-rails/main/install-skill.sh | sh

Installation

Add to your Gemfile:

gem "layered-assistant-rails"

Then run:

bundle install

Setup

Run the install generator to copy CSS, JS, and migrations to your application:

bin/rails generate layered:assistant:install

This will:

All steps are idempotent - re-running the generator will not duplicate imports, routes, or migrations.

Authorization

All non-public engine routes are blocked by default (403 Forbidden) until you configure an authorize block in config/initializers/layered_assistant.rb.

The install generator creates this file for you. Uncomment one of the examples to get started.

The block runs in controller context, so you have access to request, current_user, redirect_to, head, main_app, and all other controller methods.

Allow all requests

Layered::Assistant.authorize do
  # No-op: all requests permitted
end

Require sign-in (Devise)

Layered::Assistant.authorize do
  redirect_to main_app.new_user_session_path unless user_signed_in?
end

Restrict to admins

Layered::Assistant.authorize do
  head :forbidden unless current_user&.admin?
end

Checking access in views

The l_assistant_accessible? helper evaluates the authorize block without side effects. Use it to conditionally show navigation or links to the engine:

<% if l_assistant_accessible? %>
  <%= link_to "Assistant", layered_assistant.root_path %>
<% end %>

Record scoping

By default, all records are visible to any authorised user. If your application is multi-tenant or you need to restrict which records a user can see, configure a scope block in the initialiser.

The block receives the model class, runs in controller context, and must return an ActiveRecord::Relation. The following models are passed through the scope block:

Scopeable models reference
Model Description
Layered::Assistant::Conversation User conversations (has polymorphic owner)
Layered::Assistant::Assistant Assistant configurations (has polymorphic owner)
Layered::Assistant::Provider API provider credentials (has polymorphic owner)

Scope all owned resources to the current user

Layered::Assistant.scope do |model_class|
  model_class.where(owner: current_user)
end

Scope conversations only

Layered::Assistant.scope do |model_class|
  if model_class == Layered::Assistant::Conversation
    model_class.where(owner: current_user)
  else
    model_class.all
  end
end

When no scope block is configured, queries are unscoped. Record-level access control is the host application's responsibility; the scope block is the integration point for it.

Panel helpers

Two helpers are available to wire the layered-ui panel to the assistant engine. Add these to your application layout's content_for blocks:

<% content_for :l_ui_panel_heading do %>
  <%= layered_assistant_panel_header %>
<% end %>

<% content_for :l_ui_panel_body do %>
  <%= layered_assistant_panel_body %>
<% end %>

<%= render template: "layouts/layered_ui/application" %>

Both helpers accept keyword arguments forwarded as HTML attributes:

<%= layered_assistant_panel_body data: { controller: "panel" } %>
Panel helpers reference
Helper Description
layered_assistant_panel_header Empty Turbo Frame populated by the engine's panel views
layered_assistant_panel_body Turbo Frame that loads the conversation list from the engine's panel routes

Configuration

Optional settings can be added to your initialiser (config/initializers/layered_assistant.rb):

Configuration options reference
Option Default Description
log_errors false Log API errors to stdout from the AI API clients
api_request_timeout 210 Timeout in seconds for AI API requests
skip_db_encryption false Disable Active Record Encryption on Provider#secret. Only for development/test environments without encryption keys configured
Layered::Assistant.log_errors = true
Layered::Assistant.skip_db_encryption = true

Getting started

  1. Create a provider (e.g. Anthropic or OpenAI) with your API key
  2. Add one or more models to the provider
  3. Create an assistant and assign a default model
  4. Start a conversation

License

Released under the Apache 2.0 License.

Copyright 2026 LAYERED AI LIMITED (UK company number: 17056830). See NOTICE for attribution details.

Trademarks

The source code is fully open, but the layered.ai name, logo, and brand assets are trademarks of LAYERED AI LIMITED. The Apache 2.0 license does not grant rights to use the layered.ai branding. Forks and redistributions must use a distinct name. See TRADEMARK.md for the full policy.

Contributing

Configuration

Optional settings for config/initializers/layered_assistant.rb:

Configuration options
Option Default Description
log_errors false Log API errors to stdout.
api_request_timeout 210 Total timeout in seconds for API requests, including the full streaming response. Increase for models with high max_tokens limits or slow providers.
skip_db_encryption false Disable Active Record Encryption on Provider#secret. Development/test only.