
Install Nexo, configure the harness in one place, and build your first agent. Defaults are safe and provider-neutral — there is intentionally no hardcoded model.

---

## Installation

Add to your Gemfile:

```ruby
gem "nexo_ai"
```

Or install directly:

```sh
gem install nexo_ai
```

In a Rails app, run the install generator to create the conventional layout and an initializer:

```sh
rails g nexo:install
```

```
      create  app/agents/.keep
      create  app/workflows/.keep
      create  app/skills/.keep
      create  config/initializers/nexo.rb
```

`require "nexo"` works in plain Ruby with no Rails loaded.

---

## Configuration

Configure the harness in one place with `Nexo.configure`:

```ruby
Nexo.configure do |config|
  config.default_model       = ENV["NEXO_MODEL"] # provider-neutral: no default
  config.default_sandbox     = :virtual          # :virtual | :local | :docker | :apple | a Hash | a Sandbox
  config.default_permissions = :read_only        # :read_only | :auto | :ask | :approve
  config.skills_path         = "app/skills"
  config.concurrency         = :threaded         # :threaded | :async (opt-in fiber offload)
  config.max_in_flight       = 8                 # Nexo.concurrent fan-out bound
  config.buffer_workflow_events = false          # buffer + flush-once workflow events
end

Nexo.config.default_sandbox      # => :virtual
Nexo.config.default_permissions  # => :read_only
Nexo.config.default_model        # => nil unless set
```

There is deliberately no hardcoded model — you set `NEXO_MODEL` (or `default_model`) to any `ruby_llm`-supported model id.

---

## Build an agent in five lines

Subclass `Nexo::Agent`, declare the pieces with class macros, and call `#prompt`. No sandbox, permission, or tool object is wired by hand, and nothing is vendor-specific — the agent runs on any `ruby_llm`-supported model (set `NEXO_MODEL`, e.g. a local `gemma3:12b` via Ollama, or a hosted model):

```ruby
require "nexo"

class CodeReviewer < Nexo::Agent
  model       ENV.fetch("NEXO_MODEL")   # any ruby_llm model — never a hardcoded vendor default
  sandbox     :local
  permissions :read_only

  instructions "You are a careful code reviewer. Read files and report issues. Do not write files."
end

CodeReviewer.new(cwd: "/path/to/repo").prompt("Review the auth module")
```

Defaults are safe: an agent with no `sandbox`/`permissions` declared gets the in-memory `:virtual` sandbox and `:read_only` permissions, so an untrusted model has zero host access until you explicitly opt in.

> **Safe by default:** agents start `:virtual` + `:read_only` — an untrusted model has zero host access until you explicitly opt in.

---

## Unregistered models — local tags, self-hosted, brand-new releases

`ruby_llm` normally validates a model id against its bundled `models.json` registry and infers the provider from it. A local Ollama tag (`gemma3:12b`), a self-hosted build, or a model newer than the installed registry isn't listed there — so declare the `provider` explicitly and set `assume_model_exists` to skip the registry lookup:

```ruby
class LocalReviewer < Nexo::Agent
  model               "gemma3:12b"
  provider            :ollama         # required once the registry lookup is skipped
  assume_model_exists true            # opt out of models.json validation

  instructions "You are a careful code reviewer."
end
```

Both are class macros with the same reader/writer convention as `model`. `provider` is passed straight through to `RubyLLM.chat`; `assume_model_exists` defaults to `false` (registry validation on). Setting `assume_model_exists` **without** a `provider` raises `Nexo::ConfigurationError` — `ruby_llm` can't infer a provider once the lookup is skipped.

---

## Where to next

- [Sandboxes](../sandboxes/) — the four execution environments and hardened defaults.
- [Permissions](../permissions/) — the capability gate and the four modes.
- [Examples](../examples/) — runnable scripts including a local-Ollama code reviewer.


---

## Next steps

<div class="not-prose mt-8 grid grid-cols-1 gap-4 sm:grid-cols-2">
  <a href="../sandboxes/" class="group relative rounded-2xl border border-zinc-200 p-6 hover:border-[#50B1FD]/50 dark:border-zinc-800 dark:hover:border-[#50B1FD]/50 transition">
    <h3 class="font-sora font-semibold text-zinc-900 dark:text-white group-hover:text-[#50B1FD] transition">
      Sandboxes
    </h3>
    <p class="mt-2 text-sm text-zinc-600 dark:text-zinc-400">
      Choose where an agent's tools act — Virtual, Local, Container, or Remote.
    </p>
  </a>

  <a href="../permissions/" class="group relative rounded-2xl border border-zinc-200 p-6 hover:border-[#50B1FD]/50 dark:border-zinc-800 dark:hover:border-[#50B1FD]/50 transition">
    <h3 class="font-sora font-semibold text-zinc-900 dark:text-white group-hover:text-[#50B1FD] transition">
      Permissions
    </h3>
    <p class="mt-2 text-sm text-zinc-600 dark:text-zinc-400">
      Control what those tools may do, read-only by default.
    </p>
  </a>
</div>
