<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://bridgetownrb.com/" version="2.0.5">Bridgetown</generator><link href="https://maquina.app/feed.xml" rel="self" type="application/atom+xml" /><link href="https://maquina.app/" rel="alternate" type="text/html" /><updated>2026-08-15T21:42:37-06:00</updated><id>https://maquina.app/feed.xml</id><title type="html">Maquina</title><subtitle>Comprehensive documentation for Maquina, the open-source Rails framework designed for building modern web applications with a focus on developer experience, performance, and maintainability.</subtitle><author><name>Mario Alberto Chávez Cárdenas</name></author><entry><title type="html">equipr: Cross-Agent Skill and MCP Server Manager</title><link href="https://maquina.app/blog/2026/08/equipr-cross-agent-skill-manager/" rel="alternate" type="text/html" title="equipr: Cross-Agent Skill and MCP Server Manager" /><published>2026-08-10T00:00:00-06:00</published><updated>2026-08-10T00:00:00-06:00</updated><id>repo://posts.collection/_posts/2026-08-10-equipr-cross-agent-skill-manager.md</id><content type="html" xml:base="https://maquina.app/blog/2026/08/equipr-cross-agent-skill-manager/">&lt;p&gt;equipr installs skills, commands, and MCP servers from a marketplace or an Agent Plugins source into whichever coding agents are present on a machine: Claude Code, Codex, OpenCode, and Pi. It works through each agent’s own personal config surfaces rather than a plugin system. It is a single Go binary, MIT licensed, and available now via Homebrew or as a standalone install script.&lt;/p&gt;

&lt;h2 id=&quot;the-problem-it-addresses&quot;&gt;The problem it addresses&lt;/h2&gt;

&lt;p&gt;Claude Code has a marketplace mechanism for distributing and updating skills that none of the other three agents come close to matching. Codex, OpenCode, and Pi have no built-in equivalent. The alternatives are copying a skill’s files by hand, which then has to be kept current on its own since nothing does that automatically, or installing an npm package for the skills some maintainers publish that way. The &lt;a href=&quot;https://agent-plugins.org/specification&quot;&gt;Agent Plugins specification&lt;/a&gt; exists to standardize the package format itself, but it does not yet define how any individual agent installs from a source or keeps that installation up to date. That part is still left to each agent’s own tooling, or to something else.&lt;/p&gt;

&lt;p&gt;The same gap shows up again with isolated per-agent containers, of the kind &lt;a href=&quot;/blog/2026/07/introducing-fragua/&quot;&gt;Fragua&lt;/a&gt; uses: each container needs the same sources installed, and each one is a separate place that update has to reach.&lt;/p&gt;

&lt;h2 id=&quot;what-equipr-resolves&quot;&gt;What equipr resolves&lt;/h2&gt;

&lt;p&gt;equipr treats a source as one of two shapes. A repository with &lt;code class=&quot;highlighter-rouge&quot;&gt;.claude-plugin/marketplace.json&lt;/code&gt; at its root is a marketplace holding one or more plugins. A repository with &lt;code class=&quot;highlighter-rouge&quot;&gt;plugin.json&lt;/code&gt; at its root is a single &lt;a href=&quot;https://agent-plugins.org/specification&quot;&gt;Agent Plugins&lt;/a&gt;-conformant package. Both resolve to the same internal model, addressed the same way:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;source-id&amp;gt;/&amp;lt;plugin&amp;gt;:&amp;lt;component&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;equipr add https://github.com/coreyhaines31/marketingskills
&lt;span class=&quot;go&quot;&gt;Fetching https://github.com/coreyhaines31/marketingskills
Added marketingskills (marketplace, fetched via git) with 1 plugin(s)
  - marketing-skills 2.10.0

&lt;/span&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;equipr list
&lt;span class=&quot;go&quot;&gt;marketingskills     marketplace
  marketing-skills  2.10.0  49 skills

