Skip to content

Release Notes


v2.7.0 (2026-06-23)

Observable transport errors and documented Protocols (v2.7.0)

Added

  • AgentConfig.on_error hook for observable delivery failures. New optional on_error: Callable[[str, BaseException, dict[str, Any]], None]. HTTPTransport fires 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, and AgentHandlerProtocol upgraded 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_* returns bool meaning accepted (caller requeues on False), 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_checkable change.

Internal

  • Refactored _send_with_retry / _handle_response into 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)

  • AddedAgentConfig.dynamic_rule_interval: int (default 300, ge=60) — interval in seconds between dynamic rule polls.
  • AddedAgentConfig.status_interval: int (default 300, ge=60) — interval in seconds between agent status reports.
  • Changed_rules_loop now sleeps self.config.dynamic_rule_interval instead of a hardcoded 300. _status_loop now sleeps self.config.status_interval instead of a hardcoded 300. Both loops were previously ignoring any caller-configured value, so SecurityConfig.dynamic_rule_interval (and the new SecurityConfig.agent_status_interval in 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.py covering field defaults, persistence, lower-bound rejection (ge=60), and end-to-end assertions that both loops invoke asyncio.sleep with 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 via AgentConfig.install_id), sent on every request as X-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 via logger.exception and falls through to a fresh UUID rather than failing the start-up. New module: guard_agent.install_id exposing resolve_install_id(*, state_path, override).
  • Added — Opt-in HMAC-SHA256 payload signing. When AgentConfig.payload_signing_secret is set, every outbound request carries X-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 against request.body() without re-decoding. No header is sent when the secret is unset, preserving the existing default behavior. New module: guard_agent.signing exposing sign_payload(body, *, secret).
  • Added — Two new fields on AgentConfig: install_id: str | None (override the auto-resolved install ID) and payload_signing_secret: str | None (HMAC secret; both default to None).
  • Changed — Transport sets the install-ID header once on the cached httpx.AsyncClient default 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)

  • FixedHTTPTransport._log_request_error now formats the captured exception as <ClassName>: <repr> instead of str(exc). Several httpx exception classes raised on transport-level connection drops (RemoteProtocolError, WriteError, some httpcore wrappers) carry no message body, so str(exc) rendered empty and the previous error line was HTTP 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 on guard_agent/transport.py maintained 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 (default uuid4() via default_factory). Combined with the existing batch-stable batch_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 named idempotency_key, not event_id, to avoid collision with the SaaS API's existing event_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. Default None for callers that construct AgentConfig directly without going through a framework wrapper. Framework adapters should set config.guard_version = framework_package.__version__ immediately before passing the config to GuardAgentHandler.
  • 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 from AgentConfig.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 see agent_version (guard-agent's own version) and had no way to know which middleware version the customer was running.
  • encryption._default_json_handler now serializes UUID values to their string form alongside the existing datetimeisoformat() branch. Required for the encrypted-payload path to handle events carrying the new idempotency_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 when start_auto_flush is wired or a flush callback is in place; otherwise clear_buffer is the manual escape hatch.
  • raiseBufferFullError propagates to the caller. Appropriate for tests or strict environments where dropping events is unacceptable.
  • BufferFullError(GuardAgentError) exception class added in guard_agent.exceptions and re-exported from the top-level guard_agent module.

Fixed

  • HTTPTransport._make_request was logging the wrong URL on POST failures. When encryption was enabled, the actual request hit /api/v1/events/encrypted but the error log printed the unencrypted endpoint string (url = f"{endpoint}{plain_path}"). Operators chasing down 503s and decrypt errors saw POST .../api/v1/events in 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_key has a default_factory, and buffer_overflow_policy defaults 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_key column on security_events, the unique constraint, and the pg_insert ... on_conflict_do_nothing ingest 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 GuardAgentHandler singleton. Class-level _instance survived os.fork(), so Gunicorn pre-fork workers all inherited a stale _initialized=True flag and dead asyncio task handles from the parent loop. Calling start() in the child was a silent no-op and the child never connected to the agent endpoint. Register an os.register_at_fork(after_in_child=...) hook that resets _initialized and 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_needed was 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 an asyncio.Semaphore (default 1) to prevent runaway parallel sends under sustained pressure. New AgentConfig fields: 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_key round-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 raises EncryptionConfigError on 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_id is 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=False in 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 an os.register_at_fork(after_in_child=...) hook that resets the inherited httpx.AsyncClient, CircuitBreaker, and RateLimiter in every forked child. A pid-drift check runs on every send so spawn-style workers (uvicorn --workers without --preload) get the same protection. Fixes a class of bugs where Gunicorn --preload workers would corrupt the shared socket between parent and child.
  • Observable buffer drops. EventBuffer now exposes events_dropped and metrics_dropped counters via get_stats(). The first drop and every 100th drop log a WARN. Previously the deque silently evicted the oldest event when full.
  • Honor server Retry-After. A 429 response now raises RateLimitedError(retry_after_seconds=...), and _send_with_retry / _get_with_retry sleep 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 new confirm_event_redis_keys / confirm_metric_redis_keys helpers, and requeue_events_in_memory / requeue_metrics_in_memory push 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.
  • BufferProtocol adds 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 bundled EventBuffer.

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 sends Content-Encoding: gzip; the Guard Core SaaS decompresses request bodies via its GzipRequestMiddleware before pydantic validation. Smaller bodies skip compression and ship as plain JSON.
  • Default is ON. AgentConfig.compression_enabled=True. Set compression_enabled=False if you are pointing the agent at an ingestion endpoint that does not handle Content-Encoding: gzip request bodies (e.g. a custom backend without a decompression middleware).
  • EventBatch.compressed field now reflects whether the body was actually compressed.

Versioning hygiene (v2.2.0)

  • agent_version in HTTP request headers (User-Agent) and batch payloads now derives from guard_agent.__version__ instead of the hardcoded "1.1.0" string the previous releases were sending. SaaS-side analytics that key off agent_version will 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 in confirm_event_redis_keys / confirm_metric_redis_keys, and the requeue_metrics_in_memory end-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 verifies SecurityConfig.to_agent_config() roundtrip and request delivery through the adapter's middleware with enable_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.yml navigation 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, tornado to [project.optional-dependencies].dev so the test suite can exercise every adapter.
  • tornadoapi-guard is 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 with pytest.mark.skip in tests/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-agentguard-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, and tornadoapi-guard.
  • Legacy name preserved: a meta-package fastapi-guard-agent==1.2.0 is published alongside this release, whose only dependency is guard-agent>=2.0.0,<3.0.0. Existing pip install fastapi-guard-agent invocations continue to resolve correctly and pull the renamed distribution transitively.
  • Repository renamed on GitHub: rennf93/fastapi-guard-agentrennf93/guard-agent. GitHub auto-redirects the old URLs.
  • Documentation site moved to https://rennf93.github.io/guard-agent/.

Dependency Changes (v2.0.0)

  • Removed fastapi and fastapi-guard from 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.
  • fastapi and fastapi-guard remain as dev extras so the existing test suite keeps passing. Each framework adapter brings its own web framework.
  • Dropped Framework :: FastAPI classifier; development status promoted from Alpha to Beta.

Breaking Changes (v2.0.0)

  • None in Python APIfrom guard_agent import ..., GuardAgentHandler, AgentConfig, and every public symbol behave identically.
  • Distribution name change only: scripts, Dockerfiles, and lockfiles that install fastapi-guard-agent directly should migrate to guard-agent. The shim keeps old commands working but new projects should install guard-agent directly.

Migration Guide (v2.0.0)

  • Existing code: no changes.
  • Install commands (uv): replace uv add fastapi-guard-agent with uv add guard-agent at 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, or pip-compile after bumping will transparently update entries to guard-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 PayloadEncryptor class 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_handler ensures 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.