Agno compatibility boundaries
This page maps each known Agno weakness to its upstream tracking and to the MindRoom boundary that works around it, so the weakness can be fixed upstream and the workaround removed.
The policy lives in the Agno Compatibility Policy in AGENTS.md.
Each # AGNO_COMPAT: source comment owns its workaround's reason, exact upstream links, removal condition, and test coverage; rg -n -F 'AGNO_COMPAT:' src/mindroom lists them.
The Boundary column of both tables below is the boundary inventory; paths are relative to src/mindroom/, and one row can group several markers that share a weakness.
Workarounds adapt Agno mechanics only: approval outcomes, storage ownership, retry limits, provider configuration, request-log context, canonical replay, and OAuth policy stay with their MindRoom owners, so removing a workaround keeps that product behavior.
Upstream work
Existing tracking
Issue and PR states were checked on September 24, 2026, unless a row gives another date; recheck upstream before relying on them.
| Agno weakness | Upstream issue / PR | Boundary |
|---|---|---|
| Generic provider failures default to 502 and lose machine-readable error codes. | Issue #8869, PR #8870, both open; the PR is partial, because its OpenAI error transport fix does not resolve all generic or non-OpenAI failures. | providers/agno_compat_provider_errors.py |
| Deleted runs survive in legacy blobs or deletion spans separate transactions. | Issue #9934, PR #9939, both open. | session_storage/agno_compat_sqlite.py |
| New runs can sort before surviving runs after deletion. | Issue #9936, PR #9938, both open. | session_storage/agno_compat_sqlite.py |
| Team input flattens roleful messages. | Issue #9942, PR #9943, both open. | history/agno_compat_message_builder.py |
| Responses streams accept incomplete EOFs and publish continuation IDs too early. | PR #10135, open. | providers/agno_compat_openai_responses.py |
| Responses reasoning is lost during explicit tool-call replay. | Issue #9960, open; PR #9968, closed in favour of PR #10395, merged but unreleased; the reasoning-before-function-call ordering landed in PR #10075, released in Agno 3.0.11; both are partial, because Agno still keeps only the last reasoning item. | providers/agno_compat_openai_responses_items.py |
| Unreliable streamed tool-call indexes create malformed or merged calls. | Issue #8879, PR #8880, both open. | providers/agno_compat_openai_chat.py, which only removes empty slots. |
| Claude requests include sampling controls rejected in supported modes. | Issue #9931, PR #9933, both open. | providers/agno_compat_claude.py |
| Claude history replays response citations without their required null titles or cited documents. | PR #10147, merged, unreleased (checked September 28, 2026); it restores null document titles only, and restored titles alone still fail with an invalid document index once the cited document is not replayed. | providers/agno_compat_claude.py |
Vertex Claude tool definitions contain rejected provider-level strict fields. |
Issue #6599, open; PR #6923, closed without merge. | providers/agno_compat_vertex_claude_tools.py |
| Knowledge search failures become plausible empty results. | Issue #10150, PR #10152, both open. | knowledge/agno_compat_knowledge.py |
| Prepared requests and effective tools require private Agent/Team APIs. | Issue #7806, PR #7807, both open; to replace the RTC and live-turn tool paths, an inspection API must also support executable run-context and media bindings. | agno_compat_prepared_tools.py |
| Async runs lack supported persistence hooks for synchronous storage owners. | Issue #10149, open; PR #10148, open and partial: it only routes Agent startup through the awaitable read path, so Agent/Team writes and owner dispatch remain. | session_storage/agno_compat_session_persistence.py |
Tool schemas render Any, such as the values of dict[str, Any], as an object that only accepts {}. |
Issue #10422, PR #10423, both open (checked October 5, 2026). | custom_tools/google_sheets.py (batch_update_sheet) |
| OpenAI usage parsing omits cache-write input tokens. | Issue #10314, PR #10313, both open (checked October 6, 2026). | providers/agno_compat_openai_responses.py, providers/agno_compat_openai_chat.py |
| Gemini metrics leave thinking out of output, although Google bills thinking as output. | Issue #10763; PR #10764 counts thinking in output and PR #10722 does too since commit 2232999 and also keeps the provider total, both open (checked October 10, 2026). | providers/google_gemini.py |
| Claude history replays server-tool response blocks verbatim, so response-only fields and tool-search uses without a result fail the next request. | Issue #8687, PR #8686, both open (checked October 6, 2026); both cover only code-execution citations, so tool-search result fields and unpaired search uses still need the local repair. | providers/agno_compat_claude.py |
| Library loggers install their own stdout handler at INFO with propagation disabled, and every run resets their levels, so their records bypass the host's level, format, redaction, and stream. | Issue #4097, closed (checked October 10, 2026) after Agno 2.0 added configure_agno_logging, which swaps module globals but leaves the default handlers on the loggers that modules imported directly. |
logging_config.py |
Tracking gaps
These are unfinished contribution candidates identified by the local audit. Rows name related upstream work where any is known; each source comment records whether a search found nothing or tracking is still unverified.
| Observed gap or required extension point | Next upstream work | Boundary |
|---|---|---|
| Responses continuation depends on a hard-coded model-name predicate. | Replace the o3/o4-mini/gpt-5 prefix check with a capability that aliases and compatible endpoints can set; PR #10075, released in Agno 3.0.11, added use_previous_response_id but left the predicate. |
providers/agno_compat_openai_responses.py |
| Session totals repeat cumulative run usage on checkpoints/resumes, or omit totals for bare sessions. | Open an issue or refile a PR; PR #10353 was closed by its author without merge or replacement. | session_storage/agno_compat_session_metrics.py |
| Provider history is not available before an outer Bash call completes. | Expose a public pre-dispatch callback with actual provider history and propagating session/run persistence. | agent_cli/agno_compat_cli_checkpoint.py |
| Hidden nested control calls cannot stop their owning provider call through a public per-execution signal. | Expose a public per-call stop signal. | agno_compat_prepared_tools.py |
| Async model tool execution leaves generator consumers and synchronous threads alive after cancellation. | Own cancellation and cleanup for both execution paths. | agno_compat_prepared_tools.py |
| Tool preparation does not expose the per-run Function copy the provider calls. | Expose the bound per-run Function from public preparation. | agno_compat_prepared_tools.py |
| A running turn cannot prepare one of its own Functions for execution outside the model's tool loop without private preparation that resets tool instructions. | Expose public per-run Function preparation that leaves live instruction state intact. | agno_compat_prepared_tools.py |
| ModelsLab polls a local artifact UUID and discards completed URLs and wait outcomes. | Poll the provider job ID, accept numeric ETAs, retain completed outputs, and distinguish retryable status failures from terminal rejection. | tools/agno_compat_modelslabs.py |
| MoviePy caption rendering assumes an Arial font file, ignores advertised styles, clips glyph bounds, overlaps wrapped words, leaves temporary audio after encoding failures, names the default output from the last dot anywhere in the video path, and rasterizes every caption line before encoding starts. | Fix each listed defect. | custom_tools/agno_compat_moviepy.py |
MoviePyVideoTools hands model-chosen video, caption, and output paths to open(), os.replace, and FFmpeg by name, and FFmpeg follows the paths and URLs listed in HLS playlists and DASH manifests. |
Accept caller-supplied input readers and output writers. | custom_tools/agno_compat_moviepy.py |
| OpenAITools and GroqTools open model-chosen audio paths by name, and GroqTools reads any existing path before treating a source as a URL. | Accept a caller-supplied file opener for local audio. | tools/agno_compat_openai.py, tools/agno_compat_groq.py |
| AirflowTools defaults its DAG directory to the process working directory and reads and writes DAG files by path. | Accept a caller-supplied base directory descriptor or file reader and writer. | tools/agno_compat_airflow.py |
WebBrowserTools hands any URI scheme, including file: paths and application links, to the host's URL handlers. |
Open only http and https URLs. |
tools/agno_compat_webbrowser.py |
| Application tools lack a public accepted-execution owner and framework waiting metadata. | Add ownership around approved FunctionCalls and provider-schema metadata hooks. | tool_jobs/agno_compat_execution.py |
| A Function keeps its invoking agent or team and its run context only in private SDK fields, and Agno's asynchronous dispatch classification is private. | Expose public accessors for a Function's actor and run context, and the dispatch classification. | tool_jobs/agno_compat_functions.py |
| Chroma metadata deletion forces equality filters and spans owner collections. | Add operator-aware deletion for one explicitly selected collection. | knowledge/collections.py |
| Chroma collection deletion returns the same false result for absence and failure. | Preserve typed errors and distinguish already-absent collections from failed deletion. | knowledge/collections.py |
The Skills tool entrypoints are private methods with no hook for when an agent loads a skill, so the skill learner cannot count workspace skill loads without overriding _get_skill_instructions. |
Call a hook when a skill's instructions or files are loaded. | tool_system/skills.py |
| Skills opens skill references and scripts by path, so a workspace skill file swapped for a link or FIFO is followed or blocks. | Let a loader supply skill file contents, or refuse links and non-regular files. | tool_system/skills.py |
FileTools content search relativizes hits and exclusions against base_dir, so it cannot search other directories. |
Accept an absolute search directory outside base_dir and report absolute hit paths. |
tools/file.py |
| FileTools content search checks each walked file through a path sanitizer that strips trailing dots and spaces, then opens the unsanitized path by name and follows its links, so a file the check never saw is read. | Open exactly the validated path without following links out of base_dir, or accept a caller-supplied file reader. |
tools/file.py |
CalculatorTools computes factorial for any argument, although json.dumps refuses results above 4300 digits, so one call can hold the GIL for minutes. |
Refuse factorial arguments whose result cannot be serialized. | custom_tools/calculator.py |
CalculatorTools trial-divides is_prime up to the square root of any argument, so one large prime checks for hours. |
Bound prime-check work. | custom_tools/calculator.py |
SleepTools blocks a thread in time.sleep for any requested duration, so parallel calls exhaust the primary's default executor. |
Bound and await the pause. | custom_tools/sleep.py |
E2BTools run_python_code reads code output through the E2B SDK's run_code, which reads each output line whole, so one large print or image grows the primary's memory. |
Stop reading code output past a caller-supplied byte limit. | custom_tools/e2b.py |
E2BTools run_command waits on the E2B SDK's commands.run, which keeps a command's whole stdout and stderr until it ends. |
Stop collecting command output past a caller-supplied byte limit. | custom_tools/e2b.py |
| SpotifyTools sends one fixed access token on every request and never renews it, so every call fails once Spotify's one-hour token expires. | Accept a token provider or refresh credentials and renew expiring tokens. | custom_tools/spotify.py |
| Concise PubMed output drops title/year when the abstract is short or absent. | Keep identifying metadata independent of summary truncation. | tools/pubmed.py |
DaytonaTools treats a null verify_ssl like false and then disables certificate checks by patching daytona_api_client.Configuration.__init__ for the whole process until it restarts. |
Disable verification only for an explicit False and scope it to the toolkit's own client configuration. |
tools/daytona.py |
| Luma video creation omits the required model argument in both public methods. | Pass a configurable model on both creation paths. | tools/lumalabs.py |
| Todoist project discovery treats SDK pages as individual projects. | Flatten project pages and serialize SDK date fields safely. | tools/todoist.py |
| Calendar construction requires broad scopes even when granular scopes cover the operations. | Validate effective permissions per registered operation. | custom_tools/google_calendar.py |
| Byte-only image dimension parsing is private. | Expose a public header parser without file/network I/O or pixel decoding. | providers/openai_models.py |
| Bedrock Claude hard-codes pre-Mantle SDK clients. | Add Mantle support or public typed client factories. | providers/bedrock_claude.py |
| Claude history puts a tool result resumed after approval behind its batch's media follow-up message, which the API rejects. | Format every tool_result ahead of the other blocks in a Claude user turn so stored runs replay. |
providers/agno_compat_claude.py |
| Claude requests send every file without a text mapping as a base64 document, but Claude only accepts PDF there, so Office files and archives fail the whole request. | Keep files Claude cannot read inline out of document blocks, or report them before the request. | providers/agno_compat_claude.py |
| Parsed provider responses omit terminal stop metadata. | Preserve OpenAI finish_reason and Claude stop_reason through a stable parsed-response interface. |
providers/agno_compat_openai_chat.py, providers/agno_compat_claude.py |
| Hosted Responses tool-search items are omitted from replay data. | Reproduce lost hosted-search output and preserve ordered call/output items. | providers/agno_compat_openai_responses_items.py |
| Retrying a stream can reuse partial assistant or tool state. | Establish retry ownership and test that partial output cannot be duplicated; the completion fix in PR #10135 does not cover this. | providers/agno_compat_openai_responses.py |
| Native Responses error events are ignored and unread streams can remain open after parsing fails. | Preserve terminal error codes and explicitly close SDK streams on failure or consumer closure; retain incomplete-stream retry protections. | providers/agno_compat_openai_responses.py |
| Unsuccessful Responses streams discard received usage, and successful retries replace earlier attempt counters. | Preserve reported terminal usage through failure cleanup and across retries without counting clean completions twice. | providers/agno_compat_openai_responses.py |
| Terminal error and cancellation cleanup keep stale checkpoint or continuation messages. | Refresh the saved message list from current in-flight messages while retaining cancellation cleanup. | session_storage/agno_compat_run_messages.py |
| Interrupted model streams accumulate usage before retaining the assistant message. | Preserve the metered message before terminal persistence; issue #9489 identifies the same lost-message boundary, but does not separately track request-detail preservation. | session_storage/agno_compat_run_messages.py |
| Assistant request messages omit their model, although a resumed run can contain usage from multiple models. | Persist the model ID and provider with each request's counters. | session_storage/agno_compat_run_messages.py |
| Stopping an async run between chunks abandons its suspended model stream until garbage collection, after the run is persisted without the usage that stream already received. | Close in-flight model streams innermost first before terminal cancellation or error persistence; issue #9489 covers related abandoned-generation bookkeeping, not usage settlement. | session_storage/agno_compat_run_messages.py |
Model.aresponse without a run replaces its response usage with each tool-loop request's usage, so Agno Learning extraction, which meters the returned response once, records only the last request of each extraction. |
Return the usage of every request in the loop, or meter each extraction request. | session_storage/agno_compat_run_messages.py |
| A delegated team member that is stopped or fails mid-stream never reaches its team run, so its usage is lost. | Attach every delegated member run to its team run on cancellation and streaming errors. | session_storage/agno_compat_run_messages.py |
| A team run cancelled by cancelling its task leaves its members' delegation tasks running. | Cancel and drain in-flight member tasks before saving such a run. | session_storage/agno_compat_run_messages.py |
| Responses guesses inline-file MIME from opaque storage paths before original filenames. | Infer missing MIME from the original filename without mutating attachment metadata; retain binary MIME for compressed attachments stored under opaque paths. | providers/agno_compat_openai_responses.py |
| Run saves commit internally, with no caller-owned transaction or persistence hook. | Add an in-transaction hook, needed to persist application-owned usage and compaction-archive restores atomically. | session_storage/agno_compat_sqlite.py |
| SQLite journaling is forced, and cache statistics require private access. | Propose configurable pragmas; track public cache counters separately. | session_storage/agno_compat_sqlite.py |
| Cancelled runs persist in detached tasks without an awaited drain boundary. | Expose completion for the exact cancelled run before caller-owned history writes. | session_storage/agno_compat_session_persistence.py |
| Knowledge insertion lacks caller-controlled error propagation and validation. | Reproduce swallowed vector failures and propose validation before publishing content status. | knowledge/agno_compat_knowledge.py |
| Embedder requests are built separately across sync, async, and batch paths. | Add shared request and response hooks while retaining typed provider failures; PR #9814, merged and present in the pinned Agno 3.0.9 embedder, added typed embedding errors but not these hooks or owner-controlled batch failure handling. | embeddings/agno_compat_openai_embedder.py |
| Embedders have one entry point for search queries and documents, so asymmetric models get one fixed prompt or input type for both. | Add separate query and document embedding to the Embedder interface and call them from vector-store search and Document.embed. |
embeddings/signatures.py |
| Denying a persisted tool call requires a live Function and private continuation interception. | Support denial after tool removal and an owner callback before resumed-tool lookup. | approval/agno_compat_approval.py |
| Agent continuation runs every confirmed call in the stored run, not only the supplied requirements, and a run continued by ID is re-read from session storage first. | Continue exactly the supplied requirements, or expose a public callback over the calls about to run. | approval/agno_compat_approval.py |
| Message projection and historical-media policy require private message interception. | Propose supported message-preparation hooks with explicit history-mutation ownership. | agno_compat_model_hooks.py, history/agno_compat_message_builder.py |
Post-tool callbacks lack mutable messages/results at the required stage, and approved continuations bypass them; the existing after_tool_results callback does not expose the mutable message surface that queued notices use. |
Extend the callback surface after result formatting, media insertion, and resumed batches at response entry. | agno_compat_model_hooks.py |
| Invocation, retry-cycle, scoped request, and final SDK payload hooks are missing. | Define each lifecycle stage separately, including wrapper order, cache behavior, and cancellation cleanup. | agno_compat_model_hooks.py |
A response loop's final request is not observable: RunOutput.messages drops messages kept out of agent memory, and the tool definitions stay inside aresponse, so the skill review cannot fork the request to reuse its prompt cache. |
Report each response loop's final messages, tools, tool choice, and response format to a scoped callback. | agno_compat_model_hooks.py |
Response loops call the model for as long as it requests tools: tool_call_limit only refuses counted calls, and calls to unknown tools or with unparseable arguments bypass it; callers also have no public hook to end a run before a request, which the skill review's input budget needs. |
End every loop after a bounded number of model requests through the normal completion path and offer a public pre-request hook that can end the run; refused tool_call_limit batches are tracked in issue #8304 and issue #10041, whose open PRs #10042 and #8324 stop only after limit refusals; no upstream issue was identified for unknown-tool or unparseable-argument loops or for the pre-request hook. |
agno_compat_model_hooks.py |
| Tool-hook chains cannot express the required deferred-result and synchronous execution ownership. | Propose an execution hook with regression coverage for cache hits, argument rewrites, and cancellation. | tool_system/agno_compat_tool_hooks.py |
| Function schema postprocessors are lost or misbound during copying/preparation. | Add a supported postprocessor that survives copies and runs after schema preparation. | tool_system/agno_compat_function_schema.py |
| Google authentication requires private resolver and Function entrypoint replacement. | Propose injectable credential resolution and an entrypoint middleware hook. | oauth/agno_compat_google_auth.py |
| WebsiteReader does not expose fetch, redirect, and crawl-admission hooks. | Propose a replaceable fetch path and explicit crawl-policy callbacks. | custom_tools/agno_compat_website_reader.py |
| TrafilaturaTools extraction and crawling download through fixed module-level fetchers, and Trafilatura's spider has no fetch hook. | Accept a caller-supplied fetcher for extraction, batch extraction, and every crawler download. | tools/agno_compat_trafilatura.py |
Newspaper4kTools downloads through newspaper4k's article helper, which opens file:// paths, follows redirects anywhere, and downloads every image URL a page lists. |
Accept caller-supplied HTML or a fetcher and allow skipping image downloads. | tools/agno_compat_newspaper4k.py |
| FileGenerationTools saves model-named files by path into its output directory, or the process working directory, and follows links there. | Accept a caller-supplied writer or directory descriptor for saved files. | tools/agno_compat_file_generation.py |
| VisualizationTools saves model-named charts by path below an output directory in the process working directory and follows links there. | Accept a caller-supplied writer or directory descriptor for charts; PR #9470, open on October 3, 2026, keeps chart file names inside the output directory but still saves by path below the working directory. | tools/agno_compat_visualization.py |
| GithubTools serializes provider failures and logs provider detail through fixed message formats. | Expose typed errors; separately provide structured logging or a redaction hook. | custom_tools/agno_compat_github_errors.py |
| ReplicateTools checks its API key but calls an independent SDK default client. | Bind SDK requests to the toolkit key and expose client lifetime ownership while preserving media results. | custom_tools/agno_compat_replicate.py |
| AgentQLTools stores its resolved key without handing it to AgentQL queries. | Pass the resolved key through a per-request or per-page SDK credential API. | custom_tools/agno_compat_agentql.py |
| AgentQLTools launches Chromium with no proxy and navigates to the model-chosen URL, so neither the URL nor redirects and subresources follow a destination policy. | Accept caller-supplied browser launch options or a page factory. | custom_tools/agno_compat_agentql.py |
| AgentQLTools custom scraping returns response keys and discards extracted values. | Preserve the complete JSON response, including nested collections. | custom_tools/agno_compat_agentql.py |
| Adapter media capabilities must be inferred from a private module-name table. | Expose accurate supported-input capabilities independently of provider error learning. | providers/media_fallback.py |
Google Drive read_file refuses text stored as application/octet-stream, names download_file even when a toolkit renames or omits it, and extracts Office text without tables, grouped shapes, or line breaks. |
Decide text by content, let a toolkit name its download function, and extract complete Office text or refuse it; PR #10501 covers only .docx tables. |
custom_tools/google_drive.py |
| Session reads hand out shared run objects that are immutable only by an unenforced contract. | Return per-read copies or frozen runs. | session_storage/agent_storage.py |
Every stored run persists its system and developer messages; Agno's store_* switches cover history, tool, and media content but not these prompt roles. |
Add a flag that skips every configured prompt role. | session_storage/agent_storage.py |
Claude streams report usage only from the final message_stop snapshot, although Anthropic reports input and cache usage in message_start, so a reply stopped before the stream completes records no usage for that request. |
Keep message_start usage for a stream that ends before message_stop, while counting completed streams once; no issue tracks stopped streams, and Agno moved usage to message_stop to fix double counting in issue #6537. |
providers/agno_compat_claude.py, session_storage/agno_compat_run_messages.py |
| Groq and Cerebras metrics copy only prompt and completion totals and drop the cached-input details their SDKs report inside the prompt total. | Map cached input to cache reads; Cerebras reasoning tokens also stay unmapped, because the pinned Cerebras SDK does not declare that field. | providers/groq_model.py, providers/cerebras_model.py |
| Groq metrics drop the reasoning tokens Groq reports inside the completion total. | Map reasoning tokens. | providers/groq_model.py |