Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,21 @@

All notable changes to Rook, following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic Versioning](https://semver.org/).

## [0.6.0] - unreleased
## [0.6.1] - 2026-08-24

### Changed

- **"Backend" is now "provider", to match zot.** Rook and zot are configured the same way but spoke different dialects: rook called a named model-provider connection a *backend*, zot called it a *provider*, and where zot's connection names its implementation with `driver:`, rook's used `provider:`. They now agree on zot's vocabulary. The config keys `default_backend` → `default_provider` and `backends:` → `providers:`; the inner `provider:` field (the implementation) → `driver:`; the flag `--backend` → `--provider`; and the env var `ROOK_DEFAULT_BACKEND` → `ROOK_DEFAULT_PROVIDER`. This is a breaking config change - a config file or script using the old keys must be updated - but it makes moving between the two tools one vocabulary instead of two.

### Added

- **The engine tuning knobs, and per-model capability overrides, to match zot.** rook's `agent:` config exposed only `model` and `max_iterations`; the rest of the engine's controls were hardcoded or unreachable. It now carries the same knobs zot does - `max_settles`, `max_calls`, `max_time`, `max_tokens`, `max_tool_output`, `max_continuations`, `max_recoveries`, `max_cycles`, `max_empties`, `limit_checkpoints`, `context_strategy`, and the `compact_*` trio - each optional, zero using the engine default, and validated at load. A model entry also takes `context` (correct a too-large assumed context window for a small endpoint) and `vision` (say a model can be shown images, so the agent is offered the view tool - previously rook never enabled vision at all). rook cannot import zot's internal model catalogue, so there is no auto-detection; these are explicit operator overrides, which is the part that matters for custom endpoints.

### Fixed

- **A provider's API key never reaches a custom endpoint it was not written for, and never reaches the agent's shell.** A built-in provider's conventional key (e.g. `OPENAI_API_KEY`) was seeded as its credential purely from its name. Two consequences are now closed: (1) when a built-in provider is pointed at a custom `base_url`, its ambient key is no longer adopted as the connection's credential, so a key scoped to the provider's real host is never forwarded to a URL from the config; and (2) `ScrubProviderSecrets` now unsets every built-in provider's conventional variable by name - not only those whose value was resolved into config - so a provider key exported in the environment is stripped before the agent runs even when that provider has a `base_url` set. For an offensive-security tool whose agent runs commands against targets, both keep a provider credential out of reach of those commands.

## [0.6.0] - 2026-08-24

### Added

Expand Down
47 changes: 28 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,14 +190,14 @@ Or clone and build with the provided `Makefile`:
make build # → ./rook
```

## Backends
## Providers

A run targets a **backend** - the provider Rook talks to. Rook speaks to each one
directly over the OpenAI-compatible API; there is no gateway and no account in
between, so all you need is a provider key. Pick a backend with `--backend`, or
set `default_backend` in config.
A run targets a **provider** - the model provider Rook talks to. Rook speaks to
each one directly over the OpenAI-compatible API; there is no gateway and no
account in between, so all you need is a provider key. Pick a provider with
`--provider`, or set `default_provider` in config.

| Backend | Endpoint | Credential from |
| Provider | Endpoint | Credential from |
| ------------ | -------------------------------- | -------------------- |
| `zai` | `https://api.z.ai/api/paas/v4` | `ZAI_API_KEY` |
| `openai` | `https://api.openai.com/v1` | `OPENAI_API_KEY` |
Expand All @@ -215,7 +215,7 @@ set `default_backend` in config.

Rook defaults to **`zai`** running **`glm-5.2`** - a strong open model for
bug-hunting work: large context for reading codebases, and permissive for
offensive tasks. The model must be one the chosen backend serves.
offensive tasks. The model must be one the chosen provider serves.

The common case is one exported variable and nothing else:

Expand All @@ -231,26 +231,26 @@ Switch provider with a flag:

```bash
export OPENAI_API_KEY="sk-..."
rook --backend openai --model gpt-5 "…"
rook --provider openai --model gpt-5 "…"
```

For sensitive material that must not leave the machine, a local model is the
right choice - and the one backend that never sends data off-host:
right choice - and the one provider that never sends data off-host:

```bash
rook --backend ollama --model llama-4 "…"
rook --provider ollama --model llama-4 "…"
```

### Any other provider

Anything that speaks the OpenAI-compatible API works. Name a backend, give it a
Anything that speaks the OpenAI-compatible API works. Name a provider, give it a
base URL and a key:

```yaml
default_backend: mygateway
backends:
default_provider: mygateway
providers:
mygateway:
provider: custom
driver: custom
base_url: https://gateway.internal.example.com/v1
api_key: '$GATEWAY_KEY'
```
Expand All @@ -269,15 +269,24 @@ rook config path # print the config file location

The file lives at `~/.config/rook/config.yaml` (override with `$ROOK_CONFIG` or
`--config`). Every scalar has a matching `ROOK_*` env var (`agent.model` →
`ROOK_AGENT_MODEL`, `default_backend` → `ROOK_DEFAULT_BACKEND`). A backend's key
`ROOK_AGENT_MODEL`, `default_provider` → `ROOK_DEFAULT_PROVIDER`). A provider's key
comes from its provider's conventional variable or `api_key` in the file, which
may be a literal or a `$VAR` reference. A developer build also reads a `.env`
from the working directory - a released one does not (see
[Development](#development)). See
[configs/rook.example.yaml](configs/rook.example.yaml).

Rook strips the resolved backend credential from the environment before the
agent runs, so the commands it executes against a target cannot read it.
Under `agent:` the run's engine knobs mirror zot's - `max_settles`, `max_calls`,
`max_time`, `max_tokens`, `max_tool_output`, the recovery and cycle caps,
`limit_checkpoints`, `context_strategy` and the `compact_*` trio - each optional,
zero using the engine default. A model entry additionally takes `context` (a
smaller real context window than the model's card) and `vision` (this model can
be shown images). See [configs/rook.example.yaml](configs/rook.example.yaml).

Rook strips the resolved provider credential from the environment before the
agent runs, so the commands it executes against a target cannot read it. A
built-in provider's conventional key is withheld once you set a custom
`base_url`, so a key scoped to one host is never forwarded to another.

## Files & directories

Expand Down Expand Up @@ -307,7 +316,7 @@ recent active run.
## Usage

```bash
export ZAI_API_KEY="sk-..." # or --backend openai with OPENAI_API_KEY, etc.
export ZAI_API_KEY="sk-..." # or --provider openai with OPENAI_API_KEY, etc.

# Write an objective, then run it
rook new "Gain access to the target network and map paths to domain admin"
Expand All @@ -329,7 +338,7 @@ does not (see [Development](#development)).

| Flag | Default | Description |
| ------------------ | ---------------------------- | -------------------------------------------------- |
| `--backend` | `zai` | Backend to target: any provider, or one named in config |
| `--provider` | `zai` | Model provider to run against, or one named in config |
| `--config` | `~/.config/rook/config.yaml` | Path to the config file (or `$ROOK_CONFIG`) |
| `--model` | `glm-5.2` | Model the agent reasons with (overrides config) |
| `--max-iterations` | `10000` | Maximum agent iterations before a forced stop |
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.6.0
0.6.1
21 changes: 12 additions & 9 deletions cmd/rook/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
// # every run writes artifacts (status + events) and a ledger receipt
// rook .rook/objectives/hunt.yaml
//
// rook config # edit the config (backend, model, key)
// rook config # edit the config (provider, model, key)
// rook version
//
// Configuration is layered: built-in defaults < config file < ROOK_* env vars <
Expand Down Expand Up @@ -89,7 +89,7 @@ func run() error {

flags := pflag.NewFlagSet("rook", pflag.ContinueOnError)
configPath := flags.String("config", "", "path to the config file (default: $ROOK_CONFIG or ~/.config/rook/config.yaml)")
backend := flags.String("backend", "", "backend to target: a provider such as zai (default), openai, anthropic, groq, ollama, or a backend named in the config")
provider := flags.String("provider", "", "model provider to run against: zai (default), openai, anthropic, groq, ollama, or a provider named in the config")
model := flags.String("model", "", "model the agent reasons with (overrides config)")
dir := flags.String("dir", ".", "working directory the agent investigates: the objective runs against this tree")
maxIter := flags.Int("max-iterations", 0, "maximum agent iterations before forced stop (overrides config)")
Expand Down Expand Up @@ -149,8 +149,8 @@ func run() error {
}

// CLI flags win over file and env.
if *backend != "" {
cfg.DefaultBackend = *backend
if *provider != "" {
cfg.DefaultProvider = *provider
}
if *model != "" {
cfg.Agent.Model = *model
Expand Down Expand Up @@ -239,10 +239,10 @@ func run() error {
return fmt.Errorf("cannot enter --dir %q: %w", *dir, err)
}

// Strip backend credentials from the environment before the agent runs, so
// Strip provider credentials from the environment before the agent runs, so
// the commands it executes against a target cannot read them. The resolved
// key is still handed to the client below.
config.ScrubBackendSecrets(cfg)
config.ScrubProviderSecrets(cfg)

ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
Expand Down Expand Up @@ -356,12 +356,15 @@ func (r runRunner) execute(o objective.Objective, quitOnDone bool) error {
}

code, outcome, err := agent.Run(r.ctx, agent.Config{
Provider: r.selected.Provider,
Driver: r.selected.Driver,
APIKey: r.selected.APIKey,
BaseURL: r.selected.BaseURL,
Model: r.selected.Model,
Backend: r.cfg.DefaultBackend,
Provider: r.cfg.DefaultProvider,
MaxIterations: r.selected.MaxIterations,
ContextWindow: r.selected.ContextWindow,
Vision: r.selected.Vision,
Tuning: r.cfg.Agent,
Objective: o,
Verbose: r.verbose,
RunDir: r.runDir,
Expand Down Expand Up @@ -534,7 +537,7 @@ func printVersion() {

// editConfig ensures the config file exists - seeding it from the embedded
// template on first run - and opens it in the user's editor. This is the setup
// path: configure the backend, model and provider key by editing the file.
// path: configure the provider, model and API key by editing the file.
func editConfig() error {
path := config.DefaultConfigPath()

Expand Down
2 changes: 1 addition & 1 deletion cmd/rook/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@ func TestRunNoObjectivesErrors(t *testing.T) {
devNull.Close()
}()

// Provide a config so the backend resolves, then fail on objectives.
// Provide a config so the provider resolves, then fail on objectives.
// Actually, a bare `rook` with no config file and no API key will fail at
// cfg.Validate or cfg.Selected first. We want the "no objectives" error,
// so provide a valid config.
Expand Down
45 changes: 32 additions & 13 deletions configs/rook.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
# field's path upper-cased with dots as underscores. Env vars override the file.
# agent.model -> ROOK_AGENT_MODEL
# agent.max_iterations -> ROOK_AGENT_MAX_ITERATIONS
# default_backend -> ROOK_DEFAULT_BACKEND
# default_provider -> ROOK_DEFAULT_PROVIDER
#
# Credentials: put the key DIRECTLY in this file, or reference an environment
# variable with $VAR - either works. Each built-in backend also reads its
# variable with $VAR - either works. Each built-in provider also reads its
# provider's conventional variable, so exporting that is usually enough:
# openai -> OPENAI_API_KEY anthropic -> ANTHROPIC_API_KEY
# groq -> GROQ_API_KEY mistral -> MISTRAL_API_KEY
Expand All @@ -28,14 +28,31 @@ agent:
# codebases, and permissive for offensive tasks: glm-5.2 (default), kimi-k3,
# deepseek-v4-flash.
#
# Whatever you pick, make sure default_backend below actually serves it. A
# model and a backend that cannot talk to each other fail as a provider error
# Whatever you pick, make sure default_provider below names a driver that serves it. A
# model and provider that cannot talk to each other fail as a provider error
# rather than a configuration one, which is much harder to read.
model: 'glm-5.2'

# Safety cap on plan/act/observe cycles before the agent is forced to stop.
max_iterations: 1000000

# Engine tuning (all optional; unset uses the built-in default). These mirror
# zot's, so a run is shaped the same way in either tool.
# max_settles: 20 # nudges to record an outcome before "unsettled"
# max_calls: 0 # cap on total tool calls (0 = unbounded)
# max_time: '2h' # wall-clock cap ('30m', '2h', '90s'); empty = none
# max_tokens: 0 # cap on a single response's output (0 = unbounded)
# max_tool_output: 0 # bytes a tool result may return before truncation
# max_continuations: 0 # consecutive recovery attempts before giving up
# max_recoveries: 0 # total recovery attempts across the run
# max_cycles: 0 # nudges out of a detected repetition
# max_empties: 0 # consecutive empty turns before bailing
# limit_checkpoints: [50, 80, 90] # % of a bounded limit to warn at ([] = off)
# context_strategy: compact # 'compact' (summarise) or 'truncate' (drop oldest)
# compact_min_tokens: 0
# compact_min_messages: 0
# compact_trigger_ratio: 0.9 # fraction of the window at which compaction fires

# Run artifacts. Every run writes status.json (live state) and events.jsonl (an
# append-only log) into <run_dir>/<runid>/. This is telemetry - what the desktop
# status widget reads - and is separate from the workspace the agent works in.
Expand All @@ -50,15 +67,15 @@ agent:
# update_check:
# disabled: true

# Backends are the providers a run can target. Rook talks to each one directly
# Providers are the model-provider connections a run can target. Rook talks to each one directly
# over the OpenAI-compatible API - there is no gateway and no account in between.
# Pick one per run with --backend; otherwise default_backend is used.
# Pick one per run with --provider; otherwise default_provider is used.
#
# A backend named after a provider needs no further configuration: the name is
# the provider, and the credential comes from its conventional variable.
default_backend: zai
# A provider connection named after a driver needs no further configuration: the
# name is the driver, and the credential comes from its conventional variable.
default_provider: zai

backends:
providers:
# The default, paired with the default model above: glm-5.2 served by Z.AI.
# The common case is nothing here at all, and ZAI_API_KEY in the environment.
zai:
Expand All @@ -77,9 +94,9 @@ backends:
# base_url: 'http://localhost:11434/v1'

# Anything else that speaks the OpenAI-compatible API. A custom endpoint needs
# its own key and, unless it is a known provider, an explicit provider: custom.
# its own key and, unless it is a known provider, an explicit driver: custom.
# mygateway:
# provider: custom
# driver: custom
# base_url: 'https://gateway.internal.example.com/v1'
# api_key: '$GATEWAY_KEY'
#
Expand All @@ -89,6 +106,8 @@ backends:
# models:
# fast:
# model: 'gpt-5-mini'
# provider: 'openai'
# driver: 'openai'
# api_key: '$OPENAI_API_KEY'
# max_iterations: 200
# context: 128000 # real context window, if the endpoint's is smaller
# vision: true # this model can be shown images (offers the view tool)
4 changes: 2 additions & 2 deletions image/desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ substrate version for reproducible builds.
## The API key

Rook talks straight to a model provider, so a run needs that provider's key.
Rook defaults to the `zai` backend running `glm-5.2`, which reads `ZAI_API_KEY`.
Rook defaults to the `zai` provider running `glm-5.2`, which reads `ZAI_API_KEY`.
Setup is the built-in **`rook config`**: it seeds `~/.config/rook/config.yaml`
from the template on first run and opens it in `$EDITOR`, where you set the
backend, model and `api_key`. The file lives on the persistent `~/.config/rook`
provider, model and `api_key`. The file lives on the persistent `~/.config/rook`
volume and rook reads it directly. The panel shows `ROOK · RUN rook config`
until a key is present.

Expand Down
Loading