&lt;/span&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;equipr &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;marketingskills/marketing-skills:seo-audit &lt;span class=&quot;nt&quot;&gt;--yes&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;Installed 1 component(s) into 3 agent(s):
&lt;/span&gt;&lt;span class=&quot;gp&quot;&gt;  [claude-code] seo-audit (copy -&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;~/.claude/skills/seo-audit&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;  [opencode] seo-audit (symlink -&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;~/.config/opencode/skills/seo-audit&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;  [pi] seo-audit (symlink -&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;~/.pi/agent/skills/seo-audit&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;equipr status&lt;/code&gt; reports whether each recorded install still matches its source. &lt;code class=&quot;highlighter-rouge&quot;&gt;equipr update&lt;/code&gt; re-fetches every registered source and re-applies anything that changed. &lt;code class=&quot;highlighter-rouge&quot;&gt;equipr doctor&lt;/code&gt; reports broken symlinks and orphaned records.&lt;/p&gt;

&lt;h2 id=&quot;where-each-agent-differs&quot;&gt;Where each agent differs&lt;/h2&gt;

&lt;p&gt;The four agents agree on very little beyond the skill file format itself. A &lt;code class=&quot;highlighter-rouge&quot;&gt;SKILL.md&lt;/code&gt; is the one point of real convergence across all four. Everywhere else, they differ:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Agent&lt;/th&gt;
      &lt;th&gt;Skills directory&lt;/th&gt;
      &lt;th&gt;MCP config&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Claude Code&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;~/.claude/skills/&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;~/.claude.json&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Codex&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;~/.codex/skills/&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;~/.codex/config.toml&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;OpenCode&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;~/.config/opencode/skills/&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;~/.config/opencode/opencode.json&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Pi&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;~/.pi/agent/skills/&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;~/.config/mcp/mcp.json&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Three different config formats, and OpenCode’s MCP schema is not merely a different filename: it requires an explicit &lt;code class=&quot;highlighter-rouge&quot;&gt;type&lt;/code&gt; and a &lt;code class=&quot;highlighter-rouge&quot;&gt;command&lt;/code&gt; array where Claude Code infers both from a single string. equipr’s install step translates a source’s MCP configuration into each target’s native shape rather than writing one format everywhere.&lt;/p&gt;

&lt;h2 id=&quot;installing-into-each-agent&quot;&gt;Installing into each agent&lt;/h2&gt;

&lt;p&gt;equipr places skill and command files where each agent expects them, and merges MCP servers into each agent’s existing config without disturbing anything else already there: no unrelated key is touched, no unrelated setting is reformatted. Installing is the same command regardless of which agent it’s going into; equipr absorbs the difference in how each one stores things.&lt;/p&gt;

&lt;h2 id=&quot;npm-sources-without-an-npm-install&quot;&gt;npm sources without an npm install&lt;/h2&gt;

&lt;p&gt;equipr accepts an npm package as a source. It does not run &lt;code class=&quot;highlighter-rouge&quot;&gt;npm install&lt;/code&gt;. It runs &lt;code class=&quot;highlighter-rouge&quot;&gt;npm pack&lt;/code&gt;, extracts the resulting tarball into its own cache alongside git and archive sources, and reads &lt;code class=&quot;highlighter-rouge&quot;&gt;plugin.json&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;skills/&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;commands/&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;mcp.json&lt;/code&gt; out of the result. No dependency tree is installed alongside it, and nothing registers as a plugin or a global package in the process.&lt;/p&gt;

&lt;h2 id=&quot;what-equipr-does-not-do&quot;&gt;What equipr does not do&lt;/h2&gt;

&lt;p&gt;equipr never registers itself as a native plugin inside any agent: no entry in Claude Code’s &lt;code class=&quot;highlighter-rouge&quot;&gt;/plugin&lt;/code&gt; list, no npm-global registration, nothing to unregister if the binary is removed. It writes to personal config directories and stops.&lt;/p&gt;

&lt;h2 id=&quot;what-this-reinforced&quot;&gt;What this reinforced&lt;/h2&gt;

&lt;p&gt;Treating each agent’s personal config surface as the integration point, rather than any agent’s own plugin system, is what let one tool support four incompatible agents from a single codebase. The harder requirement turned out to be the opposite of adding capability: writing less to any given file, and writing it more carefully, specifically the single-key-merge discipline for MCP config. That constraint did more to make the tool trustworthy than any feature did.&lt;/p&gt;

&lt;h2 id=&quot;install&quot;&gt;Install&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &lt;span class=&quot;nt&quot;&gt;-fsSL&lt;/span&gt; https://github.com/maquina-app/equipr/releases/latest/download/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Checksum-verified, installs to &lt;code class=&quot;highlighter-rouge&quot;&gt;/usr/local/bin&lt;/code&gt; when writable, &lt;code class=&quot;highlighter-rouge&quot;&gt;~/.local/bin&lt;/code&gt; otherwise. A Homebrew tap is also available: &lt;code class=&quot;highlighter-rouge&quot;&gt;brew install maquina-app/tap/equipr&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;/documentation/tools/equipr/&quot;&gt;equipr documentation&lt;/a&gt; covers every command, every flag, where each file lands per agent, and exit codes for scripting. Source is on &lt;a href=&quot;https://github.com/maquina-app/equipr&quot;&gt;GitHub&lt;/a&gt;, MIT licensed.&lt;/p&gt;

&lt;p&gt;Maquina’s own Claude Code plugins are distributed as a marketplace equipr can install from: see &lt;a href=&quot;/documentation/ai-tools/&quot;&gt;AI Tools&lt;/a&gt;.&lt;/p&gt;</content><author><name>Mario Alberto Chávez Cárdenas</name></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://maquina.app/images/og-release-equipr.jpg" /><media:content medium="image" url="https://maquina.app/images/og-release-equipr.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Why I Removed execute_ruby from Rails MCP Server</title><link href="https://maquina.app/blog/2026/08/rails-mcp-server-2-0-0-removing-execute-ruby/" rel="alternate" type="text/html" title="Why I Removed execute_ruby from Rails MCP Server" /><published>2026-08-04T00:00:00-06:00</published><updated>2026-08-04T00:00:00-06:00</updated><id>repo://posts.collection/_posts/2026-08-04-rails-mcp-server-2-0-0-removing-execute-ruby.md</id><content type="html" xml:base="https://maquina.app/blog/2026/08/rails-mcp-server-2-0-0-removing-execute-ruby/">&lt;p&gt;I built the first version of Rails MCP Server before Claude Code, Codex, and tools like them were the popular way to work with AI on a codebase. Back then, Claude Desktop could talk to me and read whatever I pasted into the chat. Nothing else. If I wanted it to see a model’s associations, check a route, or run a quick query against my own data, I copied and pasted. Every question meant leaving the conversation, going to the terminal, and bringing the answer back by hand.&lt;/p&gt;

&lt;p&gt;That’s the itch Rails MCP Server scratched: let Claude Desktop introspect a Rails project directly (routes, schema, models) without me being the copy-paste layer between every question and its answer. A companion tool, &lt;a href=&quot;https://github.com/maquina-app/nvim-mcp-server&quot;&gt;nvim-mcp-server&lt;/a&gt;, closed the other half of the loop, letting Claude Desktop write code changes straight into my Neovim buffers instead of me pasting a diff and applying it by hand.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;execute_ruby&lt;/code&gt; made sense inside that setup. Claude Desktop had no way to touch my filesystem or run anything on its own. The MCP server was its only hands. Giving it a tool that could run real Ruby inside my running Rails app meant one call could answer what would otherwise take a dozen round trips: an ad-hoc scope, or a quick &lt;code class=&quot;highlighter-rouge&quot;&gt;Model.find&lt;/code&gt; to check a hunch. It was the sharpest tool in the server, and it earned its place.&lt;/p&gt;

&lt;h2 id=&quot;what-changed&quot;&gt;What changed&lt;/h2&gt;

&lt;p&gt;That gap doesn’t exist for most people building with AI anymore. Claude Code, Codex, and the agentic coding tools that followed sit directly on top of the codebase. They already have a shell and the same Ruby your app runs on. If an agent wants to check &lt;code class=&quot;highlighter-rouge&quot;&gt;User.where(inactive: true).count&lt;/code&gt;, it doesn’t need a special MCP tool for that. It can just run it, the way I would from my own terminal.&lt;/p&gt;

&lt;p&gt;That’s most of the reasoning that justified &lt;code class=&quot;highlighter-rouge&quot;&gt;execute_ruby&lt;/code&gt;, gone. The tool wasn’t wrong when I built it. It answered a real constraint, and that constraint has largely gone away.&lt;/p&gt;

&lt;h2 id=&quot;hardening-a-tool-i-was-already-rethinking&quot;&gt;Hardening a tool I was already rethinking&lt;/h2&gt;

&lt;p&gt;I didn’t arrive here in one step. Yesterday’s 1.6.0 &lt;a href=&quot;/blog/2026/08/rails-mcp-server-1-6-0-sandbox-hardening/&quot;&gt;release&lt;/a&gt; closed several real bypasses in the &lt;code class=&quot;highlighter-rouge&quot;&gt;execute_ruby&lt;/code&gt; sandbox: file reads that slipped past validation and symlinks that walked out of the project, on top of database writes that weren’t actually being rolled back. Today’s 1.6.1 went further: a researcher at Pluto Security responsibly disclosed a command-execution path through &lt;code class=&quot;highlighter-rouge&quot;&gt;require &quot;pty&quot;&lt;/code&gt;, and the fix restricted &lt;code class=&quot;highlighter-rouge&quot;&gt;require&lt;/code&gt; to a small allowlist of data-only libraries, cutting off that path along with a few adjacent ones.&lt;/p&gt;

&lt;p&gt;Both releases were worth shipping on their own. But writing 1.6.1 is what made the actual problem visible to me: I was patching individual escapes out of a tool whose entire premise is running caller-supplied Ruby with the privileges of the server process. A static scan and some runtime overrides can raise the floor, but they were never going to be a hard boundary. Path validation, transaction rollback, the &lt;code class=&quot;highlighter-rouge&quot;&gt;confirm_risky&lt;/code&gt; gate: every fence I’ve put around &lt;code class=&quot;highlighter-rouge&quot;&gt;execute_ruby&lt;/code&gt; has been best-effort, and 1.6.0 said so plainly. Best-effort is a reasonable trade when a tool is filling a real gap. It stops paying off once that’s no longer true.&lt;/p&gt;

&lt;h2 id=&quot;what-200-does&quot;&gt;What 2.0.0 does&lt;/h2&gt;

&lt;p&gt;Version 2.0.0 removes &lt;code class=&quot;highlighter-rouge&quot;&gt;execute_ruby&lt;/code&gt; outright, rather than hardening it again. The server is introspection-only now, and its dedicated analyzers already cover the ground &lt;code class=&quot;highlighter-rouge&quot;&gt;execute_ruby&lt;/code&gt; was built for:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Instead of &lt;code class=&quot;highlighter-rouge&quot;&gt;execute_ruby&lt;/code&gt; for…&lt;/th&gt;
      &lt;th&gt;Use&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Reading a file&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;get_file&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Finding files&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;list_files&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Routes, schema, models, controllers, env, structure&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;get_routes&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;get_schema&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;analyze_models&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;analyze_controller_views&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;analyze_environment_config&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;project_info&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Ad-hoc data queries (a custom scope, a one-off count) are the one thing this doesn’t replace, on purpose. That was always the part of &lt;code class=&quot;highlighter-rouge&quot;&gt;execute_ruby&lt;/code&gt; doing the most work and carrying the most risk, and it’s exactly the part an agent sitting on top of your code no longer needs a server tool for.&lt;/p&gt;

&lt;p&gt;Bootstrap tools drop from four to three: &lt;code class=&quot;highlighter-rouge&quot;&gt;switch_project&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;search_tools&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;execute_tool&lt;/code&gt;. The internal analyzers haven’t changed. They’re still discovered through &lt;code class=&quot;highlighter-rouge&quot;&gt;search_tools&lt;/code&gt; and invoked through &lt;code class=&quot;highlighter-rouge&quot;&gt;execute_tool&lt;/code&gt;, same as before.&lt;/p&gt;

&lt;p&gt;If you’re on a client without direct code execution and still want &lt;code class=&quot;highlighter-rouge&quot;&gt;execute_ruby&lt;/code&gt;, the 1.6.x line keeps it, now with the 1.6.1 hardening. It isn’t disappearing; it’s just not where the project is headed.&lt;/p&gt;

&lt;h2 id=&quot;upgrading&quot;&gt;Upgrading&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gem update rails-mcp-server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you have &lt;code class=&quot;highlighter-rouge&quot;&gt;execute_ruby&lt;/code&gt; wired into a client’s tool config, remove it from there. Replace file reads with &lt;code class=&quot;highlighter-rouge&quot;&gt;get_file&lt;/code&gt; and globs with &lt;code class=&quot;highlighter-rouge&quot;&gt;list_files&lt;/code&gt;. Everything else keeps working the way it did.&lt;/p&gt;

&lt;p&gt;For new installations:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gem &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;rails-mcp-server
rails-mcp-config
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;where-this-leaves-the-project&quot;&gt;Where this leaves the project&lt;/h2&gt;

&lt;p&gt;A tool that runs arbitrary code is always going to be the most interesting line in a security report, no matter how many layers sit around it. I’d rather ship a Rails MCP Server that doesn’t have that line at all than one that keeps explaining, release after release, why the latest patch finally closes the gap. What’s gone is the one tool whose risk had stopped being worth what it saved me.&lt;/p&gt;

&lt;p&gt;Thanks again to Pluto Security for the responsible disclosure that shaped both 1.6.1 and this release. If you find a gap, the &lt;a href=&quot;https://github.com/maquina-app/rails-mcp-server/issues&quot;&gt;issue tracker&lt;/a&gt; is open.&lt;/p&gt;

&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/maquina-app/rails-mcp-server&quot;&gt;GitHub Repository&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://rubygems.org/gems/rails-mcp-server&quot;&gt;RubyGems&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/maquina-app/rails-mcp-server/releases/tag/v2.0.0&quot;&gt;2.0.0 Release Notes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/maquina-app/rails-mcp-server/releases/tag/v1.6.1&quot;&gt;1.6.1 Release Notes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/maquina-app/rails-mcp-server/blob/main/SECURITY.md&quot;&gt;Security Policy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Mario Alberto Chávez Cárdenas</name></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://maquina.app/images/og-rails-mcp-server-2-0-0.jpg" /><media:content medium="image" url="https://maquina.app/images/og-rails-mcp-server-2-0-0.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Rails MCP Server 1.6.0: Making “Read-Only” Actually Mean Read-Only</title><link href="https://maquina.app/blog/2026/08/rails-mcp-server-1-6-0-sandbox-hardening/" rel="alternate" type="text/html" title="Rails MCP Server 1.6.0: Making &quot;Read-Only&quot; Actually Mean Read-Only" /><published>2026-08-03T00:00:00-06:00</published><updated>2026-08-03T00:00:00-06:00</updated><id>repo://posts.collection/_posts/2026-08-03-rails-mcp-1-6.md</id><content type="html" xml:base="https://maquina.app/blog/2026/08/rails-mcp-server-1-6-0-sandbox-hardening/">&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;execute_ruby&lt;/code&gt; tool lets an AI model run Ruby in the context of your Rails application. That is genuinely useful. One call can answer a question that would otherwise take a dozen tool round-trips, and it is also the single most dangerous thing this server does. The tool advertises itself as read-only. Version 1.6.0 is about making that promise closer to true.&lt;/p&gt;

&lt;p&gt;This release started with an uncomfortable review of the sandbox. What I found is that “read-only” had holes you could walk through without trying. So 1.6.0 closes them, adds several layers of defense behind them, and, importantly, is honest about what the sandbox is and isn’t.&lt;/p&gt;

&lt;h2 id=&quot;what-the-sandbox-actually-is&quot;&gt;What the sandbox actually is&lt;/h2&gt;

&lt;p&gt;Worth stating plainly, because it frames everything below: &lt;code class=&quot;highlighter-rouge&quot;&gt;execute_ruby&lt;/code&gt; runs your code through &lt;code class=&quot;highlighter-rouge&quot;&gt;bin/rails runner&lt;/code&gt;. That means real Ruby, with your full application loaded and a live database connection. The sandbox is a combination of static analysis (a scan for dangerous patterns) and runtime overrides of &lt;code class=&quot;highlighter-rouge&quot;&gt;File&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;IO&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;Dir&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;Kernel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is defense-in-depth, not a locked box. It raises the floor; it is not an isolation boundary. Keeping that distinction visible is part of what 1.6.0 fixes. The previous framing implied more safety than the implementation delivered.&lt;/p&gt;

&lt;h2 id=&quot;the-holes-that-were-closed&quot;&gt;The holes that were closed&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;File reads leaked through the siblings.&lt;/strong&gt; The sandbox overrode &lt;code class=&quot;highlighter-rouge&quot;&gt;File.read&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;File.open&lt;/code&gt;, but not &lt;code class=&quot;highlighter-rouge&quot;&gt;File.readlines&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;File.binread&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;File.foreach&lt;/code&gt;, or any of the &lt;code class=&quot;highlighter-rouge&quot;&gt;IO&lt;/code&gt; equivalents. Those are separate entry points, and they read arbitrary paths:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Blocked before 1.6.0:&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/etc/passwd&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;     &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; PATH ERROR&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Wide open before 1.6.0:&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/etc/passwd&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;       &lt;span class=&quot;c1&quot;&gt;# read anything&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;readlines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/etc/passwd&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Every read entry point now routes through the same path validation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Raw readers, still exposed:&lt;/strong&gt; the overrides kept the originals around as public aliases like &lt;code class=&quot;highlighter-rouge&quot;&gt;File.original_read&lt;/code&gt;, so user code could just call the un-sandboxed method directly. Those are gone from the public surface now; the native handles live in private constants the sandbox uses internally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Symlinks could point out of the project.&lt;/strong&gt; Path validation expanded paths but never resolved symlinks, so a link inside the project that pointed at &lt;code class=&quot;highlighter-rouge&quot;&gt;/etc/passwd&lt;/code&gt; passed the check and then read the target. Validation now resolves the real path first, and the allowlist for system data (the timezone directories Rails needs for &lt;code class=&quot;highlighter-rouge&quot;&gt;Time.zone&lt;/code&gt;) is matched against canonical locations so it keeps working on macOS, where &lt;code class=&quot;highlighter-rouge&quot;&gt;/usr/share/zoneinfo&lt;/code&gt; is itself a symlink.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Half-blocked &lt;code class=&quot;highlighter-rouge&quot;&gt;ENV&lt;/code&gt;:&lt;/strong&gt; the scan rejected &lt;code class=&quot;highlighter-rouge&quot;&gt;ENV[...]&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;ENV.fetch&lt;/code&gt;, but &lt;code class=&quot;highlighter-rouge&quot;&gt;ENV.to_h&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;ENV.values_at&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;ENV.each&lt;/code&gt; walked straight past it and handed over every secret in the process environment. The scan now rejects &lt;code class=&quot;highlighter-rouge&quot;&gt;ENV&lt;/code&gt; access broadly, while still leaving &lt;code class=&quot;highlighter-rouge&quot;&gt;Rails.env&lt;/code&gt; alone.&lt;/p&gt;

&lt;h2 id=&quot;the-layers-added-behind-them&quot;&gt;The layers added behind them&lt;/h2&gt;

&lt;p&gt;Closing bypasses is necessary but not sufficient. A blocklist can always be out-metaprogrammed. So 1.6.0 adds controls that don’t depend on catching every trick.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database writes are rolled back.&lt;/strong&gt; This is the big one, because “read-only” was never true for data. Nothing stopped &lt;code class=&quot;highlighter-rouge&quot;&gt;User.delete_all&lt;/code&gt; or a raw &lt;code class=&quot;highlighter-rouge&quot;&gt;DELETE&lt;/code&gt;. Now your code runs inside a transaction that is &lt;em&gt;always&lt;/em&gt; rolled back:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# This executes, returns a count, and then is undone:&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;inactive: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;delete_all&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# =&amp;gt; 42   (and the 42 rows are still there)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It is harm reduction, not a guarantee (DDL auto-commits on some adapters like MySQL, and &lt;code class=&quot;highlighter-rouge&quot;&gt;after_commit&lt;/code&gt; callbacks don’t fire), but the common accident is now a no-op instead of data loss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The timeout actually stops the code.&lt;/strong&gt; The previous timeout wrapped the subprocess call and, when it fired, stopped &lt;em&gt;waiting&lt;/em&gt;, while the runaway &lt;code class=&quot;highlighter-rouge&quot;&gt;rails runner&lt;/code&gt; kept going, orphaned. The command now runs in its own process group, and a timeout kills the whole group. A runaway query or an infinite loop is terminated, not abandoned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dual-use constructs ask first.&lt;/strong&gt; Some things are legitimately useful and also the classic sandbox-escape tools: &lt;code class=&quot;highlighter-rouge&quot;&gt;send&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;public_send&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;const_get&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;Kernel#open&lt;/code&gt;. Rather than silently allowing them or hard-blocking them, the tool now stops and explains:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;CONFIRMATION REQUIRED: This code uses constructs that can bypass
the sandbox&apos;s static safety checks:
  - `send`: dynamic dispatch can invoke methods the static scan
    cannot see, e.g. reaching blocked system/file APIs indirectly.

Ask the user to review the code and confirm. If they approve,
re-invoke execute_ruby with confirm_risky: true.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The model can’t wave this through on its own. A human reviews the code, and only then does the call run with &lt;code class=&quot;highlighter-rouge&quot;&gt;confirm_risky: true&lt;/code&gt;. It puts a person in the loop exactly where the static analysis runs out of road.&lt;/p&gt;

&lt;h2 id=&quot;being-honest-about-the-boundary&quot;&gt;Being honest about the boundary&lt;/h2&gt;

&lt;p&gt;I want to be direct about the limits, because a security tool that oversells itself is worse than one that doesn’t. These controls are layered defense, not hard isolation. The tool still executes real Ruby with full application access, so a determined bypass is possible; DDL and writes on non-default connections can escape the rollback; there are no per-process CPU or memory caps beyond the timeout.&lt;/p&gt;

&lt;p&gt;If you need stronger guarantees, the right moves are outside the Ruby layer: run the server against a database user with read-only grants, and/or run it inside an OS-level sandbox (a container, &lt;code class=&quot;highlighter-rouge&quot;&gt;sandbox-exec&lt;/code&gt;, seccomp). The &lt;code class=&quot;highlighter-rouge&quot;&gt;SECURITY.md&lt;/code&gt; in the repository now documents both the controls and these limitations so you can make an informed call.&lt;/p&gt;

&lt;h2 id=&quot;manager-agnostic-ruby-resolution&quot;&gt;Manager-agnostic Ruby resolution&lt;/h2&gt;

&lt;p&gt;Separate from the sandbox, 1.6.0 fixes a long-standing annoyance for anyone whose Ruby isn’t the system default. Tools that shell out to &lt;code class=&quot;highlighter-rouge&quot;&gt;bin/rails&lt;/code&gt; (&lt;code class=&quot;highlighter-rouge&quot;&gt;execute_ruby&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;get_schema&lt;/code&gt;, and the introspection halves of &lt;code class=&quot;highlighter-rouge&quot;&gt;analyze_models&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;analyze_controller_views&lt;/code&gt;) were exporting the rbenv-only &lt;code class=&quot;highlighter-rouge&quot;&gt;RBENV_VERSION&lt;/code&gt; and running a login shell. On macOS, &lt;code class=&quot;highlighter-rouge&quot;&gt;path_helper&lt;/code&gt; then reshuffled &lt;code class=&quot;highlighter-rouge&quot;&gt;PATH&lt;/code&gt; so &lt;code class=&quot;highlighter-rouge&quot;&gt;bin/rails&lt;/code&gt; booted under system Ruby and failed.&lt;/p&gt;

&lt;p&gt;The runner now prepends the active version manager’s shims directory to &lt;code class=&quot;highlighter-rouge&quot;&gt;PATH&lt;/code&gt; (mise, asdf, or rbenv, honoring &lt;code class=&quot;highlighter-rouge&quot;&gt;MISE_DATA_DIR&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;XDG_DATA_HOME&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;ASDF_DATA_DIR&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;RBENV_ROOT&lt;/code&gt;) and uses a non-login shell so the project’s Ruby survives. rvm, which has no shims, is sourced when present. The version comes from your project’s &lt;code class=&quot;highlighter-rouge&quot;&gt;.ruby-version&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;.tool-versions&lt;/code&gt;, or &lt;code class=&quot;highlighter-rouge&quot;&gt;.mise.toml&lt;/code&gt;, so different projects can use different Rubies with no extra configuration.&lt;/p&gt;

&lt;p&gt;While I was in there, the analyzer path stopped swallowing errors with &lt;code class=&quot;highlighter-rouge&quot;&gt;2&amp;gt;/dev/null&lt;/code&gt;, so a Rails boot failure now surfaces the real message instead of a blank “Error executing Rails command.”&lt;/p&gt;

&lt;h2 id=&quot;namespaced-models-resolve-properly&quot;&gt;Namespaced models resolve properly&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;analyze_models&lt;/code&gt; could report a module-namespaced model as “not found” depending on how you referred to it. It now resolves from every input form: &lt;code class=&quot;highlighter-rouge&quot;&gt;Namespace::Model&lt;/code&gt;, the path &lt;code class=&quot;highlighter-rouge&quot;&gt;namespace/model&lt;/code&gt;, the flattened &lt;code class=&quot;highlighter-rouge&quot;&gt;NamespaceModel&lt;/code&gt;, and the bare leaf &lt;code class=&quot;highlighter-rouge&quot;&gt;Model&lt;/code&gt;. That holds independent of your app’s custom inflections. The introspection runner also derives the constant from the resolved file instead of interpolating raw input, which removes an injection surface in the generated scripts along the way.&lt;/p&gt;

&lt;h2 id=&quot;breaking-change-ruby-32-is-dropped&quot;&gt;Breaking change: Ruby 3.2 is dropped&lt;/h2&gt;

&lt;p&gt;The minimum supported Ruby is now &lt;strong&gt;3.3&lt;/strong&gt; (&lt;code class=&quot;highlighter-rouge&quot;&gt;required_ruby_version &amp;gt;= 3.3.0&lt;/code&gt;), and CI tests 3.3 and 3.4. The dependency updates in this release pull in transitive gems that require 3.3, so this was forced rather than chosen. If you’re on 3.2, upgrade your Ruby before updating the gem.&lt;/p&gt;

&lt;h2 id=&quot;security-and-dependencies&quot;&gt;Security and dependencies&lt;/h2&gt;

&lt;p&gt;The dependency bump is also a security update. Upgrading to Puma 8.0.2 clears CVE-2026-47736 and CVE-2026-47737 (both HIGH, covering PROXY Protocol v1 remote memory exhaustion and repeated-header handling), and the lockfile refresh clears a &lt;code class=&quot;highlighter-rouge&quot;&gt;concurrent-ruby&lt;/code&gt; advisory. &lt;code class=&quot;highlighter-rouge&quot;&gt;bundler-audit&lt;/code&gt; reports clean.&lt;/p&gt;

&lt;h2 id=&quot;upgrading&quot;&gt;Upgrading&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gem update rails-mcp-server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Make sure you’re on Ruby 3.3 or newer first. If you use Claude Desktop, restart it to pick up the new version; the binary path in your configuration doesn’t change.&lt;/p&gt;

&lt;p&gt;For new installations:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gem &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;rails-mcp-server
rails-mcp-config
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;

&lt;p&gt;Giving a model a Ruby runtime inside your app is a sharp tool. This release makes the edges more honest: fewer accidental cuts, and a clear label on what the guard does and doesn’t cover. The durable next step is real isolation: read-only database roles and OS-level sandboxing as first-class, documented setups rather than footnotes.&lt;/p&gt;

&lt;p&gt;If you find a gap, the &lt;a href=&quot;https://github.com/maquina-app/rails-mcp-server/issues&quot;&gt;issue tracker&lt;/a&gt; is open, and security reports have a private channel through the repository’s &lt;a href=&quot;https://github.com/maquina-app/rails-mcp-server/security&quot;&gt;Security tab&lt;/a&gt;. Pull requests are welcome.&lt;/p&gt;

&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/maquina-app/rails-mcp-server&quot;&gt;GitHub Repository&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://rubygems.org/gems/rails-mcp-server&quot;&gt;RubyGems&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/documentation/ai-tools/rails-mcp-server/&quot;&gt;Documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/maquina-app/rails-mcp-server/blob/main/SECURITY.md&quot;&gt;Security Policy&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/maquina-app/rails-mcp-server/blob/main/docs/AGENT.md&quot;&gt;AI Agent Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Mario Alberto Chávez Cárdenas</name></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://maquina.app/images/og-rails-mcp-sandbox-hardening.jpg" /><media:content medium="image" url="https://maquina.app/images/og-rails-mcp-sandbox-hardening.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Maquina Components 0.6.0: Themeable Beyond Color</title><link href="https://maquina.app/blog/2026/07/maquina-components-0-6-0-themeable-beyond-color/" rel="alternate" type="text/html" title="Maquina Components 0.6.0: Themeable Beyond Color" /><published>2026-07-27T00:00:00-06:00</published><updated>2026-07-27T00:00:00-06:00</updated><id>repo://posts.collection/_posts/2026-07-27-maquina-components-0-6-0-themeable-beyond-color.md</id><content type="html" xml:base="https://maquina.app/blog/2026/07/maquina-components-0-6-0-themeable-beyond-color/">&lt;p&gt;Maquina Components, the server-rendered UI component library for Rails and Tailwind, is out in 0.6.0. It adds a token layer for radius, elevation, focus rings and font weight, moves all engine CSS into &lt;code class=&quot;highlighter-rouge&quot;&gt;@layer components&lt;/code&gt;, and fixes a focus ring that had been dead on six of seven button variants. The release is deliberately breaking: seven changes, and the first one fails silently in every existing application.&lt;/p&gt;

&lt;p&gt;Until now the library was themeable in color and in nothing else. Radius, elevation, focus rings, font weight and hover states were written directly into the stylesheets, so changing any of them meant overriding selectors rather than declaring values. Theming one real application against &lt;a href=&quot;https://maquina.app/blog/2026/07/maquina-components-0-5-0-drawer-and-scaffold-templates/&quot;&gt;the 0.5 releases&lt;/a&gt; took roughly 1,700 lines of override CSS, and most of that was not expressing a design. It was reaching past the cascade.&lt;/p&gt;

&lt;h2 id=&quot;why-tokens-alone-would-not-have-fixed-it&quot;&gt;Why tokens alone would not have fixed it&lt;/h2&gt;

&lt;p&gt;Every engine rule was unlayered and carried specificity from its variant and state qualifiers, so appearance and structure shared one flat cascade. A theme could not reach the appearance without also being able to break the structure, and the engine defended against that by being hard to override at all. Adding tokens on top of that arrangement would have changed nothing.&lt;/p&gt;

&lt;p&gt;Three things changed together. There is now a token layer: &lt;code class=&quot;highlighter-rouge&quot;&gt;--control-radius&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;--surface-radius&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;--focus-ring-width&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;--elevation-raised&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;--label-weight&lt;/code&gt; and the rest, declared in &lt;code class=&quot;highlighter-rouge&quot;&gt;@theme&lt;/code&gt; and read from every rule that used to hardcode a value. All twenty stylesheets moved into &lt;code class=&quot;highlighter-rouge&quot;&gt;@layer components&lt;/code&gt;, which is what lets a caller’s Tailwind utilities apply. And specificity is flat now: every rule sits at 0,1,0, with variants and states in &lt;code class=&quot;highlighter-rouge&quot;&gt;:where()&lt;/code&gt;, so a theme’s &lt;code class=&quot;highlighter-rouge&quot;&gt;[data-component=&quot;button&quot;]&lt;/code&gt; means every button, which it previously did not.&lt;/p&gt;

&lt;p&gt;Which gives a contract worth stating plainly: a theme changes values, not selectors. Reach for a selector only when you want to change a component’s shape, like a different padding rhythm or a variant the engine does not ship.&lt;/p&gt;

&lt;p&gt;A flat theme is six lines:&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;:root&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;py&quot;&gt;--elevation-control&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;py&quot;&gt;--elevation-raised&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;py&quot;&gt;--elevation-overlay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;py&quot;&gt;--control-radius&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.25rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;py&quot;&gt;--surface-radius&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.25rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;a href=&quot;https://maquina.app/documentation/components/theming/&quot;&gt;theming guide&lt;/a&gt; has the full token reference. Every component demo on the documentation site now carries a &lt;strong&gt;shape&lt;/strong&gt; toggle in its chrome; flip it to &lt;code class=&quot;highlighter-rouge&quot;&gt;brutal&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;soft&lt;/code&gt; and the whole library changes shape from token declarations alone.&lt;/p&gt;

&lt;h2 id=&quot;the-focus-ring-was-dead&quot;&gt;The focus ring was dead&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;[data-component=&quot;button&quot;]:focus-visible&lt;/code&gt; was declared before the variant rules, at the same specificity. Each variant then re-declared &lt;code class=&quot;highlighter-rouge&quot;&gt;box-shadow&lt;/code&gt; for its own elevation, and later-at-equal-specificity wins. The focus ring was silently overwritten on every variant that set a shadow.&lt;/p&gt;

&lt;p&gt;On the demo page, two of the sixteen buttons showed a ring, and both were &lt;code class=&quot;highlighter-rouge&quot;&gt;destructive&lt;/code&gt; — the only variant that happened to re-declare its own focus rule after its variant rule. Primary, secondary, outline, ghost and link had no visible keyboard focus at all. That is a WCAG 2.4.7 failure. It shipped, and it was invisible in code review because every rule involved looked correct on its own.&lt;/p&gt;

&lt;p&gt;Focus is now an &lt;code class=&quot;highlighter-rouge&quot;&gt;outline&lt;/code&gt; rather than a &lt;code class=&quot;highlighter-rouge&quot;&gt;box-shadow&lt;/code&gt;. Outlines do not participate in &lt;code class=&quot;highlighter-rouge&quot;&gt;box-shadow&lt;/code&gt;, so a variant’s elevation can no longer overwrite a ring. They survive forced-colors mode, and they are not clipped by &lt;code class=&quot;highlighter-rouge&quot;&gt;overflow: hidden&lt;/code&gt; ancestors, which had been quietly cutting rings off inside the sidebar and drawer. Every focusable button rings now, and a test asserts that state rules follow variant rules in every stylesheet, so the ordering that caused this cannot come back.&lt;/p&gt;

&lt;h2 id=&quot;your-utility-classes-now-win&quot;&gt;Your utility classes now win&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;css_classes:&lt;/code&gt; is the documented way to adjust one instance of a component, and it has been partly a lie. Because engine rules were unlayered, they beat any Tailwind utility passed through them:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;[data-component=&quot;input&quot;]&lt;/code&gt; set &lt;code class=&quot;highlighter-rouge&quot;&gt;w-full&lt;/code&gt;, so any &lt;code class=&quot;highlighter-rouge&quot;&gt;w-*&lt;/code&gt; you passed was dead.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;[data-form-part=&quot;actions&quot;]&lt;/code&gt; set &lt;code class=&quot;highlighter-rouge&quot;&gt;display: flex&lt;/code&gt;, so &lt;code class=&quot;highlighter-rouge&quot;&gt;sm:hidden&lt;/code&gt; did nothing.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;[data-component=&quot;form&quot;]&lt;/code&gt; set &lt;code class=&quot;highlighter-rouge&quot;&gt;display: grid&lt;/code&gt;. Pass &lt;code class=&quot;highlighter-rouge&quot;&gt;sm:flex-row&lt;/code&gt; and it silently stopped being a row.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are layout failures, not restyles, and they failed quietly, which is why the workaround was always a wrapper element. With the engine in &lt;code class=&quot;highlighter-rouge&quot;&gt;@layer components&lt;/code&gt;, utilities win. Measured: an input with a width utility goes from 448px to 137px.&lt;/p&gt;

&lt;p&gt;Worth searching your views for &lt;code class=&quot;highlighter-rouge&quot;&gt;css_classes:&lt;/code&gt; after upgrading. Anything you passed as decoration and never saw is about to take effect.&lt;/p&gt;

&lt;h2 id=&quot;before-you-upgrade-run-the-scanner&quot;&gt;Before you upgrade: run the scanner&lt;/h2&gt;

&lt;p&gt;The release ships a scanner. Run it inside your application:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bin/rails maquina:doctor
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It reads your CSS, views and JavaScript and prints &lt;code class=&quot;highlighter-rouge&quot;&gt;file:line&lt;/code&gt; for every pattern this release changes, grouped by severity: the unlayered &lt;code class=&quot;highlighter-rouge&quot;&gt;*&lt;/code&gt; rule, component overrides the token layer makes redundant, restated SVG data URIs, &lt;code class=&quot;highlighter-rouge&quot;&gt;[data-active]&lt;/code&gt; presence selectors, &lt;code class=&quot;highlighter-rouge&quot;&gt;.dark&lt;/code&gt; twins. It never edits anything and always exits 0.&lt;/p&gt;

&lt;h2 id=&quot;breaking-changes&quot;&gt;Breaking changes&lt;/h2&gt;

&lt;p&gt;Seven. The first affects every existing application and fails silently.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;The preflight shim in your &lt;code class=&quot;highlighter-rouge&quot;&gt;theme.css&lt;/code&gt; now outranks the engine.&lt;/strong&gt; Your installed &lt;code class=&quot;highlighter-rouge&quot;&gt;theme.css&lt;/code&gt; carries this rule:&lt;/p&gt;

    &lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;border-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;--color-border&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;Unlayered CSS outranks every layer at any specificity. Now that engine rules live in &lt;code class=&quot;highlighter-rouge&quot;&gt;@layer components&lt;/code&gt;, that one universal rule wins over the tinted borders on all alert and toast variants: a destructive alert renders with a plain grey border where 0.5.1 painted a red one. The generator template is fixed, but the rule lives in your file. Wrap it:&lt;/p&gt;

    &lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;@layer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;border-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;--color-border&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Utility classes now win.&lt;/strong&gt; Anything passed through &lt;code class=&quot;highlighter-rouge&quot;&gt;css_classes:&lt;/code&gt; that was previously overridden by an engine rule will take effect.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Radius and elevation defaults normalize.&lt;/strong&gt; Card goes 12px to 8px, popovers 6px to 8px, and four &lt;code class=&quot;highlighter-rouge&quot;&gt;shadow-lg&lt;/code&gt; sites collapse to &lt;code class=&quot;highlighter-rouge&quot;&gt;--elevation-overlay&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Focus rings become outlines&lt;/strong&gt;, and form fields stop ringing on mouse click.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;merge_component_data&lt;/code&gt; precedence narrows&lt;/strong&gt; to identity keys.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Surfaces that sit above the page stop painting &lt;code class=&quot;highlighter-rouge&quot;&gt;--background&lt;/code&gt;.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Tinted badges lose a stray hairline&lt;/strong&gt; the shim had been forcing onto them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every one of them is a value, so the &lt;a href=&quot;https://maquina.app/documentation/components/upgrading/&quot;&gt;upgrading guide&lt;/a&gt; closes with an appendix that restores the 0.5.1 look with a single token block.&lt;/p&gt;

&lt;h2 id=&quot;upgrading&quot;&gt;Upgrading&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bundle update maquina-components
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then re-run the installer to append the new shape and state tokens to your theme. It is idempotent and will not touch your palette:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bin/rails generate maquina_components:install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then read the &lt;a href=&quot;https://maquina.app/documentation/components/upgrading/&quot;&gt;upgrading guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;also-in-this-release&quot;&gt;Also in this release&lt;/h2&gt;

&lt;p&gt;New:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Drawer gained &lt;code class=&quot;highlighter-rouge&quot;&gt;title&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;description&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;section&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;separator&lt;/code&gt; partials. The first two had been styled by CSS since the beginning with nothing to emit them, so the documentation told you to hand-write &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;h2 class=&quot;text-lg font-semibold&quot;&amp;gt;&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Sidebar gained menu badges, menu actions and group actions on the same footing.&lt;/li&gt;
  &lt;li&gt;There is a &lt;code class=&quot;highlighter-rouge&quot;&gt;label&lt;/code&gt; partial now, which makes the required-field indicator reachable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fixed:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;dropdown_menu_simple&lt;/code&gt; raised &lt;code class=&quot;highlighter-rouge&quot;&gt;NoMethodError&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;combobox_simple&lt;/code&gt; rendered an empty popover. Both had zero call sites in the repository, which is exactly why they shipped broken.&lt;/li&gt;
  &lt;li&gt;Two components were building correct data attributes and then discarding them, so &lt;code class=&quot;highlighter-rouge&quot;&gt;[data-variant=&quot;bordered&quot;]&lt;/code&gt; on a table was unreachable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;what-this-reinforced&quot;&gt;What this reinforced&lt;/h2&gt;

&lt;p&gt;Every bug in this release looked correct in the stylesheet. The focus ring rule was right there in the file, and the dead table variant was right there in the partial. What caught them was asserting on compiled output and computed styles: does this token reach the browser, does this rule come after that one, does this element actually have a ring. The tests that came out of the audit assert those things.&lt;/p&gt;

&lt;p&gt;The quieter finding was a set of styled hooks that turned out to be emitted by nothing at all — CSS that read as supported API and matched no markup. That is worse than a missing feature, because it looks finished. Six became real partials here and two were deleted.&lt;/p&gt;

&lt;h2 id=&quot;documentation&quot;&gt;Documentation&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://maquina.app/documentation/components/theming/&quot;&gt;Theming&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://maquina.app/documentation/components/upgrading/&quot;&gt;Upgrading&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://maquina.app/documentation/components/&quot;&gt;Component documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;source&quot;&gt;Source&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/maquina-app/maquina_components&quot;&gt;Maquina Components&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/maquina-app/maquina_components/compare/v0.5.1...v0.6.0&quot;&gt;Full Changelog&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://rubygems.org/gems/maquina-components/versions/0.6.0&quot;&gt;Gem on RubyGems&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Mario Alberto Chávez Cárdenas</name></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://maquina.app/images/og-maquina-components-0-6-0.jpg" /><media:content medium="image" url="https://maquina.app/images/og-maquina-components-0-6-0.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Introducing Nexo: the harness for Ruby agents</title><link href="https://maquina.app/blog/2026/07/introducing-nexo/" rel="alternate" type="text/html" title="Introducing Nexo: the harness for Ruby agents" /><published>2026-07-20T00:00:00-06:00</published><updated>2026-07-20T00:00:00-06:00</updated><id>repo://posts.collection/_posts/2026-07-20-introducing-nexo.md</id><content type="html" xml:base="https://maquina.app/blog/2026/07/introducing-nexo/">&lt;p&gt;Ruby has most of the pieces you need to build an AI agent already, and they’re good.
&lt;code class=&quot;highlighter-rouge&quot;&gt;ruby_llm&lt;/code&gt; gives you a provider-neutral chat loop and tool calling that’s cleaner than
most language SDKs out there, one API across a dozen providers instead of a new SDK
per vendor. &lt;code class=&quot;highlighter-rouge&quot;&gt;ruby_llm-skills&lt;/code&gt; gives you SKILL.md loading. &lt;code class=&quot;highlighter-rouge&quot;&gt;ruby_llm-mcp&lt;/code&gt; gives you MCP
servers. &lt;code class=&quot;highlighter-rouge&quot;&gt;ruby_llm-schema&lt;/code&gt; gives you structured output. What none of them give you is
a front door, one place where those pieces snap together with defaults you don’t have
to think about twice.&lt;/p&gt;

&lt;p&gt;Building an agent on top of these gems means wiring the same sandbox and permissions
boilerplate into every new project, by hand, every time. Nexo exists to stop that.
It doesn’t rebuild the tool-call loop, skill loading, MCP, or structured output; those
already exist, they’re well built, and duplicating them would just make Nexo worse at
the things &lt;code class=&quot;highlighter-rouge&quot;&gt;ruby_llm&lt;/code&gt; and its ecosystem already do well. Nexo sits on top of them
instead. Two things were missing
from the ecosystem, and those are the two things Nexo adds: a sandbox and permissions
seam, and a real job primitive called &lt;code class=&quot;highlighter-rouge&quot;&gt;WorkflowRun&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;agent--model--harness&quot;&gt;Agent = Model + Harness&lt;/h2&gt;

&lt;p&gt;A model on its own forgets everything the moment it finishes a response. Turning “a
model that replies” into “an agent that does work” takes tools, a place for those
tools to act, a policy for what they’re allowed to do, and a way to track a job from
start to finish. That collection of things is the harness. Ruby has all of it, but
scattered across gems with different DSLs, and wiring them by hand is the first thing
every new agent project reinvents.&lt;/p&gt;

&lt;p&gt;Nexo is the nexus: the connective tissue between a model and everything else an agent
needs, and between the fragmented RubyLLM-ecosystem gems and one coherent whole.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;nexo&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CodeReviewer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Nexo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Agent&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;       &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;NEXO_MODEL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# any ruby_llm model, no vendor default&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;sandbox&lt;/span&gt;     &lt;span class=&quot;ss&quot;&gt;:local&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;permissions&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:read_only&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;instructions&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;You are a careful code reviewer. Read files and report issues. Do not write files.&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;CodeReviewer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;cwd: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/path/to/repo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Review the auth module&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;what-nexo-adds-a-sandbox-and-permissions-seam&quot;&gt;What Nexo adds: a sandbox and permissions seam&lt;/h2&gt;

&lt;p&gt;An agent’s tools have to act &lt;em&gt;somewhere&lt;/em&gt;, and something has to decide what they’re
allowed to do there. Nexo splits that into two questions, where (the sandbox) and
what (the permission mode), and answers both safely by default: &lt;code class=&quot;highlighter-rouge&quot;&gt;:virtual&lt;/code&gt; sandbox,
&lt;code class=&quot;highlighter-rouge&quot;&gt;:read_only&lt;/code&gt; permissions. An untrusted model gets zero host access until you opt into
more, explicitly, in your own code.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Virtual&lt;/code&gt;&lt;/strong&gt; runs in memory with zero host access. &lt;code class=&quot;highlighter-rouge&quot;&gt;#shell&lt;/code&gt; raises
&lt;code class=&quot;highlighter-rouge&quot;&gt;NotImplementedError&lt;/code&gt; on purpose, that’s the safety property, not a bug in waiting.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Local&lt;/code&gt;&lt;/strong&gt; gives you the host filesystem and shell for trusted dev and CI work.
Every path is guarded to stay inside &lt;code class=&quot;highlighter-rouge&quot;&gt;cwd&lt;/code&gt;, and the shell only sees &lt;code class=&quot;highlighter-rouge&quot;&gt;PATH&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;HOME&lt;/code&gt;,
and &lt;code class=&quot;highlighter-rouge&quot;&gt;LANG&lt;/code&gt;. It never sees your full environment.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Container&lt;/code&gt;&lt;/strong&gt; runs tools inside a throwaway Docker or Apple &lt;code class=&quot;highlighter-rouge&quot;&gt;container&lt;/code&gt; sandbox,
hardened by default (no network, dropped capabilities, read-only rootfs), with every
hardening an explicit opt-out. Full flag reference is in the
&lt;a href=&quot;/documentation/nexo/sandboxes/#container-sandbox--docker--apple-container&quot;&gt;sandboxes doc&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Remote&lt;/code&gt;&lt;/strong&gt; is a four-method contract, &lt;code class=&quot;highlighter-rouge&quot;&gt;read&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;write&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;exec&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;close&lt;/code&gt;, that you
adapt to whatever provider you’re already using. Nexo ships zero vendor code here;
swap the injected object and you’ve swapped providers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A write under &lt;code class=&quot;highlighter-rouge&quot;&gt;:read_only&lt;/code&gt;, a shell call on &lt;code class=&quot;highlighter-rouge&quot;&gt;Virtual&lt;/code&gt;, any denied action comes back
as &lt;code class=&quot;highlighter-rouge&quot;&gt;{ error: ... }&lt;/code&gt; and the loop keeps running. The model can read that and adjust; a
permission denial isn’t a reason to crash the process. A path that tries to escape its
sandbox is a different kind of failure, and it raises &lt;code class=&quot;highlighter-rouge&quot;&gt;SecurityError&lt;/code&gt; instead.&lt;/p&gt;

&lt;h2 id=&quot;what-nexo-adds-workflowrun&quot;&gt;What Nexo adds: WorkflowRun&lt;/h2&gt;

&lt;p&gt;An agent accumulates context, it’s a conversation that keeps going. A lot of real
work isn’t that. It’s a finite task that starts, does something, and finishes with a
result you can check on later, sometimes from a different process entirely. Nothing
in the ecosystem covered that cleanly, so Nexo adds &lt;code class=&quot;highlighter-rouge&quot;&gt;Workflow&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A workflow can drive an agent too, so the two primitives Nexo owns fold into one
recipe: stage inputs into the run’s sandbox, run the agent against them, capture the
output. The &lt;code class=&quot;highlighter-rouge&quot;&gt;agent&lt;/code&gt; class macro names the agent, and &lt;code class=&quot;highlighter-rouge&quot;&gt;run_agent&lt;/code&gt; runs it bound to the
run’s own sandbox:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Summarizer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Nexo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Agent&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;NEXO_MODEL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# any ruby_llm model, no vendor default&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;instructions&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Summarize the given text in two plain sentences. No commentary.&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SummarizeDocument&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Nexo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Workflow&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;agent&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Summarizer&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:started&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;doc_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:doc_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;run_agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Summarize this document:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n\n&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:summarized&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;length: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;summary: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SummarizeDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;doc_id: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;text: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Long text...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;      &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; &quot;0191d6b2-...&quot;  (UUID v7, time-ordered)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;status&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; &quot;done&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;result&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; { &quot;summary&quot; =&amp;gt; &quot;The document covers ...&quot; }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Every run gets a stable id, a status, a payload, a result, and an ordered event log
you can replay with &lt;code class=&quot;highlighter-rouge&quot;&gt;Nexo::Workflow.logs(run.id)&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;rake nexo:logs[id]&lt;/code&gt;. The
agent’s tool calls and its final response land in that same log alongside the
workflow’s own &lt;code class=&quot;highlighter-rouge&quot;&gt;:started&lt;/code&gt;/&lt;code class=&quot;highlighter-rouge&quot;&gt;:summarized&lt;/code&gt; events, so a driven run reads as one story
instead of two logs stitched together by hand. Outside Rails it all records to memory
and just works offline, no database required; inside Rails, install the migration and
the same code persists to a &lt;code class=&quot;highlighter-rouge&quot;&gt;nexo_workflow_runs&lt;/code&gt; table instead, with no other change.&lt;/p&gt;

&lt;p&gt;A workflow that raises is recorded as &lt;code class=&quot;highlighter-rouge&quot;&gt;failed&lt;/code&gt; and the exception still propagates to
your caller, the opposite of a tool failure, which returns &lt;code class=&quot;highlighter-rouge&quot;&gt;{ error: ... }&lt;/code&gt; and never
interrupts the agent loop. Jobs that need to pause and pick back up later, waiting on
a human approval or a slow external process, get the same lifecycle extended with
checkpoints, &lt;code class=&quot;highlighter-rouge&quot;&gt;suspend!&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;resume&lt;/code&gt;, covered in the
&lt;a href=&quot;/documentation/nexo/durable-workflows/&quot;&gt;durable workflows doc&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;provider-neutral-on-purpose&quot;&gt;Provider-neutral, on purpose&lt;/h2&gt;

&lt;p&gt;The only hard dependency is &lt;code class=&quot;highlighter-rouge&quot;&gt;ruby_llm&lt;/code&gt;. No hardcoded default model, no vendor SDK
called directly anywhere in the core loop; every example in the docs pulls the model
from &lt;code class=&quot;highlighter-rouge&quot;&gt;ENV.fetch(&quot;NEXO_MODEL&quot;)&lt;/code&gt;, just as happy pointing at a local Ollama model as a
hosted one. &lt;code class=&quot;highlighter-rouge&quot;&gt;ruby_llm-skills&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;ruby_llm-mcp&lt;/code&gt;, and an optional Anthropic-oriented
autonomous loop are all soft dependencies, required lazily, and they raise a clear
&lt;code class=&quot;highlighter-rouge&quot;&gt;Nexo::MissingDependencyError&lt;/code&gt; with install instructions if you haven’t added them.
You only pay for what you use.&lt;/p&gt;

&lt;h2 id=&quot;where-it-stands-today&quot;&gt;Where it stands today&lt;/h2&gt;

&lt;p&gt;Nexo is early and the API isn’t stable yet. The &lt;a href=&quot;/documentation/nexo/sandboxes/#container-sandbox--docker--apple-container&quot;&gt;Apple &lt;code class=&quot;highlighter-rouge&quot;&gt;container&lt;/code&gt; runtime parity
table&lt;/a&gt;
in the docs says so plainly: Docker is the verified path today, and Apple’s CLI flags
are encoded from the reference mapping but not yet confirmed against a live daemon.
The caveat is published rather than papered over.&lt;/p&gt;

&lt;p&gt;What’s shipped: the sandbox and permissions seam across &lt;code class=&quot;highlighter-rouge&quot;&gt;Virtual&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;Local&lt;/code&gt;,
&lt;code class=&quot;highlighter-rouge&quot;&gt;Container&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;Remote&lt;/code&gt;; the &lt;code class=&quot;highlighter-rouge&quot;&gt;Workflow&lt;/code&gt;/&lt;code class=&quot;highlighter-rouge&quot;&gt;WorkflowRun&lt;/code&gt; lifecycle with staging,
artifacts, and durable checkpoints; skills, MCP behind a fail-closed gate, a web
fetch tool with an SSRF guard, sessions, and a Rails engine with generators for the
conventional &lt;code class=&quot;highlighter-rouge&quot;&gt;app/agents&lt;/code&gt; / &lt;code class=&quot;highlighter-rouge&quot;&gt;app/workflows&lt;/code&gt; / &lt;code class=&quot;highlighter-rouge&quot;&gt;app/skills&lt;/code&gt; layout.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gem &quot;nexo_ai&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The gem publishes as &lt;code class=&quot;highlighter-rouge&quot;&gt;nexo_ai&lt;/code&gt;; everything in the code lives under &lt;code class=&quot;highlighter-rouge&quot;&gt;Nexo::&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;None of this exists without the RubyLLM ecosystem underneath it. Nexo was worth
building because that foundation, &lt;a href=&quot;https://github.com/crmne/ruby_llm&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ruby_llm&lt;/code&gt;&lt;/a&gt; and
everything built around it, is solid.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;/documentation/nexo/&quot;&gt;Nexo documentation&lt;/a&gt; has the full index, sandboxes,
permissions, tools, loop backends, workflows, durable workflows, skills, MCP, web,
sessions, Rails, and concurrency. If you’re already building on &lt;code class=&quot;highlighter-rouge&quot;&gt;ruby_llm&lt;/code&gt; and you’ve
written this same sandbox-and-permissions setup more than once, this is for you.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href=&quot;https://github.com/maquina-app/nexo&quot;&gt;github.com/maquina-app/nexo&lt;/a&gt;&lt;/p&gt;</content><author><name>Mario Alberto Chávez Cárdenas</name></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://maquina.app/images/og-nexo.jpg" /><media:content medium="image" url="https://maquina.app/images/og-nexo.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Maquina Components 0.5.0: Drawer and Scaffold Templates</title><link href="https://maquina.app/blog/2026/07/maquina-components-0-5-0-drawer-and-scaffold-templates/" rel="alternate" type="text/html" title="Maquina Components 0.5.0: Drawer and Scaffold Templates" /><published>2026-07-12T00:00:00-06:00</published><updated>2026-07-12T00:00:00-06:00</updated><id>repo://posts.collection/_posts/2026-07-12-maquina-components-0-5-0-drawer-and-scaffold-templates.md</id><content type="html" xml:base="https://maquina.app/blog/2026/07/maquina-components-0-5-0-drawer-and-scaffold-templates/">&lt;p&gt;This release adds a Turbo-aware Drawer component, a &lt;code class=&quot;highlighter-rouge&quot;&gt;scaffold_templates&lt;/code&gt; generator that makes &lt;code class=&quot;highlighter-rouge&quot;&gt;rails generate scaffold&lt;/code&gt; produce styled views, and engine helpers included in the generated helper module. It’s also a first for the project: most of it came from contributors.&lt;/p&gt;

&lt;h2 id=&quot;drawer&quot;&gt;Drawer&lt;/h2&gt;

&lt;p&gt;A slide-out panel with an overlay backdrop, built from sub-partials the same way Card and Sidebar are. A &lt;code class=&quot;highlighter-rouge&quot;&gt;provider&lt;/code&gt; owns the state; &lt;code class=&quot;highlighter-rouge&quot;&gt;header&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;content&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;footer&lt;/code&gt; structure the panel; a &lt;code class=&quot;highlighter-rouge&quot;&gt;trigger&lt;/code&gt; toggles it and a &lt;code class=&quot;highlighter-rouge&quot;&gt;close&lt;/code&gt; dismisses it.&lt;/p&gt;

&lt;div class=&quot;language-erb highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;%=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;components/drawer/provider&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default_open: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;drawer_open?&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;cp&quot;&gt;&amp;lt;%=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;components/drawer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;state: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;drawer_state&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;cp&quot;&gt;&amp;lt;%=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;components/drawer/header&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;h2&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text-lg font-semibold&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Filters&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;cp&quot;&gt;&amp;lt;%=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;components/drawer/close&quot;&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;cp&quot;&gt;&amp;lt;%=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;components/drawer/content&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- Panel body --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;cp&quot;&gt;&amp;lt;%=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;components/drawer/footer&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- Apply / Reset --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Drop a &lt;code class=&quot;highlighter-rouge&quot;&gt;trigger&lt;/code&gt; anywhere on the page as a plain toggle button:&lt;/p&gt;

&lt;div class=&quot;language-erb highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;%=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;components/drawer/trigger&quot;&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The drawer opens from the right by default; pass &lt;code class=&quot;highlighter-rouge&quot;&gt;side: :left&lt;/code&gt; to open from the other edge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt; compound structure (&lt;code class=&quot;highlighter-rouge&quot;&gt;provider&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;header&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;content&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;footer&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;trigger&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;close&lt;/code&gt;), left or right side with an overlay backdrop, a configurable &lt;code class=&quot;highlighter-rouge&quot;&gt;Cmd/Ctrl+D&lt;/code&gt; shortcut, cookie-based state persistence, and full Turbo Drive and Morph compatibility.&lt;/p&gt;

&lt;h3 id=&quot;surviving-turbo&quot;&gt;Surviving Turbo&lt;/h3&gt;

&lt;p&gt;The reason a drawer is more than a CSS transition is the lifecycle around it. The controller handles the three places Turbo usually breaks a stateful component:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache teardown.&lt;/strong&gt; Before Turbo snapshots the page for its cache, the drawer closes and hides its backdrop. A restored snapshot never comes back frozen mid-transition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Morph awareness.&lt;/strong&gt; With &lt;code class=&quot;highlighter-rouge&quot;&gt;turbo_refresh_method_tag :morph&lt;/code&gt;, the server re-renders the page in its default state—it doesn’t know the drawer was open. The controller re-reads its cookie on morph and reasserts the correct state—the same fix &lt;a href=&quot;https://maquina.app/blog/2026/02/maquina-components-0-4-0-turbo-compatibility/&quot;&gt;the sidebar got in 0.4.0&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Persistence.&lt;/strong&gt; State lives in a cookie, so the drawer holds across full page loads and Turbo navigations alike.&lt;/p&gt;

&lt;p&gt;That state is exposed through three helpers, so your server-rendered markup and the client agree on the first paint:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;drawer_state&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; :open or :closed&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;drawer_open?&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; true / false&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;drawer_closed?&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; true / false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Passing &lt;code class=&quot;highlighter-rouge&quot;&gt;default_open: drawer_open?&lt;/code&gt; into the provider and &lt;code class=&quot;highlighter-rouge&quot;&gt;state: drawer_state&lt;/code&gt; into the drawer, as in the usage above, is what closes the loop between the cookie and the initial render.&lt;/p&gt;

&lt;h2 id=&quot;scaffold-templates&quot;&gt;Scaffold Templates&lt;/h2&gt;

&lt;p&gt;Having a component library is one thing; getting your generated code to use it is another. The new &lt;code class=&quot;highlighter-rouge&quot;&gt;scaffold_templates&lt;/code&gt; generator closes that gap.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bin/rails generate maquina_components:scaffold_templates
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It copies a set of ERB scaffold templates—&lt;code class=&quot;highlighter-rouge&quot;&gt;index&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;show&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;new&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;edit&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;_form&lt;/code&gt;, and the record partial—into &lt;code class=&quot;highlighter-rouge&quot;&gt;lib/templates/erb/scaffold/&lt;/code&gt;. Rails has always let you override its generator templates from that path; what was missing was a set that renders with the component library. Now they ship with the gem.&lt;/p&gt;

&lt;p&gt;From then on, the standard scaffold generator produces styled views out of the box:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bin/rails generate scaffold Post title:string body:text
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You get tables, buttons, and form fields built with Maquina Components instead of Rails’ default markup—before writing any view code. Because the templates are copied into your app, they’re yours to edit afterward.&lt;/p&gt;

&lt;h2 id=&quot;engine-helpers-in-the-generated-module&quot;&gt;Engine Helpers in the Generated Module&lt;/h2&gt;

&lt;p&gt;A quieter change in the same direction. The generated &lt;code class=&quot;highlighter-rouge&quot;&gt;MaquinaComponentsHelper&lt;/code&gt; now includes &lt;code class=&quot;highlighter-rouge&quot;&gt;IconsHelper&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;SidebarHelper&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;ToastHelper&lt;/code&gt;. Helpers like &lt;code class=&quot;highlighter-rouge&quot;&gt;icon_for&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;sidebar_open?&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;toast_flash_messages&lt;/code&gt; are available in host-app views without an extra &lt;code class=&quot;highlighter-rouge&quot;&gt;include&lt;/code&gt; in &lt;code class=&quot;highlighter-rouge&quot;&gt;ApplicationHelper&lt;/code&gt;. It’s a one-line diff in the template that removes a papercut every new install used to hit—reaching for &lt;code class=&quot;highlighter-rouge&quot;&gt;icon_for&lt;/code&gt; and getting a &lt;code class=&quot;highlighter-rouge&quot;&gt;NoMethodError&lt;/code&gt; because the module wasn’t wired up yet.&lt;/p&gt;

&lt;h2 id=&quot;icon-class-handling&quot;&gt;Icon Class Handling&lt;/h2&gt;

&lt;p&gt;The one bug fix this release tightens &lt;code class=&quot;highlighter-rouge&quot;&gt;apply_icon_options&lt;/code&gt;. It now guards against &lt;code class=&quot;highlighter-rouge&quot;&gt;nil&lt;/code&gt; and non-string class values, HTML-escapes the class before it reaches the markup, and injects a &lt;code class=&quot;highlighter-rouge&quot;&gt;class&lt;/code&gt; attribute onto &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; elements that didn’t already have one. Small, but exactly the kind of edge case that only surfaces once icons are being passed around inside real templates.&lt;/p&gt;

&lt;h2 id=&quot;contributors&quot;&gt;Contributors&lt;/h2&gt;

&lt;p&gt;This release was built mostly by two people who aren’t me:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/GregorioNeto&quot;&gt;@GregorioNeto&lt;/a&gt; — the Drawer component (#21) and the icon class handling fix (#17)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/JuanVqz&quot;&gt;@JuanVqz&lt;/a&gt; — the &lt;code class=&quot;highlighter-rouge&quot;&gt;scaffold_templates&lt;/code&gt; generator (#20) and the engine helper modules in the generated helper (#19)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you both.&lt;/p&gt;

&lt;h2 id=&quot;upgrading&quot;&gt;Upgrading&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bundle update maquina_components
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then, when you want styled scaffolds, install the templates:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bin/rails generate maquina_components:scaffold_templates
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are no breaking changes in this release.&lt;/p&gt;

&lt;h2 id=&quot;what-this-reinforced&quot;&gt;What This Reinforced&lt;/h2&gt;

&lt;p&gt;Three of the four changes pull the same way: they make the gem lean on Rails instead of sitting next to it. The scaffold templates go through Rails’ own generator override path. Helpers are included the way any Rails helper is. And the Drawer keeps its state in a cookie and reacts to Turbo’s morph, the way the sidebar already does. Less to learn, fewer seams to trip over.&lt;/p&gt;

&lt;p&gt;The other thing worth saying is that I reviewed this release more than I wrote it. Gregorio and Juan built the components; I merged them. That says more about where the project is than any one feature does.&lt;/p&gt;

&lt;h2 id=&quot;documentation&quot;&gt;Documentation&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://maquina.app/documentation/components/drawer/&quot;&gt;Drawer&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://maquina.app/documentation/components/&quot;&gt;Component documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;source&quot;&gt;Source&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/maquina-app/maquina_components&quot;&gt;Maquina Components&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/maquina-app/maquina_components/compare/v0.4.4...v0.5.0&quot;&gt;Full Changelog&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://rubygems.org/gems/maquina-components/versions/0.5.0&quot;&gt;Gem on RubyGems&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Mario Alberto Chávez Cárdenas</name></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://maquina.app/images/og-maquina-components-0-5-0.jpg" /><media:content medium="image" url="https://maquina.app/images/og-maquina-components-0-5-0.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Introducing Fragua: the harness already composed for Rails</title><link href="https://maquina.app/blog/2026/07/introducing-fragua/" rel="alternate" type="text/html" title="Introducing Fragua: the harness already composed for Rails" /><published>2026-07-06T00:00:00-06:00</published><updated>2026-07-06T00:00:00-06:00</updated><id>repo://posts.collection/_posts/2026-07-06-introducing-fragua.md</id><content type="html" xml:base="https://maquina.app/blog/2026/07/introducing-fragua/">&lt;p&gt;&lt;a href=&quot;https://fragua.app&quot;&gt;Fragua&lt;/a&gt; is live in private beta. It’s an AI agent orchestrator built specifically for Rails developers, and it’s the newest product under the &lt;a href=&quot;https://maquina.app&quot;&gt;Maquina&lt;/a&gt; umbrella.&lt;/p&gt;

&lt;p&gt;Fragua is Spanish for &lt;em&gt;forge&lt;/em&gt; — the place where raw material becomes a finished tool under heat and pressure. That’s the intent: take a project from a one-line idea to a shipped pull request, running through research, planning, spec-driven development, and execution.&lt;/p&gt;

&lt;h2 id=&quot;why-we-built-it&quot;&gt;Why we built it&lt;/h2&gt;

&lt;p&gt;Coding agents are good at writing code and bad at remembering why. Every new session tends to start from zero — the spec gets re-pasted, the data model gets re-explained, and the agent has no memory of a decision it helped make yesterday.&lt;/p&gt;

&lt;p&gt;What Fragua actually sells isn’t an AI that writes code. It’s &lt;strong&gt;durable context and a disciplined workflow&lt;/strong&gt;. Each phase — brief, research, plan, spec — produces a structured artifact that persists in the workspace and becomes context for the next agent. By the time the execution agent runs, it’s not guessing; it’s reading the plan, the technical guide, and the spec that already exist.&lt;/p&gt;

&lt;h2 id=&quot;the-shape-of-it&quot;&gt;The shape of it&lt;/h2&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Foundation → Spec → Execution → Pull Request
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Only Foundation (a working copy of your codebase) and an accepted Spec are required. Everything else — Product Brief, Research, MVP Plan, Brand Guide, Technical Guide — is optional enrichment that makes the execution agent sharper without ever gating it. Execution itself starts on its own: a sweep every ~2 minutes picks up accepted specs and approved issue fixes, so there’s no button for “start the build.”&lt;/p&gt;

&lt;p&gt;Under the hood, Fragua splits the work across two planes — a web app that plans and directs, and your own machine that actually runs the agent and pushes the branch. We walk through that split, plus a live tour of the web app, in the video below.&lt;/p&gt;

&lt;h2 id=&quot;rails-first-not-rails-only&quot;&gt;Rails-first, not Rails-only&lt;/h2&gt;

&lt;p&gt;Every new app Fragua scaffolds is Rails 8.1 with Hotwire — Stimulus controllers, Minitest fixtures, Turbo Streams, the full set of conventions the agents already know how to read and write. That part is deliberate and non-negotiable: it’s what lets Fragua write code that looks like it belongs in your app instead of generic output.&lt;/p&gt;

&lt;p&gt;Where you’re pointing Fragua at a codebase that already exists, the door is wider. Foundation and the Spec and Issue agents read your repo rather than dictate its stack, so brownfield projects outside Rails are something we’re genuinely open to exploring — if that’s your situation, say so in your access request and tell us what you’re working in.&lt;/p&gt;

&lt;h2 id=&quot;whats-actually-different-day-to-day&quot;&gt;What’s actually different day to day&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Durable context&lt;/strong&gt; — nothing evaporates between sessions; later phases build on everything earlier ones produced.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;BYOK&lt;/strong&gt; — tokens bill straight to your own Anthropic account. Fragua never proxies the call, never stores the key, can’t see your bill.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://fragua.app/observability&quot;&gt;Full observability&lt;/a&gt;&lt;/strong&gt; — a live run timeline, per-turn cost broken down by phase and rolled up by workspace and month, and a durable audit trail. Metering, not gating — there are no spend caps to trip.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Institutional memory&lt;/strong&gt; — the Knowledge Base, every phase artifact, and the full run history live in the workspace, not in one person’s head. When someone hands off or leaves, the context stays.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Your host, your code&lt;/strong&gt; — agents run on your machine, against your repo, with your own git and GitHub credentials. Nothing you own leaves it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Claude Code today, Codex coming&lt;/strong&gt; — the agent runtime is Claude Code right now, with Codex support coming shortly.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Sandboxed if you want it&lt;/strong&gt; — run the agent directly on your host, or isolate it inside Docker or a macOS Container instead. Setup is in the CLI guide.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;watch-the-two-plane-split-in-action&quot;&gt;Watch the two-plane split in action&lt;/h2&gt;

&lt;script type=&quot;application/ld+json&quot;&gt;
{
  &quot;@context&quot;: &quot;https://schema.org&quot;,
  &quot;@type&quot;: &quot;VideoObject&quot;,
  &quot;name&quot;: &quot;What is Fragua?&quot;,
  &quot;description&quot;: &quot;A live tour of Fragua&apos;s two-plane architecture — the web app that plans and directs, and the agent that runs on your machine and pushes the branch.&quot;,
  &quot;thumbnailUrl&quot;: &quot;https://maquina.app/images/og-fragua-announcement.jpeg&quot;,
  &quot;uploadDate&quot;: &quot;2026-07-06&quot;,
  &quot;embedUrl&quot;: &quot;https://player.vimeo.com/video/1206870407&quot;,
  &quot;publisher&quot;: {
    &quot;@type&quot;: &quot;Organization&quot;,
    &quot;name&quot;: &quot;Maquina&quot;,
    &quot;logo&quot;: {
      &quot;@type&quot;: &quot;ImageObject&quot;,
      &quot;url&quot;: &quot;https://maquina.app/images/maquina_logo_horizontal.svg&quot;
    }
  }
}
&lt;/script&gt;

&lt;div class=&quot;not-prose my-8 overflow-hidden rounded-2xl&quot;&gt;
&lt;div style=&quot;padding:56.25% 0 0 0;position:relative;&quot;&gt;&lt;iframe src=&quot;https://player.vimeo.com/video/1206870407?h=4181fb6856&amp;amp;title=0&amp;amp;byline=0&amp;amp;portrait=0&amp;amp;badge=0&amp;amp;autopause=0&amp;amp;player_id=0&amp;amp;app_id=58479&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; style=&quot;position:absolute;top:0;left:0;width:100%;height:100%;&quot; title=&quot;What is Fragua?&quot; loading=&quot;lazy&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;script src=&quot;https://player.vimeo.com/api/player.js&quot;&gt;&lt;/script&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;where-things-stand&quot;&gt;Where things stand&lt;/h2&gt;

&lt;p&gt;Fragua is in &lt;strong&gt;private beta&lt;/strong&gt;, by invitation. A human reads every request before a seat opens — there’s no schedule, seats open based on fit rather than a calendar.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Plan&lt;/th&gt;
      &lt;th&gt;Price&lt;/th&gt;
      &lt;th&gt;Includes&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Solo&lt;/td&gt;
      &lt;td&gt;$19/month&lt;/td&gt;
      &lt;td&gt;1 user, unlimited workspaces, full cost dashboard, per-feature worktrees, BYOK&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Team&lt;/td&gt;
      &lt;td&gt;$69/month, flat&lt;/td&gt;
      &lt;td&gt;2–10 users, &lt;a href=&quot;https://fragua.app/teams&quot;&gt;shared workspaces&lt;/a&gt;, admin &amp;amp; member roles, audit trail across every run, priority support, BYOK&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Enterprise&lt;/td&gt;
      &lt;td&gt;Contact for quote&lt;/td&gt;
      &lt;td&gt;11+ users, unlimited workspaces, on-premises deployment, dedicated support, BYOK&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;All plans are &lt;strong&gt;free for the duration of the beta&lt;/strong&gt;. Pricing begins 30 days after public launch, with email notice ahead of time. A read-only viewer role is on the roadmap — not shipped yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Need more than 10 seats, or want it on-premises?&lt;/strong&gt; Email &lt;a href=&quot;mailto:mario@fragua.app&quot;&gt;mario@fragua.app&lt;/a&gt; and we’ll work out the details directly.&lt;/p&gt;

&lt;p&gt;If you’re building on Rails 8.x and Hotwire — solo, freelance, a small consultancy, or a growing product team — request access at &lt;a href=&quot;https://fragua.app/#access&quot;&gt;fragua.app/#access&lt;/a&gt;. Working in something else on an existing codebase? Tell us in the request; we’d like to hear about it.&lt;/p&gt;

&lt;p&gt;The full docs live at &lt;a href=&quot;https://fragua.app&quot;&gt;fragua.app&lt;/a&gt;, including dedicated pages on &lt;a href=&quot;https://fragua.app/observability&quot;&gt;observability&lt;/a&gt;, &lt;a href=&quot;https://fragua.app/teams&quot;&gt;teams&lt;/a&gt;, and &lt;a href=&quot;https://fragua.app/trust&quot;&gt;trust&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Agents that respect the craft. Your host, your keys, your repo.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Fragua is built by &lt;a href=&quot;https://maquina.app/&quot;&gt;Maquina&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</content><author><name>Mario Alberto Chávez Cárdenas</name></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://maquina.app/images/og-fragua-announcement.jpeg" /><media:content medium="image" url="https://maquina.app/images/og-fragua-announcement.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">recuerd0 Now Has an MCP Server — and a Better Look</title><link href="https://maquina.app/blog/2026/06/recuerd0-mcp-and-redesign/" rel="alternate" type="text/html" title="recuerd0 Now Has an MCP Server — and a Better Look" /><published>2026-06-11T00:00:00-06:00</published><updated>2026-06-11T00:00:00-06:00</updated><id>repo://posts.collection/_posts/2026-06-11-recuerd0-mcp-and-redesign.md</id><content type="html" xml:base="https://maquina.app/blog/2026/06/recuerd0-mcp-and-redesign/">&lt;p&gt;&lt;a href=&quot;https://recuerd0.ai/&quot;&gt;recuerd0&lt;/a&gt; has always had multiple ways to access your knowledge: a &lt;a href=&quot;https://recuerd0.ai/api-docs&quot;&gt;REST API&lt;/a&gt;, a &lt;a href=&quot;https://recuerd0.ai/cli&quot;&gt;CLI&lt;/a&gt; for terminal workflows, and an &lt;a href=&quot;https://recuerd0.ai/agents&quot;&gt;agents guide&lt;/a&gt; for hooking coding agents directly into your memories. The access model has been there from the start — Bearer token, call the endpoints, get your context back.&lt;/p&gt;

&lt;p&gt;MCP adds another path. Instead of configuring the connection upfront, any MCP client can reach your memories on demand — mid-conversation, mid-task, whenever the context is needed — without a token to copy or a config file to edit. Building that properly meant adding an OAuth 2.1 authorization server, a consent screen, and connected application management — surface area that needed to feel right in the browser.&lt;/p&gt;

&lt;p&gt;While that work was underway, the editing experience got the same attention. The textarea that had always been good enough stopped feeling good enough when you’re managing a knowledge base you actually rely on. The fix was already sitting in plain sight: 37signals shipped House MD with Writebook, and it fit recuerd0’s requirements almost exactly.&lt;/p&gt;

&lt;p&gt;Both changes landed at the same time, alongside a UI pass that tightened the information hierarchy across the workspace and memory views.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;mcp-support&quot;&gt;MCP support&lt;/h2&gt;

&lt;p&gt;recuerd0 runs a remote MCP server at &lt;code class=&quot;highlighter-rouge&quot;&gt;POST /mcp&lt;/code&gt; — Streamable HTTP, JSON-RPC 2.0, compatible with any client that speaks the protocol.&lt;/p&gt;

&lt;p&gt;The connection is consent-based. The client registers itself with Dynamic Client Registration, you approve a short permission screen in the browser, and that’s it. No token to generate, no config to edit. Approve once per client; disconnect any time from your profile.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/recuerd0-mcp/mcp.png&quot; alt=&quot;recuerd0 OAuth consent screen approving an MCP client connection, with connected application management in the browser&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The authorization server is built directly into the Rails app — no external OAuth service, no added dependencies. PKCE instead of shared secrets. Access tokens last one hour; refresh tokens rotate silently, so a connected client stays connected without interrupting you.&lt;/p&gt;

&lt;h3 id=&quot;six-tools&quot;&gt;Six tools&lt;/h3&gt;

&lt;p&gt;The server exposes six tools across three read and three write operations, all scoped to your account’s workspaces:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Tool&lt;/th&gt;
      &lt;th&gt;Type&lt;/th&gt;
      &lt;th&gt;What it does&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;list_workspaces&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;read&lt;/td&gt;
      &lt;td&gt;List the workspaces in your account&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;list_memories&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;read&lt;/td&gt;
      &lt;td&gt;List memories; optional FTS5 &lt;code class=&quot;highlighter-rouge&quot;&gt;query&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;category&lt;/code&gt; filter&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;read_memory&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;read&lt;/td&gt;
      &lt;td&gt;Read the full content of one memory&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;create_memory&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;write&lt;/td&gt;
      &lt;td&gt;Create a memory, with optional tags&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;update_memory&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;write&lt;/td&gt;
      &lt;td&gt;Update title, content, category, or tags&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;create_version&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;write&lt;/td&gt;
      &lt;td&gt;Append an immutable version, preserving history&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Read tools require the &lt;code class=&quot;highlighter-rouge&quot;&gt;memories:read&lt;/code&gt; scope. Write tools — including &lt;code class=&quot;highlighter-rouge&quot;&gt;create_version&lt;/code&gt; — require &lt;code class=&quot;highlighter-rouge&quot;&gt;memories:write&lt;/code&gt;. Scopes map to the existing &lt;code class=&quot;highlighter-rouge&quot;&gt;read_only&lt;/code&gt; / &lt;code class=&quot;highlighter-rouge&quot;&gt;full_access&lt;/code&gt; permission tiers, so there’s no parallel auth system to reason about.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;create_version&lt;/code&gt; is the one worth pausing on. Every memory in recuerd0 already has a versioning model — you can snapshot a decision or convention as it evolves and keep the full history. The MCP tool exposes that directly. If a client updates a memory and you want to preserve what was there before, it can append a version rather than overwrite.&lt;/p&gt;

&lt;h3 id=&quot;connect-it&quot;&gt;Connect it&lt;/h3&gt;

&lt;p&gt;The MCP endpoint is at your instance URL:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://recuerd0.ai/mcp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For self-hosted instances, replace the domain with your own. Any MCP client that supports custom connectors — Claude Desktop, Claude.ai, Cursor, and others — can connect by pointing at that URL. The OAuth handshake runs itself.&lt;/p&gt;

&lt;p&gt;Full documentation is at &lt;a href=&quot;https://recuerd0.ai/mcp&quot;&gt;recuerd0.ai/mcp&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-editor&quot;&gt;The editor&lt;/h2&gt;

&lt;p&gt;recuerd0 stores knowledge as markdown. The editing experience should match that — not fight it. A plain textarea works, but it asks you to hold the syntax in your head while you write. After using it long enough, the friction starts to matter.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/recuerd0-mcp/editor.png&quot; alt=&quot;recuerd0&apos;s new markdown editor based on House MD, with a minimal toolbar for bold, italic, quote, code, link, and lists&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The new editor is based on &lt;a href=&quot;https://once.com/writebook&quot;&gt;House MD&lt;/a&gt; — the markdown editor 37signals built for Writebook. Minimal toolbar: bold, italic, quote, code, link, bullet list, numbered list. It writes markdown, not HTML. No mode switching, no preview tab — what you see while editing stays close to what the rendered output looks like.&lt;/p&gt;

&lt;p&gt;One thing worth being explicit about: everything that reads your memories — the API, the CLI, MCP tools — still gets raw markdown. The editor doesn’t change that contract.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-ui&quot;&gt;The UI&lt;/h2&gt;

&lt;p&gt;recuerd0 is built on &lt;a href=&quot;https://maquina.app/documentation/components/&quot;&gt;Maquina Components&lt;/a&gt; — a Rails component library with cards, badges, buttons, dialogs, and form elements. The component foundation was already there; this pass refined how those pieces are composed and extended them with patterns specific to what recuerd0 actually is.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/recuerd0-mcp/better-ui.png&quot; alt=&quot;Redesigned recuerd0 workspace view with a deliberate information hierarchy, a labeled pinned section, and list/grid view preferences&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The workspace and memory views got a more deliberate information hierarchy — metadata where you need it, actions reachable on touch and keyboard, pinned items in their own labeled section rather than blended into the list. The color system was tightened so the app reads consistently across every page, and view preferences (list vs grid, cards vs compact) now persist between sessions.&lt;/p&gt;

&lt;p&gt;The workspace index and workspace show pages have been updated. Memory show, the editor page, and settings are next.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;The MCP server is live. Full documentation at &lt;a href=&quot;https://recuerd0.ai/mcp&quot;&gt;recuerd0.ai/mcp&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;recuerd0 is free to self-host. Managed hosting is $15/month.&lt;/p&gt;

&lt;h2 id=&quot;related-reading&quot;&gt;Related reading&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2026/04/recuerd0-api-release/&quot;&gt;Recuerd0 Now Reads Like a Filesystem&lt;/a&gt; — grep, glob, and ranged read on your memories.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2026/02/announcing-recuerd0/&quot;&gt;Announcing Recuerd0&lt;/a&gt; — the original launch and the problem we set out to solve.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2026/02/recuerd0-source-code-now-available/&quot;&gt;Recuerd0 source code is now available&lt;/a&gt; — how to self-host under OSAASY.&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Mario Alberto Chávez Cárdenas</name></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://maquina.app/images/og-recuerdo-mcp.jpg" /><media:content medium="image" url="https://maquina.app/images/og-recuerdo-mcp.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Recuerd0 Now Reads Like a Filesystem</title><link href="https://maquina.app/blog/2026/04/recuerd0-api-release/" rel="alternate" type="text/html" title="Recuerd0 Now Reads Like a Filesystem" /><published>2026-04-07T00:00:00-06:00</published><updated>2026-04-07T00:00:00-06:00</updated><id>repo://posts.collection/_posts/2026-04-07-recuerd0-api-release.md</id><content type="html" xml:base="https://maquina.app/blog/2026/04/recuerd0-api-release/">&lt;p&gt;&lt;em&gt;The new Recuerd0 API release teaches the memory store to behave like a filesystem — so AI agents already fluent in &lt;code class=&quot;highlighter-rouge&quot;&gt;grep&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;glob&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;read&lt;/code&gt; need no new vocabulary.&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;a href=&quot;https://recuerd0.ai/&quot;&gt;Recuerd0&lt;/a&gt; is the persistent memory store for AI coding agents built by Maquina, and this release reshapes how agents read from it. When an AI agent reaches into Recuerd0 for context, the bottleneck has never been storage. It has been &lt;em&gt;how much&lt;/em&gt; the agent has to pull back to get to the one fact it needs. A 2,000-line transcript should not have to fit within the agent’s context window to answer “did we decide to use Postgres?” The new release fixes that — and a handful of other long-standing rough edges — by giving the API the same primitives every coding agent already knows: &lt;code class=&quot;highlighter-rouge&quot;&gt;glob&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;grep&lt;/code&gt;, and ranged &lt;code class=&quot;highlighter-rouge&quot;&gt;read&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is what is new.&lt;/p&gt;

&lt;h2 id=&quot;file-tool-api-glob-grep-and-ranged-read-on-memories&quot;&gt;File-tool API: glob, grep, and ranged read on memories&lt;/h2&gt;

&lt;p&gt;The biggest shift in this release is conceptual. Memories are no longer monolithic blobs that you fetch whole. They are addressable like files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Glob.&lt;/strong&gt; The browse and list endpoints accept a &lt;code class=&quot;highlighter-rouge&quot;&gt;title&lt;/code&gt; glob pattern. &lt;code class=&quot;highlighter-rouge&quot;&gt;*&lt;/code&gt; matches any sequence of characters, &lt;code class=&quot;highlighter-rouge&quot;&gt;?&lt;/code&gt; matches a single character. Combined with &lt;code class=&quot;highlighter-rouge&quot;&gt;tags&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;source&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;category&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;workspace_id&lt;/code&gt;, the agent can narrow a thousand memories down to the dozen worth looking at without reading any bodies.&lt;/p&gt;

&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;GET /memories.json?title=Meeting*&amp;amp;tags=design,api&amp;amp;category=decision
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Ranged read.&lt;/strong&gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;GET /workspaces/:id/memories/:id.json&lt;/code&gt; now accepts &lt;code class=&quot;highlighter-rouge&quot;&gt;line_start&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;line_end&lt;/code&gt; (1-based, inclusive). The response always echoes &lt;code class=&quot;highlighter-rouge&quot;&gt;total_lines&lt;/code&gt;, so the client knows how much memory is available and can compute a tail window in a single follow-up call.&lt;/p&gt;

&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;GET /workspaces/1/memories/42.json?line_start=40&amp;amp;line_end=55
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There is no &lt;code class=&quot;highlighter-rouge&quot;&gt;head=&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;tail=&lt;/code&gt; parameter — and that is deliberate. &lt;code class=&quot;highlighter-rouge&quot;&gt;line_start=1&amp;amp;line_end=20&lt;/code&gt; is “head 20”; &lt;code class=&quot;highlighter-rouge&quot;&gt;line_start=(total_lines - 19)&amp;amp;line_end=total_lines&lt;/code&gt; is “tail 20”. One verb covers both, and the client never has to learn a parallel vocabulary for the same operation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grep with line numbers.&lt;/strong&gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;?mode=grep&amp;amp;q=&amp;lt;query&amp;gt;&lt;/code&gt; switches the same endpoint into a grep response. Instead of returning the body, it returns an array of matches:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;total_lines&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2174&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;matches&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;line_number&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1247&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;line&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Decided: Postgres for the analytics warehouse, SQLite for everything else.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;context_before&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;## Database choice&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;context_after&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Reason: ops simplicity outweighs the JOIN ceiling for our scale.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Optional &lt;code class=&quot;highlighter-rouge&quot;&gt;context&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;before&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;after&lt;/code&gt; parameters control how many surrounding lines to return — capped at 10 each, like &lt;code class=&quot;highlighter-rouge&quot;&gt;grep -C&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;-B&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;-A&lt;/code&gt;. The full-text search endpoint (&lt;code class=&quot;highlighter-rouge&quot;&gt;/search.json&lt;/code&gt;) supports the same grep mode for cross-memory queries.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;two-step recipe&lt;/strong&gt; the agent should reach for: first, use grep to locate the line numbers; then issue a follow-up &lt;code class=&quot;highlighter-rouge&quot;&gt;line_start&lt;/code&gt;/&lt;code class=&quot;highlighter-rouge&quot;&gt;line_end&lt;/code&gt; call to fetch only the surrounding window. A 2,000-line memory becomes a 20-line answer.&lt;/p&gt;

&lt;h2 id=&quot;memory-categories&quot;&gt;Memory categories&lt;/h2&gt;

&lt;p&gt;Every memory now carries a category: &lt;code class=&quot;highlighter-rouge&quot;&gt;decision&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;discovery&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;preference&lt;/code&gt;, or &lt;code class=&quot;highlighter-rouge&quot;&gt;general&lt;/code&gt; (the default). It is a small thing, but it changes how an agent reasons about what it is reading. A &lt;code class=&quot;highlighter-rouge&quot;&gt;decision&lt;/code&gt; is load-bearing — something the team chose and is sticking with. A &lt;code class=&quot;highlighter-rouge&quot;&gt;discovery&lt;/code&gt; is a fact about the world. A &lt;code class=&quot;highlighter-rouge&quot;&gt;preference&lt;/code&gt; is taste. The agent does not have to infer the difference from prose; it is right there in the metadata, filterable from any list endpoint.&lt;/p&gt;

&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;GET /memories.json?category=decision&amp;amp;sort=updated_at
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;cross-workspace-memory-links&quot;&gt;Cross-workspace memory links&lt;/h2&gt;

&lt;p&gt;Memories can now reference each other across workspaces with first-class “see also” links. The Rails decision in your &lt;em&gt;Backend&lt;/em&gt; workspace can point to the deployment write-up in &lt;em&gt;Infrastructure&lt;/em&gt; without copying anything. Each memory’s response includes a &lt;code class=&quot;highlighter-rouge&quot;&gt;links_count&lt;/code&gt; so the agent knows there is more context one hop away, and dedicated endpoints under &lt;code class=&quot;highlighter-rouge&quot;&gt;/memories/:id/links&lt;/code&gt; let it list and traverse them.&lt;/p&gt;

&lt;p&gt;This is the connective tissue for context that lives in more than one place — which, in practice, is most context worth keeping.&lt;/p&gt;

&lt;h2 id=&quot;workspace-wake-up-endpoint&quot;&gt;Workspace wake-up endpoint&lt;/h2&gt;

&lt;p&gt;A new endpoint, &lt;code class=&quot;highlighter-rouge&quot;&gt;GET /workspaces/:id/context.json&lt;/code&gt;, returns a compact “wake-up” payload for an agent starting a fresh session: workspace metadata, recent memory titles, and the highlights an agent should know about before it does anything else. It is the answer to “you are picking up where you left off, here is the room you just walked into.”&lt;/p&gt;

&lt;p&gt;Pair it with a Claude Code session-start hook and a new conversation begins with the right context already loaded — no manual &lt;code class=&quot;highlighter-rouge&quot;&gt;recuerd0 memory list&lt;/code&gt; dance, no asking the user to repeat themselves.&lt;/p&gt;

&lt;h2 id=&quot;http-caching-across-the-api&quot;&gt;HTTP caching across the API&lt;/h2&gt;

&lt;p&gt;All read endpoints now emit &lt;code class=&quot;highlighter-rouge&quot;&gt;ETag&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;Last-Modified&lt;/code&gt; headers and respect conditional requests. A client that sends &lt;code class=&quot;highlighter-rouge&quot;&gt;If-None-Match&lt;/code&gt; for a memory it already has receives a &lt;code class=&quot;highlighter-rouge&quot;&gt;304 Not Modified&lt;/code&gt; response with an empty body. For agents that re-fetch the same workspace several times in a session, this is a meaningful drop in tokens shipped over the wire — and a meaningful drop in load on the database.&lt;/p&gt;

&lt;p&gt;Grep and ranged-read responses are correctly bypassed by the cache, since they are derived from query parameters that change with each call.&lt;/p&gt;

&lt;h2 id=&quot;cli-recuerd0-memory-read&quot;&gt;CLI: &lt;code class=&quot;highlighter-rouge&quot;&gt;recuerd0 memory read&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/maquina-app/recuerd0-cli&quot;&gt;recuerd0-cli&lt;/a&gt; gains a &lt;code class=&quot;highlighter-rouge&quot;&gt;memory read&lt;/code&gt; command group that wraps the new endpoints so a human (or a terminal-bound agent) can use them without hand-crafting URLs:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;recuerd0 memory &lt;span class=&quot;nb&quot;&gt;read head &lt;/span&gt;42 &lt;span class=&quot;nt&quot;&gt;--lines&lt;/span&gt; 20
recuerd0 memory &lt;span class=&quot;nb&quot;&gt;read tail &lt;/span&gt;42 &lt;span class=&quot;nt&quot;&gt;--lines&lt;/span&gt; 20
recuerd0 memory &lt;span class=&quot;nb&quot;&gt;read &lt;/span&gt;lines 42 &lt;span class=&quot;nt&quot;&gt;--start&lt;/span&gt; 100 &lt;span class=&quot;nt&quot;&gt;--end&lt;/span&gt; 140
recuerd0 memory &lt;span class=&quot;nb&quot;&gt;read grep &lt;/span&gt;42 &lt;span class=&quot;s2&quot;&gt;&quot;Postgres&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--context&lt;/span&gt; 2 &lt;span class=&quot;nt&quot;&gt;--pretty&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In &lt;code class=&quot;highlighter-rouge&quot;&gt;--pretty&lt;/code&gt; mode, the grep subcommand emits a breadcrumb for each hit, suggesting the exact &lt;code class=&quot;highlighter-rouge&quot;&gt;memory read lines&lt;/code&gt;, followed by a call to fetch a window around it. The two-step pattern is right there in the output — no thinking required.&lt;/p&gt;

&lt;h2 id=&quot;agent-guidance-baked-in&quot;&gt;Agent guidance baked in&lt;/h2&gt;

&lt;p&gt;The Claude Code &lt;a href=&quot;https://github.com/maquina-app/rails-claude-code&quot;&gt;recuerd0 agent skill&lt;/a&gt; now ships guidance for &lt;em&gt;when&lt;/em&gt; to use the new primitives, not just how. The dedup-before-write protocol prefers &lt;code class=&quot;highlighter-rouge&quot;&gt;memory read grep&lt;/code&gt; over &lt;code class=&quot;highlighter-rouge&quot;&gt;memory show&lt;/code&gt; for large candidates. The workflow guidelines tell the agent: when &lt;code class=&quot;highlighter-rouge&quot;&gt;total_lines &amp;gt; ~200&lt;/code&gt;, grep first and fetch a window — reserve full reads for memories you genuinely need in their entirety.&lt;/p&gt;

&lt;p&gt;The point of teaching these patterns to the agent is the same as the point of adding them to the API in the first place: make the cheap thing the obvious thing.&lt;/p&gt;

&lt;h2 id=&quot;documentation&quot;&gt;Documentation&lt;/h2&gt;

&lt;p&gt;Every endpoint above is documented in the public &lt;a href=&quot;https://recuerd0.ai/api-docs&quot;&gt;API reference&lt;/a&gt;, and the &lt;a href=&quot;https://recuerd0.ai/cli&quot;&gt;CLI reference&lt;/a&gt; on recuerd0.ai has been updated to match. The grep→fetch-window workflow is called out as a recipe in both places, with worked examples.&lt;/p&gt;

&lt;h2 id=&quot;why-this-release-matters&quot;&gt;Why this release matters&lt;/h2&gt;

&lt;p&gt;Coding agents are getting fluent. They already know how to use &lt;code class=&quot;highlighter-rouge&quot;&gt;glob&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;grep&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;read&lt;/code&gt; — those primitives are how they navigate filesystems every day. Recuerd0’s job is not to invent a new vocabulary for context retrieval; it is to look enough like a filesystem that agents do not have to learn one.&lt;/p&gt;

&lt;p&gt;This release is that bet, made concrete. A memory is now something you can grep. A workspace is now something you can wake up in. A long transcript no longer has to fit entirely within a context window just so the agent can quote one line from it.&lt;/p&gt;

&lt;h2 id=&quot;get-the-update&quot;&gt;Get the update&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;SaaS users on &lt;a href=&quot;https://recuerd0.ai/&quot;&gt;recuerd0.ai&lt;/a&gt;:&lt;/strong&gt; the new endpoints are live now. No action needed.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Self-hosters:&lt;/strong&gt; pull the latest &lt;a href=&quot;https://github.com/maquina-app/recuerd0&quot;&gt;recuerd0&lt;/a&gt; image (or &lt;code class=&quot;highlighter-rouge&quot;&gt;git pull&lt;/code&gt; and redeploy with Kamal). Run migrations to pick up the new &lt;code class=&quot;highlighter-rouge&quot;&gt;category&lt;/code&gt; column and the &lt;code class=&quot;highlighter-rouge&quot;&gt;memory_links&lt;/code&gt; table.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;CLI users:&lt;/strong&gt; you must update to the latest version to get the new &lt;code class=&quot;highlighter-rouge&quot;&gt;memory read&lt;/code&gt; commands — &lt;code class=&quot;highlighter-rouge&quot;&gt;brew upgrade recuerd0-cli&lt;/code&gt; (or grab the latest binary from &lt;a href=&quot;https://github.com/maquina-app/recuerd0-cli/releases&quot;&gt;recuerd0-cli releases&lt;/a&gt;). Older CLI versions will not expose the new functionality.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Claude Code users:&lt;/strong&gt; update the &lt;strong&gt;recuerd0 plugin&lt;/strong&gt; from the Claude Code marketplace to pick up the new agent guidance and command reference. Without the plugin update, the agent will keep using the old &lt;code class=&quot;highlighter-rouge&quot;&gt;memory show&lt;/code&gt; flow instead of the new grep-first patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;frequently-asked-questions&quot;&gt;Frequently asked questions&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How do I grep a Recuerd0 memory?&lt;/strong&gt;
Send &lt;code class=&quot;highlighter-rouge&quot;&gt;GET /workspaces/:id/memories/:id.json?mode=grep&amp;amp;q=&amp;lt;query&amp;gt;&lt;/code&gt;. The response returns line numbers and surrounding context instead of the full body. From the CLI: &lt;code class=&quot;highlighter-rouge&quot;&gt;recuerd0 memory read grep &amp;lt;id&amp;gt; &quot;&amp;lt;query&amp;gt;&quot; --context 2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the difference between ranged read and grep mode?&lt;/strong&gt;
Grep mode finds &lt;em&gt;where&lt;/em&gt; a string appears (returns matching line numbers with context). Ranged read fetches &lt;em&gt;what&lt;/em&gt; is at known line numbers via &lt;code class=&quot;highlighter-rouge&quot;&gt;line_start&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;line_end&lt;/code&gt;. The recommended workflow is grep first to locate, then ranged read to fetch a window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I have to update the CLI and Claude Code plugin?&lt;/strong&gt;
Yes. The new &lt;code class=&quot;highlighter-rouge&quot;&gt;memory read&lt;/code&gt; commands ship in the latest &lt;a href=&quot;https://github.com/maquina-app/recuerd0-cli&quot;&gt;recuerd0-cli&lt;/a&gt;, and the grep-first agent guidance ships in the updated recuerd0 plugin in the Claude Code marketplace. Older versions will keep working but won’t expose the new endpoints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What are memory categories used for?&lt;/strong&gt;
Each memory is tagged as &lt;code class=&quot;highlighter-rouge&quot;&gt;decision&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;discovery&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;preference&lt;/code&gt;, or &lt;code class=&quot;highlighter-rouge&quot;&gt;general&lt;/code&gt;. Agents (and humans) can filter by category to find load-bearing decisions without sifting through general notes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does HTTP caching apply to grep queries?&lt;/strong&gt;
No. ETag/Last-Modified caching applies to whole-memory and list reads. Grep and ranged-read responses are derived from query parameters and bypass the cache by design.&lt;/p&gt;

&lt;h2 id=&quot;related-reading&quot;&gt;Related reading&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2026/02/announcing-recuerd0/&quot;&gt;Announcing Recuerd0&lt;/a&gt; — the original launch and the problem we set out to solve.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2026/02/recuerd0-source-code-now-available/&quot;&gt;Recuerd0 source code is now available&lt;/a&gt; — how to self-host under OSASSY.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/&quot;&gt;Maquina open-source projects&lt;/a&gt; — the rest of the Rails and AI tooling we maintain.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Recuerd0 is built by &lt;a href=&quot;https://maquina.app/&quot;&gt;Maquina&lt;/a&gt;. Source available under &lt;a href=&quot;https://osaasy.dev/&quot;&gt;OSASSY license&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</content><author><name>Mario Alberto Chávez Cárdenas</name></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://maquina.app/images/og-recuerd0-filesystem.jpg" /><media:content medium="image" url="https://maquina.app/images/og-recuerd0-filesystem.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">MVP Creator: From Idea to Documents in Three Prompts</title><link href="https://maquina.app/blog/2026/03/mvp-creator-from-idea-to-documents/" rel="alternate" type="text/html" title="MVP Creator: From Idea to Documents in Three Prompts" /><published>2026-03-23T00:00:00-06:00</published><updated>2026-03-23T00:00:00-06:00</updated><id>repo://posts.collection/_posts/2026-03-23-mvp-creator-from-idea-to-documents.md</id><content type="html" xml:base="https://maquina.app/blog/2026/03/mvp-creator-from-idea-to-documents/">&lt;p&gt;Before writing a single line of code, I need to understand what I’m building. Not abstractly — concretely: who the users are, what the real problem is, what the app is called, what voice it has, what technical decisions I’m making from the start. For a long time, that work happened informally — in scattered notes, in my head, or spread across different roles on a team: product knowledge in one conversation, brand direction in another, architecture in some document nobody kept updated. Now I formalize it with an agent called MVP Creator.&lt;/p&gt;

&lt;p&gt;In this video — the first in a series about my personal process with AI — I show how I use MVP Creator to generate the complete set of foundation documents for a new project: research report, business plan, brand guide, and technical guide. All of it with three prompts, from an initial idea to documentation ready to hand off to Claude Code.&lt;/p&gt;

&lt;script type=&quot;application/ld+json&quot;&gt;
{
  &quot;@context&quot;: &quot;https://schema.org&quot;,
  &quot;@type&quot;: &quot;VideoObject&quot;,
  &quot;name&quot;: &quot;MVP Creator: From Idea to Documents in Three Prompts&quot;,
  &quot;description&quot;: &quot;Full walkthrough of the MVP Creator Claude Code plugin. From an initial app idea to research report, business plan, brand guide, and technical guide in three prompts.&quot;,
  &quot;thumbnailUrl&quot;: &quot;https://maquina.app/images/og-blog-mvp-creator.jpg&quot;,
  &quot;uploadDate&quot;: &quot;2026-03-23&quot;,
  &quot;embedUrl&quot;: &quot;https://player.vimeo.com/video/1175986278&quot;,
  &quot;duration&quot;: &quot;PT40M&quot;,
  &quot;publisher&quot;: {
    &quot;@type&quot;: &quot;Organization&quot;,
    &quot;name&quot;: &quot;Maquina&quot;,
    &quot;logo&quot;: {
      &quot;@type&quot;: &quot;ImageObject&quot;,
      &quot;url&quot;: &quot;https://maquina.app/images/logo.png&quot;
    }
  }
}
&lt;/script&gt;

&lt;div class=&quot;not-prose my-8 overflow-hidden rounded-2xl&quot;&gt;
&lt;div style=&quot;padding:56.25% 0 0 0;position:relative;&quot;&gt;&lt;iframe src=&quot;https://player.vimeo.com/video/1175986278?badge=0&amp;amp;autopause=0&amp;amp;player_id=0&amp;amp;app_id=58479&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; style=&quot;position:absolute;top:0;left:0;width:100%;height:100%;&quot; title=&quot;MVP Creator: From Idea to Documents in Three Prompts&quot; loading=&quot;lazy&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;script src=&quot;https://player.vimeo.com/api/player.js&quot;&gt;&lt;/script&gt;
&lt;/div&gt;

&lt;h2 id=&quot;the-three-prompts&quot;&gt;The Three Prompts&lt;/h2&gt;

&lt;p&gt;The example in the video is a photo delivery platform for professional photographers. These are the exact prompts I use, in order.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;prompt-1--the-idea-and-context&quot;&gt;Prompt 1 — The Idea and Context&lt;/h3&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Help me create an MVP for a photo delivery platform for professional photographers.
Think of it as a private gallery where photographers deliver finished work to clients.

The core concept: a photographer creates a Project (for a client or personal work),
organizes photos into Collections within that project, and shares the gallery via
single-use expirable links. Invited clients can view, comment, like, and download
photos in their preferred quality.

Key features:
- Projects with collections and high-resolution photo uploads
- Active Storage for thumbnail + quality variants (low/medium/high)
- Reorderable photos within collections, cover photo per collection
- Shareable links: single-use, expire in 7 days, create read-only sessions
- Download: single photo or multi-select as zip, with quality choice

Target users: freelance and studio photographers in Latin America
Language: Spanish-first, English secondary
App name: I&apos;m thinking &quot;Liminal&quot; — open to suggestions

Research these competitors: https://www.pic-time.com and
https://www.picdrop.com/web — also look at how Google Drive handles
shared folder UX as a reference point.

Use the MVP Creator skill to generate the full documentation set.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With this prompt the agent launches competitor research, runs through the discovery questions, and generates the four foundation documents: research report, business plan, brand guide, and technical guide.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;prompt-2--brand-voice&quot;&gt;Prompt 2 — Brand Voice&lt;/h3&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Based on everything we&apos;ve defined about Liminal — the LATAM market, photographers
delivering work to clients, the quiet confidence of the name itself — write a brand
voice document.

The voice should feel like a photographer who has found their style and doesn&apos;t need
to announce it. Not austere, but economical. Someone who chooses words the way they
choose light — deliberately, with care for what gets left out as much as what stays in.

Professionalism here means craft, not corporate. The app handles something personal
— a photographer&apos;s finished work, a client&apos;s important memories. The voice should
honor that weight without becoming precious about it.

Influences: the way Magnum Photos writes about their work. The directness of a good
photo caption. Not the breathless enthusiasm of a SaaS landing page.

The document should include:
- Core personality traits (3–4, with explanation)
- Tone spectrum (when to be warmer vs. more spare)
- Vocabulary: words we use, words we avoid
- UI microcopy examples (button labels, empty states, error messages)
- Both Spanish and English examples side by side
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This second prompt goes straight to the character of the app. A brand voice guide is a document that rarely gets produced in an MVP phase — and it’s one of the most useful when the time comes to write microcopy or define how the app speaks to its users.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;prompt-3--ui-mocks&quot;&gt;Prompt 3 — UI Mocks&lt;/h3&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Using the frontend-design skill, create UI mocks for Liminal&apos;s critical screens.
Pull from the brand guide already established and the brand voice: quiet craft,
deliberate, editorial — not SaaS.

Prioritize these screens in order:

Client-facing (unauthenticated, via share link):
1. Gallery landing — the first thing a client sees when they open their link.
2. Collection view — browsing photos within a collection, with like, comment,
   and download interactions visible.
3. Download selection — choosing photos and quality before downloading as zip.

Photographer-facing (authenticated):
4. Project dashboard — list of projects with status at a glance.
5. Collection editor — uploading photos, reordering, setting cover photo.
6. Share link manager — creating and tracking links, seeing which have been used.

For each screen:
- Design for desktop first, note mobile considerations
- Show real placeholder content — no Lorem Ipsum
- Embed a short design rationale note explaining the key decision made for that screen

Aesthetic direction: editorial photography magazine meets quiet utility. The UI
should feel like it was designed by someone who photographs, not someone who ships
dashboards.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The third prompt uses the &lt;code class=&quot;highlighter-rouge&quot;&gt;frontend-design&lt;/code&gt; skill together with &lt;a href=&quot;https://maquina.app/documentation/components/&quot;&gt;Maquina Components&lt;/a&gt; to generate HTML mocks of the critical screens. The result isn’t a Figma file — it’s a functional visual reference, coherent with the brand guide, before opening the editor.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-result&quot;&gt;The Result&lt;/h2&gt;

&lt;p&gt;Three prompts. Six documents. Mocks of the main screens. All the context needed to hand off to Claude Code and start generating code with direction.&lt;/p&gt;

&lt;p&gt;It’s the same process I used to build &lt;a href=&quot;https://turesto.app/en&quot;&gt;Resto&lt;/a&gt;, a personal finance app based on the Japanese Kakeibo method.&lt;/p&gt;

&lt;p&gt;The video runs 40 minutes. It’s not an accelerated demo — it’s the real process, iterations and corrections included.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;

&lt;p&gt;All plugins are available in the &lt;a href=&quot;https://github.com/maquina-app/rails-claude-code&quot;&gt;maquina-app/rails-claude-code&lt;/a&gt; repository. Full documentation at &lt;a href=&quot;/documentation/ai-tools/mvp-creator/&quot;&gt;MVP Creator — Documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To install MVP Creator in Claude Code:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Add the marketplace&lt;/span&gt;
/plugin marketplace add maquina-app/rails-claude-code

&lt;span class=&quot;c&quot;&gt;# Install the plugin&lt;/span&gt;
/plugin &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;mvp-creator@maquina
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To install the full set of plugins used in this series:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/plugin &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;rails-simplifier@maquina
/plugin &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;rails-upgrade-assistant@maquina
/plugin &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;maquina-ui-standards@maquina
/plugin &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;mvp-creator@maquina
/plugin &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;better-stimulus@maquina
/plugin &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;spec-driven-development@maquina
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For the Claude graphical interface, download the repository as a zip, extract the &lt;code class=&quot;highlighter-rouge&quot;&gt;mvp-creator&lt;/code&gt; folder, rename the extension to &lt;code class=&quot;highlighter-rouge&quot;&gt;.skill&lt;/code&gt;, and drag it into the Claude window to install it.&lt;/p&gt;</content><author><name>Mario Alberto Chávez Cárdenas</name></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://maquina.app/images/og-blog-mvp-creator.jpg" /><media:content medium="image" url="https://maquina.app/images/og-blog-mvp-creator.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>