agno: who decides what

Read the one-page summary first · The gallery · Make a map of your own system · The repository

The agno framework's Agent (its run loop, tools, model layer, hooks and guardrails, memory, knowledge and learning) and the teams and workflows built from it, in agno-agi/agno, read at commit 9153aacd. AgentOS, the server that runs agents for you, is outside this map. Claims about how the code behaves refer to that commit. Their code is Apache-2.0-licensed; this page quotes short identifiers and cites files and lines at that commit.

How to read this page

A seat is a judgment the agent makes over and over: which model a name means, what the model sees, may this action run, is the work done. Each card records the question, how it is answered, and who or what holds it, with the code that implements or exposes that decision. Seat ids (A1, B12 …) are links.

Each answer describes what agno does by default. Exceptions named are the main ones, not a complete list.

The chip on each card has two words. The first is about the question: a RULE has one right answer given its inputs, so a run that answers differently is a bug; a RULING is one that competent people could answer differently. Enforcing a limit someone set is a RULE; choosing what to keep, what to trust or when the work is done is a RULING, even when code holds it.

The second word is who holds it: llm = a language-model call; model = another learned model; code = code in their repository; human = a person; external = code outside the repository that the agent hands the decision to.

Paths are relative to the repository root. A bare :120-140 or (323) means lines in the file named just before it.

Counts

118 seats across 9 stages: A 11, B 17, C 15, D 24, E 13, F 13, G 6, H 9, I 10.

Kind of question · holderSeats
RULING · code39
RULE · code32
RULING · llm28
RULING · external13
RULING · human4
RULING · model1
RULE · external1

What this edition does not have

There is no list of gaps. No coverage figure: this edition does not say how many of agno's model calls and decision points the seats leave out. Each card shows the question, the answer and its holder; for most seats we also recorded what checks the judgment and how it has failed, but those notes are not shown here.

What is left out of this edition

3 of the 118 cards are left out of this edition. The counts above include them.

A. Intake and routing (11 seats)

A1 When an agent or team is built without a model, which model answers its requests? RULING · code
Answered how

An agent or team built without a model is answered by OpenAI's gpt-5.4 through the Responses API; any model the developer passes, as an object or a "provider:id" string, wins.

Held by

libs/agno/agno/agent/_init.py:89-101 set_default_model (OpenAIResponses(id="gpt-5.4")), called first in initialize_agent (libs/agno/agno/agent/_init.py:476-477), which each run dispatch calls (libs/agno/agno/agent/_run.py:1388, :2771, :3368); team twin libs/agno/agno/team/_init.py:580.

A2 When a run arrives without a session id or user id, which conversation and which user does it count as? RULE · code
Answered how

A run with no session id joins the agent's own session_id (the developer's, or else one made on the first such run and kept on the object; an id passed on a call is not kept); a run with no user id (or an empty one) takes the agent's user_id, which defaults to None.

Held by

libs/agno/agno/agent/_session.py:46-67 initialize_session: no session id -> the agent's session_id, else a new uuid that is then STORED on the agent instance (:57-59, "sticky"); no or empty user id -> the agent's user_id (:64-65). Called from libs/agno/agno/agent/_run.py:1385 (run), :2768 (arun), :3362 (continue), :4256.

A3 When the developer declared an input schema, does this input match it? RULE · code
Answered how

No input schema is set by default, so input passes as given; with a schema, input that is not that model, or does not parse into it, is refused with a ValueError before the model is called.

Held by

libs/agno/agno/agent/_run.py:1374 (run; :2757 in arun) calls validate_input (libs/agno/agno/utils/agent.py:1349-1400: a Message gives its content; a JSON string becomes a dict, then the pydantic model or TypedDict).

A4 How is the caller's input (text, a list, a dict, a Message, a pydantic object, a list of messages) turned into what the model receives? RULE · code
Answered how

Text, or a list of strings (joined by newlines), becomes one user message (no input and no media adds none); a Message, or a list whose first item is a Message or role-tagged dict, is sent as written, roles included; a single dict is read as a Message and a pydantic object is sent as JSON text. A message failing validation is dropped with a warning.

Held by

libs/agno/agno/agent/_messages.py:2000-2079 in get_run_messages (str/None/list-of-non-messages -> get_user_message; Message used as is; dict validated as a Message unless the input schema is a TypedDict; pydantic -> JSON; a list whose first item is a Message or role-dict -> each appended as its own message), with get_user_message :1525-1692.

A5 When the call, the stored session and the agent disagree on streaming, history, dependencies, knowledge filters or metadata, which one wins? RULE · code
Answered how

A value passed on the call beats the agent's setting; metadata merges agent, then stored session, then call (later wins), dependencies merge with the call's keys winning, and a call-level knowledge filter on the same key replaces the agent's.

Held by

libs/agno/agno/agent/_run_options.py:79-186 resolve_run_options (call > agent for flags; dependencies merged call-over-agent :147-154; metadata agent < session < call :165-170; filters via libs/agno/agno/agent/_utils.py:23-58 get_effective_filters, run filters override agent filters); applied by apply_to_context libs/agno/agno/agent/_run_options.py:38-76 ("explicit args > existing run_context > resolved defaults", except user_id, where an owner already on the run_context wins, :50-54), called at libs/agno/agno/agent/_run.py:1444-1451.

A6 Does this run stream events, run detached in the background, or return a single result? RULE · code
Answered how

A run returns one finished result unless streaming is asked for on the call or set on the agent (stream defaults to None, read as off); background execution exists only for arun(background=True) and requires a database.

Held by

libs/agno/agno/agent/_run_options.py:107-126 (stream: call > agent.stream > False; stream_events forced off when not streaming); background: libs/agno/agno/agent/_run.py:2868-2904 (arun(background=True) only; requires a db, :2869-2872; streams through a queue when stream is also on, :2873-2890).

A8 Should the developer's pre-run hook change the input (or the session) before the model sees it? RULING · external
Answered how

No pre-hook is installed by default, so the input reaches the model unchanged; a developer's pre-hook is called with the run input and may rewrite it before the model sees it.

Held by

libs/agno/agno/agent/_run.py:466-484 -> execute_pre_hooks (libs/agno/agno/agent/_hooks.py:43-151; hooks run in order with run_input, run_context, session, user_id, :62-72; the possibly changed input is stored back on the run, :151); hooks normalised at libs/agno/agno/agent/_run.py:1376-1382.

A9 When a paused, finished or failed run is continued, which run is it, from which message does it restart, and is it continued in place or copied? RULE · code
Answered how

A continued run is found by object or by id and resumes from the end of its stored transcript by default; continuing a run that already completed makes a forked copy rather than changing the original, and a cancelled run is refused.

Held by

