fx-ruby

August 21, 2026 · View on GitHub

fx-ruby

A tiny, embeddable coding agent for the terminal. Ruby port of fx.

License Stars Ruby Dependencies

fx-ruby reading a project, asking before it runs the tests, and answering

Real CLI, real tools, real permission gate. The model's replies are scripted against a local stub so the recording stays reproducible.

Status: experimental. Built to understand fx by rebuilding it. If you want a coding agent for daily work, use the original.

What is this?

fx-ruby is a coding agent you run in a terminal: it reads your files, edits them, runs commands, and asks before doing anything it cannot undo. It is a port of fx, the Zig agent from Vercel Labs, carrying over the system prompt, tool contracts, permission model, and session format.

It runs on the Ruby standard library alone. No gems to install, nothing to compile, one executable.

Quick Start

git clone https://github.com/jagenaujagenau/fx-ruby.git
cd fx-ruby

./bin/fx setup            # store an AI Gateway API key
./bin/fx                  # start the interactive shell

Requires Ruby 4.0 or newer. There is nothing to install.

fx                          # interactive shell in the current directory
fx ask "explain the build"  # one request, then exit
fx ask "..." --json         # machine-readable result on stdout
fx sessions                 # saved sessions for this workspace
fx session resume last      # continue where you left off
fx status                   # configuration and credential state
fx doctor                   # check that fx can run here

Credentials come from FX_API_KEY (or AI_GATEWAY_API_KEY) if set, otherwise from ~/.fx/auth.json. Inside the shell, /help lists the interactive commands: /model, /models, /mode, /permissions, /status, /sessions, /clear, /exit.

How it works

An agent turn is a loop: send the conversation, run whatever tools the model asked for, append the results, repeat until it answers. Everything interesting is in what sits between the model and your disk.

graph LR
    CLI[bin/fx] --> App[App]
    App --> Runtime[Agent::Runtime]
    Runtime --> Gateway[Gateway::Client]
    Runtime --> Registry[Tools::Registry]
    Registry --> Gate{Permissions::Engine}
    Gate -->|granted| Tool[15 built-in tools]
    Gate -->|denied| Runtime
    Tool --> Workspace[(Workspace)]
    Runtime --> Session[(Session log)]

The permission engine runs at execution time, not advertisement time: the model always sees every tool, and the gate decides what actually happens.

Permissions

fx starts in auto mode. Recognized development actions run directly; anything else asks first.

ModeBehavior
autoKnown-reversible actions run; sensitive ones prompt
askEvery change prompts
yoloNothing prompts. Permissions and sandboxing disabled

What counts as "known reversible" is a strict allow-list ported from fx's command_effect.zig: git status, npm test, zig build, and similar. Pipes, redirection, shell expansion, global installs, and anything unrecognized go to the approval path. A gap in an allow-list costs an extra prompt; a gap in a deny-list costs you your files.

Approvals can be granted once, remembered as a rule, or refused permanently. Stored rules live in ~/.fx/permissions.json with stable ids, so /permissions revoke <id> works even after files move. Reaching outside the workspace always requires approval, including for reads.

Tools

AreaTools
Inspectionread_file list_files glob_files grep_files semantic_search file_info
Mutationwrite_file edit_file create_folder rename_file copy_file delete_file
Executionterminal (exec plus durable background sessions)
Othermemory web_fetch

Every result is bounded and says when it was cut. read_file returns line-numbered output with truncation sentinels, edit_file refuses ambiguous matches instead of guessing, delete_file refuses non-empty directories, and web_fetch rejects private and loopback addresses on every redirect hop.

Configuration

~/.fx/settings.json, resolved in layers. Defaults, then profile, then a per-workspace block, with environment variables winning over all of them.

{
  "model": "anthropic/claude-sonnet-4.5",
  "permission_mode": "auto",
  "statusLine": { "workspace": true },
  "workspaces": {
    "/path/to/project": { "model": "anthropic/claude-opus-4.5" }
  }
}

