AgentSkillsCN

Rig Agent Scaffold

Rig Agent 脚手架

SKILL.md
<!-- This file is generated by scripts/sync_agent_instruction_files.sh. --> <!-- Do not edit this file directly; update AGENTS.md and re-run the sync script. -->

name: rig-agent-scaffold description: > Scaffold a Rig agent implementation. Use when creating agents, chatbots, RAG pipelines, or tool-calling workflows with Rig's builder pattern. argument-hint: "[agent-description]" allowed-tools:

  • Read
  • Glob
  • Grep
  • Edit
  • Write
  • Bash

Rig Agent Scaffold

Create or refactor a Rig agent implementation.

Execution requirements:

  1. Preserve trait-based abstractions and builder-pattern ergonomics.
  2. Use WasmCompatSend / WasmCompatSync bounds where applicable.
  3. Handle all fallible paths without .unwrap() / .expect() unless impossible.
  4. Add tests or compileable examples for user-facing functionality.
  5. Run cargo fmt, cargo clippy --all-targets --all-features, and cargo test.

rig-core-patterns

Rig is built on several core principles that all contributions must respect:

Trait-Based Abstractions

Rig uses traits to define provider-agnostic interfaces:

  • CompletionModel - For text completion/chat models
  • EmbeddingModel - For embedding generation
  • VectorStoreIndex - For vector similarity search
  • Tool - For defining callable tools

New implementations should implement these traits rather than creating parallel abstractions.

Builder Pattern

Nearly every configurable type uses the builder pattern:

rust
let agent = client
    .agent(openai::GPT_5_2)
    .preamble("System prompt")
    .tool(my_tool)
    .temperature(0.8)
    .build();

Follow this pattern for any new configurable types.

Generic Client Architecture

The client system uses a generic architecture with provider extensions:

rust
pub struct Client<Ext = Nothing, H = reqwest::Client> {
    // ...
}

Where Ext is a provider extension defining capabilities and H is the HTTP backend.

Capability-Based Providers

Providers declare their capabilities explicitly:

rust
impl<H> Capabilities<H> for MyProviderExt {
    type Completion = Capable<CompletionModel<H>>;  // Supported
    type Embeddings = Nothing;                       // Not supported
    // ...
}

Use Capable<T> for supported features and Nothing for unsupported ones.

rig-wasm-compat

Rig supports WebAssembly targets. Since WASM is single-threaded, Send/Sync bounds are unnecessary and can prevent compilation.

The Pattern

rust
#[cfg(not(target_family = "wasm"))]
pub trait WasmCompatSend: Send {}  // native

#[cfg(target_family = "wasm")]
pub trait WasmCompatSend {}        // wasm

#[cfg(not(target_family = "wasm"))]
pub trait WasmCompatSync: Sync {}  // native

#[cfg(target_family = "wasm")]
pub trait WasmCompatSync {}        // wasm

Usage Rules

Always use WasmCompatSend and WasmCompatSync instead of raw Send and Sync in trait bounds.

rust
// Correct
pub trait MyTrait: WasmCompatSend + WasmCompatSync {
    fn do_thing(&self) -> impl Future<Output = ()> + WasmCompatSend;
}

// Incorrect - will break WASM builds
pub trait MyTrait: Send + Sync {
    fn do_thing(&self) -> impl Future<Output = ()> + Send;
}

Boxed Futures

Use WasmBoxedFuture for boxed futures:

rust
pub type WasmBoxedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;  // native
pub type WasmBoxedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;         // wasm

Conditional Error Types

Some error types need platform-specific bounds:

rust
#[derive(Debug, thiserror::Error)]
pub enum MyError {
    #[cfg(not(target_family = "wasm"))]
    #[error("Error: {0}")]
    Inner(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),

    #[cfg(target_family = "wasm")]
    #[error("Error: {0}")]
    Inner(#[from] Box<dyn std::error::Error + 'static>),
}

rig-prompt-hooks

Prompt hooks allow observing and controlling the agent's execution lifecycle.

The PromptHook Trait

PromptHook<M> provides callbacks for both streaming and non-streaming requests. All methods have default implementations — implement only what you need.

Hook Callbacks

  • on_completion_call - Before sending prompt to model (returns HookAction)
  • on_completion_response - After receiving response (returns HookAction)
  • on_tool_call - Before invoking a tool (returns ToolCallHookAction)
  • on_tool_result - After tool returns result (returns HookAction)
  • on_text_delta - During streaming, on each text chunk (returns HookAction)
  • on_tool_call_delta - During streaming, on each tool call chunk (returns HookAction)

Control Flow

