


Extend a Modular Rust AI Framework with New AI Providers and Vector Stores
Use when extending a Rust framework that works with many AI providers. The AI follows the existing traits and builders, keeps WebAssembly support, makes hooks act the same with and without streaming, and checks recorded test cassettes for leaked API keys.
To use this AI rule
## Stay Within the Requested Scope and Do Not Commit Unless Asked
- Read the existing implementation, manifests, tests, and public documentation before changing code.
- Keep every change within the user's requested scope.
- Prefer existing traits, builders, modules, and error types over parallel abstractions.
- Do not add TODOs, stubs, placeholder behavior, or speculative public APIs.
- Do not commit, stage, push, create branches, or open pull requests unless the user explicitly requests that action.
- Preserve unrelated user changes and work around a dirty tree safely.
- Avoid unrelated refactors even when a nearby design could be improved.
- Distinguish contributor policy from repository-specific engineering rules and follow both.
- Stop when required product intent or external authorization is missing.
## Keep the Crate Layout and Feature Flags Consistent Across the Workspace
- Keep the public facade crate, core crate, companion integrations, derive macros, examples, and tests in their established roles.
- Let the facade re-export core APIs and expose optional companion crates through feature flags.
- Check the workspace manifest and facade exports before changing public features, integrations, or module paths.
- Place provider and vector-store integrations in companion crates unless a strong architectural reason requires core placement.
- Keep workspace examples distinct from per-crate examples and integration tests.
- When adding a companion crate, update the root dependency, feature, facade re-export, examples, README, and crate documentation as applicable.
- Ensure feature combinations compile without exposing unavailable modules.
- Keep public documentation synchronized across the facade and affected companion crates.
- Do not describe a module path or feature until the code and manifests confirm it.
## Build on the Shared Traits Instead of Provider-Specific Versions
- Build integrations on the framework's central completion, embedding, vector-index, and callable-tool traits.
- Do not create provider-specific alternatives to an existing cross-provider trait.
- Follow the established builder pattern for configurable public types.
- Keep construction fluent, typed, and complete only after an explicit build step.
- Use a generic client architecture that separates provider extensions from the underlying HTTP transport.
- Declare provider capabilities explicitly so unsupported behavior fails at compile time.
- Represent absent capabilities with a deliberate marker type rather than runtime guessing.
- Preserve API-key type relationships across provider builders and public client aliases.
- Keep provider-specific request and response types behind conversion boundaries.
## Keep Shared Async Code Working on WebAssembly
- Preserve WebAssembly support in shared traits, futures, errors, and integrations.
- Use the framework's compatibility abstractions instead of raw `Send` and `Sync` bounds where targets differ.
- Use the designated portable boxed-future type for dynamically returned futures.
- Apply native thread-safety bounds only on targets that support them.
- Keep WebAssembly boxed errors free of unsupported native-only bounds.
- Test both native and WebAssembly-facing type signatures after changing shared traits.
- Avoid adding a dependency or API that silently excludes a supported target.
- Keep conditional compilation narrow and explain the target distinction.
- Ensure provider, vector-store, and hook APIs use the same portability contract.
## Use Explicit Error Types and Document Every New Public API
- Do not use `String` as the error type for a new fallible API.
- Define explicit error enums and preserve useful provider-response details through framework error helpers.
- Prefer `?` and meaningful conversions over manual loss of error context.
- Use `unwrap()` or `expect()` only when impossibility is obvious from the surrounding invariant.
- Add item documentation to every new public type, function, trait, and method.
- Add module documentation to every new public module.
- Keep examples aligned with real APIs, model constants, module paths, and feature flags.
- Mark examples as non-running when they require external credentials or services.
- Update README files and crate-level documentation whenever public behavior changes.
## Add a New AI Provider by Following the Closest Existing One
- Study the closest existing provider before implementing or modifying another one.
- Add provider extension and builder types, the provider implementation, and an explicit capability declaration.
- Support construction from environment values and direct values when the framework exposes both paths.
- Keep public client and builder aliases consistent with the provider builder's API-key type.
- Use explicit credential marker or authentication types and redact credential-bearing debug output.
- Convert framework requests into the provider's real wire format and convert responses back into framework types.
- Add streaming only when the provider actually supports it, and preserve provider errors through the standard helpers.
- Follow established telemetry conventions and extension traits.
- Do not invent request or response fields that are absent from the provider's real API.
- Add focused tests or examples that prove the supported behavior.
## Add a Vector Store Integration with Real Filters and Clear Search Results
- Place a vector-store integration in a companion crate unless core ownership is justified.
- Implement both result-returning search and identifier-only search.
- Use a filter type that reflects the backend's real filtering capabilities.
- Return the framework's vector-store error variants instead of ad hoc string failures.
- Apply the same portable async bounds used by the core framework.
- Keep score, document, identifier, and metadata conversions explicit.
- Define ordering, limit, empty-result, and backend-error behavior.
- Test filters, ranking order, result limits, missing records, and conversion failures.
- Document required features, credentials, indexes, and external setup without embedding secrets.
## Give Each Hook Event Its Own Allowed Actions
- Model hooks as run-scoped lifecycle observers and steerers.
- Pass a run context to every event, including run identity, turn, streaming mode, agent identity, and shared scratch state.
- Give each event its own action type so unsupported action combinations fail at compile time.
- Let completion-call hooks patch a request or stop processing.
- Let tool-call and tool-result hooks rewrite values for later hooks, with skip or stop as terminal actions where defined.
- Represent invalid tool-call handling with explicit fail, retry, repair, skip, and stop choices.
- Keep observe-only events limited to continue or stop.
- Register observation hooks before steering hooks when stop actions short-circuit the stack.
- Document which actions transform data, which accumulate, and which terminate processing.
## Combine Stacked Hooks the Same Way With and Without Streaming
- Preserve event-specific composition semantics when hooks are nested or stacked.
- Merge completion request patches in registration order.
- Append extra context, shallow-merge additional parameters, and intersect active-tool sets.
- Apply last-writer-wins only to documented scalar and history fields, and warn when overwriting matters.
- Keep each request patch scoped to one turn; never make it sticky accidentally.
- Thread tool-call and tool-result rewrites into every later hook.
- Stop evaluating the stack immediately when a terminal action requires it.
- Make streaming and non-streaming surfaces share the same lifecycle driver.
- Test identical hook behavior through both streaming and non-streaming entry points.
- Add composition tests for nested stacks, merge conflicts, rewrite chains, skip, and stop.
## Pick the Smallest Regression Test, Using Recorded Cassettes for Provider Calls
- Add regression coverage at the smallest reliable scope.
- Prefer a unit test beside the implementation for isolated conversion or logic defects.
- Use cassette-backed provider tests when the behavior depends on provider request and response traffic.
- Use ignored live tests only when deterministic cassette replay cannot represent the behavior.
- Read the repository's test guide before adding, updating, recording, or replaying provider tests.
- Replay cassettes by default so normal tests require no provider credentials.
- Keep test execution targeted to the provider and behavior being changed.
- Cover streaming and non-streaming behavior when both surfaces exist.
- Preserve the original failure mode in the regression assertion.
## Record Test Cassettes Only for the Target Test and Check Them for Secrets
- Treat record mode as an external, credentialed, file-overwriting operation.
- Require the relevant provider credential before recording and never expose it in output.
- Record only the targeted provider test to minimize unrelated fixture churn.
- Inspect every cassette diff manually before presenting or committing it.
- Reject fixtures containing API keys, bearer credentials, cookies, account identifiers, or other sensitive values.
- Remove unrelated request and response churn rather than accepting a noisy fixture.
- Keep automated scrub and safety checks enabled, but do not treat them as a substitute for inspection.
- Preserve deterministic replay data while redacting secrets and unstable metadata.
- Never overwrite fixtures merely to make a failing test green without explaining the behavioral change.
## Run Checks from Narrow to Broad and Report What Was Not Verified
- Run the smallest useful checks first, then broaden according to affected scope and risk.
- Format the workspace and run lints across all affected targets and features.
- Run focused tests before broad workspace tests.
- Build documentation without dependencies when public APIs or docs change.
- Verify native and WebAssembly compatibility when shared bounds or futures change.
- Confirm provider wire fields match the real API and cassette diffs contain no secrets.
- Confirm hook semantics match on streaming and non-streaming surfaces.
- Do not claim completion while a required check is failing.
- If a command cannot run, explain why and state exactly what remains unverified.
- Report changed surfaces, checks run, observed results, residual risks, and intentionally excluded scope.
