Documentation
¶
Overview ¶
Package persist wires AgentGuard's in-memory fast-path state to a durable store.Store using the write-behind dual-tier model (docs/v0.6-ARCHITECTURE- PLAN.md §2.3–2.4).
The Syncer is the ONLY component that bridges memory and disk. It:
- Hydrate(): on boot, loads persisted state back into the in-memory maps (rate-limit buckets, session-cost accumulators, approval queue) so a restart does not lose them.
- flush loop: on a ticker with a hard 1s floor, snapshots each in-memory structure and upserts it to the store. This runs on its own goroutine and NEVER touches the /v1/check request path, so the <3ms latency budget is unaffected (CLAUDE.md rule #1).
- purge loop: periodically GCs stale rows (resolved approvals, idle costs, fully-refilled buckets), mirroring the in-memory eviction.
- Close(): stops the loops and performs one final flush so the last second of state is durable across a graceful shutdown.
Index ¶
Constants ¶
const DefaultPurgeInterval = 1 * time.Minute
DefaultPurgeInterval is how often stale rows are GC'd. Much coarser than the flush — GC is housekeeping, not durability.
const MinFlushInterval = 1 * time.Second
MinFlushInterval is the hard floor on the flush cadence (plan §2.4): the syncer never writes more often than once per second, bounding write amplification / WAL churn.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
Store store.Store
Limiter *ratelimit.Limiter
Engine *policy.Engine
Approvals *proxy.ApprovalQueue
// FlushInterval is clamped up to MinFlushInterval. Zero => MinFlushInterval.
FlushInterval time.Duration
// PurgeInterval is how often GC runs. Zero => DefaultPurgeInterval.
PurgeInterval time.Duration
// Retention cutoffs for GC. Zero disables that purge (rows kept forever).
CostTTL time.Duration // idle session-cost rows
ApprovalTTL time.Duration // resolved approvals
BucketTTL time.Duration // fully-refilled buckets
}
Config wires the syncer to the store and the in-memory sources. Any source may be nil (that slice is simply skipped), which keeps the syncer usable in focused tests.
type Syncer ¶
type Syncer struct {
// contains filtered or unexported fields
}
Syncer drives the write-behind loops.
func (*Syncer) Close ¶
func (s *Syncer) Close()
Close stops the loops, waits for the goroutine to exit, then performs one final flush so the last interval of state is durable. Idempotent.
func (*Syncer) Flush ¶
Flush snapshots every in-memory source and upserts it to the store in one pass. Exposed (rather than purely internal) so tests and the shutdown path can force a synchronous flush. Safe to call concurrently with the flush loop only via Close (which stops the loop first).
func (*Syncer) Hydrate ¶
Hydrate loads persisted state into the in-memory structures. Call once, on boot, BEFORE the server starts serving traffic. A store read error is returned so the operator can decide whether to proceed with empty state.