Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
ee06172
docs(audit): data-oriented rearchitecture design for rig-core/rig-agent
gold-silver-copper Jul 29, 2026
8d03f55
docs(audit): revision 2 — classic runtime is migrated, not deleted; a…
gold-silver-copper Jul 29, 2026
dd86d42
docs(audit): revision 2.1 — compiler-validated P1 details; status ban…
gold-silver-copper Jul 29, 2026
2e18685
refactor(core,agent)!: normalize completion and streaming payloads (P1)
gold-silver-copper Jul 29, 2026
5b7455a
feat(agent): pure protocol layer — AgentConfig, ToolCatalog, prepare_…
gold-silver-copper Jul 29, 2026
25e5084
feat(core): openai provider pilot — config, descriptor, pure function…
gold-silver-copper Jul 29, 2026
d2c59fd
feat(core): provider fleet configs + pure functions across all provid…
gold-silver-copper Jul 29, 2026
305b1db
feat(facade): data-oriented runtime — ProviderConfig, Runtime, AgentS…
gold-silver-copper Jul 29, 2026
291d52a
refactor(agent): keep the facade pure — move provider/session/stream/…
gold-silver-copper Jul 29, 2026
9194516
refactor(agent)!: de-genericize the classic runtime — Agent drives Pr…
gold-silver-copper Jul 29, 2026
cc01bb5
refactor(core): CompletionRequest::with_history/from_prompt construct…
gold-silver-copper Jul 29, 2026
63c11ef
refactor(core)!: ToolOutput/hook-vocabulary serde, ConversationMemory…
gold-silver-copper Jul 29, 2026
c8d8a26
refactor(core)!: delete CompletionRequestBuilder; migrate all consume…
gold-silver-copper Jul 29, 2026
a367055
refactor(core)!: data-oriented modalities and vector stores — embed/t…
gold-silver-copper Jul 29, 2026
39db3db
docs(audit): final scope decision — classic client/model traits kept …
gold-silver-copper Jul 29, 2026
a1114dd
fix(ci): fmt, doctests, all-features clippy — bedrock suites ride see…
gold-silver-copper Jul 29, 2026
d1e7e49
docs(audit): log rig-bevy cancellation
gold-silver-copper Jul 29, 2026
1ba0a38
fix(review): protocol resume/recovery, per-turn stream usage, bridge …
gold-silver-copper Jul 29, 2026
8e22843
docs(audit): single-architecture plan — catalog of remaining generics…
gold-silver-copper Jul 29, 2026
e6c811f
docs(audit): R3 keeps hook capability — concrete non-generic hook lay…
gold-silver-copper Jul 29, 2026
6e018a2
feat(agent): R1 gap closure — ToolExecutor, concrete Hooks, SessionAg…
gold-silver-copper Jul 29, 2026
43b703b
refactor(agent)!: collapse the classic tool system onto portable reco…
gold-silver-copper Jul 29, 2026
6041b16
docs(audit): mark R3 as not-started; R2 is the clean stopping point
gold-silver-copper Jul 29, 2026
9690594
refactor(agent)!: delete the hook trait machinery and invert memory t…
gold-silver-copper Jul 29, 2026
305302e
refactor(agent)!: delete the prompting surface — inherent Agent metho…
gold-silver-copper Jul 30, 2026
cc30427
refactor(agent)!: delete the classic driver — AgentSession/AgentStrea…
gold-silver-copper Jul 30, 2026
8afa8b7
refactor(core)!: invert the provider trait stack onto the functions m…
gold-silver-copper Jul 30, 2026
81dc21f
feat(core): functions::Config::from_env for every provider (single-ar…
gold-silver-copper Jul 30, 2026
29511e9
refactor(core)!: delete the classic client layer — functions::Config …
gold-silver-copper Jul 30, 2026
1eae2d5
docs(audit): log R7 — client-layer deletion, capability moves, test l…
gold-silver-copper Jul 30, 2026
faa47f4
refactor(core)!: delete the classic client layer and sweep every cons…
gold-silver-copper Jul 30, 2026
1dee5f2
docs(audit): R8 closing census — plan §3 targets met; five leaf refac…
gold-silver-copper Jul 30, 2026
7af6d49
fix(ci): wasm Send/Sync census gating, rustdoc link fallout, postgres…
gold-silver-copper Jul 30, 2026
7859670
refactor(core)!: de-erase the loader iterators (R9/A3)
gold-silver-copper Jul 30, 2026
c8e452c
refactor(stores)!: delete the SearchFilter trait for per-backend from…
gold-silver-copper Jul 30, 2026
79aef15
feat(anthropic): restore the prompt-caching knobs on functions::Confi…
gold-silver-copper Jul 30, 2026
edbf0aa
feat(openai): EmbeddingConfig::ndims, replacing seven hardcoded EMBED…
gold-silver-copper Jul 30, 2026
be3a91e
refactor(auth)!: DeviceCodePrompter enum replaces the dyn Fn seams (R…
gold-silver-copper Jul 30, 2026
30a9b90
docs(audit): refresh the R9 closing census against plan section 3
gold-silver-copper Jul 30, 2026
2be7664
ci: retarget the MCP wasm guard at rig-mcp (R9)
gold-silver-copper Jul 30, 2026
d29b63b
refactor!: rename the rig-mcp crate to rig-rmcp
gold-silver-copper Jul 30, 2026
5174158
docs: retarget the contributor docs at the data-oriented architecture
gold-silver-copper Jul 30, 2026
63a3d63
refactor(derive): delete the unreachable contextual-tool expansion
gold-silver-copper Jul 30, 2026
0849082
refactor(stores)!: concretize the portable filter and the erased wrap…
gold-silver-copper Jul 31, 2026
24166d6
refactor(core)!: delete RetryPolicy; SSE backoff is configuration
gold-silver-copper Jul 31, 2026
2379430
refactor(core)!: tool discovery is owned data, not a trait
gold-silver-copper Jul 31, 2026
7ca188d
docs(audit): R10 census — four mechanism generics removed, two deferred
gold-silver-copper Jul 31, 2026
12241d3
feat(core): restore CompletionRequestBuilder, without the model
gold-silver-copper Jul 31, 2026
50a73b8
feat(agent): provider configs convert into ProviderConfig
gold-silver-copper Jul 31, 2026
301febb
feat(agent): fluent extraction via agent.extractor(prompt)
gold-silver-copper Jul 31, 2026
8e6c05c
feat(core, agent): EmbeddingJob and ToolExecutorBuilder
gold-silver-copper Jul 31, 2026
21d9c8e
refactor(examples): move onto the restored fluent surfaces
gold-silver-copper Jul 31, 2026
f7cdbec
docs: record the restored builders and the preamble hazard
gold-silver-copper Jul 31, 2026
11f76d6
refactor(examples): companion examples use the fluent surfaces
gold-silver-copper Jul 31, 2026
887522d
test: move the safe completion-request call sites onto the builder
gold-silver-copper Jul 31, 2026
516b569
refactor(agent)!: stream_run returns an Unpin stream
gold-silver-copper Jul 31, 2026
e2e5deb
test(core): pin the two system-instruction forms as equivalent
gold-silver-copper Jul 31, 2026
ddff002
feat(agent): HookEntry::sync for hooks that do not await
gold-silver-copper Jul 31, 2026
7cc84e4
refactor: adopt the builder and the sync hook constructor across tests
gold-silver-copper Jul 31, 2026
f84f1f0
refactor(core)!: completion_span replaces CompletionSpanBuilder
gold-silver-copper Jul 31, 2026
521887d
refactor(core)!: delete CompletionRequest::preamble
gold-silver-copper Jul 31, 2026
4c9651f
fix(chatgpt): materialize default instructions before telemetry
gold-silver-copper Jul 31, 2026
9c77742
refactor(gemini)!: drop the input-side system_instruction channel
gold-silver-copper Jul 31, 2026
4d55cfb
test: pin system-instruction conversion for the five unproven providers
gold-silver-copper Jul 31, 2026
ec6df64
docs(audit): a reviewer's guide for an 806-file PR
gold-silver-copper Jul 31, 2026
673c8f7
docs: correct three claims the rearchitecture outran
gold-silver-copper Aug 1, 2026
4c2bc13
refactor: restore concrete fluent provider facade
gold-silver-copper Aug 1, 2026
eb543ea
chore: remove two zero-byte files committed by accident
gold-silver-copper Aug 1, 2026
6a4aa62
docs(audit): drop the in-repo design directory from the PR
gold-silver-copper Aug 1, 2026
101fb08
refactor(agent): relax hook invocation lifetimes
gold-silver-copper Aug 1, 2026
aac3573
feat(agent): expose concrete run stream
gold-silver-copper Aug 1, 2026
b3aba69
refactor: make portable callbacks data-driven
gold-silver-copper Aug 1, 2026
2fd9ea0
test(agent): execute wasm hook semantics
gold-silver-copper Aug 2, 2026
a7221c1
refactor(agent): default extractor runner to classic protocol
gold-silver-copper Aug 2, 2026
078f535
refactor(core, agent): simplify erased construction
gold-silver-copper Aug 2, 2026
267a07c
refactor(streaming)!: encapsulate live stream handles
gold-silver-copper Aug 2, 2026
69c6518
refactor(streaming): move owned completion state at EOF
gold-silver-copper Aug 2, 2026
9dd749e
fix: make streaming and tool execution transactional
gold-silver-copper Aug 2, 2026
16dbc6e
Unify provider configuration and capability boundaries
gold-silver-copper Aug 2, 2026
5b1981e
Separate driven stream events and share hook payloads
gold-silver-copper Aug 2, 2026
068f367
fix(agent): align turn and tool execution lifecycle
gold-silver-copper Aug 3, 2026
5404f13
Harden agent run lifecycle invariants
gold-silver-copper Aug 3, 2026
c66506a
Eliminate overlay clone and unify reasoning fallback
gold-silver-copper Aug 3, 2026
ed857ce
Document always-present mock provider boundary
gold-silver-copper Aug 3, 2026
8e88f48
Harden agent attempt lifecycle invariants
gold-silver-copper Aug 3, 2026
bca8b89
feat(core): restore custom HTTP transports
gold-silver-copper Aug 4, 2026
adcccb2
feat(agent): add external provider registry
gold-silver-copper Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
78 changes: 42 additions & 36 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,14 @@ jobs:
- name: Run cargo check wasm target
run: cargo check --package ${{ matrix.package }} --target wasm32-unknown-unknown

# `rmcp` is native-only: rmcp's `ClientHandler` requires `Send + Sync`
# unconditionally, which rig's wasm tool registry cannot satisfy. Asking for it
# on wasm must fail with exactly one actionable sentence rather than a wall of
# `dyn ErasedTool` trait errors, so assert both the message *and* the error
# count — a new ungated `#[cfg(feature = "rmcp")]` would leak follow-on errors
# and is the regression this guards.
check-rmcp-native-only:
name: stable / rmcp rejected on wasm
# Compile checks prove the retained hook records stay Send + Sync, but they
# cannot prove that a worker-local future resolves or that folds preserve
# ordering and short-circuiting after wasm-bindgen lowers the code. Execute
# the dedicated package under Node: it supplies the same JavaScript Promise
# and microtask boundary these tests need without paying for a headless
# browser when no tested behavior touches the DOM.
test-wasm-hooks:
name: stable / execute wasm hooks
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -126,35 +126,41 @@ jobs:
target: wasm32-unknown-unknown
toolchain: ${{ env.RUST_VERSION }}

- name: Assert the native-only diagnostic is the only error
- name: Install pinned wasm-pack
uses: taiki-e/install-action@v2
with:
tool: wasm-pack@0.12.1

# `pipefail` preserves wasm-pack's exit status through `tee`; the grep
# prevents a zero-tests-matched run from becoming a false green.
- name: Execute browser-wasm hook semantics under Node
shell: bash
run: |
set +e
out=$(cargo check --package rig-agent --features rmcp \
--target wasm32-unknown-unknown 2>&1)
status=$?
set -e
echo "$out"
# Parse a decolorized copy. The workflow sets CARGO_TERM_COLOR=always,
# so rustc prefixes every diagnostic with SGR escapes and an anchored
# `^error` matches nothing — the count came out 0 and the step failed
# claiming the gate had leaked. The log above keeps its colors.
plain=$(printf '%s\n' "$out" | perl -pe 's/\e\[[0-9;]*[a-zA-Z]//g')
if [ $status -eq 0 ]; then
echo "::error::expected the rmcp native-only compile_error, but the build succeeded — the gate is gone"
exit 1
fi
if ! printf '%s\n' "$plain" | grep -q 'the `rmcp` feature is native-only'; then
echo "::error::build failed for the wrong reason; the native-only compile_error did not fire"
exit 1
fi
# Exclude cargo's trailing "could not compile ..." summary, which is
# itself printed as an `error:` line.
count=$(printf '%s\n' "$plain" | grep -E '^error(\[|:)' | grep -cv 'could not compile' || true)
if [ "$count" -ne 1 ]; then
echo "::error::expected exactly 1 error, got $count — an ungated \`#[cfg(feature = \"rmcp\")]\` is leaking follow-on errors"
exit 1
fi
echo "ok: rmcp on wasm fails with exactly one actionable error"
set -o pipefail
output="$(mktemp)"
wasm-pack test --node examples/wasm_hooks 2>&1 | tee "$output"
grep -F "test result: ok. 3 passed; 0 failed" "$output"

# MCP remains native-only: its rmcp/tokio client and cancellation machinery
# are cfg'd off wasm, so `rig-rmcp` compiles to an empty library there. This is
# independent of Rig's stored tool records, which are Send + Sync on every
# target. Assert that the empty wasm shell stays clean — a stray ungated
# `use rmcp::…` is the regression this guards.
check-mcp-native-only:
name: stable / mcp empty on wasm
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install Rust stable
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: wasm32-unknown-unknown
toolchain: ${{ env.RUST_VERSION }}

- name: rig-rmcp compiles to an empty crate on wasm
run: cargo check --package rig-rmcp --target wasm32-unknown-unknown

clippy:
name: stable / clippy
Expand Down
144 changes: 108 additions & 36 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,29 @@ facade re-export, examples, README, and crate docs as applicable.

## Core Architecture

Rig is built around provider-agnostic traits:

- `CompletionModel` for text completion and chat models
- `EmbeddingModel` for embedding generation
- `VectorStoreIndex` for vector similarity search
- `Tool` for callable tools

Use these traits instead of creating parallel abstractions.
Rig is data-oriented: agents store serde provider configuration, not provider
implementations, and the runtime is not generic over a model type.

- Completion and streaming go through each provider's `functions` module — a
serde `Config` plus free `complete`/`open_stream` functions.
- `ProviderConfig` (`rig-agent`) is the one enum an `Agent` holds. It is
deliberately **not** `#[non_exhaustive]`: adding a bundled provider is a
breaking change by design, so hosts can match exhaustively. Out-of-tree
completion providers use its one `External` arm.
- `ProviderDescriptor` (`rig-core/src/providers/descriptor.rs`) is the
capability sheet for a provider.
- `ExternalCompletionProvider` is the typed out-of-tree authoring contract.
`ExternalCompletionProviderEntry::from_provider` erases it immediately into
a concrete record stored in a host-owned `Runtime` registry; no agent/runtime
type carries the provider or its associated config as a generic parameter.
- Embeddings, transcription, image generation, audio generation, and rerank
are per-provider free functions over plain configs.
- Vector stores expose concrete inherent methods over a pre-embedded
vocabulary. There is no shared store trait, and none should be added.
- `Tool` is the portable record contract (`rig_core::tool::PortableTool`).

Prefer concrete records and enums over new trait abstractions. Do not
reintroduce a generic model, client, or store parameter.

Configurable public types should follow Rig's builder style:

Expand All @@ -56,15 +71,25 @@ let agent = client
.build();
```

Provider clients use the generic client architecture:
A provider is a serde `Config` plus free functions, not a generic client:

```rust
pub struct Client<Ext = Nothing, H = reqwest::Client> {
// ...
}
// crates/rig-core/src/providers/<provider>/functions.rs
pub const DESCRIPTOR: ProviderDescriptor = ProviderDescriptor { /* … */ };

pub struct Config { /* model, api_key, base_url, knobs — all serde */ }

pub async fn complete(cfg: &Config, rt: &HttpRuntime, request: CompletionRequest)
-> Result<CompletionResponse, CompletionError>;
pub async fn open_stream(/* … */) -> Result<CompletionStream, CompletionError>;
```

Providers declare capabilities explicitly with `Capable<T>` and `Nothing`.
Bundled capabilities are declared as data on the `DESCRIPTOR` const, not as
trait `const`s or marker types. External capabilities are owned data on the
registered handler and never duplicated into serialized config. I/O goes
through `HttpRuntime`; keep request building and response parsing as pure
functions (`build_request_body`, `parse_response`) so they are testable without
a transport.

## WASM Compatibility

Expand Down Expand Up @@ -110,13 +135,14 @@ implementation. For OpenAI-compatible chat APIs, start with:

Provider implementations should include:

- Provider extension and builder types
- `Provider` implementation
- `Capabilities` declaration
- `ProviderBuilder` implementation
- `ProviderClient::{from_env, from_val}`
- public `Client` and `ClientBuilder` aliases; the `ClientBuilder` API-key generic must match `ProviderBuilder::ApiKey`
- explicit API-key marker/auth types with redacted debug behavior for credential-bearing values
- a `functions` module holding the provider's serde `Config`
- a `DESCRIPTOR: ProviderDescriptor` const declaring capabilities honestly —
fulfilment code reads it to fail fast, so a wrong flag is a runtime bug
- `Config::new`, `Config::from_env`, and `with_*` knob setters
- redacted `Debug` for credential-bearing values; never log an API key
- pure `build_request*` / `parse_*` functions, with `complete` and
`open_stream` composing them over `HttpRuntime`
- a `ProviderConfig` variant in `rig-agent` so the provider can drive an agent
- model constants where useful
- request conversion from Rig request types
- response conversion into Rig response types
Expand Down Expand Up @@ -150,34 +176,80 @@ Return `VectorStoreError` variants instead of ad hoc string errors.

Use `WasmCompatSend` and `WasmCompatSync` bounds.

## Agent Hook Changes
## Agent Hooks

Agent hooks are per-run lifecycle observers and steerers. `AgentHook` exposes
one method per lifecycle event, and every method receives the run-scoped
`HookContext` (run id, turn, streaming flag, agent name, shared `Scratchpad`).
Each method returns an event-specific action type, so unsupported combinations
are rejected by the compiler.
Agent hooks are attach-and-forget **records**, not trait impls. A hook is a
`HookEntry` (`rig::hooks`) wrapping a named async callback over owned
`HookEvent` values, returning a `HookDecision`. Immutable event payloads use
shared `Arc` handles so the owned event may cross await points without deep
copies per entry. `Hooks` is the ordered list; `AgentBuilder::add_hook` takes
one `HookEntry`.

```rust
use rig::hooks::{HookDecision, HookEntry, HookEvent};

fn logger() -> HookEntry {
HookEntry::sync("logger", |event| match event {
HookEvent::BeforeModelCall { turn, .. } => {
tracing::info!(turn, "model call");
HookDecision::Continue
}
_ => HookDecision::Continue,
})
}
```

Composition through `HookStack` remains event-dependent:
A callback answers with the `HookDecision` variant matching the event it
received; any other variant (including `Continue`) means "no opinion". The
decision vocabulary itself — `RequestPatch`, `CompletionCallAction`,
`ToolCallAction`, `ToolResultAction`, `InvalidToolCallAction`,
`ObservationAction`, `ModelTurnAction` — is unchanged and still lives at
`rig::agent::hook`. A wrong-lane non-`Continue` decision emits a content-free
warning containing only the hook name and event/decision kinds.

There is no `HookContext` and no `Scratchpad`. Run identity and shared state
are host-owned: capture them in the closure (see
`examples/request_hook`). Note the lifetime difference from the old
run-scoped scratchpad — closure state lives as long as the `HookEntry` and is
shared by every clone of it, so it spans **all** runs of an agent and
interleaves across concurrent ones. State that must be per-run has to be keyed
or reset explicitly. `turn` is a field on the events that carry it.

`HookEvent::TextDelta` and `HookEvent::ToolCallDelta` fire once per streamed
token and are opt-in: an entry receives them only if it was built with
`HookEntry::observing_deltas()`. Drivers check `Hooks::observes_deltas()` once
per run and skip building delta events entirely when no entry opted in — the
data form of the old `observes(StepEventKind)` interest hint.

Composition is event-dependent:

- **Completion calls accumulate and merge.** Every
`CompletionCallAction::Patch(RequestPatch)` is merged in registration order;
`Stop` short-circuits the stack.
the first `Stop` short-circuits, and later entries are not invoked.
- **Tool calls and results chain.** `ToolCallAction::Rewrite` and
`ToolResultAction::Rewrite` are threaded into later hooks. A tool-call `Skip`
or either event's `Stop` is terminal.
`ToolResultAction::Rewrite` are threaded into later entries. A tool-call
`Skip` or either event's `Stop` is terminal, preserving the rewrite
accumulated before it. Tool-call argument rewrites chain as
`serde_json::Value`, not JSON-encoded strings.
- **Invalid tool calls** return `InvalidToolCallAction` (`Fail`, `Retry`,
`Repair`, `Skip`, or `Stop`).
- **Observe-only events** return `ObservationAction` (`Continue` or `Stop`).
`Repair`, `Skip`, or `Stop`). The first `Some` resolution wins; `None`
everywhere preserves fail-fast behavior.
- **Model-turn and observe-only events.** The first non-`Continue` wins.
Observation events return `ObservationAction`.

Register observe-only entries before steering entries because stop actions
short-circuit. The folds live in `rig::agent::hook`
(`fold_completion_actions`, `fold_observation_actions`,
`fold_invalid_resolutions`, `ToolCallResolution`, `ToolResultResolution`) and
are shared by both drivers, so every driver composes decisions identically —
reuse them rather than reimplementing a fold.

Register observe-only hooks before steering hooks because stop actions
short-circuit. Nested `HookStack`s must preserve merge and chaining semantics.
`RequestPatch` remains per-turn and non-sticky; its documented merge rules are
append `extra_context`, shallow-merge `additional_params`, intersect
`active_tools`, and last-writer-wins scalars/history with a warning.

Every hook semantic must behave identically on streaming and non-streaming
surfaces (`AgentRunner::stream` and `AgentRunner::run` share `drive_agent`).
Every hook semantic must behave identically on the blocking and streaming
session drivers (`Agent::run` and `Agent::stream_run`).

## Style

Expand Down
Loading
Loading