goose: who decides what

The gallery · Make a map of your own system · The repository

The goose agent (its core crates, CLI, built-in extensions and desktop app) in aaif-goose/goose, read at commit cfb67d72 (2026-11-02). Claims about how the code behaves refer to that commit. Their code is Apache-2.0; 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 provider runs a session, what the model sees, may this tool call 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, D12 …) are links.

Each answer describes what goose does by default at the commit named above. 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; code = code in their repository; human = a person; external = something outside the repository that the agent hands the decision to.

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

Counts

102 seats across 9 stages: A 14, B 14, C 11, D 18, E 13, F 10, G 11, H 10, I 1.

Kind of question · holderSeats
RULE · code52
RULING · code18
RULING · llm18
RULING · human8
RULING · external5
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 goose'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

15 of the 102 cards are left out of this edition. The counts above include them.

A. Intake and routing (14 seats)

A1 When a person resumes without naming a session, which session continues? RULE · code
Answered how

For goose session --resume and goose run --resume, code takes the given session id as is, or the first session whose name or id equals the given name, or the id from a given path's file stem; with no identifier it takes the first SessionType::User session from the store's list, ordered by the latest message timestamp (else the session's updated_at), newest first, so hidden and scheduled sessions are never picked. build_session then re-checks the id exists and has its own copy of the no-identifier rule.

Held by

crates/goose-cli/src/cli.rs:417-447 (get_or_create_session_id); second copy crates/goose-cli/src/session/builder.rs:470-493; ordering crates/goose/src/session/session_manager.rs:2317-2318, :2350

A2 Which provider and model run a CLI session? RULE · code
Answered how

Code picks the provider as CLI --provider, else the saved session's provider, else the recipe's settings.goose_provider, else the configured GOOSE_PROVIDER. The model is the CLI --model, else (only with a --provider flag that equals the configured provider, on resume) GOOSE_MODEL from the environment, else the saved model when the provider is unchanged, else, only with a --provider flag: the recipe model (if the recipe names no provider or the same one), GOOSE_MODEL env and the configured model (if that provider is the configured one), the provider entry's saved model, the registry default; without a flag: the recipe model, then the configured model. Nothing found exits.

Held by

crates/goose-cli/src/session/builder.rs:289-445 (resolve_provider_and_model; provider :301-310, model :366-405)

A3 Which provider and model run a desktop/ACP session? RULE · code
Answered how

On session/new, code takes the recipe's settings.goose_provider, else the client's _meta.provider; the model is the recipe's goose_model, else the chosen provider's registry default model. When neither recipe nor client names a provider, it uses the configured provider with the recipe model if there is one, else the configured provider and model.

Held by

crates/goose/src/model_config.rs:23-60 (resolve_provider_and_model_config), called from crates/goose/src/acp/server/new_session.rs:139-150

A4 Which provider and model run a scheduled recipe job? RULE · code
Answered how

Code resolves the provider and model at run time through resolve_provider_and_model_config with no client-requested provider: the recipe's settings.goose_provider, else the global GOOSE_PROVIDER; the recipe's goose_model, else the recipe provider's registry default model when the recipe names a provider, else the global GOOSE_MODEL; a recipe temperature is applied. It then clamps the cache TTL and switches the job's session to that provider and model.

Held by

crates/goose/src/scheduler/full.rs:874-876 (calls resolve_provider_and_model_config with the recipe's settings and no requested provider); rule crates/goose/src/model_config.rs:23-60; applied crates/goose/src/scheduler/full.rs:902-904

A5 When a resumed CLI session's original provider cannot start, what runs instead? RULE · code
Answered how

Only on resume without a --provider flag, and only when the provider error's text contains "is not set", "not configured" or "Configuration value not found", code switches to the globally configured provider and model and prints a yellow warning on stderr; any other provider error, or a failure of the fallback, exits.

Held by

crates/goose-cli/src/session/builder.rs:753-798 (fallback branch); test of the error crates/goose-cli/src/session/builder.rs:908-913 (string match)

A6 Which approval mode does an interactive (CLI or desktop) session run under? RULE · code
Answered how

Code reads GOOSE_MODE through the lenient get_goose_mode() (get_param) and turns any error, missing or unparseable alike, into the enum default Auto. The CLI applies it to the session on every build, new or resumed; ACP stamps it on the session row at session/new. The person can change it later: CLI /mode sets the session and also writes the global config, ACP session/set_mode sets only that session.

Held by

crates/goose-cli/src/session/builder.rs:831-836 (CLI); crates/goose/src/acp/server/new_session.rs:52 (ACP); reader crates/goose/src/config/base.rs:243-244, :1277; default crates/goose-provider-types/src/goose_mode.rs:23-25; person's changes crates/goose-cli/src/session/mod.rs:892-910, crates/goose/src/acp/server.rs:2431-2445

A7 Which approval mode does a scheduled job run under? RULE · code
Answered how

Code creates every scheduled job's session with GooseMode::Auto and sets the agent's mode to Auto again before the run; no config value, recipe field or person changes it.

Held by

crates/goose/src/scheduler/full.rs:884 (session created Auto, in the create_session call :878-886); crates/goose/src/scheduler/full.rs:905-907 (forced again)

A8 Is this kickoff message a slash command, and which handler takes it (built-in, recipe, skill, MCP prompt)? RULE · code
Answered how

Unless GOOSE_SLASH_COMMANDS_ENABLED is false (default true), code looks only at a turn whose kickoff is a single message, trims its agent-visible text, maps /compact, /summarize and "Please compact this conversation" to /compact, and treats anything starting with / as a command word plus parameters. It offers the command to each operation in pipeline order (skills before recipes before tool execution, compaction, doctor, retry, status); the first that applies wins. Name collisions are settled inside the resolvers: a skill resolves only if no built-in or recipe has that name, a recipe only if no built-in does. An unclaimed /word goes to the model as text.

Held by

crates/goose/src/agents/state_machine/ops_slash_command.rs:19-83 (parse and dispatch); order crates/goose/src/agents/state_machine_builder.rs:72-139; collision rules crates/goose/src/slash_commands/skill_slash_command.rs:50-52, crates/goose/src/slash_commands/recipe_slash_command.rs:158-160; switch crates/goose/src/agents/execute_commands.rs:3-7

A9 Should a kickoff starting with ! run directly as a shell command? RULE · code
Answered how

When the turn's kickoff message, as the user sees it, starts with ! (after leading whitespace) and has non-empty text after it, code emits an assistant tool request to shell with that text as command, without a model call; once the tool result is the last message, the operation returns yielded (its effect on the rest of the turn not traced here). No setting turns it off.

Held by

crates/goose/src/agents/state_machine/ops_bang_shell.rs:18-24 (bang_shell_command); crates/goose/src/agents/state_machine/ops_bang_shell.rs:40-74 (emits the shell request)

A10 Are /compact and /clear handled by goose or passed through to the provider? RULE · code
Answered how

When the pipeline is built, code asks the provider manages_own_context(); if it does not, the compaction operation is added and handles /compact and /clear; if it does, no operation claims them and the text goes to the provider. Separately, the CLI's own /clear and /compact handlers refuse with an error for such providers before anything reaches the agent (the ACP path was not read here).

Held by

crates/goose/src/agents/state_machine_builder.rs:70, crates/goose/src/agents/state_machine_builder.rs:99-106 (compaction op only when the provider does not manage its own context); CLI refusal crates/goose-cli/src/session/mod.rs:1088-1096, :1298-1306

A11 When a resumed CLI session's working directory differs from the current one, which directory does it run in? RULING · human
Answered how

Only on resume and only when the current directory differs from the session's stored one: in an interactive session the person answers a yes/no prompt whose preselected answer is "switch back"; yes changes the process directory to the original (or keeps the current one with an error if it no longer exists). A non-interactive session stays in the current directory with a yellow warning, so code holds it there.

Held by

crates/goose-cli/src/session/builder.rs:522-549 (interactive confirm, .initial_value(true)); crates/goose-cli/src/session/builder.rs:550-560 (non-interactive: stay, warn); called crates/goose-cli/src/session/builder.rs:722-724

A12 Should this recipe be allowed to start a desktop session? RULING · human
Answered how

Before session/new is sent for a desktop session started from a saved recipe id or a recipe deeplink, code first checks whether this recipe's hash was accepted before and, if so, starts without asking. Otherwise it scans the recipe and the person accepts or declines in a consent request that carries the scan's has_security_warnings; acceptance records the hash, decline throws and no session is created.

Held by

ui/desktop/src/sessions.ts:90-105 (ensureRecipeConsent, called ui/desktop/src/sessions.ts:111); request ui/desktop/src/recipe/consent.ts:37-45

A14 What text does a scheduled job start from, and in which directory? RULE · code
Answered how

Code sends the recipe's prompt as the first user message, or its instructions when the prompt is empty or whitespace, and refuses the job when both are empty. The session's working directory is the scheduler process's current directory at run time, which is also where plugin MCP servers are looked up when the recipe lists no extensions.

Held by

crates/goose/src/scheduler/full.rs:952-964 (prompt, else instructions); crates/goose/src/scheduler/full.rs:881 (working dir = std::env::current_dir()); crates/goose/src/scheduler/full.rs:889-893 (plugin MCP servers looked up there when the recipe lists no extensions)

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

B6 Which skills are listed to the model as available? RULE · code
Answered how

On every inference step code scans skill roots in a fixed order: the working directory's .agents/skills, .goose/skills, .claude/skills, then project-scope plugin skill dirs, then ~/.agents/skills, the goose config dir's skills, ~/.claude/skills, ~/.config/agents/skills, user-scope plugins, then built-ins. The first skill parsed with a given name wins (exact, case-sensitive; within one root the winner is unspecified), and the model is shown each surviving skill's name and description, sorted by name.

Held by

crates/goose/src/agents/state_machine/ops_skills.rs:62-85 (the listed skills); roots crates/goose/src/skills/mod.rs:317-389, built-ins crates/goose/src/skills/mod.rs:442-469; first name wins crates/goose-agent/src/skills/discovery.rs:230-247

B7 Which skill (or skill supporting file) should be loaded into context now? RULING · llm
Answered how

The session's model decides, by calling the load_skill tool with a name. Code then runs pre-tool hooks (a denial stops it), looks the name up among discovered skills on each call: an exact name returns the rendered skill (or an error if its arguments fail to parse), skill/relative-path returns that skill's supporting file (or an error if it is missing or unreadable), anything else returns an error naming up to three near matches. In Chat mode the call is answered with a skipped message and nothing loads.

Held by

the session's model, via the load_skill tool (crates/goose/src/agents/state_machine/ops_skills.rs:47-60); executed crates/goose/src/agents/state_machine/ops_skills.rs:87-149, crates/goose/src/agents/state_machine/ops_skills.rs:321-396

B9 Does the tool list reach the model as native tool schemas or as text inside the system prompt? RULE · code
Answered how

On every model call code checks the session's model config flag toolshim: if true, the tool list is written into the system prompt and no tools are sent natively; otherwise they go as native schemas. The flag is set from GOOSE_TOOLSHIM (default false) when a model config is built from user config; a resumed CLI session that reuses its saved model config keeps the saved flag.

Held by

crates/goose/src/agents/reply_parts.rs:213-224 (prepare_tools_for_provider), called from crates/goose/src/agents/state_machine/ops_llm.rs:156-161; flag crates/goose/src/model_config.rs:174, crates/goose/src/model_config.rs:226-240

B10 What per-turn context block (time, directory, turn budget, extension state) is added this turn? RULE · code
Answered how

On every inference step code builds an agent-only user message tagged as turn context: the turn's start time (to the minute), the working directory, then the non-empty parts the pipeline operations contribute (platform extensions' context, and <turn-budget> once at least half the max turns are used). It is not added when the context limit is under 32,000 tokens, when the moim::SKIP flag is set, or when its text equals the most recent turn-context message already in this turn.

Held by

crates/goose/src/agents/state_machine/inference_preparation.rs:64-84 (dedupe within the turn); crates/goose/src/agents/moim.rs:92-134 (compose; under 32,000 tokens none :6, :109-111); parts crates/goose/src/agents/extension_manager/lease.rs:577-591 (platform extensions), crates/goose/src/agents/state_machine/ops_maxturns.rs:20-28, :42-51 (turn budget)

B11 Which part of the conversation history reaches the model? RULE · code
Answered how

On each model call code sends every agent-visible message of the stored conversation, reduced to its agent-visible content, dropping tool requests from before this turn that never got a response and messages left empty, then appends this step's turn-context message, drops signed and redacted thinking from messages whose inference metadata names a different provider, repairs the sequence (fix_conversation) and merges consecutive messages. If the most recent message carrying inference metadata came from this same provider with a provider session id, code first asks the provider to resume that session and continues with a warning if it cannot. What a provider then forwards is its own business (see notes).

Held by

crates/goose-agent/src/inference.rs:259-287 (messages_for_provider); crates/goose-agent/src/inference.rs:482-504 (turn context appended, other providers' signed thinking dropped :290-305, provider resume, fix and merge)

B12 Which project description and notes enter the system prompt? RULE · code
Answered how

On every inference step, only when the session has a project_id, code reads that project and adds a project prompt part: "# Project: <name>", then its description and its content when non-empty. ACP sets project_id at session/new from the client's _meta.projectId; if the project cannot be read, nothing is added and nothing is logged.

Held by

crates/goose/src/agents/state_machine/ops_project.rs:19-38 (prompt part); project_id from crates/goose/src/acp/server/new_session.rs:205-207 (_meta.projectId, crates/goose/src/acp/server/new_session.rs:276)

B13 Which recipe instructions and final-output rules enter the system prompt? RULE · code
Answered how

On every inference step, when the session record carries a recipe, code adds its instructions as the recipe prompt part and, when the recipe has a response, the final-output tool's own system-prompt text as the final_output part. A response that fails to build a final-output tool raises and stops the step.

Held by

crates/goose/src/agents/state_machine/ops_recipe.rs:73-82 (recipe_prompt_parts); called crates/goose/src/agents/state_machine/ops_recipe.rs:240-246

C. Choosing the next action (11 seats)

C2 Is it the model's turn now (call the provider, or not)? RULE · code
Answered how

Code calls the model only when no earlier step applied and three checks pass: the conversation has a kickoff user message, its last message carries no error, and the conversation as the provider would see it (unanswered tool requests from earlier turns dropped) ends on a non-empty user or tool message. Otherwise the inference step is not applicable and the run yields.

Held by

crates/goose-agent/src/inference.rs:329-338 (should_infer) and crates/goose-agent/src/inference.rs:437-442 (applies).

C3 Which pipeline step runs next (pending tool, approval, compaction, retry, stop, or the model)? RULE · code
Answered how

Code walks a fixed list on every step and runs the first operation that applies: slash commands, tool approval, skills, recipe, tool execution, remaining tool calls, subagents, steer, bang shell, context compaction (only when the provider does not manage its own context), tool-pair compaction, doctor, project, retry, empty response, stop hook, exit on error, max turns, then an automatic effort-selection step only when that feature is enabled and available (crates/goose/src/agents/services.rs:162-205), and last the model call. If none applies the run yields; an applied step's status decides whether the loop continues.

Held by

fixed step order crates/goose/src/agents/state_machine_builder.rs:72-144, walked first-applies-wins at crates/goose-agent/src/machine.rs:167-219.

C4 Is the model's tool name one we know, and if it is garbled, which tool did it mean? RULING · code
Answered how

Only when the model's tool name is not exactly an advertised name, code trims it, strips a functions. or functions: prefix, and compares it with each advertised tool as itself, as ext.tool for ext__tool, and as owner.name or owner__name from owner metadata; exactly one match renames the call, zero or two-plus matches leave it unchanged. The same recovery runs again at dispatch against the extension catalog when an exact lookup fails.

Held by

crates/goose/src/agents/extension_manager/mod.rs:527-567 (recover_mangled_tool_name), called from crates/goose/src/agents/state_machine/ops_llm.rs:92-104 against the advertised set and again at dispatch (crates/goose/src/agents/extension_manager/lease.rs:605-622).

C5 Do the tool-call arguments fit the tool (coerce or reject)? RULE · code
Answered how

For a request whose (possibly recovered) name matches an advertised tool, code walks the top-level arguments and, where the value is a string and the schema property's type is number/integer or boolean, converts it if it parses; everything else passes through unchanged. Arguments the provider parser could not read arrive as an Err and are answered with a parse-error text instead of running; nothing validates required fields or nested types before dispatch.

Held by

crates/goose/src/agents/reply_parts.rs:105-128 (coerce_tool_arguments), called at crates/goose/src/agents/state_machine/ops_llm.rs:112-114; parse failures become ToolDisposition::ParseError at crates/goose/src/agents/state_machine/ops_toolcalling.rs:696-700.

C6 Native tool-call parsing: which tool calls does the model's reply contain (provider format)? RULE · code
Answered how

When toolshim is off, the provider's own format parser turns its structured tool-use blocks into tool requests; in the Anthropic stream, for example, the accumulated argument string of each finished block is parsed, an empty string becomes {}, and an unreadable one becomes an Err request carrying a truncation or 'Could not parse tool arguments' message. The argument parser shared by the Anthropic, OpenAI and Databricks formats is tolerant: it unwraps double-encoded objects and a JSON wrapper and attempts a repair unless the text looks truncated. The OpenAI Responses format has its own parser, which turns malformed arguments into {}.

Held by

the provider's format parser, e.g. Anthropic streaming at crates/goose-provider-types/src/formats/anthropic.rs:1195-1233, with the shared argument parser crates/goose-provider-types/src/json.rs:270-297 (OpenAI Responses: its own at crates/goose-provider-types/src/formats/openai_responses.rs:945-952); the other formats (openai, openai_responses, google, databricks, ollama, snowflake) sit in the same crates/goose-provider-types/src/formats/ directory. Reached through crates/goose/src/agents/reply_parts.rs:446-489 when model_config.toolshim is false (crates/goose/src/agents/reply_parts.rs:213-224).

C7 Tool shim: which tool calls did a model WITHOUT native tool calling mean (interpreter model)? RULING · llm
Answered how

Only in toolshim mode (model_config.toolshim true), and only when the code parsers of C8 found no call and the reply carries no native tool request, code sends the main model's joined reply text to a second local model and adds each {name, arguments} it returns (except noop) as a tool request. The backend is Ollama unless GOOSE_TOOLSHIM_BACKEND names local/llama.cpp; the Ollama model is GOOSE_TOOLSHIM_OLLAMA_MODEL, else mistral-nemo; the local model is GOOSE_TOOLSHIM_MODEL, else config LOCAL_LLM_MODEL, else an error.

Held by

a second, local model: Ollama mistral-nemo by default at crates/goose/src/providers/toolshim.rs:1143-1150 (constant crates/goose/src/providers/toolshim.rs:55), or a llama.cpp model when GOOSE_TOOLSHIM_BACKEND is local (crates/goose/src/providers/toolshim.rs:74-116, crates/goose/src/providers/toolshim.rs:1160-1199). Selected at crates/goose/src/providers/toolshim.rs:1371-1385; invoked through toolshim_postprocess (crates/goose/src/agents/reply_parts.rs:130-144) from crates/goose/src/agents/reply_parts.rs:382-445 only when model_config.toolshim is true.

C8 Tool shim, code pass: does the text already contain tokenized or inline-JSON tool calls? RULING · code
Answered how

In toolshim mode, on the joined reply text, code first looks for <|tool_call_begin|> name [<|tool_call_argument_begin|>] {json} <|tool_call_end|> blocks, then for inline {"name":..., "arguments":{...}} objects, keeping a call only when the name resolves to exactly one known tool (exact, without index or functions. prefix, dots as __, the last dotted segment, or a unique __suffix match). A code-mode execute/execute_code call is converted to a shell call when its code contains exactly one Developer.shell({command: "..."}) (tree-sitter feature only, the rest of the code dropped); otherwise an unresolved execute alias inside a tokenized block rejects the whole text (the inline-JSON pass has no such check): markup stripped, no call added from that text (tool requests already in the message stay), and the interpreter of C7 is skipped.

Held by

crates/goose/src/providers/toolshim.rs:1333-1352 (parse_tokenized_tool_calls_with_status crates/goose/src/providers/toolshim.rs:551-658, parse_inline_json_tool_calls crates/goose/src/providers/toolshim.rs:706-733, resolve_tool_name crates/goose/src/providers/toolshim.rs:118-164), execute-alias conversion or rejection at crates/goose/src/providers/toolshim.rs:183-350 and crates/goose/src/providers/toolshim.rs:443-493.

C9 Should a failed model request be retried, and after how long? RULE · code
Answered how

When the provider does not manage its own context and the stream opened, code retries an error that arrives before the stream's first item if it is a rate limit, server or network error, up to the provider's retry_config().max_retries (default 3), sleeping the server's rate-limit delay or 1 s doubling to a 30 s cap with 0.8-1.2 jitter (no sleep when GOOSE_PROVIDER_SKIP_BACKOFF is true). An error opening the stream, or one after the first item, is not retried at this level. A separate generic helper that providers may call retries 4xx too (except permanent markers) and refreshes credentials once on an auth error.

Held by

crates/goose/src/agents/reply_parts.rs:322-380 using crates/goose-provider-types/src/retry.rs:108-117 (should_retry), crates/goose-provider-types/src/retry.rs:71-87 (backoff) and crates/goose-provider-types/src/retry.rs:14-17 (defaults); generic helper crates/goose-provider-types/src/retry.rs:197-271.

C10 The model answered with nothing: retry, or give up and tell the person? RULE · code
Answered how

Code calls an assistant reply empty when every block is whitespace text or unsigned empty thinking and no chunk hit the output-token limit. An empty reply is re-requested up to 3 more times (4 calls in all), except when the conversation ends on an all-successful tool result or the turn is cancelled; after the last empty reply code stores a hidden fallback message, which the empty-response step then reveals unless the session's recipe has a retry block.

Held by

crates/goose-agent/src/inference.rs:507-602 (emptiness crates/goose-agent/src/inference.rs:179-187, MAX_EMPTY_RESPONSE_RETRIES crates/goose-agent/src/inference.rs:78), revealed by crates/goose/src/agents/state_machine/ops_empty_response.rs:23-54.

C11 Which of the model's repeated or duplicated stream pieces are kept? RULE · code
Answered how

While streaming one attempt, code keeps the first tool request for each id and silently drops any later request with the same id; when a chunk carries a tool request, any thinking block in it equal to one already accumulated from the same role is removed. A chunk left empty (and not flagged as hitting the output-token limit) is accumulated without being shown.

Held by

crates/goose-agent/src/inference.rs:540-568 (id de-duplication crates/goose-agent/src/inference.rs:556-561; repeated thinking crates/goose-agent/src/inference.rs:99-116).

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

D1 Given the session's mode and the tool's saved permission, does this tool call run, wait for a person, or get refused? RULE · code
Answered how

Code switches on the session's mode. Chat gives no verdict (and the approval pass does not run at all in Chat); Auto allows every call before any saved permission is read; Approve and SmartApprove first use the tool name's saved user permission (AlwaysAllow / NeverAllow / AskBefore), then in SmartApprove a read-only annotation allows (D3), then manage_extensions always asks, then SmartApprove hands uncached tools to the LLM judge (D2), and anything left asks the person.

Held by

crates/goose/src/permission/permission_inspector.rs:144-227 (PermissionInspector::inspect); mode read from session.goose_mode at crates/goose/src/agents/state_machine/ops_tool_approval.rs:49.

D2 Is this tool call read-only, so it can run in Smart Approve mode without asking the person? RULING · llm
Answered how

Only in SmartApprove, for a tool with no saved user permission, no read-only annotation and no cached AskBefore: one LLM call (the session's provider and the session's model config, else the global GOOSE_PROVIDER/GOOSE_MODEL) is shown the batch of calls as untrusted JSON and asked to return the ids of the read-only ones through the offered tool. A call whose id comes back runs without asking; every other candidate asks the person and its tool name is cached as AskBefore.

Held by

crates/goose/src/permission/permission_judge.rs:145-185 (detect_read_only_requests), called from crates/goose/src/permission/permission_inspector.rs:229-246. Model: the session's model_config, else global provider/model (crates/goose/src/permission/permission_judge.rs:13-33); provider: the session's (crates/goose/src/permission/permission_inspector.rs:231-234). No separate judge-model setting exists.

D3 Does the tool's author declare it read-only, so Smart Approve may run it without asking? RULING · external
Answered how

In SmartApprove only, each turn's tool list is scanned and every tool whose MCP definition carries read_only_hint == Some(true) goes into a per-agent set; a call to a tool in that set runs without asking, unless the person has a saved permission for that tool name, which is checked first. A tool marked read_only_hint == Some(false) is written to the smart_approve cache as AskBefore; a tool with no annotation falls through to the LLM judge (D2).

Held by

crates/goose/src/permission/permission_inspector.rs:173-176 honours the MCP server's annotations.read_only_hint, collected at crates/goose/src/permission/permission_inspector.rs:52-64 and refreshed each turn in SmartApprove by crates/goose/src/agents/state_machine/inference_preparation.rs:56-59. A write annotation is cached as AskBefore (crates/goose/src/config/permission.rs:91-107).

D4 May this tool call run? (the person's Allow once / Always allow / Deny) RULING · human
Answered how

When a call needs approval, the desktop app shows Allow Once, Always Allow and Deny; Always Allow is hidden when the request carries a security message. The click resolves the pending ACP permission request; back in the agent, Allow Once or Always Allow marks the call executable, anything else does not, and Always Allow (or an Always Deny from another ACP client) is also saved for the tool name.

Held by

ui/desktop/src/components/ToolApprovalButtons.tsx:95-143 (buttons; Always Allow hidden when a security prompt is attached, :132), resolved by resolveAcpPermissionRequest (ui/desktop/src/acp/permissionRequests.ts:33-52); request built at crates/goose/src/agents/state_machine/ops_tool_approval.rs:107-127, answer applied at crates/goose/src/agents/state_machine/ops_tool_approval.rs:57-76.

D5 May this tool call run? (the person's Allow / Always Allow / Deny / Cancel in the terminal) RULING · human
Answered how

In an interactive CLI session, when the agent emits a tool confirmation the terminal shows the tool and its arguments and a select of Allow / Always Allow / Deny / Cancel; Always Allow is left out when a security message is attached. The choice is submitted to the agent as the confirmation; Cancel also stops the turn and records the call as cancelled.

Held by

crates/goose-cli/src/session/mod.rs:2297-2337 (prompt_tool_confirmation), called at crates/goose-cli/src/session/mod.rs:1400-1408 when interactive is true; Always Allow only when no security prompt is attached (crates/goose-cli/src/session/mod.rs:2312-2336).

D6 May this tool call run? (the paired chat user's reply: approve, approve always, deny, deny always) RULING · human
Answered how

When the agent asks for a confirmation during a gateway turn, the paired chat user is sent the tool name, pretty-printed arguments, any security message and the four reply options, and the request id is queued for that user. Their next message is matched exactly (case-insensitive, trimmed): approve/yes/y, approve always, deny/no/n, deny always; the answer is applied to the oldest queued request. Anything else re-asks.

Held by

crates/goose/src/gateway/handler.rs:215-245 (reply parsing), prompt sent at crates/goose/src/gateway/handler.rs:623-683, answer submitted at crates/goose/src/gateway/handler.rs:247-265. Reached only by a paired user, and only a listed one when a user allowlist is configured (crates/goose/src/gateway/handler.rs:126-134, :192-199; no allowlist admits everyone, :829-833).

D7 In a goose run with no terminal attached, what happens to a tool call that is waiting for approval? RULE · code
Answered how

When the CLI is not interactive and a confirmation arrives, code reads the mode from the global config (not from the session): Approve or SmartApprove cancels the run and returns an error; any other mode answers AllowOnce automatically, recorded only by a WARN log line.

Held by

crates/goose-cli/src/session/mod.rs:1409-1428: Approve or SmartApprove stops the run with an error (crates/goose-cli/src/session/mod.rs:1415-1422); otherwise AllowOnce with a warning log (crates/goose-cli/src/session/mod.rs:1424-1427).

D8 Should any tool call run in this session? RULE · code
Answered how

Code: when the session's mode is Chat, the approval pass does not run, and each tool-handling operation answers every executable call with the fixed CHAT_MODE_TOOL_SKIPPED_RESPONSE text instead of running it (a call that failed to parse still gets its parse error, and one for a tool its turn did not advertise gets "Tool '<name>' is not available.").

Held by

crates/goose/src/agents/state_machine/ops_tool_approval.rs:50-52 (no approval pass) and crates/goose/src/agents/state_machine/ops_remaining_tool_calls.rs:148-153 (call answered with CHAT_MODE_TOOL_SKIPPED_RESPONSE); same check in crates/goose/src/agents/state_machine/ops_toolcalling.rs:998, crates/goose/src/agents/state_machine/ops_skills.rs:345, crates/goose/src/agents/state_machine/ops_recipe.rs:277.

D9 When a scheduled recipe runs with nobody watching, which approval regime governs its tool calls? RULING · code
Answered how

Code fixes it to Auto for every scheduled job: the scheduler creates the job's session in Auto and sets Auto again after switching the provider, ignoring the configured GOOSE_MODE. So D1 allows every call; only an inspector override (scanner, adversary) or a PreToolUse hook can still stop one, and an override that asks for approval is never answered.

Held by

crates/goose/src/scheduler/full.rs:878-907: session created with GooseMode::Auto (crates/goose/src/scheduler/full.rs:884) and set to Auto again after the provider switch (crates/goose/src/scheduler/full.rs:905-907); the in-file test sets GOOSE_MODE=chat (crates/goose/src/scheduler/full.rs:1367) and asserts Auto (crates/goose/src/scheduler/full.rs:1423).

D10 Is this shell command dangerous enough to stop and ask the person first? RULING · code
Answered how

Only when SECURITY_PROMPT_ENABLED is true (default false; env SECURITY_PROMPT_ENABLED_OVERRIDE wins) and only for a tool named exactly shell: code scores the command against fixed patterns, takes the max with an optional command-classifier score, may blend in an optional prompt-classifier score of recent user messages (never below the pattern score), and if the result is strictly above SECURITY_PROMPT_THRESHOLD (default 0.8) turns the call into RequireApproval with a Security Alert message. Every other call gets no verdict from this seat.

Held by

crates/goose/src/security/security_inspector.rs:59-87 -> crates/goose/src/security/mod.rs:77-224 (analyze_tool_requests) -> crates/goose/src/security/scanner.rs:132-201. Pattern rules in crates/goose/src/security/patterns.rs; optional classifier via crates/goose/src/security/classification_client.rs:141 (a classifier model). Threshold SECURITY_PROMPT_THRESHOLD, default 0.8 (crates/goose/src/security/scanner.rs:126-130); a finding above it becomes RequireApproval with a Security Alert (crates/goose/src/security/security_inspector.rs:27-36).

D11 Given the user's task and their written rules, is this tool call safe to run? RULING · llm
Answered how

Only when adversary.md exists in the goose config dir, and only for tools named in its tools: line (default just shell), in every mode: one LLM call per such tool call, with the session's own provider and model, sees the first user message, up to four recent user messages, the call as JSON and the rules, and is asked for ALLOW or BLOCK. A reply that starts with BLOCK or has a line starting BLOCK becomes a Deny that overrides every other verdict; anything else is an Allow, which changes nothing in the combination (D12).

Held by

crates/goose/src/security/adversary_inspector.rs:249-347 (consult_llm) via crates/goose/src/security/adversary_inspector.rs:364-469 (inspect); the session's own provider and model (crates/goose/src/security/adversary_inspector.rs:257-262, :302).

D12 When the inspectors disagree about a call, which verdict wins? RULE · code
Answered how

Code: the permission inspector's verdicts form the baseline (a call with none needs approval); then every other inspector's result is applied in order: a Deny moves the call to denied, a RequireApproval moves an approved call to needs-approval unless it is already denied, and an Allow changes nothing. So Deny beats RequireApproval beats Allow, whichever inspector said it.

Held by

crates/goose/src/tool_inspection.rs:170-261 (apply_inspection_results_to_permissions) after the permission baseline at crates/goose/src/permission/permission_inspector.rs:73-131. Deny beats RequireApproval beats Allow; an Allow never upgrades another verdict (crates/goose/src/tool_inspection.rs:253-256).

D13 Does the installed policy hook allow this tool call? RULING · external
Answered how

Only when an enabled plugin ships hooks/hooks.json with a PreToolUse rule whose matcher fits the tool: just before the call executes, code runs each matching command with the call's context as JSON; the first one that exits 2 or prints {"decision":"block"} denies the call and the model gets a policy-denial error. With no matching rule the call proceeds.

Held by

crates/goose/src/hooks/mod.rs:688-760 (emit_blocking_with_outcome) runs the plugin's command (an external program from <plugin-root>/hooks/hooks.json, crates/goose/src/hooks/mod.rs:4, :438); called before execution at crates/goose/src/agents/state_machine/ops_toolcalling.rs:120-161 and crates/goose/src/agents/state_machine/ops_remaining_tool_calls.rs:161-171. Deny = exit status 2 or {"decision":"block"} (crates/goose/src/hooks/mod.rs:681-683).

D16 May a tool called from inside Code Mode's JavaScript run? RULE · code
Answered how

Only when Code Mode is compiled in (feature code-mode) and enabled (off by default): code lets the script call any tool whose name is in the catalog built at the start of the outer execute_javascript call (the current extension lease's tools, minus MCP-app tools, tools hidden from the model, Code Mode's own and summon's delegate), and runs it straight through the extension lease. No permission check, inspector, approval or PreToolUse hook is consulted for these nested calls; only the outer call went through D1-D13. This is deliberate: a maintainer's answer on issue #12376, which asked for one policy boundary, was "if you select code mode, you opt out of the rest of the machinery."

Held by

crates/goose/src/agents/platform_extensions/code_execution.rs:116-136 (callback; catalog filter at crates/goose/src/agents/platform_extensions/code_execution.rs:57-67) -> dispatch_tool (crates/goose/src/agents/platform_extensions/code_execution.rs:172-199) -> ExtensionLease::call (crates/goose/src/agents/extension_manager/lease.rs:657-667), which resolves and calls the tool directly.

D17 May an MCP app's UI call its extension's tool directly, without the model? RULE · code
Answered how

Code: when an ACP client asks to call a tool on an app's behalf, the tool must exist under that extension and be visible to apps, the arguments must be an object or null, and the session must be in Auto mode; if all hold, the tool runs at once through the extension lease with no permission inspector, scanner, adversary, approval or PreToolUse hook. Any failed check returns an error and nothing runs.

Held by

crates/goose/src/acp/server/tools.rs:68-160 (on_call_tool): tool owned by the app's extension and app-visible (crates/goose/src/acp/server/tools.rs:80-89), session in Auto (crates/goose/src/acp/server/tools.rs:117-120); then call_for_app runs it directly (crates/goose/src/agents/extension_manager/lease.rs:669-682).

D18 When goose drives another agent over ACP (Claude, Codex, Copilot, Amp, Pi) and that agent asks to run a tool, may it? RULING · code
Answered how

Code answers from the goose mode the ACP provider holds, read once at the start of each streamed request: Auto replies AllowOnce at once, Chat replies RejectOnce (and records the call as rejected), and Approve or SmartApprove forward the request to the person as an ActionRequired carrying the agent's tool title, raw input and first text content, then relay their answer back to the delegated agent.

Held by

crates/goose/src/acp/provider.rs:1028-1037 with permission_decision_from_mode (crates/goose/src/acp/provider.rs:2388-2394): Auto -> AllowOnce, Chat -> RejectOnce, Approve/SmartApprove -> ask the person (crates/goose/src/acp/provider.rs:1039-1062; ActionRequired built at crates/goose/src/acp/provider.rs:2120-2161; answered in D4).

E. Execution and observation (13 seats)

E1 When the model asks for several tools at once, do they run together or one after another? RULING · code
Answered how

Always together: code walks the reply's pending Execute requests in order, awaiting each one's PreToolUse hooks and tool resolution, which returns a not-yet-started call future; it then polls all those futures at once through one select_all, with no cap, and records each result as it completes. Only subagent calls have a concurrency limit (a separate operation).

Held by

crates/goose/src/agents/state_machine/ops_toolcalling.rs:1021-1055 (sequential dispatch, then select_all), polled at crates/goose/src/agents/state_machine/ops_toolcalling.rs:1082-1111; pre-hooks per call at crates/goose/src/agents/state_machine/ops_toolcalling.rs:419-439; the lazy call future at crates/goose/src/agents/extension_manager/lease.rs:719-749.

E2 Which extension and server tool does a tool name run on? RULE · code
Answered how

Before dispatch, the tool-execution step keeps only requests whose name is in the lease's current tool list (minus the skills extension); the rest fall to the remaining-tool-calls step. At dispatch, code looks the name up exactly in the lease's tool catalog, else repairs it with C4's recovery against the catalog, and runs it on the catalog entry's extension client and server tool name; for a call from an MCP app the search is limited to that app's extension and the tool must be app-visible.

Held by

crates/goose/src/agents/extension_manager/lease.rs:593-655 (resolve_tool), called from crates/goose/src/agents/extension_manager/lease.rs:657-682 (call, call_for_app); known-set filter at crates/goose/src/agents/state_machine/ops_toolcalling.rs:981-992.

E3 How long may an extension tool call run before goose gives up on it? RULING · code
Answered how

When an MCP extension is connected, code fixes its timeout: the extension config's timeout, else the GOOSE_DEFAULT_EXTENSION_TIMEOUT setting, else 300 s. Each tool call to that client then races the server's reply against that timer and the turn's cancel token; on the timer, code sends an MCP cancel notification and the call fails with a timeout error.

Held by

crates/goose/src/agents/mcp_client.rs:616-641 (await_response), duration from crates/goose/src/agents/extension_manager/mod.rs:123-129 (resolve_timeout, applied at connect, crates/goose/src/agents/extension_manager/mod.rs:889-890) and crates/goose/src/config/extensions.rs:10.

E4 How long may a shell command run? RULING · llm
Answered how

The model may pass timeout_secs with each shell call; if it does not, code uses the GOOSE_DEFAULT_EXTENSION_TIMEOUT setting, else 300 s. A resolved value of 0 (from the model or the setting) means no deadline at all; otherwise the deadline covers building the command and running it, and a command past it is reported as timed out.

Held by

the model's timeout_secs argument to the developer shell tool (crates/goose/src/agents/platform_extensions/developer/shell.rs:182-189), applied at crates/goose/src/agents/platform_extensions/developer/shell.rs:416-440; default crates/goose/src/agents/platform_extensions/developer/shell.rs:408-414.

E5 How much of a shell command's output does the model see, and where does the rest go? RULING · code
Answered how

Code checks stdout, stderr and the interleaved display text separately: one that is over 2000 lines or 50 000 bytes is written whole to a per-tool temp file (one of 8 rotating slots) and replaced by its last 50 lines, trimmed to the last 10 000 bytes, with a separate notice naming the file and how to read it. Anything under both limits passes in full.

Held by

crates/goose/src/agents/platform_extensions/developer/shell.rs:747-787 (truncate_output), limits crates/goose/src/agents/platform_extensions/developer/shell.rs:161-166, applied at crates/goose/src/agents/platform_extensions/developer/shell.rs:286-337; notice crates/goose/src/agents/platform_extensions/developer/shell.rs:717-731.

E7 What does the model read back when its tool request cannot run (unknown, not advertised, unparsed, declined, chat mode, unhandled)? RULE · code
Answered how

Code answers each request no earlier step ran with a fixed text by case: unparsed arguments get the parse-error text; a request for a name the producing turn did not advertise gets "Tool '<name>' is not available." (marked unclaimed, so the next model call also lists the available tools); in chat mode every other executable request gets the skip text as a success; a declined one gets DECLINED_RESPONSE. An advertised request nobody executed first runs the PreToolUse hooks (a denial's message becomes the answer), then gets "not available" or, for recipe__final_output without a recipe response, "Final output tool not defined"; a request still left over gets "This tool call was not handled.". With no cached extension lease, and unless every execute request was generated by an operation and has no approval history (then a lease is resolved), the tool-execution step answers execute requests with the expired-approval text.

Held by

crates/goose/src/agents/state_machine/ops_remaining_tool_calls.rs:81-247, with crates/goose/src/agents/state_machine/ops_toolcalling.rs:955-979 (no lease) and crates/goose/src/agents/state_machine/ops_toolcalling.rs:998-1019 (chat mode); texts in crates/goose/src/agents/tool_execution.rs:151-162.

E9 Which parts of an extension's reply are trusted to change goose itself (enable or disable extensions, app metadata)? RULE · code
Answered how

On every tool result, code deletes the trusted-tool-update key and goose.mcpApp metadata, and, unless the extension is a platform (in-process) one, removes any goose_extension_mutation request. On the tool-execution path, a mutation still present (so only from a platform extension) is then parsed as enable/disable of a named extension and applied to the session before the result reaches the model.

Held by

crates/goose/src/agents/extension_manager/lease.rs:732-736, using crates/goose/src/agents/extension_manager/mod.rs:583-607 (remove_untrusted_mcp_app_meta) and crates/goose/src/agents/extension_manager/mod.rs:62-69 (ExtensionMutation::take); applied by crates/goose/src/agents/extension_manager/mod.rs:1107-1130 (applying_mutation), called at crates/goose/src/agents/state_machine/ops_toolcalling.rs:456-458.

E11 An MCP server asks the person for input mid-tool (elicitation): who answers? RULING · human
Answered how

If the client capabilities carry an elicitation_handler, that function answers first and the person is never asked. Otherwise code resolves the session and tool call, turns the server's form or URL request into an action-required prompt, and waits up to 300 s for the person's accept (with data), decline or cancel, which is passed back to the server.

Held by

the person, through ActionRequiredManager::request_and_wait at crates/goose/src/agents/mcp_client.rs:418-426, unless an elicitation_handler at crates/goose/src/agents/mcp_client.rs:381-383 answers first; answers mapped at crates/goose/src/acp/server/elicitation.rs:216-230 (ACP) and crates/goose/src/agents/services.rs:868-873.

E12 Did the developer edit find exactly the text the model meant to replace? RULE · code
Answered how

Code reads the file and counts exact, non-overlapping occurrences of the model's before text: exactly one is replaced by after and the file is written; zero or more than one returns an error and writes nothing. There is no fuzzy or whitespace-tolerant matching; an empty before matches at every character boundary, so it is refused as many matches unless the file is empty, in which case after becomes the content.

Held by

crates/goose/src/agents/platform_extensions/developer/edit.rs:129-168 (string_replace), called at crates/goose/src/agents/platform_extensions/developer/edit.rs:83-120.

F. Limits and memory (10 seats)

F1 Has the conversation grown big enough that goose should summarise it before the next model call? RULING · code
Answered how

Before a model call (never when the turn is about to end on an assistant reply), code counts the session's reported total tokens plus tool-result tokens not yet reported (or estimates all agent-visible messages when no usage is recorded) and compacts when tokens / context limit exceeds the threshold. The threshold is GOOSE_AUTO_COMPACT_THRESHOLD (default 0.8), lowered to GOOSE_AUTO_COMPACT_TOKEN_LIMIT (default 225,000) / context limit when that is smaller; a threshold of 0 or less, or 1 or more, switches the trigger off. The operation is not built at all when the provider manages its own context.

Held by

crates/goose/src/agents/state_machine/ops_compaction.rs:218-227 (proactive branch of CompactionOperation::run; ratio test over_threshold at crates/goose/src/agents/state_machine/ops_compaction.rs:66-71); threshold read by TurnSettings::from_config (GOOSE_AUTO_COMPACT_THRESHOLD and GOOSE_AUTO_COMPACT_TOKEN_LIMIT, crates/goose/src/agents/services.rs:150-155) and capped by capped_compaction_threshold (crates/goose/src/context_mgmt/mod.rs:44-53, called at crates/goose/src/agents/services.rs:761-765; default 0.8 at crates/goose-context-management/src/lib.rs:32; token limit 225,000 at crates/goose/src/context_mgmt/mod.rs:25). auto_compact_threshold (crates/goose/src/context_mgmt/mod.rs:30-41) applies the same rule but a Grep over crates/ found it called only from crates/goose/tests/auto_compact_token_limit.rs.

F2 The provider refused the request as too long: compact and try again, or stop trying? RULE · code
Answered how

When the conversation ends in a provider error already classified as ContextLengthExceeded, code counts the agent-hidden context-length errors since the kickoff; while that count is at most MAX_CONTEXT_ERROR_COMPACTIONS (2) it hides the error from the model and compacts, then the turn retries. Past the cap it declines, and the exit-on-error step ends the turn with the error left visible (the lifecycle test pins exactly 2 compactions).

Held by

crates/goose/src/agents/state_machine/ops_compaction.rs:202-217 (cap MAX_CONTEXT_ERROR_COMPACTIONS = 2 at crates/goose/src/agents/state_machine/ops_compaction.rs:25; error hidden before compacting at crates/goose/src/agents/state_machine/ops_compaction.rs:229-240).

F3 When the conversation is compacted, what from the work so far is carried forward in the summary? RULING · llm
Answered how

The session's own provider and model get one call (re-sent on a context overflow with a rising share of tool responses requested for removal, crates/goose-context-management/src/summarize.rs:133-174): the compaction.md template (a user copy under <config>/prompts/ wins) rendered with every agent-visible message except turn-context events, plus a fixed user line asking for the summary. If the reply parses as the structured JSON summary and compaction_summary.md renders it to non-empty text, that rendering replaces it; otherwise the raw reply text is kept as the summary unchanged.

Held by

crates/goose-context-management/src/summarize.rs:124-182 (summarize), called by crates/goose/src/context_mgmt/mod.rs:381-410 (do_compact) through GooseCompactionModel::complete (crates/goose/src/context_mgmt/mod.rs:331-347, which calls model_config::complete_one_shot). Model: the session's own provider and model.

F4 When the history is too long even for the summariser, which tool results are thrown away first? RULING · code
Answered how

Only when the summariser call itself fails with a context-length error, code retries with 10%, 20%, 50% and then 100% of the messages carrying tool responses removed (asking for at least one each time; with an odd number of tool responses the 100% pass leaves the last one, so a single tool response is never removed), taking them alternately from just before and just after the middle of the list of tool-response messages, so the earliest and latest survive longest. With no tool responses at all it fails on the first overflow.

Held by

crates/goose-context-management/src/summarize.rs:38-75 (filter_tool_responses); percentages REMOVAL_PERCENTAGES = [0,10,20,50,100] at crates/goose-context-management/src/summarize.rs:14; retry loop crates/goose-context-management/src/summarize.rs:133-177.

F5 After compaction, which original messages does the model still see word for word, and is it told to keep working or just continue the chat? RULING · code
Answered how

Code makes every original message invisible to the model while keeping whatever visibility it already had for the person, and gives the model the summary, then one continuation line. On automatic compaction it also re-appends, text only, the most recent agent-visible user message that has text and no tool content, plus that turn's latest turn-context event; manual /compact keeps no message. The continuation line is the manual text for /compact, 'continue the conversation naturally' when the preserved message was the last one (only turn-context after it), and 'continue calling tools' otherwise.

Held by

crates/goose/src/context_mgmt/mod.rs:143-239 (compact_messages: preserved message crates/goose/src/context_mgmt/mod.rs:143-176; originals made agent-invisible crates/goose/src/context_mgmt/mod.rs:189-193; continuation choice crates/goose/src/context_mgmt/mod.rs:200-206; turn-context carry crates/goose/src/context_mgmt/mod.rs:222-239).

F6 Which earlier tool calls and their results are replaced by a one-line summary to save context? RULING · code
Answered how

Only when GOOSE_TOOL_PAIR_SUMMARIZATION is set true (default false) and the provider does not manage its own context: code counts agent-visible tool calls, excludes the ones made since the current kickoff, and once more than cutoff + 10 remain it takes the oldest 10. The cutoff is GOOSE_TOOL_CALL_CUTOFF, else 3 x (context limit x compaction threshold) / 20,000 clamped to 10..500. A pair is skipped if it is not exactly two messages, shares a message with a pair already summarised, or has sibling calls answered elsewhere.

Held by

crates/goose/src/agents/state_machine/ops_tool_pair_compaction.rs:65-183 (ToolPairCompactionOperation::run); selection crates/goose/src/context_mgmt/mod.rs:424-455 (tool_ids_to_summarize); cutoff crates/goose/src/context_mgmt/mod.rs:414-422 (compute_tool_call_cutoff, applied at crates/goose/src/agents/services.rs:766-768) or GOOSE_TOOL_CALL_CUTOFF (crates/goose/src/agents/services.rs:156).

F7 What does the one-line replacement for an old tool call and its result say? RULING · llm
Answered how

For each pair F6 selects, the session's provider and model get one call with a fixed inline system prompt and the pair's agent-visible messages formatted as text; whatever comes back is kept as the replacement, re-roled as a user message, visible to the model only, dated to the pair's last message. Present only when F6 is on.

Held by

crates/goose/src/context_mgmt/mod.rs:492-538 (summarize_tool_call; complete_one_shot at crates/goose/src/context_mgmt/mod.rs:523-531), session model.

F8 Has the agent taken as many steps as it may without coming back to the person? RULING · code
Answered how

Code counts assistant turns since the person's kickoff message; once the count reaches the budget it ends the turn with a fixed 'Would you like me to continue?' message. The budget is the session's max_turns (e.g. CLI --max-turns), else GOOSE_MAX_TURNS, else 1000. From half the budget on, the model is shown a <turn-budget>n/max used</turn-budget> part each turn.

Held by

crates/goose/src/agents/state_machine/ops_maxturns.rs:53-67 (MAX_TURNS_MESSAGE at crates/goose/src/agents/state_machine/ops_maxturns.rs:14); budget crates/goose/src/agents/services.rs:760 (session_config.max_turns, else TurnSettings.max_turns from config.default_max_turns() at crates/goose/src/agents/services.rs:135, which is GOOSE_MAX_TURNS else 1000 at crates/goose/src/config/base.rs:1339-1341); budget hint crates/goose/src/agents/state_machine/ops_maxturns.rs:20-28,42-51.

F9 Has the agent called the same tool with the same arguments too many times in a row? RULING · code
Answered how

At this pin it is never answered 'too many': the agent registers the inspector with no limit, and with no limit check_tool_call always allows. Even with a limit, inspect checks each call on a temporary copy of the inspector's state that is never written back, so repeats never accumulate. The CLI --max-tool-repetitions value is stored and never read.

Held by

crates/goose/src/tool_monitor.rs:51-80,99-134 (RepetitionInspector), registered with None at crates/goose/src/agents/services.rs:572.

F10 After how many stop-hook refusals since the kickoff does goose override the hook and end the turn anyway? RULE · code
Answered how

When a stop hook denies, code counts the denial notes already in the conversation since the kickoff, plus this one; if that exceeds the cap (GOOSE_STOP_HOOK_BLOCK_CAP, default 8) it ends the turn anyway with a visible override warning, otherwise it adds the denial to the model's context and the agent keeps going. Every denial since the kickoff counts, whatever work happened between them.

Held by

crates/goose/src/agents/state_machine/ops_stop_hook.rs:101-111; cap GOOSE_STOP_HOOK_BLOCK_CAP, default 8 (crates/goose/src/agents/services.rs:52,146-148).

G. Judging the result (11 seats)

G1 The person set a goal with /goal; has the agent fully met it before it stops? RULING · llm
Answered how

Only when the person has set a goal with /goal <text>: the first time the turn would end after the kickoff, code adds one hidden user message asking the session model to check whether the goal is fully met and continue if not. The model's own next actions are the answer; the next time the turn would end (unless a /grind goal is also set), code clears the goal without reading any verdict from the reply.

Held by

crates/goose/src/agents/state_machine/ops_retry.rs:183-201 (one hidden nudge per kickoff, marked NUDGED); goal cleared at crates/goose/src/agents/state_machine/ops_retry.rs:219-221. The model that answers is the session model.

G2 With /grind, is the work finished, or must the agent keep going? RULING · code
Answered how

Only when the person has set /grind <text>: every time the turn would end, code answers 'not done' and adds a hidden 'Keep working' message, without reading the model's reply. The work stops when the turn budget (F8) runs out or the person clears the grind (/grind off|clear|none); if a /goal is also set and not yet nudged, the goal nudge goes first.

Held by

crates/goose/src/agents/state_machine/ops_retry.rs:203-217 (grind nudge on every turn end); cleared by the person at crates/goose/src/agents/state_machine/ops_retry.rs:110-117.

G3 When a recipe with a retry block finishes, did the work actually succeed? RULING · external
Answered how

Only for a recipe with a retry block, and only when the turn ends with no /goal or /grind pending: code runs each of the recipe author's shell commands in order (sh -c, or cmd /C on Windows), and the run counts as successful only if every one exits 0; the first non-zero exit means 'failed'. Each command gets the recipe's timeout_seconds, else GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS, else 300 s; an empty check list passes.

Held by

the recipe author's shell commands, run by crates/goose/src/agents/retry.rs:14-39 (execute_success_checks_with_timeout), called at crates/goose/src/agents/state_machine/ops_retry.rs:223-242; timeout default 300 at crates/goose/src/agents/types.rs:4, read at crates/goose/src/agents/services.rs:136-140.

G4 The success checks failed: wipe the attempt and run the recipe again, or stop and report failure? RULE · code
Answered how

When G3 says 'failed', code reads the attempt count stored on the kickoff message; if it has reached the recipe's max_retries it stops with a 'Maximum retry attempts exceeded' error. Otherwise it runs the recipe's optional on_failure command (timeout on_failure_timeout_seconds, else GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS, else 600 s; if that command fails, its error is shown and no retry follows), cuts the conversation back to the kickoff, increments the count and runs again. So a recipe gets at most max_retries retries after the first run, and on_failure is not run after the last failure.

Held by

crates/goose/src/agents/state_machine/ops_retry.rs:244-277 (attempt counter crates/goose/src/agents/state_machine/ops_retry.rs:63-72; on_failure crates/goose/src/agents/state_machine/ops_retry.rs:262-271, timeout default 600 at crates/goose/src/agents/types.rs:7; reset crates/goose/src/agents/state_machine/ops_retry.rs:49-55).

G5 Does a configured policy hook accept that the agent's turn is finished? RULING · external
Answered how

Only when a plugin has configured a Stop hook: when the turn would end, code runs each matching hook command in order with a JSON payload carrying the trailing user-visible assistant text and the working directory. The first hook that denies (exit status 2 with the reason on stderr, or {"decision":"block"} on stdout) blocks the end and its reason goes to the model (capped by F10); a hook that fails to run or errors is skipped as if it allowed, even with on_failure: block, because blocking on failure applies only to PreToolUse.

Held by

plugin hook commands, run by crates/goose/src/hooks/mod.rs:684-784 (emit_blocking -> emit_blocking_with_outcome), called by crates/goose/src/agents/state_machine/ops_stop_hook.rs:80-119 with the trailing user-visible assistant text and working dir (crates/goose/src/agents/state_machine/ops_stop_hook.rs:90-94).

G6 Is the structured answer the recipe promised valid against the recipe's JSON schema? RULE · code
Answered how

Only for a recipe that declares a response JSON schema: when the model calls recipe__final_output on its own (not alongside other tools, not in Chat mode, and not denied by a PreToolUse hook), code validates the call's arguments against the schema with the jsonschema crate. Valid output is accepted and echoed as one line; any validation error is returned to the model as an error tool result listing the errors and the expected schema.

Held by

crates/goose/src/agents/final_output_tool.rs:154-175 (validate_json_output), executed by crates/goose/src/agents/state_machine/ops_recipe.rs:304-353; schema sanity at recipe load crates/goose/src/agents/final_output_tool.rs:36-53.

G7 The model stopped; has it delivered the recipe's final output, or must it be told to? RULE · code
Answered how

Only for a recipe with a response schema: when the turn would end, code looks since the kickoff for a recipe__final_output call whose tool result is a non-error 'Final output successfully collected.'; if one exists the turn may end, otherwise it adds a model-only 'You MUST call the final_output tool NOW' message and the agent continues. A provider that cannot receive goose's built-in tools gets an error up front instead.

Held by

crates/goose/src/agents/state_machine/ops_recipe.rs:380-390 (FinalOutputTool::successful_output at crates/goose/src/agents/final_output_tool.rs:55-95; continuation FINAL_OUTPUT_CONTINUATION_MESSAGE at crates/goose/src/agents/final_output_tool.rs:14-15).

G8 In this changed file, which lines have real problems, and how serious is each? RULING · llm
Answered how

When a person runs goose review (default orchestrated mode, not --checks-only): code splits the diff per file and, up to 4 at a time, runs one goose run --no-session --quiet --no-profile subprocess per file with the base review prompt (default or --prompt), the file's diff and a JSON-only output demand. That model's reply is the answer: the first balanced {...} is parsed as {findings:[...]}, missing severity becomes 'medium' and missing path becomes the file. Model is --model or the provider default; turn limit --turn-limit, else 25.

Held by

crates/goose-cli/src/commands/review/orchestrator.rs:307-394 (run_main_pass_in_parallel; subprocess crates/goose-cli/src/commands/review/orchestrator.rs:228-290; turn limit DEFAULT_CHECK_TURN_LIMIT = 25 at crates/goose/src/checks/mod.rs:17).

G9 Does the change break this review check (an .agents/checks/*.md file in the repo or a sub-tree, a global check under ~/.config/goose/checks or ~/.config/agents/checks, or the rules of an .agents/REVIEW.md), and how badly? RULING · llm
Answered how

On the default orchestrated goose review path (not --dry-run, --summary-only or --no-orchestrate, except that --no-orchestrate --checks-only uses the same check runner): code discovers the checks (closer scopes shadow global ones of the same name; --check-filter narrows them) and, up to 4 at a time, runs one goose run --no-profile subprocess per check with the check's body and the WHOLE diff, asking for JSON findings on changed lines only. The model's reply is the verdict, parsed as in G8; a finding with no severity takes the check's frontmatter default, else 'medium'. Model: --override-model, else, when --provider is given, --model (or the provider default when it is absent, skipping the check's own model), else the check's own model:, else --model/default; turns: the check's limit, else --turn-limit, else 25.

Held by

crates/goose-cli/src/commands/review/orchestrator.rs:86-154 (run_checks_in_parallel, MAX_WORKERS = 4 at crates/goose-cli/src/commands/review/orchestrator.rs:46) -> crates/goose-cli/src/commands/review/orchestrator.rs:192-222 (run_single_check_subprocess); discovery crates/goose/src/checks/mod.rs:318-392 (discover); model precedence crates/goose-cli/src/commands/review/orchestrator.rs:169-180.

G10 If a review pass or check crashed, ran out of turns or returned garbage, does the review say so or report clean? RULING · code
Answered how

Code answers 'report clean': a check or per-file pass whose subprocess failed, panicked, or returned unparseable output contributes an empty findings list and one stderr warning line; the review prints only the findings that pass the severity floor (--severity, default medium) as JSONL and exits successfully, and unless --quiet it also prints a stderr summary line that counts findings but not failures. A failed check therefore looks the same on stdout and in the exit status as a check that found nothing.

Held by

crates/goose-cli/src/commands/review/orchestrator.rs:133-150 and crates/goose-cli/src/commands/review/orchestrator.rs:384-389 (failure -> empty findings); crates/goose-cli/src/commands/review/handler.rs:246-272 (summary and Ok(())); severity floor crates/goose-cli/src/commands/review/handler.rs:84-91 applied by emit_findings (crates/goose-cli/src/commands/review/orchestrator.rs:774-787).

G11 (legacy path) Same as G8+G9, answered by one full agent session over the whole diff. RULING · llm
Answered how

Only with goose review --no-orchestrate and without --checks-only (that combination falls back to the G9 runner), and refused outright if any discovered check declares a tools: allowlist: code builds one in-process session with no profile and the developer and summon builtins, and hands it a single prompt carrying the base review prompt, the discovered check bodies and the whole diff. Whatever the model streams as text is the review; nothing parses it.

Held by

crates/goose-cli/src/commands/review/handler.rs:189-229 (build_session with developer + summon builtins, session.headless(prompt)).

H. Delegation (10 seats)

H1 Should part of the task go to a subagent, and with what instructions, source (sub-recipe, recipe or agent file), extensions, model and turn budget? RULING · llm
Answered how

The session model answers by choosing to call the delegate tool (offered by the summon extension, on by default, and only to non-subagent sessions) with the instructions, source and overrides it picks, or with resume and the id of a failed or cancelled delegate to continue. For a new delegation, code first checks that instructions or a source is present, that parameters come only with a source, and that an explicit max_turns is at least 1; building the delegate then validates further, and extension names that match nothing are logged and dropped, not refused. A resume accepts only instructions, and is refused for an id that is not a subagent of this session or whose subagent already produced its final output.

Held by

the session model's call of the delegate tool, crates/goose/src/agents/platform_extensions/summon.rs:475-546 (tool), handled at crates/goose/src/agents/platform_extensions/summon.rs:849-884 (handle_delegate).

H2 Which provider, model, extensions, working directory, approval mode and turn budget does the subagent run with? RULE · code
Answered how

Code resolves each setting by a fixed precedence. Provider: delegate recipe's goose_provider > env GOOSE_SUBAGENT_PROVIDER > the model's provider > config-file GOOSE_SUBAGENT_PROVIDER > the parent's. Model (each candidate only if its paired provider is unset or matches): recipe > env GOOSE_SUBAGENT_MODEL > the model's model > config > parent > provider default. Turns: the model's max_turns > delegate recipe > env, then config GOOSE_SUBAGENT_MAX_TURNS > GOOSE_MAX_TURNS > 1000. Extensions: the source recipe's, else the parent's, which the model may only narrow. The approval mode is copied from the parent session, and a requested working dir must resolve inside the parent's.

Held by

crates/goose/src/agents/platform_extensions/summon.rs:1238-1299 (resolve_subagent_config), with crates/goose/src/agents/platform_extensions/summon.rs:925-1013 (create_subagent), crates/goose/src/agents/platform_extensions/summon.rs:1301-1393 (model), crates/goose/src/agents/platform_extensions/summon.rs:1395-1429 (provider), crates/goose/src/agents/platform_extensions/summon.rs:1431-1437 (turns, resolve_max_turns); approval mode crates/goose/src/agents/platform_extensions/summon.rs:416-425; working dir crates/goose/src/agents/platform_extensions/summon.rs:1520-1543 (resolve_working_dir).

H3 Can a subagent start subagents of its own? RULE · code
Answered how

Always no: code does not list the delegate tool to a session whose type is SubAgent, refuses a delegate call from one with 'Delegated tasks cannot spawn further delegations', and the subagent runner returns no children for a SubAgent parent. Delegation depth is fixed at one.

Held by

crates/goose/src/agents/platform_extensions/summon.rs:874-876 (refuses); tool hidden at crates/goose/src/agents/platform_extensions/summon.rs:1448-1460; runner never starts children of a subagent session at crates/goose/src/agents/state_machine/ops_subagent.rs:94-96.

H4 When a subagent stops, did it complete, fail or get cancelled? RULE · code
Answered how

After the child's run ends, code reloads its session and checks in a fixed order: a successful recipe__final_output call anywhere in it means Completed (even if the run was cancelled or errored afterwards); else a cancelled run is Cancelled; else a run error, then a trailing error message, then the last message's text ('max turns reached; last response: ...' when it was the max-turns notice, 'stopped without final output' when empty) make it Failed.

Held by

crates/goose/src/agents/subagent_handler.rs:140-176 (ForegroundSubagentRunner::run; final-output test crates/goose/src/agents/final_output_tool.rs:55-95; failure_reason crates/goose/src/agents/subagent_handler.rs:265-287).

H5 Should the parent trust and build on what the subagent returned? RULING · llm
Answered how

The parent's session model decides, with no code check in between: each finished child's outcome arrives as one user-role message hidden from the person, 'Subagent <id> completed: <final-output JSON>' or '... failed: <reason>' (a cancelled child gets a 'Subagent <id> was cancelled before it finished.' line), and the model uses it however it chooses.

Held by

the parent's session model, which receives the outcome as a hidden user message (crates/goose-agent/src/subagent.rs:82-101), delivered by SubagentOperation::run (crates/goose-agent/src/subagent.rs:252-295).

H6 While a subagent runs, which of its messages are passed up to the parent session (and so to the person)? RULE · code
Answered how

Code passes up only the child's messages that carry an action-required tool confirmation or elicitation, after re-addressing each request id through the parent session's action-required table; the parent emits them as its own messages. Every other child message (text, tool calls, results) stays in the child session; the person also gets the start and finish notices built separately (see H5).

Held by

crates/goose/src/agents/subagent_handler.rs:125-136 (filter asks_for_a_decision at crates/goose/src/agents/subagent_handler.rs:251-263; re-addressing forwarded at crates/goose/src/agents/subagent_handler.rs:231-249).

H7 With several delegations pending, which start now and which wait? RULE · code
Answered how

Code walks the conversation in order and keeps as pending each delegated (or resumed) subagent that has no delivered or cancelled note after its latest delegation, then starts each pending one not already running until the number running reaches the cap (GOOSE_MAX_CONCURRENT_SUBAGENTS, default 5); the rest wait. It then waits for the next finished child and delivers that one result per pass, so later passes start the waiting ones.

Held by

crates/goose-agent/src/subagent.rs:252-295 (SubagentOperation::run; pending list from pending_subagents, crates/goose-agent/src/subagent.rs:173-187); cap GOOSE_MAX_CONCURRENT_SUBAGENTS, default 5 (crates/goose/src/agents/state_machine_builder.rs:89-95).

H8 Asked a question about a set of files, what does a one-shot model call answer? RULING · llm
Answered how

Only when the summarize extension has been enabled (off by default) and the session model calls its tool: code resolves each named relative path and refuses one that escapes the working directory. A named file is read as given; a named directory is walked, honouring the working directory's top-level .gitignore and skipping symlink entries. Files over 100 KB and unreadable files are skipped, more than 1 MB in total is an error. Then the session's provider and model get one call with a fixed system line and the file contents plus question. Whatever text comes back is the answer, returned with an 'Analyzed N files (L lines)' footer.

Held by

crates/goose/src/agents/platform_extensions/summarize.rs:158-212 (execute_summarize, provider.complete at crates/goose/src/agents/platform_extensions/summarize.rs:183-188), session model; extension off by default at crates/goose/src/agents/platform_extensions/mod.rs:141-152.

H9 Given the requested app, what name, description, HTML and window size does a separate model call produce? RULING · llm
Answered how

When the session model asks the apps extension for a new app: the session's provider and (registry-normalised) model get one call with the apps_create.md system prompt (a user copy wins), a user prompt carrying the request and the names of existing apps, and create_app_content as the only tool. The arguments of that tool call, coerced toward its schema and deserialised, must give the name, description and HTML and may give width, height and resizable, each on its own; after the call, code fills any it left out with 800, 600 and resizable.

Held by

crates/goose/src/agents/platform_extensions/apps.rs:305-352 (generate_new_app_content); window defaults applied at crates/goose/src/agents/platform_extensions/apps.rs:473-477 (values :61-65); apps extension default_enabled: true at crates/goose/src/agents/platform_extensions/mod.rs:66-79, compiled in only with the platform-apps cargo feature.

H10 Given the current app and feedback, what updated HTML and description does a separate model call produce? RULING · llm
Answered how

When the session model asks the apps extension to change an app with some feedback text: the session's provider and model get one call with the apps_iterate.md system prompt, a user prompt carrying the original PRD, the current HTML and the feedback, and update_app_content as the only tool. That tool call's arguments are the new description, HTML, PRD and optional window size; a reply whose reported output tokens reach the configured max_tokens (when both are known), or that made no such call, is an error.

Held by

crates/goose/src/agents/platform_extensions/apps.rs:354-401 (generate_updated_app_content).

I. Learning and feedback (1 seat)

I1 Should something the person said be remembered for later sessions, and for this project or everywhere? RULING · llm
Answered how

Only when the memory builtin extension is enabled: the session model decides, by calling remember_memory with a category, data, tags and is_global of its choosing, guided by instructions that say both 'Save proactively' and 'Always confirm with the user before saving'. Code appends the entry to <category>.txt under the working directory's .goose/memory/ (local) or the config dir's memory/ (global); no code asks the person, beyond whatever the normal tool-approval seat does.

Held by

the session model's call of the memory extension's tools (instructions crates/goose-mcp/src/memory/mod.rs:116-131; tool remember_memory at crates/goose-mcp/src/memory/mod.rs:391-424 -> remember crates/goose-mcp/src/memory/mod.rs:260-285; local vs global dir crates/goose-mcp/src/memory/mod.rs:133-135,205-213). A builtin MCP extension named at crates/goose-mcp/src/mcp_server_runner.rs:21, not a platform extension.

How this map was made

Four readers each took a part of the agent and wrote seat cards from the source at an earlier commit. The map's author re-read every seat and corrected what the source did not support. For this edition every shown card was carried to the commit named above: its citations whose lines moved were renumbered, and every shown card citing code that changed was re-read at that commit. Reviews by a model from another family (read-only, at the commit named above) checked the answers against the code, and each of their findings was checked against the code before anything changed. Nothing on this page was produced by running their code.