libs/agno/agno/agent/_run.py:3285-3500 continue_run_dispatch: run by object or by id (:3340-3344, :3467-3480); boundary _resolve_continue_from :3106-3138 ("end" default, "last_user", index, or regenerate's own checkpoint); a completed run is always forked (:3456-3457); owner falls back to the paused run's user (:3379-3381, _resolve_continue_owner :3161-3172); reserved metadata restored from the stored row, never from the caller (:3386-3399).

A10 Does the team leader coordinate members, route the request to one member, broadcast it to all, or work from a task list? RULE · code
Answered how

A team with no mode set runs in coordinate mode; an explicit mode wins, and otherwise respond_directly selects route and delegate_to_all_members selects broadcast (both set: broadcast, with a warning). Tasks mode is reached only by naming it.

Held by

libs/agno/agno/team/_init.py:210-231 (explicit mode wins and resets the two flags to match; else respond_directly -> route, delegate_to_all_members -> broadcast, else coordinate); conflict resolved at libs/agno/agno/team/_init.py:868-877 (both flags -> broadcast, with a warning).

A11 When a workflow step runs an agent or a team, what does that step receive as its input? RULING · code
Answered how

From the second step on, an agent or team step receives only the previous step's output, not the user's original request; the original reaches it only when the previous step produced no content. Workflow history is off by default.

Held by

libs/agno/agno/workflow/step.py:2722-2737 _prepare_message: when earlier step outputs exist and the executor is an agent, team or workflow, the step gets ONLY the last step's deepest content and the workflow's original input is dropped; then optional workflow history is prepended as text (libs/agno/agno/workflow/step.py:1285-1297), and HITL extras appended (:1299-1317). The docstring says "combining message and previous step outputs" (:2727); the code replaces instead.

A12 When the calling front end sends its own tools with the run (AG-UI frontend tools), are they added to what the model may call? RULE · code
Answered how

A bare agent receives no client tools; when a run context carries them (in agno only AgentOS's AG-UI interface sets them), they are offered after the developer's tools, and a client tool reusing a developer tool's name is skipped.

Held by

libs/agno/agno/agent/_tools.py:226-228 (run_context.client_tools appended to the resolved tools).

B. Context: what the model sees (17 seats)

B1 Does the model get the developer's fixed system message, a framework-built one, or none at all? RULE · code
Answered how

With no system message given, agno builds one from the agent's settings; a developer-supplied system message replaces that whole build, including memories, knowledge instructions and JSON-output instructions. build_context=False sends none.

Held by

libs/agno/agno/agent/_messages.py:941-968 (a system_message replaces the whole build; it may be a callable, :949-954; build_context=False -> no system message, :966-968).

B2 Which of the agent's settings and which framework texts are written into the default system message? RULE · code
Answered how

A default agent's system message holds only what the developer set (description, role, instructions, expected output, additional context) plus the model provider's own lines and any toolkit instructions; every other block is switched on by its own setting or feature.

Held by

libs/agno/agno/agent/_messages.py:973-1210 (order: description, <your_role>, <instructions> (developer then model), <additional_information> (markdown, datetime, location, name, offload), tool instructions; state-variable substitution :1076-1081; then expected output, additional context, skills, memories, session summary, learnings, knowledge instructions, the model's own system text, JSON-output prompt, session state). Team twin libs/agno/agno/team/_messages.py (JSON block :411-421).

B3 Should {name} (and $name) placeholders in the system message and in the user's own message be replaced with values from the session state, dependencies, metadata and user id? RULE · code
Answered how

Yes, by default: a {key} or $key in the instructions or in the user's own text message is replaced by the matching session-state, dependency, metadata or user-id value. A default agent's session state already holds the run's session and run ids (and its user id, when there is one).

Held by

libs/agno/agno/agent/_messages.py:838-881 format_message_with_state_variables (ChainMap, first match wins: session_state, dependencies, metadata, user_id; {var} rewritten to ${var}, then string.Template.safe_substitute); applied to the system message (:956-961, :1076-1081, only the part built before :1076) and to a plain-text USER message (:1653-1658) when resolve_in_context is True (default, libs/agno/agno/agent/agent.py:287); session state seeded with current_user_id, current_session_id, current_run_id (libs/agno/agno/agent/_init.py:409-427, called at libs/agno/agno/agent/_run.py:451). Team twin libs/agno/agno/team/_messages.py:1609.

B4 Which earlier messages of this conversation are replayed to the model? RULING · code
Answered how

None by default; with history switched on (and a database) the model is replayed the last 3 runs of this session, or the count the developer sets, skipping team-member runs, runs in skip statuses and stored system messages.

Held by

libs/agno/agno/agent/_messages.py:1964-1998 (only when add_history_to_context, default False, libs/agno/agno/agent/agent.py:163) -> _history_for_run libs/agno/agno/agent/_messages.py:245-291 -> libs/agno/agno/session/agent.py:141-262 get_messages (last num_history_runs runs, which the constructor sets to 3 when neither limit is given, libs/agno/agno/agent/agent.py:601-602; skips member runs :195, runs in skip statuses :183-198, messages already tagged as history :171-172, and the system-role messages when system_message_role is not user/assistant/tool, libs/agno/agno/agent/_messages.py:1969-1971); tool calls trimmed by max_tool_calls_from_history (:1979-1980).

B5 Which remembered facts about this user are written into the system message? RULING · code
Answered how

Memory is off by default, so nothing is added. Once on, every memory stored for the run's user goes into every system message, with no cap or relevance filter; runs with no user id all read the one shared "default" user's memories.

Held by

libs/agno/agno/agent/_messages.py:1095-1133 (when add_memories_to_context, which set_memory_manager turns on whenever memory is on, libs/agno/agno/agent/_init.py:116-119, called when update_memory_on_run, enable_agentic_memory or a memory_manager is set, :484-485): every memory get_user_memories returns, libs/agno/agno/memory/manager.py:174-186; a run with no user id reads user "default" (libs/agno/agno/agent/_messages.py:1097-1098, libs/agno/agno/memory/manager.py:177-178).

B6 Is the stored summary of earlier turns added to the system message? RULE · code
Answered how

Off by default. With session summaries on, the session's stored summary, when one exists, is added to every system message; with compaction also on, the model reads two summaries of the same history, which agno warns about.

Held by

libs/agno/agno/agent/_messages.py:1135-1144 (when add_session_summary_to_context, auto-on with enable_session_summaries or a session_summary_manager, libs/agno/agno/agent/_init.py:180-183, and the session has a summary).

B7 Which saved learnings (user profile, user observations, learned knowledge) are recalled into the system message for this message? RULING · code
Answered how

Learning is off by default. With learning=True and a database, the user-profile and user-memory stores are recalled for the run's user and written into the system message with their usage guidance; without a database learning stays off, with a warning.

Held by

libs/agno/agno/agent/_messages.py:1146-1164 (when a LearningMachine was set up, libs/agno/agno/agent/_init.py:122-169, and add_learnings_to_context, default True) -> libs/agno/agno/learn/machine.py:493-534 build_context -> recall :901-945 (each enabled store's recall, given user, session, agent and the message text from _learning_message_text libs/agno/agno/agent/_messages.py:889-914).

B8 When the agent has a knowledge base, what does the system message tell the model about searching it? RULE · code
Answered how

Only when a knowledge base is attached: by default the system message tells the model to search it with search_knowledge_base before answering; with agentic filters on, it also lists the filter keys found in stored content for this user.

Held by

libs/agno/agno/agent/_messages.py:1166-1178 -> libs/agno/agno/knowledge/knowledge.py:5321-5347 build_context (search instructions; plus the valid filter keys, read from stored content scoped to the user, when agentic filters are on).

B9 Which documents does the knowledge base return, and how many, for the query the model (or the user's message) supplies? RULING · model
Answered how

Up to 10 documents from the knowledge base's search (after any configured reranking), narrowed by any filters and scoped to the run's user id when there is one; with no user id, every owner's documents are searched. A failing search looks like an empty one (a ValueError is raised instead).

Held by

libs/agno/agno/agent/_messages.py:2523-2643 get_relevant_docs_from_knowledge -> Knowledge.retrieve (libs/agno/agno/knowledge/knowledge.py:5771-5802) -> Knowledge.search :1187-1258 (vector db search with the embedder, max_results default 10 :93; optional query transformer :1236-1240 and reranker :1248; owner scope user_id, "None searches everything" :1199).

B10 Should the framework retrieve documents up front and append them to the user's message (classic RAG)? RULE · code
Answered how

Off by default; the model searches through a tool instead (B8, B9). When switched on, documents are retrieved for the user's raw text, before placeholders are filled, and appended to that message under <references>.

Held by

libs/agno/agno/agent/_messages.py:1619-1651 (retrieve on the raw user text) and :1663-1673 (<references> appended), when add_knowledge_to_context (default False, agent.py:178).

B11 When agentic filters are on, which metadata filters does the model put on its knowledge search? RULING · llm
Answered how

Off by default (enable_agentic_knowledge_filters=False). When on, the model may pass filters to search_knowledge_base; they are used only when the run carries no filters of its own (agent or call), and unknown keys are dropped with a warning.

Held by

libs/agno/agno/agent/_default_tools.py:128-196 search_knowledge_base(query, filters), filled by the agent's model (A1); filters merged by libs/agno/agno/utils/knowledge.py:7-36 (the run's own filters win outright when both exist, :26-28; a list-type run filter with model filters raises, :29-33), validated against known keys with validate_filters=True (libs/agno/agno/agent/_messages.py:2559-2581, invalid keys dropped with a warning).

B12 When the developer supplies their own retrieval function, what does it return for this query? RULING · external
Answered how

No custom retriever by default. When one is set it is called first and fully replaces agno's own search, and the model is given the search tool even if no knowledge base is attached.

Held by

libs/agno/agno/agent/_messages.py:2583-2609 (agent.knowledge_retriever called first, with agent, filters, run_context, query, num_documents, user id).

B13 Which skills (name, description, scripts, references) does the system message list? RULE · code
Answered how

No skills by default. When given, every loaded skill's name, description, script names and reference names are listed in every system message, with no selection; of two skills sharing a name, the last loaded wins.

Held by

libs/agno/agno/agent/_messages.py:1089-1093 -> libs/agno/agno/skills/agent_skills.py:90-148 get_system_prompt_snippet (every loaded skill); loading libs/agno/agno/skills/agent_skills.py:34-52, run once when Skills is constructed (:29-32), where a later skill with the same name replaces the earlier one (:44-46).

B14 Which tools (developer tools, framework tools, client tools) are offered to the model on this run, and which copy wins when two share a name? RULE · code
Answered how

A default agent offers exactly the developer's tools; framework tools join only when their feature is on, except knowledge search, which joins once a knowledge base or retriever is attached. On a name clash the first registered (the developer's) wins, with a warning.

Held by

libs/agno/agno/agent/_tools.py:202-323 get_tools (developer tools first, then client tools, then framework tools by setting: compaction search, filesystem, chat history, tool-call history, past sessions, agentic memory, offload read-back, learning, session-state update, knowledge search :304-314, add-to-knowledge, skills) -> parse_tools :473-649 (first registration of a name wins; later duplicates skipped with a warning :541-546, :574-578, :611-615; a callable that fails to parse is dropped with a warning :646-647).

B15 When the model calls a context provider's query_<id> tool (web, Slack, Gmail, Drive, wiki, database, MCP, workspace), what answer comes back? RULING · llm
Answered how

Absent by default. Only when the developer adds a provider's tools does the model get a query_<id> tool, answered by that provider's sub-agent (run with the caller's user, session, metadata and dependencies) or, without one, by the provider's own query code.

Held by

libs/agno/agno/context/provider.py:300-338 _query_tool / _query_chunks -> the provider's sub-agent (_aget_query_agent, :214-216, overridden per provider; run with the caller's user_id, session_id, metadata and dependencies, :222-239, :246-257), or the provider's own aquery when it has no sub-agent (:321-327). Wired only when the developer passes provider.get_tools() (:203-208) as tools; in tools mode that returns the provider's underlying tools instead of a query tool (:206-207).

B16 What does the team leader's system message say about each member and about how to delegate? RULE · code
Answered how

The leader sees each member's name, role and description (with an id for direct members only) plus mode-specific delegation rules; in the default coordinate mode it is told members see only the text it writes for them. Member tools are listed only with add_member_tools_to_context, off by default.

Held by

libs/agno/agno/team/_messages.py:287-319 _build_team_context -> roster :99-146 (id for top-level members only, name, role, description; tool names only with add_member_tools_to_context, default False, libs/agno/agno/team/team.py:187) and _get_mode_instructions libs/agno/agno/team/_messages.py:171-284 (per mode; what members see, :250-268).

B17 When the leader delegates, does the member get only the leader's task text, the user's raw message, the team's history, or other members' exchanges? RULE · code
Answered how

By default a member receives only the task text the leader writes for it, not the conversation. Separate settings send the user's raw message instead, the team's history, or other members' exchanges.

Held by

libs/agno/agno/team/_default_tools.py:529-566 in _setup_delegate_task_to_member: task = the leader's text unless determine_input_for_members is False, then the raw input (:546-550); team history added when add_team_history_to_members (:537-544); other members' exchanges and their media when share_member_interactions (libs/agno/agno/team/_tools.py:549-568); the member's own history when the member has add_history_to_context (libs/agno/agno/team/_default_tools.py:559-565). Defaults libs/agno/agno/team/team.py:122 (True), :145, :149 (False); the leader is told the same at libs/agno/agno/team/_messages.py:250-268.

C. Choosing the next action (15 seats)

C1 Given the conversation and the tools it can see, does the agent answer now, or call which tools with which arguments? RULING · llm
Answered how

At every turn the agent's model decides whether to answer or call tools; agno sends every registered tool and lets the model choose unless the integrator sets tool_choice.

Held by

the agent's model (integrator's choice), called per turn at libs/agno/agno/models/base.py:718 (_process_model_response, which calls _invoke_with_retry at libs/agno/agno/models/base.py:1125, then the provider invoke); tool calls read back at libs/agno/agno/models/base.py:742-749.

C2 When the model's tool-call arguments arrive as text, what object do they mean (including malformed or string-typed values)? RULING · code
Answered how

agno decodes the arguments as JSON (falling back to Python-literal syntax), returns an error to the model for anything that is not an object (an empty list means no arguments), and turns top-level string values 'none', 'null', 'true' and 'false' into null and booleans.

Held by

libs/agno/agno/models/base.py:2054-2090 get_function_calls_to_run (called by _prepare_function_calls, :3119): each tool call goes through get_function_call_for_tool_call (libs/agno/agno/utils/tools.py:8-24, missing arguments default to "{}") and get_function_call (libs/agno/agno/utils/functions.py:10-75: json.loads, then ast.literal_eval; an empty list means no arguments; any other non-object is refused; top-level none/null/true/false strings converted); a decode error is sent back as the tool's result and the call is skipped (libs/agno/agno/models/base.py:2079-2088).

C3 When a tool call streams in pieces, which pieces belong to which call? RULE · code
Answered how

For streams from the OpenAIChat model class (not the default OpenAIResponses model), agno stitches tool-call fragments together by their index; a fragment with no index is put on call 0, so parallel calls from a provider that omits it merge into one (#11148).

Held by

libs/agno/agno/models/openai/chat.py:750-787 (OpenAIChat.parse_tool_calls): pieces grouped by index, a missing index read as 0 (:762), argument fragments concatenated (:781-782). Each provider adapter has its own assembler (e.g. libs/agno/agno/models/cerebras/cerebras.py:587 filters incomplete calls); only OpenAIChat was read.

C4 Do the model's arguments fit the tool's declared parameter types, and if not exactly, may they be coerced? RULING · code
Answered how

agno checks the arguments against the tool function's type hints with pydantic in lax mode, so near-misses such as the text '5' for an integer are coerced; a call that still does not fit is not run and the model gets the error. A tool with a parameter named, or typed as, agent or team is not checked at all.

Held by

pydantic validate_call around the tool's callable (libs/agno/agno/tools/function.py:1746-1822, lax mode, arbitrary_types_allowed), applied via _wrap_callable (libs/agno/agno/tools/function.py:1723) from process_entrypoint (libs/agno/agno/tools/function.py:1718) and _build_from_callable (libs/agno/agno/tools/function.py:1588). Skipped for coroutines on pydantic < 2.10 (libs/agno/agno/tools/function.py:1774-1779), for any callable with a parameter NAMED agent or team (:1785-1791) and for any parameter typed to reach Agent/Team (:1793-1818); each of these skips leaves the WHOLE callable unvalidated. A callable already wrapped keeps its existing validation wrapper (:1781-1783).

C5 Should the provider be made to enforce each tool's schema strictly at sampling time? RULING · code
Answered how

Off by default: tool schemas become strict only when the run has an output_schema, no parser_model, and a model with native structured outputs, or when a tool author sets strict on that tool.

Held by

libs/agno/agno/agent/_tools.py:517-525 (strict when output_schema is set, no parser_model, structured_outputs true or use_json_mode false, and the model supports native structured outputs); a Toolkit function's or Function's own strict wins (libs/agno/agno/agent/_tools.py:552-556, libs/agno/agno/agent/_tools.py:582-590), a plain callable takes the run-wide value (libs/agno/agno/agent/_tools.py:620, :640-641); schema rewrite at libs/agno/agno/tools/function.py:1824-1880.

C6 After this turn, is the run over, or does the agent take another turn? RULING · llm
Answered how

The model decides: agno keeps calling it while it asks for tools and stops when a reply has none, with no turn limit by default. tool_call_limit refuses calls past the limit but never ends the loop itself.

Held by

the model, by emitting tool calls or not: the loop continues after every batch of tool results (libs/agno/agno/models/base.py:870-871) and ends when a reply has no tool calls (libs/agno/agno/models/base.py:873-874). Code overrides end it early: a result flagged stop-after (libs/agno/agno/models/base.py:838-840, C7/C8), a tool needing confirmation, external execution or user input (libs/agno/agno/models/base.py:851-861), an unresolved requirement on the run (libs/agno/agno/models/base.py:866-868). Same structure in aresponse (libs/agno/agno/models/base.py:884, stop check :1062), response_stream (libs/agno/agno/models/base.py:1367, :1570), aresponse_stream (libs/agno/agno/models/base.py:1648, :1851).

C7 Should the run end right after this tool's result, with no further model turn (and is the tool's output the answer)? RULING · code
Answered how

No tool ends the run by default. A tool author can set stop_after_tool_call so the loop ends after the batch containing that tool, and show_result so the tool's output becomes part of the answer text.

Held by

the tool's stop_after_tool_call flag (default False, libs/agno/agno/tools/function.py:1259), copied onto the result (libs/agno/agno/models/base.py:2199) and read by the loop (:838-840); the tool's show_result flag (default False, libs/agno/agno/tools/function.py:1257) makes the tool's own output part of the answer text (libs/agno/agno/models/base.py:2357-2358, accumulated at :809-814).

C8 May a tool (or a tool hook) redirect the run mid-flight: stop it, or inject new messages and continue? RULING · external
Answered how

Only when the integrator's own tool or hook raises StopAgentRun or RetryAgentRun; agno then adds that exception's messages after the tool results and either ends the loop (Stop) or lets the model continue (Retry). A tool flagged stop-after also ends the run, without raising (C7).

Held by

libs/agno/agno/models/base.py:2256-2273 (run_function_call catches the integrator's StopAgentRun / RetryAgentRun, libs/agno/agno/exceptions.py:64-93, re-raised out of the call at libs/agno/agno/tools/function.py:2706-2723), turns its user_message / agent_message / messages into extra messages (libs/agno/agno/models/base.py:98-131) placed after the tool results (:2577-2579), and with stop_execution ends the loop (:2389).

C9 Before the main model answers, what does a separate reasoning model think about the task? RULING · llm
Answered how

Off by default. When reasoning_model is set to a model agno recognises as a native reasoning model, that model reads the run's input first and its thinking is appended to what the main model sees.

Held by

ReasoningManager.reason (libs/agno/agno/reasoning/manager.py:859-904), from handle_reasoning (libs/agno/agno/agent/_response.py:75-88) at run step 8 (libs/agno/agno/agent/_run.py:546); the reasoning model runs as a fresh inner Agent (libs/agno/agno/reasoning/helpers.py:7-24) unless reasoning_agent is given (libs/agno/agno/reasoning/manager.py:178-181); per-provider calls e.g. libs/agno/agno/reasoning/openai.py:43-79.

C10 Is the configured reasoning model a "native reasoning model" agno will use? RULE · code
Answered how

agno decides from the model's class name and substrings of its id (e.g. o3, gpt-5, deepseek-r1); a model it does not recognise gets a warning and no reasoning step. Only reached when reasoning_model is set.

Held by

libs/agno/agno/reasoning/manager.py:143-176 dispatches to per-provider checkers, e.g. libs/agno/agno/reasoning/openai.py:14-40: class name in a fixed list AND the id CONTAINS o4, o3, o1, gpt-5 or gpt-6 (substring), or an OpenAI-compatible id containing deepseek-r1, minimax-m2, etc.

C11 In what format is the model asked to return a structured answer? RULING · code
Answered how

Absent by default. With an output_schema, a model class marked for native structured outputs gets the schema itself; other models get a JSON-schema wrapper or a plain 'JSON object' request, and the text is parsed afterwards (C12).

Held by

libs/agno/agno/agent/_response.py:857-896 (get_response_format): the schema itself for models with native structured outputs unless use_json_mode (then {"type":"json_object"}); for models marked supports_json_schema_outputs, a json_schema wrapper unless structured_outputs is truthy and use_json_mode false, which sends NO format (libs/agno/agno/agent/_response.py:891-892); otherwise {"type":"json_object"}. No response format when a parser_model is set (libs/agno/agno/agent/_run.py:1454).

C12 Does the model's final text become the structured object the caller asked for, and how hard does agno try? RULING · code
Answered how

Absent by default. With an output_schema, agno parses the final text into it, repairing and even merging JSON fragments (a dict schema is only JSON-decoded, never checked against the schema); if every attempt fails it keeps the raw text and the run still reports completed.

Held by

libs/agno/agno/agent/_response.py:904-938 (convert_response_to_structured_format, run step 12 at libs/agno/agno/agent/_run.py:621) with the salvage parser libs/agno/agno/utils/string.py:195-249: (1) strict JSON, (2) after _clean_json_content (:117-152: strips code fences, unwraps markdown around keys, deletes control characters, re-escapes quotes in values), (3) the single extracted JSON object, (4) a MERGE of every JSON object found, field by field, lists concatenated and the last scalar winning (:155-192). A dict schema is only JSON-parsed, never checked against the schema (libs/agno/agno/agent/_response.py:913-921).

C13 Given the main model's free-text answer, what structured object does a second "parser" model say it means? RULING · llm
Answered how

Absent by default. When an integrator sets both parser_model and output_schema, a second model turns the main answer into the structured object and its reply replaces the main answer.

Held by

agent.parser_model, called at libs/agno/agno/agent/_response.py:349-395 after the main loop (libs/agno/agno/agent/_run.py:585-587).

C14 Should a different model rewrite the final answer, and what does it say? RULING · llm
Answered how

Absent by default. When output_model is set, a second model writes the final answer from the same conversation, and its text replaces the main model's answer unchecked.

Held by

agent.output_model, called at libs/agno/agno/agent/_response.py:582-605 (libs/agno/agno/agent/_run.py:582).

C15 What follow-up prompts should be suggested to the user after the answer? RULING · llm
Answered how

Off by default (followups=False). When enabled and the answer has content, a model (the followups model, else the agent's own) suggests follow-up prompts; a failure leaves them empty and never changes the answer.

Held by

generate_followups (libs/agno/agno/agent/_response.py:1885-1892), run step 12b (libs/agno/agno/agent/_run.py:624); model chosen at libs/agno/agno/agent/followup.py:146-147 (the FollowupConfig model, else followup_model, else the agent's own model); messages _build_followup_messages (libs/agno/agno/agent/_response.py:1737-1796), parse _parse_followups_response (libs/agno/agno/agent/_response.py:1799-1824).

D. Gatekeeping: may this action run? (24 seats)

D1 May a tool call the model issued run, without a person being asked? RULING · code
Answered how

Code runs every tool call the model makes without asking anyone; it pauses for a person only on tools the integrator marked requires_confirmation, requires_user_input or external_execution, or on the ask-the-person tools when the integrator adds them (D7).

Held by

libs/agno/agno/models/base.py:2431-2575 (sync run_function_calls: a call pauses only when its Function has requires_confirmation :2450, requires_user_input :2463, external_execution :2550, or is the get_user_input / ask_user tool :2484, :2521; otherwise run_function_call :2570) and libs/agno/agno/models/base.py:2659-2823 (async, same flags :2662, :2674, :2694, :2736, :2770; unflagged calls run in parallel via asyncio.gather :2821).

D2 Approve or reject this paused tool call? RULING · human
Answered how

Absent by default. When a tool is marked for confirmation the run pauses and returns; the integrator's code collects the person's yes or no and calls continue_run, and a missing answer counts as a no.

Held by

libs/agno/agno/run/requirement.py:109-123 (RunRequirement.confirm() / .reject(), set by the person behind the integrator's code that calls continue_run). Applied at libs/agno/agno/agent/_tools.py:1084-1095 (sync; stream libs/agno/agno/agent/_tools.py:1136-1148; async from :1178): confirmed and no result yet → run_tool; anything else → reject_tool_call (libs/agno/agno/agent/_tools.py:961-971, error text "Function call was rejected by the user"). Team: a member's pause is copied to the team run (libs/agno/agno/team/_tools.py:645-672) and decided at libs/agno/agno/team/_run.py:5412,5472,5545,5626 (same branch, copied).

D3 If nobody answers a paused run, does anything proceed without a person? RULE · code
Answered how

Nobody: a paused agent run is stored and returned, then waits until some caller continues it; nothing in the library answers for the person or expires the pause.

Held by

libs/agno/agno/agent/_run.py:236-257 (handle_agent_run_paused: status paused, stored, returned); libs/agno/agno/run/requirement.py:13-187 (a RunRequirement has no deadline field).

D4 When one tool call in a turn needs a person, do the other calls in the same turn still run? RULING · code
Answered how

Yes. In the turn where one flagged call pauses, the model's other unflagged calls still run before the person answers: one after another in a sync run, in parallel in an async run.

Held by

libs/agno/agno/models/base.py:2562-2575 (sync: a paused call continues and the loop runs the next call) and libs/agno/agno/models/base.py:2802-2823 (async: every unflagged call in the batch runs in parallel).

D5 Has this approval-required tool's approval record been resolved before the run continues? RULE · code
Answered how

Absent by default (needs @approval and a database). An agent continue by id that passes no answers is refused until every approval record is resolved; one that passes answers skips this check. A team continue refuses a pending approval only when answers are passed.

Held by

libs/agno/agno/run/approval.py:805-835 (check_and_apply_approval_resolution; async :838-861), called from libs/agno/agno/agent/_run.py:3538-3552 (sync; async libs/agno/agno/agent/_run.py:4866-4884, :5385-5403) and libs/agno/agno/team/_run.py:7643-7666 (async libs/agno/agno/team/_run.py:9460-9477, :9943-9960). Record created at pause: libs/agno/agno/run/approval.py:219-279, called from libs/agno/agno/agent/_run.py:248 and libs/agno/agno/team/_hooks.py:100-217. Each tool paired with its own record: libs/agno/agno/run/approval.py:570-631.

D6 What values does the person give a user-input tool, and does the tool then run with them? RULING · human
Answered how

Absent by default. When a tool is marked requires_user_input, the run pauses for the listed fields; on continue the person's values (through the integrator's code) are copied into the tool's arguments and the tool runs.

Held by

libs/agno/agno/run/requirement.py:125-144 (RunRequirement.provide_user_input, called by the person behind the continue_run caller). Applied at libs/agno/agno/agent/_tools.py:1114-1121 (copies each field's value into the tool arguments via handle_user_input_update, libs/agno/agno/agent/_tools.py:767-771, then runs the tool).

D7 Should the agent stop and ask the person? RULING · llm
Answered how

Absent by default (needs UserControlFlowTools or UserFeedbackTools). With either toolkit on, the model decides when to stop and ask, and the run pauses for the person.

Held by

libs/agno/agno/models/base.py:2484-2518 (the agent's model, Agent.model, calls get_user_input, libs/agno/agno/tools/user_control_flow.py:35, or ask_user, libs/agno/agno/tools/user_feedback.py:47; intercepted BY TOOL NAME here and at libs/agno/agno/models/base.py:2521-2547; async libs/agno/agno/models/base.py:2694-2768).

D8 What does the person answer to the agent's questions? RULING · human
Answered how

Absent by default. When the model asks, the person's answers (through the integrator's code) go back to the model as the tool's result and the run continues; unanswered fields arrive as empty values.

Held by

libs/agno/agno/run/requirement.py:125-171 (RunRequirement.provide_user_input / provide_user_feedback, called by the person behind the continue_run caller). Applied at libs/agno/agno/agent/_tools.py:1102-1112 (answers sent to the model as the tool result, libs/agno/agno/agent/_tools.py:774-822).

D9 Who runs this tool, and what result comes back to the model? RULING · external
Answered how

Absent by default. A tool marked external_execution never runs inside agno: the run pauses, the integrator runs it, and whatever result they pass back is what the model sees.

Held by

libs/agno/agno/models/base.py:2550-2560 (pause for a Function with external_execution=True; the integrator's code outside agno runs it); result accepted at libs/agno/agno/agent/_tools.py:732-764 (handle_external_execution_update).

D10 Does this input look like a prompt-injection attempt? RULING · code
Answered how

Absent by default (add it to pre_hooks). When on, it refuses a run whose user input contains any of 18 fixed phrases such as "ignore previous instructions"; tool results, retrieved documents and history are not checked.

Held by

libs/agno/agno/guardrails/prompt_injection.py:16-52 (PromptInjectionGuardrail.check: a lowercased substring match against 18 phrases, :17-36), run as a pre-hook at libs/agno/agno/agent/_hooks.py:98-151 (normalized to a bound check at libs/agno/agno/utils/hooks.py:90-94).

D11 Does this input contain personal data, and is it masked or refused? RULING · code
Answered how

Absent by default. When on, it refuses user input matching US SSN, card, email or phone patterns, or with mask_pii stars the matches out; only the user's input text is checked.

Held by

libs/agno/agno/guardrails/pii.py:23-78 (PIIDetectionGuardrail: four regexes :35-42 plus custom :44-54; mask_pii rewrites the input :64-72, else raises :74-78), run as a pre-hook (libs/agno/agno/agent/_hooks.py:98-151).

D12 Does this input violate the moderation policy? RULING · external
Answered how

Absent by default. When on, OpenAI's moderation service decides whether the user's input (text, plus images) is refused; if the call fails for any reason, the input goes through unchecked.

Held by

libs/agno/agno/guardrails/openai.py:54-98 (OpenAIModerationGuardrail.check sends the input to OpenAI's moderation endpoint, model omni-moderation-latest by default :28; call :73, async :119; decision mapped :77-98).

D13 May this run's input proceed, per the integrator's own pre-hook or guardrail? RULING · external
Answered how

Absent by default. An integrator's pre-hook can refuse a run's input by raising agno's input-check or output-check error; any other failure inside the hook is logged and the run goes on.

Held by

libs/agno/agno/agent/_hooks.py:43-151 (runs the integrator callables in Agent.pre_hooks, any BaseGuardrail or function; async libs/agno/agno/agent/_hooks.py:154-259), called at libs/agno/agno/agent/_run.py:466-484 before tools and messages are built (:486-519). Team: normalized at libs/agno/agno/team/_run.py:1969 and run by its own hook runner.

D14 May this run's output be returned, per a post-hook check? RULING · external
Answered how

Absent by default. A post-hook can raise agno's output-check error, which marks the run as an error; the generated text is replaced by the error only when it was empty, and a streamed run has already sent it.

Held by

libs/agno/agno/agent/_hooks.py:262-360 (runs the integrator callables in Agent.post_hooks / Team.post_hooks; async libs/agno/agno/agent/_hooks.py:363-461), called at libs/agno/agno/agent/_run.py:628-639 (stream :1103-1114).

D16 May this tool call proceed, and with what arguments, per the tool hooks? RULING · external
Answered how

Absent by default. Tool hooks wrap every tool call and can refuse it, change its arguments or replace its result (an Agent's tool_hooks replace a tool's own, #10754); under synchronous run(), hooks written async are skipped with a warning.

Held by

libs/agno/agno/tools/function.py:2508-2537 (sync chain over the integrator middleware in Agent.tool_hooks, libs/agno/agno/agent/agent.py:208, or Function.tool_hooks; async chain libs/agno/agno/tools/function.py:2833-2865); a hook refuses by not calling next_func.

D17 May this permission-gated operation run for this caller? RULE · external
Answered how

Absent by default. Without a trusted RunAuthorization set by the application (AgentOS sets one), there is no authority to check against, and permission-gated tools run unchecked (a user id on the run is not authority).

Held by

libs/agno/agno/run/authorization.py:46-50 (RunAuthorization.require asks an AuthorizationProvider from agno.os.authz.provider, TYPE_CHECKING import at :19-20), applied by requires_permission (libs/agno/agno/run/authorization.py:117-151) and activated per tool by _tool_authorization (libs/agno/agno/run/authorization.py:154-167) from libs/agno/agno/tools/function.py:2598-2609.

D18 May the model set an argument the framework injects (identity, run context, media)? RULE · code
Answered how

No, for parameters agno fills itself (agent, team, run context, media, identity): the model's value is dropped before any hook or the tool sees it, unless the tool's own schema declares that name; identity names are dropped regardless.

Held by

libs/agno/agno/tools/function.py:2620-2623 (call of _drop_injected_overrides, before any hook runs; body libs/agno/agno/tools/function.py:2384-2436).

D19 May this workflow step run (person's confirmation)? RULING · human
Answered how

Absent by default. A step with HumanReview(requires_confirmation=True) pauses until a person answers; a no skips that step and the workflow continues unless on_reject says otherwise (cancel, else-branch or retry).

Held by

libs/agno/agno/workflow/utils/hitl.py:73-126 (step_pause_status decides the pause from HumanReview, libs/agno/agno/workflow/types.py:64-106); the person behind Workflow.continue_run answers (libs/agno/agno/workflow/workflow.py:6670-6672), and a rejected step follows its on_reject (libs/agno/agno/workflow/workflow.py:6719-6731).

D21 Does this workflow step's output need a person's review? RULING · external
Answered how

Absent by default. An integrator can make output review conditional with a function of the step's output; if that function raises, the output passes without review (#11284).

Held by

libs/agno/agno/workflow/utils/hitl.py:299-317 (evaluates the integrator predicate HumanReview.requires_output_review when it is callable, libs/agno/agno/workflow/types.py:94).

D22 May the agent read, write or run in this path of the workspace? RULING · code
Answered how

Absent by default (the Workspace toolkit is opt-in). When used, it refuses paths outside its root (default: the process's working directory) and paths matching a default credential-and-noise list, and asks before any write, edit, move, delete or shell command. An approved shell command starts in the root but is not confined to it.

Held by

libs/agno/agno/tools/workspace.py:597-620 (Workspace._resolve: root-escape check :608-611, exclude patterns :612-619); default partitions (reads auto, writes and shell confirm) at :313-319, :416-446.

D23 May PythonTools touch this file path? RULE · code
Answered how

Absent by default. PythonTools keeps its file helpers inside a base folder (default: the working directory), but the code it runs can do anything the process can, and it asks nobody unless the integrator adds confirmation.

Held by

libs/agno/agno/tools/python.py:103-105 (_check_path against base_dir when restrict_to_base_dir=True, default libs/agno/agno/tools/python.py:50; same check at libs/agno/agno/tools/python.py:139-141 and libs/agno/agno/tools/python.py:164-167).

D24 Is this file-system path one the agent may address? RULE · code
Answered how

Absent by default. agno's file-system store refuses paths with . or .. segments, control characters, backslashes or a trailing slash (a leading slash is stripped), so a path cannot climb out of its namespace.

Held by

libs/agno/agno/fs/_paths.py:39-57 (control characters, backslashes, trailing slashes, ./.. segments refused) and the local backend root at libs/agno/agno/fs/local.py:28.

E. Execution and observation (13 seats)

E1 Do the tool calls in one model reply run one at a time, or all at once? RULING · code
Answered how

With run() agno runs one reply's tool calls one after another; with arun() it runs them all at once, sync tools on worker threads. No setting chooses this; the entry point does.

Held by

chosen by the entry point, not by a setting: sync run() executes them in order (libs/agno/agno/models/base.py:2431-2575, run_function_calls); async arun() runs every non-paused call concurrently with asyncio.gather(..., return_exceptions=True) (libs/agno/agno/models/base.py:2802-2823, arun_function_calls), sync callables on worker threads (libs/agno/agno/models/base.py:2608-2610).

E2 When a tool fails, what does the model see, and does the run go on? RULING · code
Answered how

The error text goes back to the model as the tool's result and the model decides what next; the run does not fail. Toolkits that return 'Error: ...' strings instead of raising are recorded as successful calls.

Held by

FunctionCall._execute (libs/agno/agno/tools/function.py:2713-2717): any exception becomes a failed result carrying str(e); the loop sends it to the model as a tool message flagged tool_call_error=True (libs/agno/agno/models/base.py:2192-2199); a generator tool that fails mid-iteration is marked failed and the model gets the error text, not the partial output (libs/agno/agno/models/base.py:2322-2327, async libs/agno/agno/models/base.py:2950-2959, :3013-3018; content chosen at libs/agno/agno/models/base.py:2194). Control exceptions are C8; cancellation re-raises (libs/agno/agno/tools/function.py:2711-2712).

E3 When the model provider returns an error, should the same call be retried? RULING · code
Answered how

agno does not retry a failed model call by default (retries=0); apart from one retry with guidance for a malformed Gemini reply (E6), any retry comes from the provider SDK's own client default where the adapter leaves max_retries unset (the default OpenAIResponses model and OpenAIChat among them).

Held by

Model._invoke_with_retry and its three siblings (libs/agno/agno/models/base.py:226-272, :274-321, :323-373, :375-427) with _is_retryable_error (:199-224: never 400/401/403/404/413/422, never a context-window error by class or message pattern; everything else retryable). retries default 0 (:178), fixed 1s delay, optional doubling (:180-197). Beneath it, the vendor SDK's own client retry applies wherever the agno adapter leaves max_retries unset (e.g. libs/agno/agno/models/openai/chat.py:83, dropped when None at :121-127).

E4 When a whole run fails, should agno start the run over from the beginning? RULING · code
Answered how

No: by default a failed run is not restarted (retries=0). With retries set, any exception other than cancellation or a guardrail check restarts the whole run, including tool calls that already succeeded.

Held by

libs/agno/agno/agent/_run.py:426-437 and :727-737 (Agent.retries, default 0, libs/agno/agno/agent/agent.py:308): any non-cancel, non-guardrail exception restarts the run at step 1 after a delay; same pattern at libs/agno/agno/agent/_run.py:834, :1571, :2179 and the continue paths :3697, :3915, :4724, :5245.

E5 When the main model fails, should a different model take over (or should the request first be shrunk)? RULING · code
Answered how

No fallback model is configured by default, so a model error that survives the model's own retries (E3, E6) fails the run. One shrink-and-retry of the same model after a "context too long" error happens only with compaction=True (or a Compaction set to on_context_overflow=True, F7).

Held by

call_model_with_fallback (libs/agno/agno/models/fallback.py:203-242, async and stream siblings from libs/agno/agno/models/fallback.py:245) and get_fallback_models (libs/agno/agno/models/fallback.py:121-156): rate-limit errors go to on_rate_limit, context-window errors to on_context_overflow, other errors to on_error except 4xx client errors other than 429/529 (libs/agno/agno/models/fallback.py:143-155). Before any fallback, a context-window error triggers one 'shrink and retry the same model' attempt (libs/agno/agno/models/fallback.py:219-227) through _recompact_after_overflow (libs/agno/agno/agent/_messages.py:608-644, passed at libs/agno/agno/agent/_run.py:557-559).

E6 When Gemini emits a malformed function call, should agno ask it again with a corrective hint? RULING · code
Answered how

Only for Gemini models: a reply ending with finish reason MALFORMED_FUNCTION_CALL is retried once with a fixed corrective user message (retry_with_guidance default True), then the call fails with an error.

Held by

libs/agno/agno/models/google/gemini.py:1242-1249 raises RetryableModelProviderError on finish reason MALFORMED_FUNCTION_CALL when retry_with_guidance (default True, libs/agno/agno/models/base.py:185); the stream parser does the same (libs/agno/agno/models/google/gemini.py:1410); libs/agno/agno/models/base.py:255-269 appends MALFORMED_FUNCTION_CALL_GUIDANCE (libs/agno/agno/models/google/utils.py:132) as a temporary user message and retries, at most retry_with_guidance_limit = 1 time (libs/agno/agno/models/base.py:187).

E7 In what form does a tool's result reach the model? RULING · code
Answered how

agno turns whatever a tool returns into text with str() (a dict reaches the model as its Python repr) and sends all of it, with no size limit by default; media a tool produces goes to the model in a follow-up user message.

Held by

libs/agno/agno/models/base.py:2281-2358 (sync) and :2977-3058 (async): a plain return value is converted with str() (:2355, :3055), so a dict or list reaches the model as its Python repr, not JSON; a ToolResult gives its content plus media (:2340-2353); a generator's items are concatenated, with nested agent/team/workflow run events contributing only their content (:2281-2336). Media is moved out of the tool message into a follow-up USER message "The tool call above generated the attached media." when send_media_to_model (default True, libs/agno/agno/agent/agent.py:236) (libs/agno/agno/models/base.py:3152-3197).

E9 May a stored earlier result answer this call instead of running it (tool cache, model-reply cache)? RULING · code
Answered how

Off by default for both: tool results are cached only for tools with cache_results=True, model replies only with cache_response=True. A cached tool's key includes user and session only when the tool takes a run context.

Held by

tool cache: libs/agno/agno/tools/function.py:2628-2694 (key from tool name, arguments and the run context's identity, :1915-1942; files under gettempdir()/agno_cache by default, :1944-1956; TTL 3600s, :1304); bypassed for authorization-sensitive tools (:2631-2636) and attached media (:2637-2639). Model-reply cache: libs/agno/agno/models/base.py:682-689, :878-880 (cache_response, default False, :172).

E10 Is the model's reply complete, or was it cut off or refused? RULE · code
Answered how

agno does not stop or flag a reply cut off by the token limit or refused: whatever text came back is the answer, and the run reports completed (the default Responses model only logs a warning on an "incomplete" reply).

Held by

no reader: the shared loop ends on 'no tool calls' alone (libs/agno/agno/models/base.py:873-874) and never looks at a finish or stop reason (Grep finish_reason|stop_reason|truncat over libs/agno/agno/models/base.py -> 0; positive control tool_calls in the same file -> 22). The OpenAI chat parser reads finish_reason only for metrics (libs/agno/agno/models/openai/chat.py:802); Claude reads stop_reason only for "tool_use" (libs/agno/agno/models/anthropic/claude.py:1058); the OpenAI Responses non-streamed path logs a warning on incomplete and continues, in every call, not only in background mode (libs/agno/agno/models/openai/responses.py:925-929). The run is then marked completed (libs/agno/agno/agent/_run.py:665).

E11 Which part of the model's text is hidden reasoning and which is the answer? RULE · code
Answered how

The default OpenAIResponses model keeps the provider's reasoning summary apart from the answer. For OpenAI-chat replies (non-streamed) and before structured parsing, the text inside think-tags (or before a lone </think>) becomes reasoning and only what follows </think> stays the answer; Ollama replies are split this way only when they contain <think>.

Held by

extract_thinking_content applied to a non-streamed OpenAI-chat reply before any parsing (libs/agno/agno/models/openai/chat.py:831-836), to an Ollama chat reply only when it contains <think> (libs/agno/agno/models/ollama/chat.py:366-374), and before structured parsing (libs/agno/agno/utils/string.py:202-205, libs/agno/agno/utils/string.py:257-260); the rule itself at libs/agno/agno/utils/reasoning.py:12-30: everything up to the first </think> (from <think> if present, else from the start) is reasoning, text before <think> is dropped, the rest is the answer.

E12 When a run fails, does the caller get an exception or a normal-looking result? RULING · code
Answered how

Agent.run() does not raise when a run fails: it returns a result whose status is 'error' and, if the run produced no content, whose content is the error message.

Held by

libs/agno/agno/agent/_run.py:727-758 (and the guardrail branch :692-711): the run is marked status=error, the error text is put into content if empty, the run is stored, and RunOutput is RETURNED, not raised; same pattern in the async and continue paths.

E13 How much of a stored file may one FileSystem read return, and what does the agent see when a store is full? RULING · code
Answered how

Absent by default (filesystem=None). With a FileSystem attached, a whole-file read over 100,000 characters is refused with a hint to read a range (ranged reads are not capped), and a listing shows at most 200 folders and 200 files.

Held by

libs/agno/agno/fs/toolkit.py:47 (_MAX_READ_CHARS 100,000: a whole-file read above it is refused with a hint to read a range, :274-280); :45 (_MAX_DIR_ENTRIES 200, listing truncated with an "...and N more" row, :365-388); quota messages (:226-237) instruct the model "Never overwrite or discard records you might still need to make room; if nothing is safely disposable, stop and report that storage is full."

E14 How much of a shell command's output does the coding toolkit return, and how long may a command run? RULING · code
Answered how

Only when an integrator adds CodingTools and enables run_shell (off by default): output past 2,000 lines or 50 KB is cut (the full output is saved to a temporary file whose path is returned), and a command stops after the model's timeout argument, else 120 seconds.

Held by

libs/agno/agno/tools/coding.py:234-263 (_truncate_output: 2,000 lines / 50,000 bytes, libs/agno/agno/tools/coding.py:144-145; on truncation the full output is written to a temp file whose path is appended, libs/agno/agno/tools/coding.py:647-658); run_shell is off by default (enable_run_shell=False, libs/agno/agno/tools/coding.py:150) and takes a model-supplied timeout, falling back to shell_timeout 120s (libs/agno/agno/tools/coding.py:588, :612, :146).

F. Limits and memory (13 seats)

F1 When an agent keeps asking for tools, when are further tool calls refused? RULING · code
Answered how

No limit by default: code runs every tool call the model asks for. With tool_call_limit set, calls past it are not run; the model is told so and asked again, and the run goes on.

Held by

libs/agno/agno/models/base.py:2431-2445 (run_function_calls: counts calls, past the limit appends create_tool_call_limit_error_result, libs/agno/agno/models/base.py:2222-2230); limit read from Agent.tool_call_limit (libs/agno/agno/agent/agent.py:198, default None) and passed at libs/agno/agno/agent/_run.py:563.

F3 In task mode, when does the team leader's planning loop stop if the goal is never marked complete? RULING · code
Answered how

Only in task mode (mode=TeamMode.tasks; default is coordinate): after 10 leader turns by default. A run that hits the cap is still marked completed, with only a log warning that tasks were left unfinished.

Held by

libs/agno/agno/team/_run.py:371 (for iteration in range(team.max_iterations)), exhaustion at :452-456; default max_iterations: int = 10 (libs/agno/agno/team/team.py:124).

F4 Has this session's history grown enough that older turns should be folded into a summary? RULING · code
Answered how

Off by default. compaction=True never folds on size, only after a too-long rejection (F7). A Compaction(...) object folds once replayed history reaches 150,000 tokens, counted locally unless told otherwise.

Held by

libs/agno/agno/compaction/compaction.py:258-277 (should_compact: token count >= compact_at_tokens, default 150,000 :56,122), applied at run start in libs/agno/agno/agent/_messages.py:700-716; the fold-worth guard _worth_compacting libs/agno/agno/compaction/compaction.py:825-861 (off unless enforce_min_fold_ratio, :152).

F5 When history is folded, which recent turns are kept word for word? RULING · code
Answered how

Off by default. When a fold happens, the last five runs stay word for word (fewer when they exceed the token tail limit or after a too-long rejection); a turn is never split and the newest question is never folded.

Held by

libs/agno/agno/compaction/compaction.py:397-454 (boundary_for / _run_tail_boundary: keep last uncompacted_runs=5 runs by default :65,135, capped by a token tail limit :364-376, never inside the newest exchange :415-426), pair-safe cut in libs/agno/agno/compaction/_cut.py (choose_boundary).

F6 What should the summary of the folded conversation say? RULING · llm
Answered how

Off by default. When compaction folds, the agent's own model (unless Compaction.model is set) writes the summary from a fixed prompt; it replaces the older turns in what the model sees, while the stored session keeps the originals.

Held by

libs/agno/agno/compaction/compaction.py:628-645 (_summarize, calling Compaction.model); the model defaults to the agent's (libs/agno/agno/agent/_init.py:243-244).

F7 After the provider rejects a request as too long, should agno fold and retry? RULING · code
Answered how

Only with compaction=True (where it is the only trigger) or a Compaction set to on_context_overflow=True: agno folds the oldest turns of the rejected request and retries it once. Off by default; with no safe cut the original error stands.

Held by

libs/agno/agno/agent/_messages.py:608-644 (_recompact_after_overflow), planner :497-554 (shrinks the kept tail one run at a time), shrink check :557-585; wired at libs/agno/agno/agent/_run.py:557-559 (recover_from_overflow).

F8 When a compaction summary runs over its length budget, what is cut? RULING · code
Answered how

Only when a compacted_token_budget is set (none by default): the summary's end is cut at line breaks, keeping whole leading lines and words, and a note says it was cut. Nothing checks that the model put the important parts first.

Held by

libs/agno/agno/compaction/compaction.py:566-626 (_fit_to_budget: keeps leading whole lines, drops trailing ones, appends SUMMARY_CUT_NOTE).

F9 Have enough raw tool results piled up that they should be compressed? RULING · code
Answered how

Off by default. With compress_tool_results=True, before each model call code compresses every uncompressed tool result once three of them have piled up (or a token limit, if set, is reached).

Held by

libs/agno/agno/compression/manager.py:69-103 (should_compress: 3 uncompressed tool results by default :62-64, or a token limit); called at the top of every model turn libs/agno/agno/models/base.py:705-712.

F10 What part of a tool result is worth keeping when it is compressed? RULING · llm
Answered how

Off by default. With compression on, the agent's model (unless the manager names another) rewrites each tool result to its facts, dates, names and ids, and later model calls see the rewrite instead of the original.

Held by

libs/agno/agno/compression/manager.py:105-140 (_compress_tool_result, calling CompressionManager.model); the model defaults to the agent's (libs/agno/agno/agent/_init.py:186-197).

F11 Is this tool result too long to keep in the conversation, so it is stored and replaced by a pointer? RULING · code
Answered how

Off by default. With offload_tool_results=True and a SQLite or Postgres database, a tool result over 16,000 characters is stored aside and the model gets a short preview plus an id it can read back with a tool.

Held by

libs/agno/agno/offload/store.py:653-660 (should_offload: length over threshold_chars, default DEFAULT_THRESHOLD_CHARS = READ_MAX_CHARS = 16,000, libs/agno/agno/offload/store.py:40-42; never for read-back tools NEVER_OFFLOADED_TOOLS); envelope at libs/agno/agno/offload/store.py:667-707.

F12 What should the running summary of this session say, for later runs? RULING · llm
Answered how

Off by default. With enable_session_summaries=True (and a database or a cached session, for later runs to see it), after each run the agent's model re-reads the session's user and assistant turns (not tool calls) and writes a fresh summary that later runs can see.

Held by

libs/agno/agno/session/summary.py:242-276 (create_session_summary, calling SessionSummaryManager.model, which defaults to the agent's model, libs/agno/agno/agent/_init.py:172-183); fired after the run at libs/agno/agno/agent/_run.py:655-663 (requires both a manager and enable_session_summaries) and in a team at libs/agno/agno/team/_run.py:491-496 (requires only a manager).

F13 What of a finished run is kept in the stored session? RULE · code
Answered how

By default each stored run keeps its own messages, tool results and media, but not the history messages that were replayed into it.

Held by

libs/agno/agno/agent/_run.py:5872-5882 (scrub_run_output_for_storage, applied to a storage copy at libs/agno/agno/agent/_run.py:6003; for members at libs/agno/agno/team/_default_tools.py:616); settings store_media=True, store_tool_messages=True, store_history_messages=False (libs/agno/agno/agent/agent.py:238-248).

F14 In a workflow loop, when does the loop stop if its end condition never says so? RULING · code
Answered how

After three passes by default (max_iterations=3), unless a step asks to stop first. Exists only in a workflow that contains a Loop step.

Held by

libs/agno/agno/workflow/loop.py:462 (while iteration < self.max_iterations), default max_iterations: int = 3 (:76).

G. Judging the result (6 seats)

G1 Before an agent's answer is returned, does anything check that it is right or complete? RULING · code
Answered how

Nothing, by default. The run ends when the model replies without asking for a tool, and that reply is returned as completed; a check runs only if the integrator adds post-hooks (guardrails or evals).

Held by

libs/agno/agno/agent/_run.py:620-665: after the model loop the run converts to the output schema if one is set (:621), runs post_hooks only if any were given (:627-639; default None, libs/agno/agno/agent/agent.py:214), then sets RunStatus.completed (:665). The answer's "done" is the model stopping its tool calls (C6).

G2 In task mode, has the team's overall goal been met? RULING · llm
Answered how

Only in task mode (mode=TeamMode.tasks; absent by default): the leader's model decides by calling mark_all_complete, which code accepts without checking that any task completed.

Held by

libs/agno/agno/team/_task_tools.py:304-315 (mark_all_complete, called by the team leader's model; sets goal_complete), read by the loop at libs/agno/agno/team/_run.py:425-428.

G3 In task mode, does a finished task list (or a turn with nothing to do) end the run? RULE · code
Answered how

Task mode only: a list whose tasks all completed ends the loop; if any failed, the leader gets another turn; with no tasks, two turns without a tool call end it. All bounded by F3's 10 turns.

Held by

libs/agno/agno/team/_run.py:430-451: all tasks terminal with no failure ends the loop (TaskList.all_terminal, libs/agno/agno/team/task.py:144-148); with failures the loop continues; with no tasks, two turns without a tool call end it (libs/agno/agno/team/_run.py:439-449).

G4 When a member finishes a task, did the task succeed? RULING · code
Answered how

Task mode only. Code decides from the member's run: an exception or error status marks the task failed; anything else, including an empty reply, marks it completed. Whether the content does the task is left to the leader (G2).

Held by

libs/agno/agno/team/_task_tools.py:605-650 (execute_task): exception → failed (:605-611); member RunStatus.error → failed (:629-635); any content → completed (:636-643); no content → completed with "No content returned" (:644-650). Parallel copy :888-1027, async :655-835, :1092-1217. Dependent tasks fail automatically when a dependency failed (libs/agno/agno/team/task.py:206-222).

G5 Did this workflow step succeed, so the workflow may go on? RULE · code
Answered how

Workflow only. A step fails when its agent or team run ended in error or was cancelled, but by default the workflow goes on: every step gets review settings whose failure action is "skip". It stops only for a step set to on_error="fail" that does not skip failures. Its content is not judged.

Held by

libs/agno/agno/workflow/step.py:2779-2821 (_process_step_output: success = status not in (cancelled, error), :2805); the workflow stops on a failed step whose on_error is fail (libs/agno/agno/workflow/workflow.py:261-275, called at :3166).

G6 Has a workflow loop done enough to stop? RULING · external
Answered how

Workflow Loop only: whatever end condition the author wrote (a function or CEL expression), checked after each pass; with none, or if it errors, the loop runs to its maximum (3 by default, F14).

Held by

libs/agno/agno/workflow/loop.py:290-316 (_evaluate_end_condition: the integrator's end_condition callable, or a CEL expression evaluated in code), checked after each pass at libs/agno/agno/workflow/loop.py:536-537; optional per-iteration person review at :545 (human_review.requires_iteration_review).

H. Delegation (9 seats)

H1 Which team member should take this piece of the work? RULING · llm
Answered how

In the default coordinate mode the team leader's model picks the member for each sub-task from the roster's roles and descriptions, and may hand out several in one turn. A wrong id just returns the roster to it.

Held by

libs/agno/agno/team/_default_tools.py:706-723 (delegate_task_to_member: the team leader's model chooses member_id), at the default mode coordinate (libs/agno/agno/team/_init.py:225-231).

H2 What exactly does a member receive as its task? RULING · llm
Answered how

By default the leader writes the member's task in its own words and the member sees only that (plus team history or member interactions if those are turned on). With determine_input_for_members=False the user's original message is passed instead.

Held by

libs/agno/agno/team/_default_tools.py:546-550 (the leader's task argument, or the user's input when the flag is off), with determine_input_for_members=True by default (libs/agno/agno/team/team.py:122).

H3 What does the leader get back from a member, and can it tell a failure from an answer? RULE · code
Answered how

The member's reply text comes back as a tool result. A member that errored returns its error message as if it were the reply, so the leader must recognise failure from the words; its prompt tells it to say so plainly.

Held by

libs/agno/agno/team/_default_tools.py:844-870 (non-streaming path): content string, or concatenated tool results when the content is empty (:855-861), or "No response from the member agent." (:846-849), or JSON of a structured reply; a paused member yields "requires human input" (:832-842).

H4 When a member changes shared session state, whose change survives? RULE · code
Answered how

Only matters when session state is used. Each member runs on a copy of the team's state, merged back after it returns, so on a key two members both changed, the member that finishes last wins.

Held by

libs/agno/agno/team/_default_tools.py:728 (each member runs on a shallow copy(...)) and the copy is merged back after the member returns (merge_dictionaries, libs/agno/agno/team/_default_tools.py:626-627); in task mode a deepcopy (libs/agno/agno/team/_task_tools.py:511) merged at libs/agno/agno/team/_task_tools.py:404-405.

H5 In task mode, which member runs each task, and which tasks run together? RULING · llm
Answered how

Task mode only (absent by default): the leader's model creates tasks, names who does each, and chooses which independent ones run together; code only enforces dependencies and statuses.

Held by

libs/agno/agno/team/_task_tools.py:181-217 (create_task(assignee)), with execute_task(task_id, member_id) (libs/agno/agno/team/_task_tools.py:476-500) and execute_tasks_parallel(task_ids) (libs/agno/agno/team/_task_tools.py:845), all called by the leader's model.

H6 Should this team's leader rewrite the member's reply, or pass it straight to the user? RULING · llm
Answered how

By default (coordinate mode) the leader writes its own answer from what members returned. In route mode the chosen member's reply goes to the user as written and the leader gets no turn to review it.

Held by

libs/agno/agno/team/_messages.py:233-248 (coordinate mode, the default: the leader's model writes the final answer from member outputs, "Write one answer ... Never concatenate member outputs"); route mode instead marks the delegate tool stop_after_tool_call (libs/agno/agno/team/_default_tools.py:1523-1524).

H7 Which steps does a workflow router send this input down? RULING · external
Answered how

Workflow Router only: the author's selector function or CEL expression decides; if it names an unknown step, the router runs nothing and only logs a warning. A person picks instead when the router is set to ask.

Held by

libs/agno/agno/workflow/router.py:528-566 (_route_steps: the integrator's selector callable :551-564, or a CEL expression evaluated in code :536-548); a person picks instead when human_review.requires_user_input=True (libs/agno/agno/workflow/router.py:138-191, :418-421).

H8 Should a workflow's conditional steps run for this input? RULING · external
Answered how

Workflow Condition only: the author's condition (function, bool or CEL) decides; the default evaluator is True. If a CEL condition fails to evaluate, or a function returns a non-bool, agno treats it as false.

Held by

libs/agno/agno/workflow/condition.py:371-412 (_evaluate_condition: the integrator's evaluator callable, a bool, or CEL); default evaluator True (libs/agno/agno/workflow/condition.py:101-106); a person confirms instead when requires_confirmation (comment :100).

H9 Should this message re-run the workflow, or be answered from earlier runs? RULING · llm
Answered how

Only when a workflow is given a WorkflowAgent (absent by default): its model decides whether a message needs the whole workflow run again, with its own rewritten query, or can be answered from earlier runs.

Held by

libs/agno/agno/workflow/agent.py:15-78 (WorkflowAgent), calling the run_workflow tool (libs/agno/agno/workflow/agent.py:109-192) or answering itself.

I. Learning and feedback (10 seats)

I1 After a run, what should be remembered about this user, and what changed or should be dropped? RULING · llm
Answered how

Off by default. With update_memory_on_run=True and a database, a background model call (the agent's model by default) reads the user's message and existing memories and adds, rewrites or deletes memories itself. Without a user_id all users share the bucket "default".

Held by

libs/agno/agno/memory/manager.py:1044-1100 (create_or_update_memories, calling MemoryManager.model, which defaults to the agent's model, libs/agno/agno/agent/_init.py:104-119), reached via create_user_memories (libs/agno/agno/memory/manager.py:377-430) from a background future started before the model call (libs/agno/agno/agent/_managers.py:180-215, called at libs/agno/agno/agent/_run.py:526-531); team copy libs/agno/agno/team/_managers.py:41-44,106-107 (consolidated, same function).

I2 During a run, should the agent change what it remembers about the user? RULING · llm
Answered how

Off by default. With enable_agentic_memory=True and a database, the agent's own model decides mid-run when something about the user is worth saving and describes the change in words for the memory model (I3); the end-of-run pass (I1) is then skipped.

Held by

libs/agno/agno/agent/_default_tools.py:37-74 (update_user_memory, called by the agent's main model) when enable_agentic_memory=True (libs/agno/agno/agent/agent.py:137; tool added at libs/agno/agno/agent/_tools.py:273).

I3 Given the agent's memory request, which memory records change? RULING · llm
Answered how

Off by default (needs I2). A second call to the memory model turns the request into adds and edits on this user's memories; delete and clear exist only if the integrator turns them on, and clear empties the whole table.

Held by

libs/agno/agno/memory/manager.py:1197-1251 (run_memory_task) via update_memory_task (libs/agno/agno/memory/manager.py:490-526).

I4 After a run, what should the learning machine record about the user's profile and memories? RULING · llm
Answered how

Off by default. With learning=True and a database, background model calls (the agent's model by default), started alongside the main model call, read the conversation so far and update this user's profile and memory notes; with no user_id nothing is saved.

Held by

libs/agno/agno/learn/stores/user_memory.py:728-799 (UserMemoryStore.extract_and_save) and libs/agno/agno/learn/stores/user_profile.py:819 (UserProfileStore.extract_and_save), both in ALWAYS mode, built by learning=True with user_profile=True, user_memory=True (libs/agno/agno/agent/_init.py:149-151; needs a db, :142-145), dispatched by LearningMachine.process (libs/agno/agno/learn/machine.py:827-866) from a background future started before the model call (libs/agno/agno/agent/_run.py:534-541, libs/agno/agno/agent/_managers.py:265-298).

I6 Which notes about this session's context should be kept? RULING · llm
Answered how

Off by default, and learning=True does not turn it on. When a LearningMachine enables session context, a background model call, started alongside the main model call, reads the session's messages so far and updates its notes; people, things and decisions are I9.

Held by

libs/agno/agno/learn/stores/session_context.py:128-162 (SessionContextStore.process: ALWAYS mode only, :147-150; a background extraction through extract_and_save after the run, :155-162, with tool_call_limit=max_updates_per_run, :567).

I7 Should this exchange be added to the agent's knowledge base? RULING · llm
Answered how

Off by default. With update_knowledge=True and a knowledge base, the agent's model may save any question-and-answer pair into the knowledge base that later retrieval reads; with no user_id it goes into the shared bucket.

Held by

libs/agno/agno/agent/_default_tools.py:397-418 (add_to_knowledge, called by the agent's model) when update_knowledge=True (libs/agno/agno/agent/agent.py:232) and a knowledge base is set (libs/agno/agno/agent/_tools.py:316); write at libs/agno/agno/agent/_default_tools.py:361-394.

I8 Should the agent change its own saved session state? RULING · llm
Answered how

Off by default. With enable_agentic_state=True the agent's model can overwrite top-level keys of the session state, which is saved with the session and read by later runs when there is a database or a cached session.

Held by

libs/agno/agno/agent/_default_tools.py:325-358 (update_session_state_tool, called by the agent's model) when enable_agentic_state=True (libs/agno/agno/agent/agent.py:106).

I9 During a run, which people, things and decisions should the agent record in its learning stores? RULING · llm
Answered how

Off by default, and learning=True does not turn it on. When a LearningMachine enables entity memory or decision logs, the agent's own model records people, things and decisions through tools during the run; entity notes default to the shared "global" namespace.

Held by

the agent's main model, calling the entity-memory tools (libs/agno/agno/learn/stores/entity_memory.py:825-853, get_tools; the store refuses any mode but AGENTIC, :402-410) and the decision-log tools (libs/agno/agno/learn/stores/decision_log.py:207-234, log_decision at :378; AGENTIC only, :67-71). Both stores' process is a no-op (decision_log.py:139-141), so nothing is extracted after the run. Neither is turned on by learning=True, which builds user profile and user memory only (libs/agno/agno/agent/_init.py:149-151).

I10 When the agent records a new fact about an entity, which existing facts does it replace? RULING · llm
Answered how

Only with entity memory enabled (off by default): one model call judges which of the entity's latest 50 live facts a new fact replaces; only judgments at confidence 0.8 or above retire a fact, and a failed judgment keeps every fact.

Held by

libs/agno/agno/learn/stores/entity_memory.py:1328-1374 (_judge_superseded: one structured call to the store's model with a single strict mark_superseded tool and tool_call_limit=1, over the entity's last 50 live facts); applied at :1480-1484 (a judgment below supersession_threshold, default 0.8, libs/agno/agno/learn/config.py:369-372, is ignored).

How this map was made

Four readers each took a part of the agent and wrote seat cards from the source. The map's author then re-read every seat against the cited lines and corrected what the source did not support, and rewrote each answer in a short form that describes the default. Reviews by a model from another family (read-only, at the same commit) checked the answers against the code, and each of their findings was checked against the code before anything changed. A checker confirms that each seat's holder line exists at the pinned commit and carries its anchor text. Nothing on this page was produced by running their code.