# How to Use FFF with AI Agents like Claude Code: Complete Integration Guide

> Learn how to control FFF Fast File Finder with AI agents like Claude Code using its Lua API via Neovim RPC. This guide shows headless file picking and remote command execution without plugin changes.

- Repository: [Dmitriy Kovalenko/fff](https://github.com/dmtrKovalenko/fff)
- Tags: how-to-guide
- Published: 2026-06-02

---

**You can control FFF (Fast File Finder) from AI agents like Claude Code by invoking its pure Lua API through Neovim's RPC interface, allowing headless file picking and remote command execution without modifying the plugin.**

FFF is a high-performance Neovim file picker developed in the `dmtrKovalenko/fff` repository that combines a Rust core for indexing and fuzzy matching with a thin Lua UI layer. Because the plugin exposes its functionality through pure Lua functions, AI agents can programmatically trigger file searches and consume results via standard Neovim remote APIs.

## Architecture Overview: Rust Core and Lua UI

FFF separates performance-critical operations from editor integration. The **Rust core** (located in `crates/fff-core/` and `crates/fff-nvim/`) handles indexing, frecency scoring, Git status caching, and fuzzy matching. This compiled layer exposes low-level functions like `search`, `index`, and `git_status` in [`crates/fff-nvim/src/lib.rs`](https://github.com/dmtrKovalenko/fff/blob/main/crates/fff-nvim/src/lib.rs).

The **Lua UI layer** consumes these capabilities through a tiny FFI bridge defined in [`lua/fff/rust/init.lua`](https://github.com/dmtrKovalenko/fff/blob/main/lua/fff/rust/init.lua). This bridge loads the compiled shared library (`fff_nvim.so`) using `ffi.load` and exposes its symbols to the Lua runtime. The public API surface lives entirely in [`lua/fff/main.lua`](https://github.com/dmtrKovalenko/fff/blob/main/lua/fff/main.lua), which exports stable functions including:

- `require'fff'.find_files()` - Opens the file picker
- `require'fff'.search()` - Generic search interface  
- `require'fff'.change_directory()` - Directory navigation

Because these entry points are pure Lua, any external process capable of executing Lua within Neovim can drive FFF programmatically.

## Integration Methods for AI Agents

AI agents like Claude Code can interact with FFF through multiple RPC-based approaches, depending on whether they need to spawn new Neovim instances or attach to existing editor sessions.

### Headless Neovim Execution

For one-shot file selection tasks, agents can spawn a headless Neovim process that executes FFF commands and returns results via stdout:

```bash
nvim --headless +"lua require'fff'.find_files({prompt='Pick a file: '})" +qa

```

This approach uses `--headless` to suppress the UI and `+qa` to quit after execution. The Rust core performs the fuzzy search, and the selected path prints to standard output for the agent to parse.

### Attaching to Running Sessions with neovim-remote

When integrating with an active editor instance, Claude Code can use `nvr` (neovim-remote) to send commands through the Unix socket defined in `NVIM_LISTEN_ADDRESS`:

```bash
nvr --remote-send ":lua require'fff'.search({prompt='Search symbols: '})<CR>"

```

This method preserves the user's existing editor state while allowing the AI to trigger picker windows. The agent can poll for results using `nvr --remote-expr` to capture the selection without blocking the main workflow.

### Custom Lua Scripts for Tool Integration

For complex orchestration, agents can load custom Lua scripts that wrap FFF calls with error handling and result formatting:

```lua
-- Save as fff_agent.lua
local fff = require'fff'

local function pick_file()
  local ok, result = pcall(fff.find_files, {prompt = 'Select file → '})
  if ok and result then
    print('USER_SELECTION:' .. result)
    return result
  else
    print('FFF_CANCELLED')
    return nil
  end
end

-- Expose via keymap or RPC
vim.api.nvim_set_keymap('n', '<Leader>fa',
  '<Cmd>lua pick_file()<CR>', { noremap = true, silent = true })

```

Claude Code can request the user press `<Leader>fa` or call `pick_file()` directly via RPC, parsing the `USER_SELECTION:` prefix to identify valid file paths.

### Python Client Integration via pynvim

External Python scripts using the `pynvim` library can attach to Neovim sockets and execute FFF functions synchronously:

```python
import neovim

def fff_pick(socket_path='/tmp/nvim'):
    nvim = neovim.attach('socket', path=socket_path)
    result = nvim.exec_lua('return require"fff".find_files({prompt="Pick: "})')
    return result

selected = fff_pick()
print(f'AI agent received: {selected}')

```

This pattern allows Claude Code to embed Python tool scripts that block until the user selects a file, then feed that path into subsequent generation steps.

## Key Source Files and API Contracts

Understanding the internal structure helps AI agents predict behavior when calling FFF:

| File | Role | Key Details |
|------|------|-------------|
| [`lua/fff/main.lua`](https://github.com/dmtrKovalenko/fff/blob/main/lua/fff/main.lua) | Public Lua API | Exports `find_files()`, `search()`, `change_directory()` with optional parameter tables |
| [`lua/fff/rust/init.lua`](https://github.com/dmtrKovalenko/fff/blob/main/lua/fff/rust/init.lua) | FFI loader | Calls `ffi.load('fff_nvim')` to bind the Rust shared library |
| [`crates/fff-nvim/src/lib.rs`](https://github.com/dmtrKovalenko/fff/blob/main/crates/fff-nvim/src/lib.rs) | Rust FFI definitions | Implements `search`, `index`, `git_status` functions compiled to `fff_nvim.so` |
| [`crates/fff-core/src/score.rs`](https://github.com/dmtrKovalenko/fff/blob/main/crates/fff-core/src/score.rs) | Fuzzy algorithm | Core matching logic using frizbee integration |
| [`crates/fff-core/src/git.rs`](https://github.com/dmtrKovalenko/fff/blob/main/crates/fff-core/src/git.rs) | Git integration | Status detection and caching for repository-aware file listing |
| [`lua/fff/picker_ui.lua`](https://github.com/dmtrKovalenko/fff/blob/main/lua/fff/picker_ui.lua) | UI rendering | Handles buffer management, keymaps, and layout calculations |
| [`plugin/fff.lua`](https://github.com/dmtrKovalenko/fff/blob/main/plugin/fff.lua) | Bootstrap | Vim-script entry point that initializes the plugin |

The **Lua API** in [`lua/fff/main.lua`](https://github.com/dmtrKovalenko/fff/blob/main/lua/fff/main.lua) represents the only stable integration surface. Agents should rely exclusively on these documented functions rather than internal Rust or UI implementation details.

## Implementation Workflow

To integrate FFF with Claude Code or similar agents:

1. **Install FFF** in Neovim using your plugin manager (Packer, lazy.nvim, etc.)
2. **Build the Rust core**: Run `make build` to produce `target/release/libfff_nvim.so`
3. **Expose the RPC socket**: Set `export NVIM_LISTEN_ADDRESS=/tmp/nvim` before starting Neovim
4. **Choose an integration method**: Use headless mode for scripts, `nvr` for active sessions, or Python clients for complex orchestration
5. **Parse return values**: Handle `nil` returns for cancellations and capture file paths from stdout or RPC responses

Because FFF uses Neovim's standard remote API rather than custom protocols, it works with any LLM-driven tool capable of shell execution or socket communication.

## Summary

- FFF exposes a **pure Lua API** through [`lua/fff/main.lua`](https://github.com/dmtrKovalenko/fff/blob/main/lua/fff/main.lua) that accepts optional configuration tables for prompts and working directories
- The **Rust core** handles performance-critical operations while the **FFI bridge** in [`lua/fff/rust/init.lua`](https://github.com/dmtrKovalenko/fff/blob/main/lua/fff/rust/init.lua) connects it to Neovim's Lua runtime
- AI agents can invoke FFF via **headless Neovim commands**, **neovim-remote (`nvr`)**, or **Python clients (`pynvim`)** using standard RPC mechanisms
- The plugin requires no modifications for AI integration; it responds to the same Lua functions whether called interactively or programmatically
- Stable integration depends on calling documented functions like `require'fff'.find_files()` rather than internal Rust implementation details

## Frequently Asked Questions

### Can Claude Code use FFF without installing additional plugins?

Yes. FFF works with Claude Code through standard Neovim RPC calls. As long as FFF is installed in the target Neovim instance, Claude Code can trigger it via headless mode or remote sockets without requiring additional wrapper plugins or Python dependencies.

### How does FFF handle Git repository information when called by an AI agent?

FFF caches Git status data in the Rust core ([`crates/fff-core/src/git.rs`](https://github.com/dmtrKovalenko/fff/blob/main/crates/fff-core/src/git.rs)) and updates it asynchronously. When an AI agent calls `find_files()` or `search()`, the Rust layer already has repository state indexed, so file listings include status symbols (modified, staged, untracked) without additional latency in the RPC response.

### What happens if the user cancels the FFF picker during an AI-driven workflow?

The Lua functions in [`lua/fff/main.lua`](https://github.com/dmtrKovalenko/fff/blob/main/lua/fff/main.lua) return `nil` when the picker is cancelled or an error occurs. AI agents should wrap calls in `pcall()` to catch errors and check for `nil` returns to distinguish between user cancellation and successful selection.

### Is it possible to customize the fuzzy matching behavior when using FFF programmatically?

The fuzzy matching algorithm lives in [`crates/fff-core/src/score.rs`](https://github.com/dmtrKovalenko/fff/blob/main/crates/fff-core/src/score.rs) and uses frecency scoring by default. While the Rust core's scoring parameters are fixed at compile time, the Lua API accepts options like `cwd` (current working directory) and `prompt` strings. For custom scoring, you would need to modify the Rust source and rebuild `fff_nvim.so`, as the FFI bridge binds directly to compiled symbols.