Skip to content

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 07 Sep 14:55
· 14 commits to main since this release
63da80a

What's Changed

Added

  • Static type checking with mypy: the eggai package is type-checked in CI
    (poetry run mypy, config in pyproject.toml); mypy is a new dev dependency.
  • RedisTransport: new group_start subscribe option ("$" default, "0" or
    a stream id) chooses where a NEW consumer group starts reading, so a consumer
    added to a channel that already carries traffic can pick up the existing
    backlog. The transport now creates its consumer groups itself before the
    broker starts (#260).

Fixed

  • A2A executor: the JSON-serialisation fallback caught the non-existent
    json.JSONEncodeError, so a failing json.dumps raised AttributeError and
    masked the real error. It now catches (TypeError, ValueError).
  • A2A executor: the "unknown skill" error path enqueued a raw dict instead of a
    proper A2A message; a skill registered with data_type=None raised
    TypeError when building the message and now falls back to BaseMessage.
  • Typed subscriptions: a data_type whose type field has no default (e.g. raw
    BaseMessage) silently dropped every message; subscribe() now raises
    ValueError asking for a default discriminator.
  • Agent.subscribe(): plugin-prefixed kwargs (e.g. a2a_*) on an agent whose
    plugin was never initialised raised an opaque KeyError; now ValueError.
  • RedisTransport: agent.stop() could hang forever on Python 3.10/3.11 with
    redis-py >= 8. redis-py 8 sends every command through asyncio.wait_for
    (socket_timeout now defaults to 5s), and on CPython < 3.12 wait_for can
    swallow the CancelledError when the inner await completes concurrently, so
    the reclaimer task survived its own cancellation. The reclaimer loop now exits
    on a running flag as well as on cancellation.
  • RedisTransport retries with a non-default EGGAI_NAMESPACE (#261): the
    reclaimer, group monitor, .retry and .dlq streams were keyed under
    eggai.<ns>.<topic> while consumption and publishing used <ns>.<topic>, so
    retry_on_idle_ms / max_retries never fired outside the default namespace.
    The transport no longer adds its own eggai. prefix; Channel already
    namespaces the name exactly once. After upgrading, entries stuck in the PEL
    under a custom namespace are retried immediately and dead-lettered once the
    retry budget is exhausted. Stale empty eggai.<ns>.* shadow keys can be
    deleted.
  • PendingReclaimer: the retry envelope's header length prefix counted
    characters instead of bytes, truncating non-ASCII header values on every
    retry delivery (#263).
  • Agent.subscribe() without a channel listened on the literal eggai.channel
    instead of <EGGAI_NAMESPACE>.channel, so under a custom namespace a bare
    Channel().publish() never reached it (#264).

Changed

  • Type-annotation cleanups across transports, channel, agent and hooks to
    satisfy mypy (no behaviour change). Transport.subscribe()'s second
    parameter is now named handler in the abstract base and all transports.
  • BREAKING: BaseMessage.data is now required. The generic base previously
    declared data: TData = Field(default_factory=dict). Pydantic does not
    validate defaults, so an envelope missing data validated cleanly on any
    typed subclass (BaseMessage[Order]) with data as a plain {} — the
    handler then crashed on first attribute access, and under NACK_ON_ERROR
    that single malformed message wedged the subscription. A missing or malformed
    payload now fails validation, which typed subscriptions
    (wrap_handler_with_filters) already treat as "not ours: skip and ack".
    The concrete Message keeps its {} default, where the annotation really is
    a dict. Migration: construct payload-less envelopes with Message (or pass
    data= explicitly).
  • Allow ruff 0.16 (ruff >=0.14.4,<0.17) and exclude Markdown from ruff, which
    now formats fenced code blocks by default.
  • RedisTransport: last_id other than ">" together with a consumer group
    now raises ValueError. That combination never delivered new entries (Redis
    returns only the consumer's own pending entries for an explicit id) and
    hot-looped XREADGROUP; the earlier note about replaying a backlog with
    last_id="0" was wrong. Use group_start="0" instead.

Installation

pip install eggai==0.4.0