Release Notes¶
v2.7.0 (2026-06-23)¶
Observable transport errors and documented Protocols (v2.7.0)¶
Added¶
AgentConfig.on_errorhook for observable delivery failures. New optionalon_error: Callable[[str, BaseException, dict[str, Any]], None].HTTPTransportfires it at the real failure points — serialization/encryption (stage="encryption"), unencrypted serialization and delivery (stage="transport_send"), on a permanent client error, and after retry exhaustion — so a host can observe that telemetry could not be shipped. The hook is best-effort and guaranteed never to propagate into the send path: a hook that raises is caught and logged, never destabilizing the application.
Documentation¶
- Documented the integrator-facing Protocols.
RedisHandlerProtocol,TransportProtocol,BufferProtocol, andAgentHandlerProtocolupgraded from thin one-line class docstrings to WHAT/WHEN/HOW class contracts plus a per-method docstring on every method, documenting the previously implicit semantics:send_*returnsboolmeaning accepted (caller requeues onFalse),None-on-miss for reads, the buffer's drain → confirm-on-success / requeue-on-failure at-least-once handshake, and TTL in seconds. Docstrings only — no signature, name, or@runtime_checkablechange.
Internal¶
- Refactored
_send_with_retry/_handle_responseinto smaller helpers (_evaluate_send_result,_sleep_or_record_giveup,_handle_200) to satisfy the complexity gate, and enabled branch coverage. Behavior-preserving.
v2.6.0 (2026-05-12)¶
Configurable rules and status loop intervals (v2.6.0)¶
- Added —
AgentConfig.dynamic_rule_interval: int(default 300, ge=60) — interval in seconds between dynamic rule polls. - Added —
AgentConfig.status_interval: int(default 300, ge=60) — interval in seconds between agent status reports. - Changed —
_rules_loopnow sleepsself.config.dynamic_rule_intervalinstead of a hardcoded300._status_loopnow sleepsself.config.status_intervalinstead of a hardcoded300. Both loops were previously ignoring any caller-configured value, soSecurityConfig.dynamic_rule_interval(and the newSecurityConfig.agent_status_intervalin guard-core >= 3.1.0) had no effect on the agent's poll cadence. The fields are now honored end-to-end. - Tests added in
tests/test_loop_intervals.pycovering field defaults, persistence, lower-bound rejection (ge=60), and end-to-end assertions that both loops invokeasyncio.sleepwith the configured value.
v2.5.0 (2026-05-06)¶
Install ID fingerprinting and optional HMAC payload signing (v2.5.0)¶
- Added — Persistent install ID. Each agent process now resolves a stable UUID per installation (default storage at
~/.guard-agent/install-id, override viaAgentConfig.install_id), sent on every request asX-Agent-Install-Id. The server uses this to detect when a single API key is being used from many distinct installs (a signal that the key has leaked or is being shared across hosts). Auto-creates the file on first call; OSError on read or write is logged vialogger.exceptionand falls through to a fresh UUID rather than failing the start-up. New module:guard_agent.install_idexposingresolve_install_id(*, state_path, override). - Added — Opt-in HMAC-SHA256 payload signing. When
AgentConfig.payload_signing_secretis set, every outbound request carriesX-Payload-Signature: v1=<hex>computed over the exact bytes that go on the wire — post-gzip and post-encryption — so the server can verify integrity againstrequest.body()without re-decoding. No header is sent when the secret is unset, preserving the existing default behavior. New module:guard_agent.signingexposingsign_payload(body, *, secret). - Added — Two new fields on
AgentConfig:install_id: str | None(override the auto-resolved install ID) andpayload_signing_secret: str | None(HMAC secret; both default toNone). - Changed — Transport sets the install-ID header once on the cached
httpx.AsyncClientdefault headers (applies to every request) and computes the signature per-request inside both encrypted and unencrypted send paths. - Tests added for both modules, full suite at 363 passed / 2 skipped.
v2.4.1 (2026-04-29)¶
Diagnostic-friendly transport error logging (v2.4.1)¶
- Fixed —
HTTPTransport._log_request_errornow formats the captured exception as<ClassName>: <repr>instead ofstr(exc). Several httpx exception classes raised on transport-level connection drops (RemoteProtocolError,WriteError, somehttpcorewrappers) carry no message body, sostr(exc)rendered empty and the previous error line wasHTTP client error for POST <url>:with no diagnostic suffix. Operators chasing a CloudFlare/origin RST storm could not tell which httpx class actually fired without attaching a debugger. The new format always shows the class identity even when the message is empty, e.g.HTTP client error for POST https://example/api/v1/events/encrypted: RemoteProtocolError: RemoteProtocolError(''). No behavior change beyond log accuracy. Coverage onguard_agent/transport.pymaintained at 100% line + 100% branch.
v2.4.0 (2026-04-29)¶
Per-event idempotency keys, configurable overflow policy, and framework-version reporting (v2.4.0)¶
Added¶
SecurityEvent.idempotency_key: UUID— every emitted event now carries a stable per-event identifier (defaultuuid4()viadefault_factory). Combined with the existing batch-stablebatch_id, this lets the SaaS dedup at the event level when an ACK is lost mid-batch and the batch is retried. The field is namedidempotency_key, notevent_id, to avoid collision with the SaaS API's existingevent_id(the prefixed external id, e.g.evt_abc123). Backward-compatible: callers that don't set the field automatically get a generated one.AgentConfig.guard_version: str | None— new optional config field set by the framework adapter (e.g. fastapi-guard middleware) at agent init time, identifying the wrapper package's version. DefaultNonefor callers that constructAgentConfigdirectly without going through a framework wrapper. Framework adapters should setconfig.guard_version = framework_package.__version__immediately before passing the config toGuardAgentHandler.EventBatch.guard_version: str | None— propagated through the wire payload on both the plaintext (/api/v1/events,/api/v1/metrics) and encrypted (/api/v1/events/encrypted) ingestion paths. Sourced fromAgentConfig.guard_version. The SaaS persists this on the project record so analytics can attribute telemetry to the wrapper version, not just the agent version. Without this field the SaaS could only seeagent_version(guard-agent's own version) and had no way to know which middleware version the customer was running.encryption._default_json_handlernow serializesUUIDvalues to their string form alongside the existingdatetime→isoformat()branch. Required for the encrypted-payload path to handle events carrying the newidempotency_key.AgentConfig.buffer_overflow_policy: Literal["drop", "block", "raise"] = "drop"— operators can now choose how the in-memory event/metric buffer behaves at capacity:drop(default) — silent eviction of the oldest entry; preserves prior behavior verbatim. Production-safe for high-throughput; loses events when the SaaS is unreachable.block— backpressures the caller until a flush frees space. Appropriate when event integrity is critical. Use only whenstart_auto_flushis wired or a flush callback is in place; otherwiseclear_bufferis the manual escape hatch.raise—BufferFullErrorpropagates to the caller. Appropriate for tests or strict environments where dropping events is unacceptable.BufferFullError(GuardAgentError)exception class added inguard_agent.exceptionsand re-exported from the top-levelguard_agentmodule.
Fixed¶
HTTPTransport._make_requestwas logging the wrong URL on POST failures. When encryption was enabled, the actual request hit/api/v1/events/encryptedbut the error log printed the unencrypted endpoint string (url = f"{endpoint}{plain_path}"). Operators chasing down 503s and decrypt errors sawPOST .../api/v1/eventsin their logs even though the wire request went to/api/v1/events/encrypted. Fix: compute the actual posted URL (encrypted vs plain) up-front and pass that to_log_request_error. No behavior change beyond log accuracy.
Compatibility¶
- Default behavior unchanged for callers that don't opt into either feature:
idempotency_keyhas adefault_factory, andbuffer_overflow_policydefaults to"drop"(which preserves prior eviction semantics including the silent-overflow counter and warning-every-100th log). - SaaS-side coordination: this release is paired with the SaaS dedup work that ships the
idempotency_keycolumn onsecurity_events, the unique constraint, and thepg_insert ... on_conflict_do_nothingingest path. SaaS deployments that don't yet recognize the field treat it as an unknown column and silently drop the bytes — no behavior change to those callers.
v2.3.0 (2026-04-26)¶
Production Safety (v2.3.0)¶
- Fork-safe
GuardAgentHandlersingleton. Class-level_instancesurvivedos.fork(), so Gunicorn pre-fork workers all inherited a stale_initialized=Trueflag and dead asyncio task handles from the parent loop. Callingstart()in the child was a silent no-op and the child never connected to the agent endpoint. Register anos.register_at_fork(after_in_child=...)hook that resets_initializedand clears inherited task references. Add a per-call PID guard for non-fork-aware multiprocessing setups. Companion fix to the transport-level fork-safety shipped in 2.2.0. - Real watermark-driven early flush.
EventBuffer._flush_if_neededwas previously a no-op marker — bursts that filled the buffer continued dropping events for the entire flush_interval window even though the early-flush task was scheduled. Trigger a real async flush when buffer occupancy exceeds the high-watermark ratio (default 80%). Cap concurrent flushes via anasyncio.Semaphore(default 1) to prevent runaway parallel sends under sustained pressure. NewAgentConfigfields:high_watermark_ratio: float = 0.8,max_concurrent_flushes: int = 1.EventBuffer.stop_auto_flush()now awaits in-flight watermark-triggered flushes before returning, eliminating data loss on shutdown. - Hard-fail on encryption init. When the
project_encryption_keyround-trip failed at startup, transport logged a warning and proceeded with plaintext over the wire. Operators got no signal stronger than a log line and could ship traffic encrypted in the dashboard's mind but not on the network. Now raisesEncryptionConfigErroron any startup encryption failure. No plaintext fallback. Operators get a loud failure they can react to.
Internal (v2.3.0)¶
- Test coverage maintained at 100% line + branch across
guard_agent/buffer.py,client.py,encryption.py,models.py,protocols.py,transport.py,utils.py(1135 statements, 0 missed). - Added test coverage to verify
EventBatch.batch_idis stable across retries (the underlying behavior was already correct in 2.2.0 — the new tests pin it down so future refactors can't regress). - Performance tests (
test_agent_performance_impact,test_memory_usage) hardened against coverage-instrumentation noise:gc.collect()before RSS baseline, coverage-aware overhead threshold,enable_redis=Falsein test apps to eliminate ResourceWarning pollution. - Fixed pre-existing typing gaps in test mocks; all resolved at the root without any suppression directives.
v2.2.0 (2026-04-25)¶
Production Safety (v2.2.0)¶
- Fork-safe transport.
HTTPTransport.__init__registers anos.register_at_fork(after_in_child=...)hook that resets the inheritedhttpx.AsyncClient,CircuitBreaker, andRateLimiterin every forked child. A pid-drift check runs on every send so spawn-style workers (uvicorn--workerswithout--preload) get the same protection. Fixes a class of bugs where Gunicorn--preloadworkers would corrupt the shared socket between parent and child. - Observable buffer drops.
EventBuffernow exposesevents_droppedandmetrics_droppedcounters viaget_stats(). The first drop and every 100th drop log aWARN. Previously the deque silently evicted the oldest event when full. - Honor server
Retry-After. A 429 response now raisesRateLimitedError(retry_after_seconds=...), and_send_with_retry/_get_with_retrysleep that exact value (capped at 300s) instead of falling back to client-side exponential backoff. Prevents the agent from hammering an already-overloaded SaaS. - Persist-confirm Redis recovery. Redis persist keys are now
event_{ns}_{uuid8}/metric_{ns}_{uuid8}so two events arriving in the same millisecond no longer collide. Deletion happens only after the transport confirms via the newconfirm_event_redis_keys/confirm_metric_redis_keyshelpers, andrequeue_events_in_memory/requeue_metrics_in_memorypush unsent events back to the front of the buffer on transport failure. Previously a transport failure cleared both deque and Redis simultaneously, dropping the events permanently. BufferProtocoladds new methods.flush_events_with_keys,flush_metrics_with_keys,confirm_event_redis_keys,confirm_metric_redis_keys,requeue_events_in_memory,requeue_metrics_in_memory. Custom buffer implementations need to implement these or fall back to the bundledEventBuffer.
Compression (v2.2.0)¶
- Gzip compression of outgoing batch bodies above
compression_threshold(default 1024 bytes). When the body exceeds the threshold the agent compresses with gzip and sendsContent-Encoding: gzip; the Guard Core SaaS decompresses request bodies via itsGzipRequestMiddlewarebefore pydantic validation. Smaller bodies skip compression and ship as plain JSON. - Default is ON.
AgentConfig.compression_enabled=True. Setcompression_enabled=Falseif you are pointing the agent at an ingestion endpoint that does not handleContent-Encoding: gziprequest bodies (e.g. a custom backend without a decompression middleware). EventBatch.compressedfield now reflects whether the body was actually compressed.
Versioning hygiene (v2.2.0)¶
agent_versionin HTTP request headers (User-Agent) and batch payloads now derives fromguard_agent.__version__instead of the hardcoded"1.1.0"string the previous releases were sending. SaaS-side analytics that key offagent_versionwill now see the real installed version.
Test coverage (v2.2.0)¶
- Test coverage raised to 100% across
guard_agent/buffer.py,client.py,encryption.py,models.py,protocols.py,transport.py,utils.py(1053 statements, 0 missed). Adds the previously-missing branches for the fork-hook unavailable path, the Retry-After exhausted-attempts path, the GET retry-after path, the buffer overflow drop accounting, the empty-key forget paths, the Redis-failure swallowing inconfirm_event_redis_keys/confirm_metric_redis_keys, and therequeue_metrics_in_memoryend-to-end + overflow-drop paths.
v2.1.0 (2026-04-24)¶
Multi-Adapter Coverage (v2.1.0)¶
- Added per-adapter integration smoke tests:
tests/test_adapter_fastapi.py,test_adapter_flask.py,test_adapter_django.py. Each verifiesSecurityConfig.to_agent_config()roundtrip and request delivery through the adapter's middleware withenable_agent=True. - Added per-adapter documentation pages under
docs/adapters/: FastAPI, Flask, Django, Tornado. Each page covers install, minimal example, and agent wiring specific to that framework. mkdocs.ymlnavigation updated with a new top-level Adapters section.
Dependency Changes (v2.1.0)¶
- Added
django,djapi-guard>=2.0.0,flask,flaskapi-guard>=2.0.0,tornadoto[project.optional-dependencies].devso the test suite can exercise every adapter. tornadoapi-guardis not yet included in dev extras — it has not been published to PyPI (only a yanked 0.0.1 exists). Integration tests for Tornado are stubbed withpytest.mark.skipintests/test_adapter_tornado.py. Re-enable once the adapter ships a 1.0.0+ release.
v2.0.0 (2026-04-24)¶
Package Rename (v2.0.0)¶
- Renamed on PyPI:
fastapi-guard-agent→guard-agent. The Python import path (from guard_agent import ...) is unchanged — no code changes are required in consuming applications. - Repositioned as a framework-agnostic telemetry agent serving
fastapi-guard,flaskapi-guard,djapi-guard, andtornadoapi-guard. - Legacy name preserved: a meta-package
fastapi-guard-agent==1.2.0is published alongside this release, whose only dependency isguard-agent>=2.0.0,<3.0.0. Existingpip install fastapi-guard-agentinvocations continue to resolve correctly and pull the renamed distribution transitively. - Repository renamed on GitHub:
rennf93/fastapi-guard-agent→rennf93/guard-agent. GitHub auto-redirects the old URLs. - Documentation site moved to
https://rennf93.github.io/guard-agent/.
Dependency Changes (v2.0.0)¶
- Removed
fastapiandfastapi-guardfrom runtime dependencies — the agent is framework-agnostic and speaks HTTP to the dashboard, not to any web framework. - Runtime deps are now:
cryptography,httpx,pydantic,typing-extensions. fastapiandfastapi-guardremain as dev extras so the existing test suite keeps passing. Each framework adapter brings its own web framework.- Dropped
Framework :: FastAPIclassifier; development status promoted fromAlphatoBeta.
Breaking Changes (v2.0.0)¶
- None in Python API —
from guard_agent import ...,GuardAgentHandler,AgentConfig, and every public symbol behave identically. - Distribution name change only: scripts, Dockerfiles, and lockfiles that install
fastapi-guard-agentdirectly should migrate toguard-agent. The shim keeps old commands working but new projects should installguard-agentdirectly.
Migration Guide (v2.0.0)¶
- Existing code: no changes.
- Install commands (uv): replace
uv add fastapi-guard-agentwithuv add guard-agentat your leisure — both resolve to the same underlying package. - Poetry / pip equivalents:
poetry add guard-agent/pip install guard-agent. - Lockfiles: running
uv lock,poetry lock, orpip-compileafter bumping will transparently update entries toguard-agent.
v1.1.1 (2026-03-11)¶
Bug Fixes (v1.1.1)¶
- Fixed misalignment on documentation headers and model parameters.
- Added support for Python 3.14.
Maintenance (v1.1.1)¶
- Code alignment and cleanup.
v1.1.0 (2025-10-14)¶
New Features (v1.1.0)¶
- Added end-to-end payload encryption for secure telemetry transmission using AES-256-GCM.
- Implemented
PayloadEncryptorclass with project-specific encryption keys. - Added encrypted endpoint support for events and metrics (
/api/v1/events/encrypted). - Integrated automatic datetime serialization in encrypted payloads via custom JSON handler.
- Added encryption key verification during transport initialization.
Technical Details (v1.1.0)¶
- Encryption uses AES-256-GCM with 96-bit nonces and 128-bit authentication tags.
- Pydantic models are serialized using
.model_dump(mode="json")before encryption. - Custom
_default_json_handlerensures datetime objects are properly ISO-formatted.
v1.0.2 (2025-09-12)¶
Enhancements (v1.0.2)¶
- Added dynamic rule updated event type.
v1.0.1 (2025-08-07)¶
Enhancements (v1.0.1)¶
- Added path_excluded event type.
v1.0.0 (2025-07-24)¶
Official Release
v0.1.1 (2025-07-09)¶
Enhancements (v0.1.1)¶
- Standardized Redis Protocl/Manager methods across libraries.
v0.1.0 (2025-07-08)¶
Enhancements (v0.1.0)¶
- Switched from aiohttp to httpx for HTTP client.
- Completed implementation.
- 100% test coverage.
v0.0.1 (2025-06-22)¶
New Features (v0.0.1)¶
- Initial release FastAPI Guard Agent.