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

Skip to content

fix(builder): MCP-only flow with reliable persona authoring (#1532)#1533

Merged
itomek merged 17 commits into
mainfrom
autofix/issue-1532
Jun 9, 2026
Merged

fix(builder): MCP-only flow with reliable persona authoring (#1532)#1533
itomek merged 17 commits into
mainfrom
autofix/issue-1532

Conversation

@itomek

@itomek itomek commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Closes #1532

Why this matters

Before: the Builder created an agent successfully but ignored what you asked for — every scaffolded agent.py shipped the built-in zookeeper persona, zoo conversation-starter chips, and a TODO: Replace this docstring placeholder, and it offered a capability menu (RAG, file search, shell, browser…) the local model frequently mis-wired (likely a driver of the "~40% of runs fail" pattern in #1428).

After: the Builder reliably scaffolds a purpose-matched starter template and is honest about what that means. It authors a tailored system_prompt + conversation_starters from the described purpose, asks one question (MCP), and — framed as an alpha feature — states up front that it creates a template you enhance yourself, not a turnkey agent that autonomously fetches data or runs complex logic. The generated agent says the same on its first reply, so it never pretends to already do its job.

Reviewer threads (each evaluable on its own):

  • Reliability — MCP-only surface (dropped the tools param + capability menu); persona authored from the purpose with a description-derived fallback (never the zoo); deterministic confirmation (returns the tool result directly instead of a second LLM turn — also removes the exact turn that produced [Bug]: Custom Agent not working #1382's degenerate output); dict-error detection so a stray kwarg fails loud instead of fabricating success.
  • Honest framing (alpha) — the Builder intro, the generated agent's first reply, and the success confirmation all state it's an alpha starter template to extend; an "Alpha" badge on the Hub card; created agents tagged (alpha template).
  • Name fix_split_camel_case preserves intentional caps (arXiv, iOS) when the name already has spaces.

Out of scope (deferred, not lost): re-exposing tool mixins (RAG, file search, …) in generated agents via a tested flow — the low-level generate_agent_source(tools=…) is retained for it.

Test plan

  • pytest builder suite + test_amd_gaia_urls.py111 passed (run against the modified src; import resolution verified)
  • python util/lint.py --all — clean
  • Durable run test — builds an agent, loads the generated file, and runs process_query through the real agent loop with a mocked LLM, asserting it executes on its authored persona (offline / CI-safe); verified it fails cleanly on a reverted persona (no false pass)
  • Real-world e2e on Qwen3.5-35B — built an arXiv agent in gaia chat --ui: MCP-only flow, persona match, alpha/template framing, and the built agent runs in-character (cited arXiv:2006.11239) while honestly disclaiming it can't fetch live data until tools are added
  • (reviewer) click-through in gaia chat --ui

Screenshots — alpha/template framing (real UI, Qwen3.5-35B)

1. "Alpha" badge on the entry card:

Alpha badge on the Build a Custom Agent card

2. Builder intro sets expectations on start — alpha feature, scaffolds a template you extend (not a turnkey agent):

Builder intro stating alpha + template-only purpose

3. Build confirmation — "starter template", alpha, won't perform the task on its own + custom-agent docs link:

Confirmation with alpha starter-template framing

4. The built agent's FIRST reply is honest — it does not pretend to fetch live arXiv: "…running as a starter template scaffolded by GAIA Agent Builder (alpha feature). At this moment, I don't have direct access to fetch live arXiv papers…" then it explains how to enable it via MCP/tools.

Built agent honestly disclaims its limits

5. The created agent in the selector, tagged (alpha template):

New agent card tagged alpha template

6. MCP-flow confirmation — the mcpServers example renders as a verbatim code block (not a markdown-collapsed one-liner), and the path is shown verbatim:

MCP confirmation with fenced JSON

Images on the non-merging assets/pr-1533-screenshots branch (safe to delete after merge); session sidebar cropped from captures.

github-actions Bot and others added 6 commits June 8, 2026 16:25
Scaffolded agents always shipped the built-in zookeeper persona, zoo
conversation starters, and a TODO-placeholder docstring regardless of the
requested purpose. The create_agent tool exposed no system_prompt or
conversation_starters parameter, so _create_agent_impl always passed the
hardcoded zoo constants to the generator.

- Add system_prompt and conversation_starters parameters to create_agent
  and thread them through _create_agent_impl into the source generator.
- Fill the class docstring from the description; drop the TODO placeholder.
- No silent zoo: when the model omits a prompt/starters, derive a generic
  purpose-based persona instead of the zookeeper.
- Guard _split_camel_case to skip splitting when the name already has a
  space, preserving intentional internal caps (arXiv, iOS).
- Update BUILDER_SYSTEM_PROMPT to author a tailored persona and starters.

Closes #1532
Remove the tools parameter from the create_agent tool and _create_agent_impl.
The Builder now asks only about MCP (yes/no); tool-mixin selection is deferred
to a future iteration so Qwen3.5-35B has fewer things to mis-wire.

Rewrite BUILDER_SYSTEM_PROMPT to the MCP-only flow: greet → name → purpose →
restate what will be built + single MCP question → author tailored persona →
call tool. Examples show only name/description/enable_mcp/system_prompt/
conversation_starters (no tools field). If asked about other capabilities the
model tells the user to add them by editing the code (custom-agent guide link).

generate_agent_source/KNOWN_TOOLS/_render_with_tools remain untouched — they
are used by cli_agent.py and tests, and will be re-exposed in a future iteration.
Replace the "To customize it, open agent.py and: ..." hints with a clear
"This is a starter setup" + docs link so users know the agent is working
but has no special capabilities yet. Both the MCP and non-MCP confirmation
strings now:
 - keep the {py_path} line (tests assert agent-id substrings appear)
 - frame the result as a simple starter agent
 - link https://amd-gaia.ai/docs/guides/custom-agent for adding tools/RAG
 - MCP variant retains the mcp_servers.json edit hint
 - contain none of the fabrication-guard markers (Agent Created / ✅ / File location)
… turn

On a successful create_agent call, short-circuit _process_query_impl by
returning the tool's confirmation string directly instead of looping for
an LLM summary. This guarantees the starter-framing and docs link reach
the user verbatim, and removes the extra model turn that produced degenerate
output in issue #1382.

Also extend error detection to cover dict results with status=="error" (e.g.
from _execute_tool wrapping errors in a dict), in addition to the existing
"Error:"-prefix string check.

Remove the now-unused created_ok variable and the post-tool LLM-summary path.
The fabricated-success guard (hallucination → corrective turn) is kept intact.
Move the 6 TestCreateAgentImplTools tests and the acceptance scenario from
_create_agent_impl(tools=...) to generate_agent_source(tools=...) directly,
since tool-mixin composition now lives only at that layer (Builder surface no
longer accepts a tools param). Coverage parity maintained: mixin imports, MRO
order, MCP-last ordering, invalid-tool ValueError, all-KNOWN_TOOLS importable.

Drop the mcp_json.exists() assertion in test_tools_combined_with_mcp (file
creation already covered by TestCreateAgentImplMCP.test_mcp_enabled_creates_json_file);
assert the class signature class OpsBotAgent(Agent, FileIOToolsMixin, MCPClientMixin)
instead.

Add TestBuilderSurface:
- test_create_agent_tool_has_no_tools_param: introspect the registered tool's
  schema/signature and assert tools is absent
- test_stray_tools_kwarg_surfaces_error: feed a tools=[...] call through
  _process_query_impl and assert honest failure (no crash, no fabricated success)
- test_confirmation_mentions_starter_and_docs_link: assert result contains
  starter framing and amd-gaia.ai/docs/guides/custom-agent

Update fenced integration + fail-loudly tests for the short-circuit:
- test_fenced_call_result_is_deterministic_confirmation: assert answer == tool_result
  and send_messages.call_count == 1
- test_bare_call_still_fires: assert answer == tool_result
- test_real_tool_call_and_file_present_returns_success: assert answer == tool_result,
  call_count == 1
- test_create_agent_error_result_returns_honest_failure: unchanged (still green)
- test_hotreload_skipped_gracefully_when_no_registry: the input "NoReg Agent"
  already contains a space, so _split_camel_case short-circuits on the space
  (not because "NoReg" is a deliberate single token), leaving agent-id "noreg".
- test_stray_tools_kwarg_surfaces_error: _execute_tool does not propagate the
  TypeError — it catches it (agent.py:1741) and returns a {"status": "error"}
  dict, which the dict-error check in _process_query_impl then surfaces.

Comment-only changes, no behavior change.
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Code Review — #1533 fix(builder): MCP-only flow with reliable persona authoring

Approve with suggestions. This is a focused, well-tested fix for a real defect: the Builder used to scaffold every agent with the hardcoded zookeeper persona and a TODO docstring regardless of what the user asked for. The new flow authors system_prompt + conversation_starters from the described purpose, falls back to a purpose-derived prompt (never the zoo) when the model omits them, and returns the tool confirmation deterministically so the docs link always reaches the user. No blocking issues — the one thing worth tracking is a now-inconsistent sibling code path (gaia agent dev scaffold) that still emits the old zoo template.

Issues

🟢 gaia agent dev scaffold still ships the zoo persona + TODO docstring (src/gaia/cli_agent.py:366-383)
The Builder agent is fixed, but cli_agent.py still calls generate_agent_source(starters=TEMPLATE_STARTERS, system_prompt=TEMPLATE_INSTRUCTIONS) — so gaia agent produces exactly the artifact this PR set out to eliminate. Intentional per the PR's "deferred" scope and keeping generate_agent_source(tools=…) alive is the right call, but the two entry points now diverge silently. Worth a one-line follow-up issue (or a default_system_prompt hookup there too) so a future reader doesn't assume the zoo template is dead everywhere.

🟢 Test comment pins a line number that will rot (tests/unit/agents/test_builder_fail_loudly.py / test_builder_agent.py)
test_stray_tools_kwarg_surfaces_error documents the catch site as "_execute_tool catches it (agent.py:1741)". Hardcoded line refs drift on the next edit to agent.py. Naming the method is enough:

        # The stray 'tools' kwarg trips a TypeError inside the tool, but
        # the base Agent._execute_tool catches it and returns a
        # {"status": "error", ...} dict. The new dict-error check in
        # _process_query_impl then surfaces it as an honest failure, not a crash.

🟢 BUILDER_SYSTEM_PROMPT changed without a baselined eval (src/gaia/agents/builder/system_prompt.py)
CLAUDE.md flags system-prompt edits for an eval run. BuilderAgent isn't one of the baselined categories (chat/doc/file), and the PR documents a real-world e2e on Qwen3.5-35B covering both branches — so this is acceptable as-is. Calling it out only so the absence of a scorecard is a conscious decision, not an oversight.

Strengths

  • Genuinely fail-loud, not silent-fallback. The fallback default_system_prompt() is a correct generic persona, not a degradation that hides intent — and the new dict-error branch (tool_result.get("status") == "error") means a stray-kwarg TypeError surfaces as an honest failure instead of a fabricated success. The accompanying test asserts no /"Agent Created" leaks through. Exactly the CLAUDE.md pattern.
  • Deterministic confirmation removes the failure mode at its source. Returning result_str directly instead of a second LLM summarization turn guarantees the docs link reaches the user and deletes the exact turn that produced [Bug]: Custom Agent not working #1382's degenerate output. The two integration tests now assert send_messages.call_count == 1, which locks the behavior in.
  • Test coverage tracks the behavior change precisely — persona-authoring, no-zoo (both supplied and fallback), capitalization preservation (arXiv), the spaced-input _split_camel_case short-circuit, and the migration of tool-composition tests down to generate_agent_source (the layer that still owns tools=). repr()-based docstrings verified to parse cleanly with quotes/newlines/backslashes.
  • URL prefix compliance — all three new amd-gaia.ai/docs/guides/custom-agent links keep the /docs/ prefix, so test_amd_gaia_urls.py stays green.

Verdict

Approve with suggestions. No 🔴/🟡 blockers. The minors are a follow-up issue for the gaia agent scaffold divergence and a rot-prone test comment — neither needs to gate the merge.

@itomek itomek marked this pull request as draft June 8, 2026 18:52
auto-merge was automatically disabled June 8, 2026 18:52

Pull request was converted to draft

- test_stray_tools_kwarg_surfaces_error: drop the hardcoded agent.py line
  number from the comment; name the base Agent._execute_tool method instead
  so the reference doesn't rot.
- cli_agent._scaffold_python: add a one-line WHY comment above the
  generate_agent_source(...) call making explicit that the dev scaffold
  intentionally seeds the playful demo persona, while the conversational UI
  Builder authors a purpose-matched one.

Comment-only changes, no behavior change.
@itomek

itomek commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks — addressed in 610037e.

  • Test comment line-ref rot — fixed. Dropped the agent.py:1741 reference; the comment now names Agent._execute_tool (catches the TypeError, returns the {"status": "error", …} dict) and _process_query_impl (the dict-error check that surfaces it). No assertion changes.
  • gaia agent dev-scaffold divergence — kept the behavior (the dev scaffold intentionally seeds a complete, runnable demo persona a developer rewrites — distinct from the non-coder UI Builder), but killed the silent part: added a one-line WHY comment above the generate_agent_source(...) call in cli_agent.py pointing to agents/builder/agent.py. So a future reader won't assume the demo template is dead everywhere. No separate issue needed.
  • System-prompt change without a baselined eval — acknowledged as a conscious call: BuilderAgent isn't one of the baselined eval categories (chat/doc/file), so the real-world e2e on Qwen3.5-35B (both MCP branches; screenshots in the description) is the behavioral gate here, not a scorecard.

pytest tests/unit/agents/test_builder_agent.py → 99 passed; lint clean.

@itomek itomek marked this pull request as ready for review June 8, 2026 19:04
@itomek itomek self-assigned this Jun 8, 2026
kovtcharov-amd
kovtcharov-amd previously approved these changes Jun 8, 2026
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review — fix(builder): MCP-only flow with reliable persona authoring (#1533)

Approve. This is a tight, well-tested fix for a real reliability problem: scaffolded agents no longer ship the zookeeper persona/starters/TODO docstring regardless of what the user asked for. The Builder now authors a purpose-matched system_prompt + conversation_starters, collapses the capability surface to a single MCP question, and returns the tool's confirmation deterministically (no second LLM turn to mangle). The persona/fallback logic correctly follows the no-silent-fallback rule — a generic-but-correct prompt derived from name+description, never a thematically-wrong placeholder. Behavior changes are LLM-affecting (system prompt + tool schema), and the author ran the required real-world e2e on Qwen3.5-35B rather than relying on unit tests alone — exactly what CLAUDE.md asks for. No /docs/ prefix regressions in the new amd-gaia.ai runtime strings.

Note: I couldn't execute the suite (pytest isn't installed in this review sandbox), so the 107-passed claim is taken on the author's word; I verified the test logic against the code by reading and it's internally consistent.

Issues

🟢 _render_with_tools / generate_agent_source(tools=…) now have no production caller (src/gaia/agents/builder/template.py:210, :321) — with tools dropped from the Builder surface and the dev scaffold (cli_agent.py:380) not passing tools, the entire tools-composition path is reached only by tests. The PR description says this is intentional (kept for the dev scaffold + a future tested iteration), so this is a heads-up, not a request: the test migration to generate_agent_source keeps it covered, but it's now test-only code until the deferred iteration lands. Fine to keep as-is.

🟢 _split_camel_case space short-circuit changes agent-id derivation for spaced multi-cap names (src/gaia/agents/builder/agent.py:82) — e.g. "NoReg Agent" → id noreg (was no-reg). This is the intended fix (preserves arXiv/iOS), correctly documented and tested, and the new system prompt steers the model toward spaced names so the short-circuit path is the common one. Calling it out only because it's a silent change to generated directory names for that input shape — no action needed.

Strengths

  • Removing created_ok is a genuine simplification, not just a rename. Breaking immediately on the successful create_agent call means the fabricated-success heuristic (, "Agent Created") can no longer misfire on a legitimate summary — the failure mode the old created_ok flag existed to paper over is now structurally impossible. src/gaia/agents/builder/agent.py:321.
  • Dict-error detection closes a real gap honestly. A stray tools=[…] kwarg from the model now surfaces as an actionable failure via the {"status": "error"} path instead of crashing or fabricating success, and there's a dedicated test for it (test_stray_tools_kwarg_surfaces_error).
  • Test coverage tracks the behavior change precisely — new persona/no-zoo/no-TODO assertions, the call_count == 1 checks proving the summarization turn is gone, and the schema-level test_create_agent_tool_has_no_tools_param guard against the old surface creeping back.

Verdict

Approve — no blocking issues. The two 🟢 notes are informational; both reflect documented, intentional decisions.

itomek added 2 commits June 8, 2026 15:17
Close a real coverage gap: existing tests prove the Builder *writes* a correct
agent.py, but none *runs* a built agent. test_built_agent_runs_with_authored_persona
builds a non-MCP agent, dynamically imports the generated file, instantiates the
class with skip_lemonade=True + a mocked send_messages (offline, CI-safe), and
drives process_query through the base Agent loop.

Asserts the run succeeds (status == success, error_count == 0), returns the
mocked answer, and — the load-bearing check — runs on ITS authored persona
(system_prompt == the authored prompt), proving the built agent uses its own
persona rather than a placeholder.
…ated-agent caveat, confirmation, UI badge)

The Builder previously read as if it produced a turnkey agent that performs the
described task. It actually scaffolds a starter template the user extends in code.
Set honest expectations on every surface:

- Builder intro (system_prompt.py): opens by stating it's an alpha feature whose
  sole job is to scaffold a starter template; first reply sets this expectation
  before asking for a name.
- Scaffolded agent persona (template.py STARTER_CAVEAT, appended in
  _create_agent_impl): the built agent itself tells the user it's a starting
  point with no real tools yet, linking the custom-agent guide.
- Generated file header gains an "Alpha: scaffolded by the Gaia Builder" line.
- Description tagged "(alpha template)" so the Hub card reads honestly.
- Confirmation strings (MCP + non-MCP): replace "starter setup" wording with an
  explicit alpha caveat — the agent can chat but won't fetch data or perform the
  task on its own until tools/MCP are added. No fabrication-guard markers.
- Agent UI: small "Alpha" pill on the Build-a-Custom-Agent card.

Tests cover the caveat in the generated source, the alpha/template framing in
the Builder prompt, and the alpha + "won't/own" framing + docs link in both
confirmation strings.
@github-actions github-actions Bot added the electron Electron app changes label Jun 8, 2026
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

🟡 template.py(alpha template) tag leaks into the class docstring

agent.py:459 appends " (alpha template)" to desc before passing it to generate_agent_source. That same desc is used by _class_docstring(description) in template.py to generate the Python class docstring, so every scaffolded agent will have:

'Summarizes arXiv papers daily. (alpha template)'

as its class docstring — visible in IDE tooltips and help(). The tag is meant for Hub card display (AGENT_DESCRIPTION), which is correct, but it shouldn't pollute the docstring.

Fix: separate the Hub-display description from the docstring description:

hub_desc = f"{desc} (alpha template)"   # Hub card only
source = generate_agent_source(
    ...
    description=hub_desc,               # → AGENT_DESCRIPTION
    docstring_description=desc,         # → class docstring (new param, or keep desc clean)
    ...
)

Or, simpler: apply the tag only to AGENT_DESCRIPTION inside _build_class_attrs, not to the description argument flowing into _class_docstring.

@itomek itomek marked this pull request as draft June 8, 2026 19:40
@itomek

itomek commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed a framing pass + refreshed all screenshots (commits through 07cffea1):

  • Honest, alpha-labelled framing — the Builder now states up front it's an alpha feature that scaffolds a starter template you enhance yourself (not a turnkey agent that autonomously does the task). That message appears on three surfaces: the Builder's intro, the generated agent's own first reply, and the success confirmation. Alpha is labelled in three places too: an "Alpha" badge on the Hub card, the intro text, and an (alpha template) tag on created agents.
  • Durable run test — added a CI-safe test that builds an agent, loads it, and runs process_query through the real loop with a mocked LLM, asserting it executes on its authored persona. Verified it fails cleanly if the persona is reverted (no false pass). Suite: 111 passed, lint clean.
  • Screenshots refreshed — the description's screenshots now show the new behavior on Qwen3.5-35B, including the built agent honestly saying "…starter template scaffolded by GAIA Agent Builder (alpha feature)… I don't have direct access to fetch live arXiv papers…" rather than pretending to be a working tool. (The earlier shots showed the pre-framing flow.)

On the prior review's two 🟢 notes: both are intentional and unchanged — the generate_agent_source(tools=…) path is test-only by design (deferred tool-mixin iteration), and the _split_camel_case id change for spaced multi-cap names is the intended arXiv/iOS fix.

@itomek itomek dismissed stale reviews from kovtcharov-amd and itomek-amd via 2fadfb3 June 8, 2026 20:49
@itomek

itomek commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for testing on Windows, @amd-pworfolk — glad the zookeeper's retired! 🎉 And good catch.

The file on disk was always correct; this was display-only. The chat renders messages with react-markdown + remark-gfm (CommonMark), where \. is an escape sequence — so the backslash before .gaia got consumed. That's exactly why only that one separator vanished while \Users\pworfolk survived: a backslash before a letter isn't an escape, but a backslash before . is.

Fixed in 2fadfb39 by wrapping the path in inline-code backticks, where markdown does no escape processing — so the full C:\Users\pworfolk\.gaia\agents\...\agent.py now renders verbatim. I can't reproduce backslash paths on macOS, so if you could re-confirm on Windows after the next build that'd close it out. 115 tests pass; added one asserting the path stays backtick-wrapped.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review: fix(builder): MCP-only flow with reliable persona authoring (#1533)

Summary

Solid, well-tested fix that makes the Builder do what users ask instead of always shipping the zookeeper persona. The change is internally consistent end-to-end — the rewritten system prompt, the persona-authoring path in _create_agent_impl, the generated agent's appended caveat, the confirmation copy, and the UI "Alpha" badge all tell the same honest story (alpha → starter template you extend). The single most valuable change is the deterministic confirmation (agent.py): returning the tool result directly instead of a second LLM turn removes the exact summarization step that previously fabricated/garbled success messages, and the fabrication-guard now only runs on the genuine no-tool-call path so it no longer misfires on a real .

I verified the moving parts that could silently break: card_description (with the (alpha template) tag) flows into AGENT_DESCRIPTION via repr() while the clean description feeds the class docstring — so IDE tooltips stay clean, matching the tests; the #python-agent doc anchor resolves to an existing heading; all amd-gaia.ai links carry the required /docs/ prefix; and cli_agent.py still imports TEMPLATE_INSTRUCTIONS/TEMPLATE_STARTERS for the dev scaffold, so removing them from the Builder's imports left nothing dangling.

No prompt-injection or security concerns: all user-supplied strings (name, description, system prompt, starters) reach generated source through repr() or slug/identifier validation.

Issues Found

🟢 Minor — STARTER_CAVEAT is appended unconditionally, including to MCP agents (template.py, applied in agent.py:_create_agent_impl)

The caveat hard-codes "you do not yet have tools to take real actions — you cannot actually fetch, browse, or parse live data" into every generated agent's first reply, even when the user chose enable_mcp=True. It's accurate at creation time (no servers configured in mcp_servers.json yet), and the generated source carries a clear "delete this block once your agent has real tools/MCP" hint — so this is acceptable as-is. Flagging only so it's a conscious choice: for the MCP path the disclaimer is slightly blunt. No change required.

🟢 Nit — deterministic confirmation returns raw text verbatim to the UI (agent.py)

final_answer = result_str sends the multi-line confirmation (including the literal JSON mcpServers block for the MCP variant) straight to the chat bubble. That's the intended behavior and the right call for reliability; just confirm the MCP confirmation's embedded { "mcpServers": ... } block renders acceptably in the chat view (it's plain text, not fenced). The PR screenshots cover the non-MCP confirmation — an MCP-flow screenshot would close the loop.

Strengths

  • Test coverage is genuinely thorough, not box-checking. The new test_built_agent_runs_with_authored_persona actually imports the generated agent.py and runs process_query through the real loop with a mocked LLM, asserting it executes on its authored persona (zoo absent) — that's the durable test that would have caught the original bug. The tools= tests were correctly relocated to exercise generate_agent_source directly now that the param left the Builder surface, rather than deleted.
  • Fail-loud discipline matches CLAUDE.md. Dict-error detection (status == "error") plus the Error:-prefix check means a stray tools=[...] kwarg surfaces an honest failure instead of a fabricated success, and the fallback persona is "generic-but-correct" rather than a silent placeholder.
  • Clean separation of concerns for the alpha tagdesc vs card_desc keeps the marketing tag out of help()/docstrings while still labeling the Hub card, and it's pinned by tests on both the basic and MCP render paths.

Verdict

Approve. No blocking issues — the two items above are optional polish. The change is well-scoped to the Builder, the docs/UI/code/tests are mutually consistent, and the reliability fixes are backed by tests that run a built agent end-to-end.

@itomek itomek marked this pull request as draft June 8, 2026 20:49
@itomek itomek marked this pull request as ready for review June 8, 2026 20:52
…verbatim

The MCP confirmation emitted its mcp_servers.json example as a plain
2-space-indented block. react-markdown/remark-gfm collapses its newlines (and
["mcp-server-time"] can be mis-parsed as a link), so it rendered as a mangled
one-liner in the chat. Same display-only markdown-safety class as the path bug.

Render the example as a fenced ```json block (no leading indent so the fence is
recognized) and wrap the inline mcp_servers.json filename in backticks. Path
line, docs link, and the non-MCP confirmation are unchanged.
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review: fix(builder): MCP-only flow with reliable persona authoring (#1532)

Summary

Approve with suggestions — this is a clean, well-tested fix that makes the Builder do what users actually ask for. The core win: scaffolded agents now carry a purpose-matched persona + conversation starters instead of the hard-coded zookeeper, the capability menu the local model kept mis-wiring is gone in favor of a single MCP question, and the success path is honest that it produces an alpha starter template rather than a turnkey agent. The single most important design choice to understand is the deterministic confirmation (agent.py:318-323): on a successful create_agent, the tool result is returned verbatim and the loop breaks, so the demo framing + docs link reach the user without a second LLM turn — which also deletes the exact summarization turn behind #1382. The fallback persona (template.py:default_system_prompt) keeps this honest with no silent zoo: a missing prompt yields a generic-but-correct, description-derived one. No security concerns, no silent fallbacks, and the diff is scope-clean (docs + UI text + tests all move together).

I could not run pytest in the review environment (deps not installed), so I validated statically: all four changed Python modules byte-compile, the dict-error path is consistent with the base _execute_tool returning {"status": "error", ...} on a stray-kwarg TypeError (base/agent.py:1696,1729), and the test assertions line up with the code. I'm trusting the PR's "111 passed" CI claim for the runtime behavior.

Issues Found

🟢 Minor — error branch interpolates a raw dict into the user-facing message (src/gaia/agents/builder/agent.py:313-316)
When tool_result is the error dict, the message renders as I was unable to create the agent: {'status': 'error', 'error': '...'}. It's loud (good), but the dict repr leaks into user text. Extracting the message reads better:

                    detail = (
                        tool_result.get("error")
                        if isinstance(tool_result, dict)
                        else tool_result
                    )
                    final_answer = (
                        f"I was unable to create the agent: {detail}\n\n"
                        "Please check the name is valid and try again."
                    )
                    break

🟢 Minor — STARTER_CAVEAT is appended unconditionally (src/gaia/agents/builder/agent.py:455 area)
Correct today because the Builder surface can no longer attach tools, so every Builder-created agent genuinely is tool-less. Worth a one-line note (or a guard) if the deferred "re-expose tool mixins through the Builder" work lands — at that point an agent with tools would still ship the "you cannot fetch/browse/parse live data" disclaimer. Not blocking; the generated removal-hint comment mitigates it for the user.

🟢 Nit — starter count mismatch (src/gaia/agents/builder/template.py:default_conversation_starters)
The tool docstring and system prompt promise "2-3" conversation starters; the fallback returns exactly 2. Harmless, just inconsistent with the stated contract.

Note on evals (not a blocker)

This PR substantially rewrites BUILDER_SYSTEM_PROMPT and the generated-agent prompt (default_system_prompt + STARTER_CAVEAT). CLAUDE.md's eval-required surfaces are ChatAgent / DocumentQAAgent / FileIOAgent / ChatAgentLite — BuilderAgent isn't on that list, so a gaia eval agent run isn't mandated here. The durable mocked-LLM run test (test_built_agent_runs_with_authored_persona) plus the real-world Qwen3.5-35B e2e in the description are the right substitutes for an agent the eval harness doesn't cover. Flagging only so the reviewer knows the omission is intentional and acceptable, not an oversight.

Strengths

  • No silent fallback, done right — the missing-prompt path derives a correct persona from name + description and the tests pin both the provided-prompt and fallback cases to assert zoo/zookeeper never appear (test_no_zoo_persona_in_fallback). This is exactly the "fail loud / degrade honestly" posture CLAUDE.md asks for.
  • The new run-test closes a real coverage gap — prior tests proved the Builder writes a valid agent.py; test_built_agent_runs_with_authored_persona loads the generated file and drives process_query through the actual loop with a mocked LLM, asserting it runs on its authored persona offline. That's the test that would have caught the original "ignores your description" bug.
  • Clean docstring/description split — routing the (alpha template) tag to AGENT_DESCRIPTION (Hub card) while keeping the class docstring clean via repr() means IDE tooltips / help() stay honest, and it's verified by ast-level assertions rather than string matching. Nice touch.
  • _split_camel_case space short-circuit is the minimal correct fix for arXiv/iOS and is covered for all three intent cases.
  • Tools-param tests were correctly relocated to exercise generate_agent_source directly, matching the surface change instead of being deleted — coverage preserved.

Verdict

Approve with suggestions — no blocking issues. The three minor items above are polish; none need to gate the merge. Recommend applying the error-message extraction before merge and leaving a breadcrumb about the unconditional caveat for the deferred tool-mixin work.

kovtcharov-amd
kovtcharov-amd previously approved these changes Jun 8, 2026
@itomek

itomek commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed in 17704d3d.

  • MCP confirmation JSON rendering — you were right to flag it: the mcpServers example was a plain indented block, which markdown would collapse (same class as the Windows-path bug). Fixed by rendering it as a fenced ```json code block and backticking the mcp_servers.json filename, so it shows verbatim. Added an MCP-flow confirmation screenshot to the PR description (image Prevent Users from Installing Hybrid mode on Unsupported Systems #6) showing the code block rendering cleanly in the chat — closes the loop you asked about. Test `test_mcp_confirmation_json_is_fenced` pins it; 116 passed, lint clean.
  • Caveat blunt for MCP — leaving as-is per your call: it's accurate at creation (a freshly-scaffolded MCP agent has an empty mcp_servers.json, so genuinely no tools yet), and the generated file carries the "delete this once you add tools/MCP" hint.

@itomek itomek marked this pull request as draft June 8, 2026 21:00
…ters

1. _process_query_impl create_agent error path: extract a clean detail from the
   error dict (key "error", falling back to "error_brief" for the exception path)
   instead of interpolating the raw dict, so the chat no longer shows
   {'status': 'error', ...}.
2. Add a standing note where STARTER_CAVEAT is appended: the caveat is
   unconditional because the Builder attaches no tools today; gate it if
   tool-mixin re-exposure is added so a tool-equipped agent doesn't keep
   disclaiming "no tools yet".
3. default_conversation_starters now returns three starters to honor the "2-3"
   contract in the tool docstring and Builder system prompt.

Strengthen test_stray_tools_kwarg_surfaces_error to assert no raw dict repr
leaks into the user-facing answer.
@itomek

itomek commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

All three addressed in b3ac2bac.

  • Raw dict in the error message — fixed: the error branch now extracts a clean detail before interpolating. Worth noting the dict shape isn't uniform — most error returns use {"status":"error","error":...}, but the _execute_tool exception path (which the stray-tools= TypeError actually hits) uses error_brief with no error key — so the extraction is tool_result.get("error") or tool_result.get("error_brief") or str(tool_result). The stray-kwarg case now reads "I was unable to create the agent: …unexpected keyword argument 'tools'". Strengthened test_stray_tools_kwarg_surfaces_error to assert no raw dict repr leaks.
  • Unconditional caveat — added a standing breadcrumb where STARTER_CAVEAT is appended, noting it's unconditional because the Builder attaches no tools today, and to gate it if the deferred tool-mixin re-exposure lands here.
  • Starter countdefault_conversation_starters now returns 3, honoring the "2-3" contract.

116 passed, lint clean.

@itomek itomek marked this pull request as ready for review June 8, 2026 22:09
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Code Review — fix(builder): MCP-only flow with reliable persona authoring (#1532)

Summary

Solid, well-tested fix that addresses a real correctness problem: the Builder previously scaffolded a zookeeper persona and a TODO docstring regardless of what the user asked for, and surfaced a capability menu the local model frequently mis-wired. After this PR it authors a purpose-matched system_prompt + conversation_starters, asks one question (MCP), and is honest about being an alpha starter template. The single most important thing I verified: the deterministic confirmation path (final_answer = result_str; break) is safe — _execute_tool returns a tool's raw value on success (agent.py:1734), and create_agent returns a plain string, so the confirmation reaches the user verbatim with no JSON wrapping. The scope is clean (Builder-only), the tools capability is deferred not deleted (retained on generate_agent_source, still used by the dev scaffold in cli_agent.py), and tests cover the new behavior thoroughly.

No blocking issues. No security concerns. No prompt-injection in the diff.

Issues Found

🟢 Minor — detail extraction mixes an or-chain with a conditional expression (builder/agent.py:316)
The precedence is correct — (A or B or C) if dict else D — but it's hard to parse on a first read. An explicit if/else would read better with no behavior change:

if isinstance(tool_result, dict):
    detail = (
        tool_result.get("error")
        or tool_result.get("error_brief")
        or str(tool_result)
    )
else:
    detail = tool_result

Optional; the current form is functionally correct and already passes lint.

🟢 Minor — caveat-location comment is slightly misleading (template.py, the three _render_* paths)
The injected comment says the caveat is "appended after the --- below", but _get_system_prompt emits the prompt as a single-line repr(...), so there's no visually-separate block "below" — the --- lives inside the one-line string literal. The guidance itself (delete the trailing block once you add tools) is genuinely useful; consider rewording to "…appended to the end of the returned prompt (after the --- marker)" to avoid implying a separate code block. Not worth a re-spin on its own.

Strengths

  • Behavior change is backed by matching test updates, not left to rot. The removed second-LLM-summarization turn is asserted via call_count == 1 in both test_builder_fail_loudly.py and test_builder_fenced_integration.py, and the new test_built_agent_runs_with_authored_persona actually runs a generated agent through the real loop with a mocked LLM — closing the "we test that it writes a file, never that the file runs" gap. That's the right kind of coverage.
  • Genuinely honors the CLAUDE.md "no silent fallbacks" rule. The zoo persona is demoted to a documented demo-only constant; the real fallback (default_system_prompt) derives a generic-but-correct persona from name+description, and the dict-error detection makes a stray tools= kwarg fail loud instead of fabricating success — both with tests.
  • Clean separation of description (docstring/help()) vs card_description (the (alpha template) Hub tag), verified by AST in test_alpha_tag_only_on_description_not_docstring so the tag can't leak into IDE tooltips.
  • All new amd-gaia.ai URLs keep the required /docs/ prefix (per the issue [Bug]: Google Connector docs URL broken #1058 rule).

Verdict

Approve with suggestions — both findings are 🟢 optional polish; safe to merge as-is. Nice work on the test discipline and the honest alpha framing.

1. _process_query_impl create_agent error branch: replace the ternary
   detail-extraction one-liner with an explicit if/else for readability. Same
   behavior, same final_answer + break.
2. The starter-caveat comment in all three _render_* System-Prompt blocks said
   the caveat is "appended after the '---' below", implying a separate code
   block; _get_system_prompt emits the prompt as a single-line repr(). Reword to
   "appended to the end of the returned prompt (after the '---' marker)" so it's
   accurate.

Cosmetic only — no behavior change. Test assertion updated to the new wording.
@itomek

itomek commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

Both polish items addressed in f2dc2200 (no behavior change):

  • detail extraction — replaced the (or-chain) if dict else … ternary with an explicit if isinstance(tool_result, dict): … else: … block. Reads cleaner; same behavior.
  • Caveat comment wording — reworded to "appended to the end of the returned prompt (after the --- marker)" across all three _render_* paths, since _get_system_prompt emits the prompt as a single-line repr() (no separate block "below"). Updated the matching test assertion.

116 passed, lint clean.

@itomek itomek marked this pull request as draft June 8, 2026 22:19
@itomek itomek marked this pull request as ready for review June 8, 2026 22:22
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Solid, well-tested refactor that makes the Builder honest about what it produces. It drops the capability menu (MCP-only surface), authors a purpose-matched system_prompt + conversation_starters from the described purpose (with a description-derived fallback instead of the zookeeper placeholder), returns a deterministic confirmation instead of a second LLM turn, and threads "alpha starter template" framing through the Builder intro, the generated agent's own prompt, the success message, and the UI. The single most important thing: this is an intentional UX/behavior change — the Builder no longer scaffolds tool mixins via the conversational flow — and the PR handles that cleanly by retaining generate_agent_source(tools=…) at the generator layer for future re-exposure. Nicely aligned with CLAUDE.md's "no silent fallbacks": a stray tools kwarg now fails loud with a clean message rather than fabricating success.

Issues Found

🟢 Unnecessary f prefix on a placeholder-free string (src/gaia/agents/builder/agent.py:551)
f"with MCP support:\n\n" has no interpolation. Cosmetic only — it won't fail util/lint.py (pylint runs errors-only), but it's an easy tidy-up.

            "with MCP support:\n\n"

🟢 Builder system prompt is a large LLM-affecting change not covered by the eval harness (src/gaia/agents/builder/system_prompt.py)
CLAUDE.md flags system-prompt edits for gaia eval agent, but BuilderAgent isn't one of the eval categories (Chat/DocQA/FileIO/ChatAgentLite), so the harness doesn't apply here. The documented real-world e2e run on Qwen3.5-35B (MCP-only flow, persona match, in-character reply with honest disclaimer) is the right substitute. No action needed — noting it so the gap is explicit rather than silent.

Strengths

  • The offline run-the-built-agent test closes a real coverage gap (test_built_agent_runs_with_authored_persona): other tests prove the Builder writes a correct agent.py; this one imports the generated file and drives process_query through the real loop with a mocked LLM, asserting it runs on its authored persona — and the PR notes it was verified to fail on a reverted persona (no false pass). That's the right way to test generated code.
  • Honest-failure design is clean: the dict-error detection extracts error/error_brief so a raw {'status': 'error', …} dict can never leak into the user-facing message, with a test that asserts exactly that.
  • card_description vs class docstring split keeps the (alpha template) tag on the Hub card (AGENT_DESCRIPTION) while leaving IDE tooltips / help() clean — a thoughtful detail, and tested for both basic and MCP render paths.
  • Divergence is documented where it matters: the dev scaffold in cli_agent.py intentionally keeps the playful demo persona, with a comment pointing to why the conversational Builder differs. URLs correctly keep the /docs/ prefix.

Verdict

Approve. No blocking issues — only one trivial cosmetic nit. The behavior change (MCP-only Builder surface) is intentional, documented, and the low-level tool-composition path is preserved for the deferred re-exposure work.

@kovtcharov-amd kovtcharov-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.

Re-approving after the latest polish commits (my earlier approval was auto-dismissed by the new pushes). The refinements are all solid:

  • Clean error-detail extraction (error/error_brief) so a raw dict never leaks into the user-facing message — fail-loud, not silent.
  • Fenced the MCP confirmation JSON so it renders as a code block, plus 3 fallback starters honoring the 2–3 contract. Well-commented (the caveat-gating note is a nice breadcrumb) and covered by new tests.

All substantive CI is green (Code Quality, Unit Tests 3.10/3.11/3.12 + macOS, Security Linux/Windows).

⚠️ Heads-up unrelated to your change: behavior-e2e is red due to a CI infra bug, not your code — the Windows runner's PowerShell rejects uv venv && uv pip install -e ".[dev]" (The token '&&' is not a valid statement separator in this version) at the Setup step, before any test runs. That Windows job needs its run-step split onto separate lines (or shell: bash); worth a separate CI fix and shouldn't block this merge.

@itomek itomek marked this pull request as draft June 9, 2026 12:52
@itomek itomek marked this pull request as ready for review June 9, 2026 12:53
@itomek itomek added this pull request to the merge queue Jun 9, 2026
Merged via the queue into main with commit dcc3d69 Jun 9, 2026
49 of 52 checks passed
@itomek itomek deleted the autofix/issue-1532 branch June 9, 2026 12:53
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review — fix(builder): MCP-only flow with reliable persona authoring (#1532)

Approve. This is a clean, well-tested fix that closes a real correctness gap — the Builder no longer ships every scaffolded agent with the zookeeper persona and a TODO docstring while pretending it produced a turnkey agent. The persona-authoring path, deterministic confirmation, dict-error handling, and code-generation safety all check out, and the test suite genuinely exercises the new behavior end-to-end (including a built agent running on its authored persona through the real loop). No blocking issues.

No prompt-injection content in the diff or description.

Issues Found

🟢 Doubled "Error" phrasing on the string-error path (agent.py:324)
When _create_agent_impl returns an "Error: …"-prefixed string (e.g. invalid name from the generator), detail is that whole string, so the user sees I was unable to create the agent: Error: Invalid agent name…. The dict path strips cleanly; the string path doesn't. Minor cosmetic — strip a leading Error: before interpolating, or leave it. Not worth blocking.

🟢 Confirmation-message text lives in _create_agent_impl (agent.py:547-578)
The two ~15-line success strings (MCP / non-MCP) are now load-bearing user-facing copy embedded in the impl function and asserted across several tests by substring. That's fine as-is, but if this copy grows it'd read better as a small _format_confirmation(...) helper or module constant — keeps the impl focused on creation logic and gives the tests one source of truth. Pure suggestion.

Strengths

  • Generated source stays injection-safe. Every LLM-authored field now flowing into the scaffold — system_prompt, conversation_starters, description — is embedded via repr() (template.py:105, _class_docstring, _get_system_prompt). Worth calling out because these went from fixed constants to free-form model output in this PR, and the repr() discipline is exactly what keeps that from becoming a code-gen hole. The path-traversal guard in _create_agent_impl (agent.py:443-448) is a nice belt-and-suspenders too.
  • Fail-loudly handling is correct and leak-proof. The dict-error branch reads error (registry/missing-arg returns) then error_brief (exception path) — matching the two real shapes _execute_tool produces — so a stray tools=[…] kwarg surfaces as an honest failure with no raw {'status': 'error'} dict leaking to the user. test_stray_tools_kwarg_surfaces_error pins exactly that.
  • The fallback honors CLAUDE.md's no-silent-fallback rule the right way. Omitting system_prompt derives a generic-but-correct persona from name + description rather than reusing an unrelated placeholder — a thematically-wrong default is genuinely worse than a plain one, and default_system_prompt + the zoo-free assertions encode that.
  • Real coverage, not just count. test_built_agent_runs_with_authored_persona loads the generated agent.py and runs process_query on a mocked LLM, asserting the composed prompt is the authored persona — and the description notes it was verified to fail on a reverted persona (no false pass). Docs (custom-agent.mdx) and UI affordances (Alpha badge, aria-labels) were updated in lockstep.

Verdict

Approve — no 🔴/🟡 issues; the two 🟢 notes are optional polish. The system-prompt rewrite is to the BuilderAgent, which isn't part of the gaia eval agent baselines, so the eval-on-prompt-change rule doesn't apply here. Nicely scoped and verified.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents documentation Documentation changes electron Electron app changes tests Test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(builder): scaffolded agents always get the zoo placeholder persona — requested purpose is ignored

4 participants