  • ToolCallHookAction::Continue (or .cont()) - Allow tool execution
  • ToolCallHookAction::Skip { reason } (or .skip("reason")) - Reject tool; reason becomes tool result
  • ToolCallHookAction::Terminate { reason } (or .terminate("reason")) - Terminate the agentic loop early
  • HookAction::Continue (or .cont()) - Continue normal execution
  • HookAction::Terminate { reason } (or .terminate("reason")) - Terminate the agentic loop early

Design Rationale

  1. Default implementations - All methods have defaults; implement only what you need
  2. Per-request hooks - Attached via .with_hook(), not globally
  3. WASM compatible - Uses WasmCompatSend/WasmCompatSync bounds

rig-quality-gates

Trait Bounds

Use full where clause syntax for readability:

rust
// Correct
impl<T> CompletionModel for MyModel<T>
where
    T: HttpClientExt + Clone + WasmCompatSend + Debug + Default + 'static,
{
    // ...
}

// Avoid inline bounds for complex signatures
impl<T: HttpClientExt + Clone + WasmCompatSend + Debug + Default + 'static> CompletionModel for MyModel<T> {
    // ...
}

Error Handling

DO NOT use String as an error type. Define proper error enums:

rust
// Correct
#[derive(Debug, thiserror::Error)]
pub enum MyError {
    #[error("Failed to parse response: {0}")]
    ParseError(#[from] serde_json::Error),

    #[error("Provider returned error: {0}")]
    ProviderError(String),
}

// Absolutely forbidden
fn do_thing() -> Result<(), String>  // NO

DO NOT stub out error handling:

rust
// Forbidden
let result = fallible_operation().unwrap();  // NO
let result = fallible_operation().expect("this should work");  // NO unless truly impossible

// Correct
let result = fallible_operation()?;
let result = fallible_operation().map_err(MyError::from)?;

Comments

Comments explain WHY, not WHAT.

If you need to explain what code is doing, the code itself is unclear. Rename variables, extract functions, or restructure.

rust
// Bad - explains what
// Increment counter by one
counter += 1;

// Bad - explains what
// Check if user is admin
if user.role == Role::Admin {

// Good - explains why
// Rate limiting resets at midnight UTC, so we need the day boundary
let boundary = timestamp.truncate_to_day();

// Good - explains non-obvious business logic
// Providers may return tool calls split across multiple chunks,
// so we accumulate them by index until the stream ends

Documentation

  • Add doc comments (///) to all public items
  • Add module-level documentation (//!) to modules
  • Include usage examples in doc comments where helpful

TODO Items

TODO items are not allowed in submitted code.

If you need a TODO, the implementation is incomplete. Either:

  1. Complete the implementation
  2. Don't submit it yet
  3. Create a separate issue tracking the future work
rust
// Forbidden
fn process() {
    // TODO: handle edge case
}

// Forbidden
fn process() {
    unimplemented!("will add later")
}

Before Submitting

Required Checks

Run these commands and fix all issues before submitting:

bash
cargo fmt
cargo clippy --all-targets --all-features
cargo test

If you cannot run these commands (e.g., environment limitations), explicitly ask the user to run them before the PR is submitted.

Architectural Changes

If your change involves:

  • New traits or significant trait modifications
  • Changes to the client/provider architecture
  • New abstractions or patterns
  • Breaking changes to public APIs

Discuss with the user first. Do not implement major architectural changes without explicit approval. Open an issue for discussion if needed.

Self-Review Checklist

Before considering code complete:

  • All error cases handled properly (no .unwrap() or .expect() on fallible operations unless truly impossible)
  • No String error types
  • No TODO comments
  • No stubbed implementations
  • Comments explain "why", not "what"
  • Uses WasmCompatSend/WasmCompatSync instead of Send/Sync
  • Follows existing code patterns in the codebase
  • Includes tests or examples that compile
  • cargo fmt passes
  • cargo clippy --all-targets --all-features passes
  • cargo test passes

Commits

DO NOT MAKE COMMITS UNLESS THE USER HAS ASKED YOU TO DO SO. Users should be able to manually verify that what you have done has worked before proceeding with a commit, and may also want to write their own commit messages.

If the PR contains breaking changes, the PR message must list the public API items that have broken and the migration path for each.

What Will Be Rejected

PRs will be rejected if they contain:

  • Lazy workarounds - String error types, .unwrap() everywhere, incomplete handling
  • TODO items - Submit complete work or don't submit
  • Stubbed error handling - Every error path must be properly handled
  • Provider API divergence - Don't add fields/features that don't exist in real provider APIs
  • Missing WASM compatibility - Using Send/Sync instead of WasmCompat* variants
  • Unclear code requiring explanatory comments - Refactor instead
  • Architectural changes without discussion - Major changes need approval first

Remember: The quality bar exists because Rig is used in production by many projects. Every contribution must maintain that standard.