FX_MODEL, FX_PERMISSION_MODE, FX_MAX_STEPS, and FX_HOME override at runtime.

Sessions

Each session is an append-only JSONL file under ~/.fx/sessions. A crash or an interrupt leaves everything up to that moment readable, and resuming replays the file. A torn final line from an interrupted write is skipped rather than failing the resume.

Embedding

Fx::App is the only place collaborators are constructed, which is the seam for embedding. Supply your own transport, approval handling, or renderer:

app = Fx::App.new(
  workspace_root: "/path/to/project",
  client: MyTransport.new,      # anything answering to #stream
  prompter: MyApprovalUI.new,   # anything answering to #ask
  renderer: Fx::UI::Renderer.new(out: io)
)

turn = app.runtime.run("summarize the recent changes")
puts turn.text

Fx::Agent::Runtime accepts any observer implementing the Fx::Agent::NullObserver interface, which is how the shell renders progress and how tests assert on it.

Project Structure

fx-ruby/
├── assets/
│   └── fx-demo.gif        The recording above
├── benchmarks/
│   ├── README.md          Methodology and results
│   ├── cli.sh             Zig vs Ruby CLI latency (hyperfine)
│   └── micro.rb           In-process timings
├── bin/
│   └── fx                 Executable
├── lib/
│   └── fx/
│       ├── agent/         Runtime loop, system prompt, turn context
│       ├── core/          Workspace, settings, auth, command classifier
│       ├── gateway/       AI Gateway client and stream assembly
│       ├── permissions/   Modes, rules, approval prompts
│       ├── session/       Append-only JSONL persistence
│       ├── tools/         The 15 built-in tools
│       └── ui/            Renderer, shell, slash commands
├── test/                  154 tests, minitest
├── LICENSE
├── README.md
├── Rakefile
└── fx.gemspec

Development

brew install ruby   # 4.0.6; the version is pinned in .ruby-version
rake test

154 tests covering the tool contracts, permission decisions, agent loop, streaming assembly, session persistence, and CLI behavior. They run against throwaway workspaces and a throwaway FX_HOME, so they never touch real credentials or sessions. The suite runs clean under ruby -w.

Values that flow through the agent loop, including completions, tool calls, stream events, resolved paths, and tool results, are immutable Data types. The two genuinely mutable types stay Struct: the tool context, whose session is attached after construction, and a terminal session, which is a live process handle.

Performance

Benchmarked against the Zig implementation with hyperfine and profiled in process. See benchmarks/README.md for the full tables and methodology.

OperationBeforeAfterGain
Build system message24.1 ms35.6 µs677×
Tokenize a command115.6 µs12.9 µs9.0×
fx help (end to end)125.0 ms57.9 ms2.2×
grep, 300 files62.2 ms33.0 ms1.9×

The system message was spawning three git subprocesses per model request. Startup was loading net/http for commands that never open a socket. Neither was a Ruby problem.

Against a 50.9 ms bare-interpreter floor, fx help now spends about 7 ms on its own work. That floor is the wall Ruby cannot get past, and on I/O-bound commands like fx status the gap to the Zig build closes to 1.21×.

Documentation

ResourceDescription
benchmarks/README.mdBenchmark methodology, full results, and what each optimization was
LICENSEApache-2.0
fx.shUpstream fx, its docs, and the real thing

Scope

Ported: the agent runtime, tool suite, permission engine, command classifier, session store, layered configuration, gateway client, interactive shell, and CLI.

Not ported: the WebAssembly and N-API embedding targets, the ACP server, MCP client support, subagents, skills, hooks, and the terminal monitor's condition system. Those are additive surfaces around the core rather than parts of it.

Contributing

Issues and pull requests are welcome. Run rake test before opening one; the suite is fast and covers the parts that matter.

License

Apache-2.0, matching upstream fx.

fx is a project of Vercel Labs. This is an independent port, built to learn from it.


Star History Chart