Problem
@tool accepts arbitrary keyword arguments and silently discards them
(src/gaia/agents/base/tools.py:19-25):
def tool(func=None, *, atomic=False, display_label=None, timeout=None,
**kwargs) -> Callable: # <- kwargs documented as "ignored, for
# backward compatibility", never read
So a misspelled or unsupported decorator argument produces no error, no warning,
and no log line — the tool registers with the silent default instead. Reproduced
against the real module:
@tool(risk_tier='destructive', atmoic=True, timeuot=600)
def demo_tool(x: str) -> str:
'Demo.'
registry entry: {"name": "demo_tool", "description": "Demo.",
"parameters": {"x": {"type": "string", "required": true}},
"atomic": false, "display_label": null, "timeout": null}
risk_tier stored? False
atomic (typed "atmoic") -> False # silently default
timeout (typed "timeuot") -> None # silently default
This is a Fail-Loudly violation in shared base code: the caller asked for
behaviour, got the opposite, and nothing said so. Every agent in the repo goes
through this decorator.
Two concrete ways it bites today
1. A timeout typo silently un-caps a long tool. triage_inbox declares
@tool(timeout=600.0) (hub/agents/email/python/gaia_agent_email/tools/read_tools.py:2897)
precisely because it legitimately runs long. Mistype the kwarg and it falls back
to the global GAIA_AGENT_TOOL_TIMEOUT with no signal — surfacing later as an
unexplained mid-triage timeout, far from the cause.
2. The email spec instructs a parameter that does not exist.
docs/plans/email-triage-agent.mdx §8 writes @tool(risk_tier="read"),
@tool(risk_tier="write") and @tool(risk_tier="destructive") throughout, and
its own prerequisite note says the decorator must be extended with risk_tier
"before C1 ships". It never was — risk_tier appears nowhere in
src/gaia/agents/base/. Anyone implementing that spec literally gets a
destructive tool that registers cleanly and is gated by nothing, with no error to
tell them. (The security outcome is currently fine — the spec's named interim
gate, CONFIRMATION_REQUIRED_TOOLS, is real and in use at
hub/agents/email/python/gaia_agent_email/agent.py:628 — but that is the fallback
holding, not the decorator behaving.)
Found while auditing the email agent's tool surface (#2835/#2836); the bug is
core-framework, not email-specific.
Outcome
An unsupported keyword argument to @tool fails loudly at import time, naming the
offending argument and the tool, instead of registering a tool that silently
ignores it.
Acceptance criteria
Scope & expectations
- Where:
src/gaia/agents/base/tools.py (the tool decorator). Tests in
tests/unit/.
- Out of scope:
- Constraints:
- Fail at decoration time, not first call — a bad decorator argument is an
import-time programming error and should never reach runtime.
- Keep both
@tool and @tool(...) call syntaxes working; the repo uses both.
How to verify
CLI surface — show the new failure and the preserved behaviour:
- A scratch module using
@tool(risk_tier="destructive") now fails at import
with the exact message above (paste it).
python -m pytest tests/unit/ -q green — proves no existing call site relied
on the silent sink.
gaia chat (or any agent) starts and lists its tools normally, proving
registration is otherwise unchanged.
Problem
@toolaccepts arbitrary keyword arguments and silently discards them(
src/gaia/agents/base/tools.py:19-25):So a misspelled or unsupported decorator argument produces no error, no warning,
and no log line — the tool registers with the silent default instead. Reproduced
against the real module:
This is a Fail-Loudly violation in shared base code: the caller asked for
behaviour, got the opposite, and nothing said so. Every agent in the repo goes
through this decorator.
Two concrete ways it bites today
1. A
timeouttypo silently un-caps a long tool.triage_inboxdeclares@tool(timeout=600.0)(hub/agents/email/python/gaia_agent_email/tools/read_tools.py:2897)precisely because it legitimately runs long. Mistype the kwarg and it falls back
to the global
GAIA_AGENT_TOOL_TIMEOUTwith no signal — surfacing later as anunexplained mid-triage timeout, far from the cause.
2. The email spec instructs a parameter that does not exist.
docs/plans/email-triage-agent.mdx§8 writes@tool(risk_tier="read"),@tool(risk_tier="write")and@tool(risk_tier="destructive")throughout, andits own prerequisite note says the decorator must be extended with
risk_tier"before C1 ships". It never was —
risk_tierappears nowhere insrc/gaia/agents/base/. Anyone implementing that spec literally gets adestructive tool that registers cleanly and is gated by nothing, with no error to
tell them. (The security outcome is currently fine — the spec's named interim
gate,
CONFIRMATION_REQUIRED_TOOLS, is real and in use athub/agents/email/python/gaia_agent_email/agent.py:628— but that is the fallbackholding, not the decorator behaving.)
Found while auditing the email agent's tool surface (#2835/#2836); the bug is
core-framework, not email-specific.
Outcome
An unsupported keyword argument to
@toolfails loudly at import time, naming theoffending argument and the tool, instead of registering a tool that silently
ignores it.
Acceptance criteria
TypeErrorat decoration (import) time, with thetool name and the accepted set in the message:
TypeError: @tool(...) got unexpected keyword argument 'risk_tier' for tool 'demo_tool'. Accepted: atomic, display_label, timeout.atmoic=Trueandtimeuot=600each raise rather than silently defaulting.@tool(...)call site in the repo still imports cleanly.Sweep first: the
**kwargssink means an unsupported kwarg may alreadybe in use somewhere and passing silently. Grep every
@tool(call witharguments before changing the signature, and list what was found in the PR
— if a real call site relies on a discarded kwarg, that is its own bug to
surface, not a reason to keep the sink.
each accepted name raises; bare
@tooland@tool()both still work.**kwargsparameter and its "ignored, for backward compatibility"docstring line are removed, not merely validated around.
Scope & expectations
src/gaia/agents/base/tools.py(thetooldecorator). Tests intests/unit/.risk_tier. This issue only makes the missing parameterfail loudly. Whether GAIA adopts risk tiers at all is a separate design
decision — the interim
CONFIRMATION_REQUIRED_TOOLSgate works today.docs/plans/email-triage-agent.mdx§8 with the shipped toolsurface — tracked with perf(email-agent): cut the model-facing tool surface 65 → 49 (consolidate duplicates, demote redundant scanners) #2835.
import-time programming error and should never reach runtime.
@tooland@tool(...)call syntaxes working; the repo uses both.How to verify
CLI surface — show the new failure and the preserved behaviour:
@tool(risk_tier="destructive")now fails at importwith the exact message above (paste it).
python -m pytest tests/unit/ -qgreen — proves no existing call site reliedon the silent sink.
gaia chat(or any agent) starts and lists its tools normally, provingregistration is otherwise unchanged.