aa_storage_redis/lib.rs
1//! Redis L2 shared-cache storage driver for Agent Assembly.
2//!
3//! This crate implements three of the high-frequency [`aa_storage`] traits
4//! against a Redis (or Valkey) instance so multiple Assembly processes can
5//! coordinate through one shared cache instead of each hitting the L3 store:
6//!
7//! - [`RedisSessionStore`] — [`SessionStore`](aa_storage::SessionStore)
8//! - [`RedisRateLimitCounter`] — [`RateLimitCounter`](aa_storage::RateLimitCounter)
9//! - [`RedisPolicyStore`] — [`PolicyStore`](aa_storage::PolicyStore), used as a
10//! read-through cache in front of the authoritative store
11//!
12//! Build a [`RedisBackend`] from a [`RedisStorageConfig`] and hand out the
13//! individual stores, or construct each store directly over a shared [`Pool`].
14//!
15//! # Key layout
16//!
17//! Every key is namespaced under `aa:`:
18//!
19//! | Store | Key | Value |
20//! |---|---|---|
21//! | session | `aa:session:<session_id>` | hash (`agent_id`, `started_at_ns`) |
22//! | rate limit | `aa:ratelimit:<key>` | integer counter |
23//! | policy | `aa:policy:<agent_id>` | JSON [`PolicyDocument`](aa_storage::PolicyDocument) |
24//!
25//! `<session_id>` and `<agent_id>` are the lower-case hex encodings of the
26//! 16-byte ids.
27//!
28//! # TTL and invalidation semantics
29//!
30//! - **Sessions** expire after [`SESSION_TTL_SECS`] seconds. The TTL is
31//! re-armed on every [`save`](aa_storage::SessionStore::save) so an actively
32//! written session never lapses; [`delete`](aa_storage::SessionStore::delete)
33//! drops it immediately and is idempotent.
34//! - **Rate-limit counters** carry the window length supplied to
35//! [`increment`](aa_storage::RateLimitCounter::increment) as their TTL. The
36//! expiry is armed exactly once — on the first increment that creates the key
37//! — so the window is **fixed**: it starts at the first hit and is not pushed
38//! forward by later increments within the same window.
39//! [`reset`](aa_storage::RateLimitCounter::reset) deletes the key.
40//! - **Policies** are cached with an explicit per-entry TTL via
41//! [`RedisPolicyStore::cache_policy`] ([`DEFAULT_POLICY_CACHE_TTL_SECS`] is the
42//! suggested default). [`invalidate`](aa_storage::PolicyStore::invalidate)
43//! deletes the cached key so the next read misses and reloads from the
44//! authoritative store; it is idempotent.
45
46#![warn(missing_docs)]
47
48mod backend;
49mod config;
50mod error;
51pub mod factory;
52mod policy;
53mod pool;
54mod rate_limit;
55mod registration;
56mod session;
57mod util;
58
59pub use backend::RedisBackend;
60pub use config::RedisStorageConfig;
61pub use policy::{RedisPolicyStore, DEFAULT_POLICY_CACHE_TTL_SECS};
62pub use pool::build_pool;
63pub use rate_limit::RedisRateLimitCounter;
64pub use registration::register;
65pub use session::{RedisSessionStore, SESSION_TTL_SECS};
66
67/// Pooled Redis connection handle, re-exported for callers that build stores
68/// directly with [`RedisSessionStore::new`] and friends.
69pub use deadpool_redis::Pool;
70
71/// The name this driver registers under in storage configuration, i.e. the
72/// `[storage.<name>]` subsection and the registry key (`storage.backend = "redis"`).
73pub const DRIVER_NAME: &str = "redis";
74
75#[cfg(test)]
76mod tests {
77 #[test]
78 fn driver_name_is_redis() {
79 assert_eq!(super::DRIVER_NAME, "redis");
80 }
81}