Usage
Lifecycle
use guard_agent_rs::{AgentConfig, GuardAgent};
let config = AgentConfig::new("your-api-key-at-least-10-chars");
let agent = GuardAgent::new(config)?; // validates config, resolves install id
agent.start().await; // starts flush, status, and rules loops
agent.send_event(event).await; // enqueue a security event (never fails)
agent.send_metric(metric).await; // enqueue a security metric (never fails)
agent.try_send_event(event).await?; // error-surfacing variant
agent.flush_buffer().await; // force a flush cycle
agent.get_status().await; // current status snapshot
agent.health_check().await; // true when the ingestion API is reachable
agent.get_stats().await; // buffer occupancy, drops, retries
agent.stop().await; // final flush, stop loops
GuardAgent::new rejects an empty or too-short APIKey (minimum 10
characters) with a config error. start is loop-spawn guarded; stop
performs one final forced flush and confirms persisted records for
everything it successfully sends.
Events and metrics
SecurityEvent and SecurityMetric mirror the ingestion API's
BatchTelemetryRequest payload. Timestamps serialize as RFC 3339;
idempotency_key (generated per event by SecurityEvent::new) deduplicates
retries server-side. SecurityEvent carries event_type, ip_address,
endpoint, method, action_taken, reason, status_code,
decorator_type, rule_type, pattern_matched, and a free-form
metadata JSON value; SecurityMetric carries a MetricType
(request_count, response_time, error_rate, bandwidth_usage,
threat_level, block_rate, cache_hit_rate), a value, an optional
endpoint scope, and string tags. Field-by-field descriptions live on the
struct definitions in src/models.rs.
Buffering and overflow
Each kind (events, metrics) has its own buffer (buffer_size, default 100).
When a buffer fills, buffer_overflow_policy decides:
| Policy | Behavior |
|---|---|
Drop (default) |
evicts the oldest item of the same kind, deletes its persisted record, counts a drop |
Block |
waits for a flush to free a slot; durability over the new writer |
Raise |
try_send_* returns a GuardAgentError without buffering |
A combined occupancy at or above high_watermark_ratio (default 0.8)
triggers an early flush.
Delivery semantics
- Flushes run on
flush_interval(default 30s), on watermark, and on demand - A failed batch retries up to
retry_attemptswith exponential backoff (backoff_factor), honoringRetry-Afteron 429 - A 413 (the ingestion API rejects batches above 262144 bytes decompressed) splits the batch in half recursively or drops its oldest item rather than retrying a permanently oversized payload
- 400, 404, and 422 are permanent: the batch is dropped and confirmed, not retried
- 401, 403, other 4xx, 5xx, and network errors are retryable
- A 200 with
success: false(or a non-emptyerrorslist) requeues the failed items in the original order - The circuit breaker opens after 5 consecutive transport failures and half-opens to probe recovery after 60 seconds
- Per-kind failure streaks gate flushes for up to 300 seconds; success resets
Signing
When payload_signing_secret is set, every request carries
X-Payload-Signature: v1=<hex hmac-sha256>. The signature covers the
UNCOMPRESSED JSON body; the server verifies after decompression. Gzip
(compression_enabled) only affects the wire bytes.
Encryption
When project_encryption_key is set (a urlsafe-base64-encoded 256-bit key
issued by the core backend), event and metric batches are encrypted with
AES-256-GCM and POSTed to /api/v1/events/encrypted as
{encrypted_payload, batch_id, agent_version, guard_version,
guard_core_version}; the wire format is byte-compatible with the Python
agent (canonical JSON plaintext, 12-byte nonce prefix, 16-byte auth tag,
padded urlsafe base64). An invalid key fails GuardAgent::new; the agent
never falls back to plaintext. Signing and compression apply to the
envelope body exactly as they do to plaintext batches.
Persistence
With the persistence feature and a redis: Option<RedisConfig> set, every
buffered item is persisted before the send attempt and deleted only on
confirmation, so a crash between buffer and network loses nothing. Without
the feature, implement the RedisHandler trait and call
agent.attach_redis_handler(Arc::new(store)).await before start().
Persistence is fail-open: a store error never blocks buffering or sending.
See Configuration.
Dynamic rules
get_dynamic_rules() fetches the SaaS rule document from GET /api/v1/rules
(the full Python-agent DynamicRules surface: IP lists and ban duration,
countries, global and per-endpoint rate limits, cloud providers, user agents,
suspicious patterns, engine overrides, and emergency mode). The document is
cached for its own ttl (seconds); a failed poll returns None and keeps the
last good rules cached, mirroring the Python agent. Outgoing rules polls share
the batch-send retry machinery: the local rate limiter pre-check, the circuit
breaker, capped Retry-After handling on 429, and exponential backoff.
start also spawns a background rules loop on dynamic_rule_interval
(default 300s, minimum 60s). get_stats() reports the rules surface:
rules_fetched (lifetime refreshes), cached_rules (whether a document is
cached), rules_last_update (Unix seconds of the last refresh), and
loop_failures.rules (consecutive failed polls).
use guard_agent_rs::{AgentConfig, GuardAgent};
let config = AgentConfig::new("your-api-key-at-least-10-chars");
let agent = GuardAgent::new(config)?;
agent.start().await;
if let Some(rules) = agent.get_dynamic_rules().await {
println!("rules {} v{}, {} blacklisted IPs", rules.rule_id, rules.version, rules.ip_blacklist.len());
}
let stats = agent.get_stats().await;
println!("rules refreshed {} time(s)", stats.rules_fetched);