Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(agents): support parallel tool_calls (#944)#946

Merged
kovtcharov-amd merged 3 commits into
mainfrom
fix/parallel-tool-calls-944
May 11, 2026
Merged

fix(agents): support parallel tool_calls (#944)#946
kovtcharov-amd merged 3 commits into
mainfrom
fix/parallel-tool-calls-944

Conversation

@kovtcharov

@kovtcharov kovtcharov commented May 3, 2026

Copy link
Copy Markdown
Collaborator

Closes #944.

Why this matters

GAIA's default tool-calling model (Gemma-4-E4B-it-GGUF, set in #865) emits multiple tool_calls per turn on multi-intent inputs. The agent loop rejected those, retried with a misleading "malformed arguments" prompt, and bailed with zero tools fired. Result: the default model couldn't handle multi-intent utterances at all.

After this PR, multi-intent utterances work — N parallel calls drain in one loop iteration, errors don't short-circuit, and assistant text emitted alongside tool_calls (some Gemma variants do this) is preserved instead of dropped.

Test plan

  • pytest tests/unit/agents/test_parallel_tool_calls.py — 5 new integration tests covering the issue's acceptance criteria
  • pytest tests/unit/test_tool_call_priority.py — 4 new parser tests + 1 updated for the new shape
  • pytest tests/unit/ — full suite still green (2 pre-existing context-overflow flakes are unrelated, fail on main too)
  • python util/lint.py --all — clean
  • Live repro from the issue (Gemma-4-E4B + 4-event utterance against running Lemonade) — happy to run on hardware if a sanity check is wanted before merge

The agent loop raised NotImplementedError on any response with
len(tool_calls) > 1, which then hit the generic "malformed arguments"
recovery prompt and bailed after three retries with zero tools fired.
Tool-calling-trained models — including GAIA's own default
Gemma-4-E4B-it-GGUF (#865) — routinely emit parallel tool_calls on
multi-intent inputs, so this was a silent capability regression.

Changes:

- Parser (_parse_llm_response): normalises native tool_calls into a
  list of {id, name, tool_args} entries; preserves model-supplied ids
  and synthesises one when absent. Single-call legacy fields stay
  populated for N==1 so embedded-JSON consumers are unaffected.
- Loop (process_query): adds a fan-out branch that drains all N calls
  sequentially in the same iteration before re-prompting the LLM.
  Errors no longer short-circuit the drain — all N results land in
  conversation history before STATE_ERROR_RECOVERY fires.
- Assistant message: emits a proper OpenAI-shape assistant turn
  (content + tool_calls[]) instead of stuffing the raw sentinel
  envelope into content. Tool result messages now reference the
  originating call's real tool_call_id so spec-strict providers can
  correlate results to calls — also fixes parallel-same-name calls
  that previously had no way to disambiguate which result belonged
  to which call.
- Provider (lemonade.py): surfaces assistant text emitted alongside
  tool_calls in the envelope's content field instead of dropping it,
  so models that explain themselves before calling (Gemma-4-E4B
  does this regularly) keep that context.

Tests: 5 new integration tests covering the issue's acceptance
criteria — two parallel calls execute and correlate; assistant turn
carries OpenAI-shape tool_calls; three calls with one error drain
fully; misleading recovery prompt is not triggered; single native
calls now use real tool_call_id. Plus 4 new parser-layer tests
(parallel returns list / three calls with mixed args / content
alongside tool_calls / synthesised id when missing) and one updated
to assert the new shape. 1800 unit tests pass.
@kovtcharov kovtcharov requested a review from kovtcharov-amd as a code owner May 3, 2026 02:36
@github-actions github-actions Bot added llm LLM backend changes tests Test changes performance Performance-critical changes agents labels May 3, 2026
@github-actions

github-actions Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Solid fix for #944 — restores parallel tool_calls support that GAIA's own default model (Gemma-4-E4B) needs. The PR correctly drains all N calls before transitioning to error recovery, preserves model-supplied tool_call_ids for spec-strict OpenAI providers, surfaces assistant content emitted alongside tool calls, and now emits a well-shaped assistant turn (content + tool_calls[]) on the next LLM round. Test coverage is thorough — 9 new tests across parser and integration layers, all 4 acceptance criteria from the issue are pinned. Most important thing for the author: a few minor cleanups and one nontrivial duplication that's worth a follow-up but not blocking.

Issues Found

🟢 Minor

1. Dead pre-loop assignment of last_error (src/gaia/agents/base/agent.py:2869)

The last_error = None reset inside the fan-out is only ever read inside the same loop body, where it's already unconditionally re-assigned in the if is_error: branch before being consumed by console.print_error(last_error). There is no post-loop reader (the legacy last_error outer scope is repurposed elsewhere). It's dead initialization.

                tc_list = parsed["tool_calls"]
                any_error = False
                fanout_repeat_break = False

2. _QUERY_TOOLS tuple duplicated between fan-out and legacy paths (agent.py:2918 vs agent.py:3112)

Same 3-tuple defined twice in the same function. Promote to a class-level constant (e.g. _QUERY_DEDUP_TOOLS) so a future addition (e.g. query_chunks) only has to be added in one place. Not blocking — feel free to defer.

3. Significant logic duplication between fan-out and legacy single-call paths (agent.py:2866-3027 vs agent.py:3032-3204)

The fan-out body mirrors ~80 lines of the legacy single-call path almost verbatim: loop detection, query-tool dedup, _post_process_tool_result, _handle_large_tool_result, previous_outputs.append, error classification, error-message extraction. Two near-identical implementations are a drift hazard — the next bugfix in one block will silently miss the other (the legacy block already has subtly different logic for plan/step counter and STATE_COMPLETION that the fan-out skips, which is intentional but undocumented in the diff).

Not blocking for this PR — the duplication is reviewable and the comment at line 2849 calls out the asymmetry. But worth a follow-up issue to extract _dispatch_single_call(tool_name, tool_args, tool_call_id, ...) and have both sites call it. Reference pattern: similar deduplication helpers exist in src/gaia/agents/base/agent.py (e.g. _create_tool_message, _handle_large_tool_result).

4. Test assertion in test_three_calls_one_errors_all_drain is over-permissive (tests/unit/agents/test_parallel_tool_calls.py:768-770)

assert agent.execution_state == agent.STATE_ERROR_RECOVERY or any(
    "Recovered" in str(s) for s in (agent.last_result or {}).values()
)

The or short-circuits — if either branch is true, the test passes. The first branch is the actual acceptance criterion (transition to STATE_ERROR_RECOVERY happens after draining all N calls); the second branch describes the post-recovery state after the second LLM round. Either passes, so the test will pass even if N drain stops early but recovery happens later. Tighten by asserting both: that error recovery WAS entered (e.g. by inspecting agent.error_history for the right error_count increment, or by stubbing the second LLM response to not produce a final answer and asserting STATE_ERROR_RECOVERY directly), and separately that the final result mentions recovery. As written, the test still passes pre-fix if the first errored call raised and the second LLM round happened to emit "Recovered" — unlikely but uncovered.

5. Streaming path is exercised only implicitly (tests/unit/agents/test_parallel_tool_calls.py:540)

The fixture sets a.streaming = False. The streaming branch at agent.py:2273-2293 produces the same sentinel envelope on is_complete chunks, so it should flow through _parse_llm_response identically — but no test pins that. Consider one streaming smoke test (set streaming=True, stub send_messages_stream to yield a single is_complete=True chunk carrying the parallel envelope) so a future change to the streaming-completion path doesn't silently regress parallel calls.

Strengths

  • Acceptance criteria genuinely pinned in tests, not just code. The five test classes map 1:1 to the issue criteria (a/b/c + no-misleading-prompt + single-call regression). The _native_envelope helper makes the fixtures readable. _stub_chat's "called more times than preloaded" assertion is a nice trap for runaway loops.
  • Backward-compat strategy is clean. Populating legacy tool/tool_args only when len(normalised) == 1 and intentionally omitting them for N>1 forces the new code path everywhere it matters, with zero churn for embedded-JSON consumers. The docstring at agent.py:944-958 documents the contract precisely. The test_parse_native_parallel_calls_returns_list assertion that "tool" not in result for N>1 is exactly the right pin to prevent regressions where someone re-introduces the legacy field.
  • Tool result correlation now actually works. Fixing _create_tool_message to accept tool_call_id and propagating the model's real id closes a long-standing latent bug — the synthesised uuid was never linked back to anything, and any spec-strict downstream consumer would have rejected the assistant message shape. The single-native-call regression test (test_single_native_call_uses_real_tool_call_id) explicitly pins this.
  • reasoning_content fallback in the provider (lemonade.py:311-315) is a genuinely useful catch — some llama.cpp builds put the model's pre-call thought there instead of content, and the comment explains why exact-None discrimination matters per OpenAI spec.

Verdict

Approve with suggestions. The minor items above are non-blocking; the duplication in #3 deserves a follow-up issue but doesn't justify holding this fix, since #944 is an active capability regression on the default model. Suggest applying #1 inline before merge and tracking #3 separately.

@theonlychant

Copy link
Copy Markdown
Contributor

Summary

Closes #944. The agent loop rejected any LLM response with more than one entry in the OpenAI tool_calls array — a NotImplementedError raise that then routed into the generic "malformed arguments" recovery prompt and bailed after three retries with zero tools fired. Tool-calling-trained models, including GAIA's own default Gemma-4-E4B-it-GGUF (#865), routinely emit parallel tool_calls on multi-intent inputs. Result: a silent capability regression — the default model could not handle multi-intent utterances.

This PR makes the loop drain all N calls sequentially in the same iteration before re-prompting, matching OpenAI Chat Completions tools-API semantics.

What changed

  • Parser (Agent._parse_llm_response) — normalises native tool_calls into a list of {id, name, tool_args} entries. Single-call legacy fields stay populated for N==1 so embedded-JSON consumers are unaffected; for N>1 callers must use the new tool_calls field.
  • Loop (Agent.process_query) — new fan-out branch dispatches each call sequentially with proper console / dedup / post-processing per call. Errors don't short-circuit the drain: all N results land in conversation history before transitioning to STATE_ERROR_RECOVERY. The legacy single-tool branch is gated to embedded-JSON only so native single calls go through the new path too.
  • Assistant message bookkeeping — emits a proper OpenAI-shape assistant turn (content + tool_calls[]) instead of stuffing the raw {"__tool_calls__": ...} sentinel envelope into content. Tool result messages now reference each call's real tool_call_id (was synthesising fresh uuids that didn't link back to anything — broke disambiguation for parallel-same-name calls).
  • Provider (LemonadeProvider) — surfaces assistant text emitted alongside tool_calls (some Gemma variants explain themselves before calling) in the envelope's content instead of dropping it.
  • Misleading recovery prompt — the "malformed arguments" copy is no longer triggered for parallel calls because parsing now succeeds. The copy itself is unchanged for the legitimate ValueError arg-parsing case it was written for.

Test plan

  • pytest tests/unit/agents/test_parallel_tool_calls.py -xvs — 5 new integration tests covering issue Agent loop rejects parallel tool_calls from native tool-calling models (Gemma-4-E4B default) #944 acceptance criteria (a/b/c + no-misleading-prompt + single-call regression).
  • pytest tests/unit/test_tool_call_priority.py -xvs — 4 new parser-layer tests (parallel-returns-list / three-calls-mixed-args / content-alongside-tool_calls / synthesised-id-when-missing) and 1 updated single-call test asserting the new shape.
  • pytest tests/unit/ --timeout=60 — 1800 passed, 14 skipped, 0 failed.
  • python util/lint.py --all — passes (1 Black auto-fix applied).
  • Reproducer from the issue (Gemma-4-E4B + 4-event utterance against running Lemonade) — author has the harness; happy to run if a real-hardware sanity check is wanted.

Should I close my pull request at #945 because I don't want to add any confusion to this

Audit follow-up to the parallel tool_calls fix: the plan-response
branch (process_query line ~2751) was still appending the raw
{"__tool_calls__": ...} sentinel envelope as content, which would
leave a malformed assistant turn in history if a tool-calling-trained
planner emitted native tool_calls instead of (or alongside) the
expected plan JSON. The fan-out below would then add tool_call_ids
that don't link back to any structured assistant turn.

Factor the OpenAI-shape construction into _build_assistant_message
and use it from both the main response path and the plan response
path.
@kovtcharov kovtcharov force-pushed the fix/parallel-tool-calls-944 branch from 0f126a4 to be24697 Compare May 3, 2026 03:09
@github-actions

github-actions Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Solid, well-motivated fix for a real default-model regression: Gemma-4-E4B (the GAIA default per #865) was emitting parallel tool_calls and the agent was hard-rejecting them, retrying with a misleading "malformed arguments" prompt and bailing with zero tools fired. The implementation is faithful to the OpenAI Chat Completions tools API contract — N calls drain in one loop iteration with proper tool_call_id correlation, errors don't short-circuit, and assistant text emitted alongside tool_calls is preserved. Strong test coverage (5 new integration tests + 4 new parser tests) matches the issue's acceptance criteria.

The single most important thing the author should know: the new fan-out path duplicates ~100 lines of tool-execution logic from the legacy single-tool path (repeat detection, query dedup, _post_process_tool_result, _handle_large_tool_result, error classification). It works today, but creates real drift risk. See the first item below.

Issues Found

🟡 Important — Tool-execution logic duplicated between fan-out and legacy single-tool path (src/gaia/agents/base/agent.py:2884-3045 vs. :3050-3222)

The new fan-out at L2884 and the legacy embedded-JSON branch at L3050 share essentially identical bodies: print_tool_usagestart_progress → repeat detection (L2908-2920 mirrors L3091-3107) → _execute_tool → query-dedup block (L2935-2963 mirrors L3128-3153) → _post_process_tool_result_handle_large_tool_resultprevious_outputs append → error classification (L2999-3028 mirrors L3190-3222). The only meaningful behavioural deltas are:

  1. The fan-out passes tool_call_id=tc["id"] to _create_tool_message; the legacy path doesn't.
  2. The fan-out drains all N before transitioning to STATE_ERROR_RECOVERY; the legacy path transitions on the first error (correct for N=1).
  3. The fan-out skips the "plan + tool both present → defer to plan" guard at L3062-3068 and the single-step STATE_COMPLETION flip at L3071-3075.

Item (3) is the one worth a second look — see the next two bullets. But the broader concern is maintenance: the next contributor who fixes a bug in error classification or query dedup has to remember to fix it in two places. A follow-up that factors _dispatch_single_call(tc_name, tc_args, tc_id) and has both paths call it would erase the drift risk. Not a blocker for this PR (refactoring the legacy path into the fan-out shape carries regression risk for embedded-JSON consumers), but worth filing.

🟡 Important — Plan-vs-tool_calls priority guard missing in fan-out (src/gaia/agents/base/agent.py:2884)

The legacy path at L3062-3068 has:

if "plan" in parsed and self.current_plan and self.total_plan_steps > 0:
    logger.debug("Plan and tool both present - deferring to plan execution logic")
    continue  # Skip tool execution, let plan execution handle it

The fan-out has no equivalent. If the planner emits native tool_calls and a plan field in the same response (rare but the be24697 commit message acknowledges this is possible), the fan-out fires the tool_calls immediately rather than deferring to plan execution. Could be intentional (parallel calls don't fit the single-step plan model), but the asymmetry is undocumented. Either add the same guard for symmetry, or add a comment explaining why parallel calls bypass plan deferral.

🟢 Minor — last_error = None reset wipes function-level state on every fan-out entry (src/gaia/agents/base/agent.py:2887)

last_error is declared at function scope on L1934 and consumed in the error-recovery prompt at L2237. The legacy path at L3197 only assigns inside if is_error:, so last_error is preserved across iterations. The fan-out unconditionally resets to None at L2887 on every entry, which means a prior iteration's error context is wiped even if this iteration's fan-out has no failures. Probably benign in practice (state transitions to STATE_ERROR_RECOVERY on the same iteration that records the error, and last_error is consumed on the next), but the asymmetry with the legacy path is unintentional. Drop the L2887 line — Python's function scope means last_error is already in scope:

            if parsed.get("tool_calls"):
                tc_list = parsed["tool_calls"]
                any_error = False
                fanout_repeat_break = False

🟢 Minor — STATE_COMPLETION flip for single-step plans skipped in fan-out (src/gaia/agents/base/agent.py:2884)

The legacy path at L3070-3075 sets self.execution_state = self.STATE_COMPLETION when total_plan_steps == 1 before executing the tool. The fan-out skips this. With this PR, single native calls (len(tool_calls) == 1) flow through the fan-out instead of the legacy path, so a single-step plan that resolves to a single native tool_call no longer flips to STATE_COMPLETION. The downstream comment at L3179-3188 says "Don't break here — let the loop continue so the LLM can process the tool result", suggesting the flip mostly drives bookkeeping rather than control flow, but a one-line if len(tc_list) == 1 and self.total_plan_steps == 1: self.execution_state = self.STATE_COMPLETION near L2890 would preserve parity. Worth verifying with an existing single-step-plan test before merge.

🟢 Minor — _register_tool test helper bypasses the @tool decorator (tests/unit/agents/test_parallel_tool_calls.py:75-85)

The fixture injects directly into _TOOL_REGISTRY with "parameters": {} and "atomic": False, sidestepping any normalisation @tool performs. Today the agent's tool-execution path doesn't depend on extra fields, but if @tool ever adds e.g. JSON-schema validation or argument coercion, these tests will silently diverge from real-world behaviour. Either:

  • Use @tool directly in the test (preferred — it's what production code does), or
  • Add a comment in the helper explicitly noting the intentional bypass and what's being skipped.

Strengths

  • Backward-compat shape is well-thought-through. The N==1 native path populates both the new tool_calls list (real id, correlated result) and the legacy tool/tool_args fields, then gates the legacy execution branch on not parsed.get("tool_calls") (agent.py:3053) so single calls still get the new id-correlated result message without breaking embedded-JSON consumers. Documented in the docstring at agent.py:944-958.
  • Defensive parsing. Each tool_call entry is normalised independently (agent.py:1004-1035), so one bad-arguments entry only poisons that call's parse, not the others. Synthesised id when llama.cpp omits one (agent.py:1034) is a nice belt-and-braces — and the corresponding test (test_parse_native_synthesises_id_when_missing) pins it.
  • Empty tool_calls list now raises (agent.py:994-997) instead of silently tool_calls[0]-indexing — aligns with CLAUDE.md's no-silent-fallbacks rule.
  • _build_assistant_message factored out (agent.py:1677-1715) removes duplication on the assistant-message side and is correctly applied to both the main response path and the planner path (be24697). The docstring explains why the OpenAI shape matters — spec-strict providers reject the raw sentinel envelope as content.
  • Content carry-through with reasoning_content fallback (lemonade.py:310-315) catches the llama.cpp variant where assistant text lands in reasoning_content instead of content — a real-world quirk worth pinning.
  • Test pins the acceptance criteria, not just happy paths. test_three_calls_one_errors_all_drain enforces the "drain all N before recovery" contract; test_no_malformed_arguments_prompt_injected is a regression guard against the exact symptom in the issue.

Verdict

Approve with suggestions — the 🟡 items are real concerns but none are blockers. The duplication issue is best handled in a follow-up; the plan-priority asymmetry and last_error reset are easy in-PR cleanups if the author wants to land them now. Tests are strong, motivation is clear, and the OpenAI-shape correctness work is exactly the right fix.

@kovtcharov

Copy link
Copy Markdown
Collaborator Author

Should I close my pull request at #945 because I don't want to add any confusion to this

Didn't realize you fixed it, you can complete yours since you already pushed a fix before me.

@theonlychant

Copy link
Copy Markdown
Contributor

Should I close my pull request at #945 because I don't want to add any confusion to this

Didn't realize you fixed it, you can complete yours since you already pushed a fix before me.

yea I saw it the second you posted and attempted to tackle it I can close my PR if you want though I don't want to cause much confusion

@itomek-amd itomek-amd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kovtcharov — approving. Verified locally on the PR branch:

  • pytest tests/unit/agents/test_parallel_tool_calls.py tests/unit/test_tool_call_priority.py -v — all new tests pass.
  • The two prior bot reviews land on every code-level concern I'd raise (dead last_error reset, _QUERY_TOOLS duplication, fan-out vs. legacy path drift, missing plan-priority guard, STATE_COMPLETION flip skipped, over-permissive assertion in test_three_calls_one_errors_all_drain, _register_tool bypassing @tool). Worth working through that list before merge — the last_error reset and the plan-priority guard are easy in-PR cleanups; the broader fan-out/legacy duplication is a fair follow-up issue.

One small adjustment to the streaming concern from the first bot review (its item #5): the streaming branch in lemonade.py never sees native tool_calls because the provider force-disables streaming whenever tools are in play —

# lemonade.py:247
effective_stream = stream and not (tool_capable and tools)

So the agent's streaming-completion path can't currently regress parallel calls via this provider. A streaming smoke test is still worth having for defence in depth (a future provider could behave differently), but it's not a load-bearing risk today. Worth a one-line comment near the fan-out noting the assumption.

Beyond that — backward-compat strategy is well thought out (N==1 keeps legacy fields populated, N>1 forces callers onto the new path), _create_tool_message now propagates real tool_call_ids, and _build_assistant_message finally emits OpenAI-spec-shape assistant turns instead of the raw sentinel envelope. Solid work.

dislovelhl pushed a commit to dislovelhl/gaia-acgs that referenced this pull request May 4, 2026
The existing PR-description guidance in CLAUDE.md was directionally
right ("tight and value-focused") but loose enough that a recent PR
(amd#946 / amd#944) still shipped with a "What changed" enumeration the diff
already showed and a "Summary" section that buried the user-observable
impact behind implementation details. Future agents reading the file
would do the same.

After this change the default shape is just two sections — "Why this
matters" (with required before/after framing) and "Test plan" — with a
"user-observable impact in <30s without reading the diff" litmus check,
and three new anti-patterns lifted directly from the patterns the prior
PR exhibited.

## Test plan

- [x] No code changed; doc-only edit
- [x] Re-read the new section against amd#946 to confirm the prior
description fails the new rules
theonlychant pushed a commit to theonlychant/gaia that referenced this pull request May 5, 2026
The existing PR-description guidance in CLAUDE.md was directionally
right ("tight and value-focused") but loose enough that a recent PR
(amd#946 / amd#944) still shipped with a "What changed" enumeration the diff
already showed and a "Summary" section that buried the user-observable
impact behind implementation details. Future agents reading the file
would do the same.

After this change the default shape is just two sections — "Why this
matters" (with required before/after framing) and "Test plan" — with a
"user-observable impact in <30s without reading the diff" litmus check,
and three new anti-patterns lifted directly from the patterns the prior
PR exhibited.

## Test plan

- [x] No code changed; doc-only edit
- [x] Re-read the new section against amd#946 to confirm the prior
description fails the new rules
@kovtcharov-amd kovtcharov-amd requested a review from itomek May 11, 2026 20:06
@kovtcharov-amd kovtcharov-amd added this pull request to the merge queue May 11, 2026
Merged via the queue into main with commit a927ce3 May 11, 2026
33 of 34 checks passed
@kovtcharov-amd kovtcharov-amd deleted the fix/parallel-tool-calls-944 branch May 11, 2026 20:10
@itomek itomek mentioned this pull request May 14, 2026
6 tasks
pull Bot pushed a commit to bhardwajRahul/gaia that referenced this pull request May 15, 2026
## Why this matters

v0.18.0 ships agent memory v2 (hybrid-search second brain with LLM
extraction and observability dashboard), ChatAgent split into three
composable agents (Chat/FileIO/DocumentQA), parallel tool calls, and a
Telegram adapter scaffold — plus fixes the RAG-on-PDF timeout with Gemma
4 that broke document Q&A since v0.17.6 and adds CI gates that enforce
RAG quality baselines on every future PR.

Full notes: `docs/releases/v0.18.0.mdx`.

## What's New

- **Agent memory v2** ([amd#606](amd#606)) —
Hybrid semantic + keyword search, LLM extraction, observability
dashboard via SSE streaming
([amd#1032](amd#1032)). Per-user isolation
enforced; extraction runs async so it doesn't add latency.
- **ChatAgent split** ([amd#979](amd#979)) —
`ChatAgent`, `FileIOAgent`, and `DocumentQAAgent` replace the monolithic
class; each composable via `tools=`. Backward-compatible shim preserved.
- **Parallel tool calls** ([amd#946](amd#946))
— Multiple `tool_calls` from a single LLM turn are executed
concurrently, cutting round-trips for multi-tool workflows.
- **Telegram adapter scaffold, Phase 0**
([amd#951](amd#951)) — `gaia telegram
start|stop|status`, per-user session isolation, `[telegram]` extras.
Phase 1 (message handling + allowed-users gate) tracked in
[amd#889](amd#889).
- **Connectors: per-MCP toggle + single-writer enforcement**
([amd#1018](amd#1018),
[amd#998](amd#998)) — Disable individual MCP
servers without removing them; concurrent writes serialised with
actionable errors on contention.
- **File navigation, web browsing, and write security**
([amd#495](amd#495)) — `FileSearchToolsMixin`,
web browsing tool, and scratchpad mixin in `KNOWN_TOOLS`; write tools
check `allowed_paths` before dispatch.
- **Email UI and policy alerts**
([amd#995](amd#995),
[amd#1039](amd#1039),
[amd#952](amd#952)) — Pre-scan triage card,
in-chat Connect, policy alert cards, and durable receipts for
confirmation-gated actions.

## Bug Fixes

- **RAG-on-PDF timeouts on Gemma 4**
([amd#1034](amd#1034), closes
[amd#1030](amd#1030)) — Prompt-size budget
check added at composition time; CI gates enforce it on every PR
([amd#1040](amd#1040)).
- **Envelope-level parse failure crashed SD recovery**
([amd#1047](amd#1047), closes
[amd#1023](amd#1023)) — Falls through to a
clean recovery path with step-1 context preserved.
- **Windows-path tool args corrupted**
([amd#1027](amd#1027)) — Backslash
normalisation now happens after argument parsing.
- **Blender `send_command` hung**
([amd#1026](amd#1026), closes
[amd#1022](amd#1022)) — Read timeout applied
to persistent-connection servers.
- **`gaia chat init` in post-install banner**
([amd#1029](amd#1029), closes
[amd#1024](amd#1024)) — Replaced with the
correct `gaia init`.
- **Keyring treated as required**
([amd#1028](amd#1028)) — Import guarded;
optional on systems without `keyring`.
- **electron-builder URLs stale**
([amd#953](amd#953)) — Three doc/installer
files updated to current download paths.

## Tooling & Docs

- **RAG eval CI gates** ([amd#1040](amd#1040),
closes [amd#1033](amd#1033)) — RAG quality
baselines + prompt-size budget enforced on every PR.
- **Fork-PR authors now receive Claude review**
([amd#932](amd#932)) —
`allowed_non_write_users: "*"` with prompt-injection mitigations
documented.
- **Eval runs mandated before merging**
([amd#1036](amd#1036)) — `CLAUDE.md` requires
`gaia eval agent` for LLM-affecting changes.
- **GAIA website** ([amd#369](amd#369)) —
[amd-gaia.ai](https://amd-gaia.ai) live.
- **Custom agent guide reorganised**
([amd#997](amd#997)), Lemonade PPA docs
([amd#801](amd#801)), broken Lemonade CLI URL
fixed ([amd#996](amd#996)), WhatsApp adapter
evaluation spec ([amd#950](amd#950)).

## Release checklist

- [x] `util/validate_release_notes.py docs/releases/v0.18.0.mdx --tag
v0.18.0` passes
- [x] `src/gaia/version.py` → `0.18.0`
- [x] `src/gaia/apps/webui/package.json` → `0.18.0`
- [x] Navbar label in `docs/docs.json` → `v0.18.0 · Lemonade 10.2.0`
- [x] All 28 commits in range (v0.17.6..HEAD) are represented in the
notes
- [ ] Review from @kovtcharov-amd addressed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents llm LLM backend changes performance Performance-critical changes tests Test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Agent loop rejects parallel tool_calls from native tool-calling models (Gemma-4-E4B default)

5 participants