Skip to content

Navigation Menu

Sign in
Sign up

feat(skills): make image generation reachable from the flagship - #3073

Open
kovtcharov-amd wants to merge 4 commits into
main from
feat/image-gen-skill
Open

feat(skills): make image generation reachable from the flagship #3073
kovtcharov-amd wants to merge 4 commits into
main from
feat/image-gen-skill

Conversation

@kovtcharov-amd

@kovtcharov-amd kovtcharov-amd commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Asking the flagship agent to draw something produced nothing. PR #2995 removed the standalone SD agent on the grounds that image generation stayed available on ChatAgent behind enable_sd_tools, but nothing ever turned that flag on and no skill described the capability, so it was unreachable in practice. It now works out of the box: "draw me a picture of a red bicycle" selects the image tools, generates a PNG, and reports the path — with a starter skill covering prompt expansion, iterating on a previous image, and what to say when generation fails.

Flipping the flag alone would have been a no-op. The flagship picks tools per turn, so the three SD tools also needed a bundle or they would have been registered and still invisible to the model. Enabling them also dragged in SDToolsMixin's prompt fragment, which opens with "You are an expert image generation assistant" and grew the flagship's system prompt 40% on every turn; that guidance now lives in the skill, where it renders only when a turn calls for it.

🔍 Technical details
  • GaiaAgentConfig.enable_sd_toolsTrue. ChatAgentConfig stays False on purpose: the base class backs the doc/file profiles, where evicting the chat model to draw is the wrong trade. The flagship is the general-purpose surface.
  • New image_gen bundle in FULL_BUNDLES (+ the three names in FULL_OPTIONAL_TOOLS, since a plain ChatAgent on full lacks them). Without it test_core_and_bundles_cover_the_flagship_registry_exactly fails and load_tools has no way back to the tools.
  • ChatAgent._get_mixin_prompts now drops the SD fragment unconditionally. Measured: 12,310 → 17,281 chars with it, back to 12,378 without — the +68 is the bundle's one-line menu entry.
  • init_sd is given an absolute output_dir under GAIA_CONFIG_DIR. The mixin default is relative to cwd, which for a daemon-launched sidecar is the package directory — images would have landed in site-packages, outside the agent's own allowed_paths.
  • _describe_client_error splits timeouts from refused connections. A requests read timeout carries HTTPConnectionPool in its text, so the old "connect" in msg test told users to restart a server that was fine — the message they'd hit most, since the first generation downloads ~6.6 GB.
  • tools_count 67 → 70 in both gaia-agent.yaml and build_gaia().
  • Tool descriptions said SD-Turbo was the default; init_sd has always defaulted to SDXL-Turbo.

Test plan

  • pytest tests/unit/test_starter_skills.py — 173 passed, 13 skipped; the new image-gen parametrization is picked up automatically. Required teaching the registry_tool_names fixture about SDToolsMixin (it registers via init_sd, not a register_* method); the assertion itself was not weakened.
  • pytest hub/agents/gaia/python/tests/ tests/unit/test_chat_tool_bundles.py — 400 passed, 13 skipped, including both bundle drift gates.
  • gaia skill audit ./hub/skills/image-gen/ALLOW ✅, no findings.
  • flake8 with the repo's own arguments over src/gaia tests — 0 findings; black and isort clean on every changed file.
  • Prompt cost measured, not assumed. Built the flagship on origin/main and on this branch and diffed the composed system prompt: identical except one added line, - image_gen: Generate images from a text prompt (Stable Diffusion).
  • Tool selection verified on real requests. "draw me a picture of a red bicycle" and "generate an image of a mountain at sunset" both select all three SD tools with no skill loaded; "what does my document say about revenue" selects none of them.
  • Failure paths surface actionable errors, no silent fallback. Server down → "Cannot reach Lemonade Server. Start it with lemonade-server serve..."; bad model → lists the four valid names; load timeout → names the model and the pull command instead of blaming the connection.
  • Driven end to end through the real agent. With image-gen loaded, "draw me a picture of a red bicycle" made the model call list_sd_models() first (the skill's opening step) and, in an earlier run, call generate_image with a fully expanded prompt.
  • A generated PNG on disk — NOT verified. SDXL-Turbo downloads and registers fine (6.6 GB pulled), but Lemonade 10.10.0 on this machine cannot start its sd-cpp backend: POST /api/v1/load returns 500 model_load_error: sd-server failed to start or become ready, and no sd-server binary is present in the install. That is an environment gap, not a code path this PR changes — but it means no reviewer should treat image output as proven. Please re-run generate_image on a box with a working sd-cpp backend before merging.
  • gaia eval agent --category tool_selection — NOT comparable on this machine. Every scenario hit the 900s cap (data_vs_recall_disambiguation 2427s, known_path_read 1034s, multi_step_plan 1173s), all scoring n/a, so there is no scorecard to diff against gemma-4-e4b-d71cd914. Cause is environmental: Gemma was resident at ctx 131072 (the baseline pins 32768) and a second agent's backend shares this Lemonade. The prompt-diff and tool-selection measurements above are the substitute evidence — they show this change contributes 68 characters and zero selected tools to every one of those five scenarios — but they are not a substitute for the eval, and it should be re-run on a clean box.

@github-actions github-actions Bot added documentation Documentation changes tests Test changes labels Aug 24, 2026

github-actions Bot commented Aug 24, 2026
edited
Loading

Copy link
Copy Markdown
Contributor

Skill audit

Skill Verdict Claimed tier Cleared tiers Findings Rules
hub/agents/gaia/npm ALLOW experimental experimental, community none
hub/skills/image-gen ALLOW community experimental, community none

✅ All audited skills cleared the tier they claim.

Per-finding detail is withheld here on purpose. Read it in the Security > Code scanning tab, or download the skill-audit-reports artifact from this run. Offending source text is withheld from CI everywhere — reproduce it locally with gaia skill audit <dir> --show-snippets.

Copy link
Copy Markdown
Contributor

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

Copy link
Copy Markdown
Contributor

Request changes

This turns image generation on for the flagship agent, keeps the Stable Diffusion "persona" prompt out of the way by moving the procedure into a new image-gen skill, and rewrites the failure messages so a slow first-time model download is no longer reported as "your server is down". The trade is well argued and the tests around it are unusually good — but three things should be fixed before merge.

1. Two of the new tests need a live embedding model, and the CI job that runs them doesn't have one. They ask the agent to pick tools for "draw me a picture...", which requires the embedder; without it the agent returns "selection disabled" and the tests fail. The evidence bundle says as much — the same runner couldn't exercise this. Either skip these when no embedder is present, or feed the selector a stub, otherwise this lane goes red on every PR.

2. The new error messages tell people to run a command that no longer exists on current installs. The "pre-fetch the model" and "start the server" advice hardcodes the old lemonade-server CLI; modern installs ship a different binary, and the repo already has a helper that emits the right command for the machine it's on (with a test asserting the old form is never named). The new image-gen skill repeats the same stale command, so the agent will read it out to users. Worth fixing in all three places at once.

3. The skill's "change one thing about the last image" advice can't actually work. It tells the model to reuse the previous image's seed, which is what keeps the second picture recognisably the same — but the tool only records a seed when the user asked for one, so for the normal case the history has none. Either record a concrete seed on every generation, or soften the skill text so it doesn't promise something the history can't deliver.

Worth a quick check too: moving the image output folder to an absolute path under the user's home may trip an existing golden-prompt test that relied on it being relative — please run that one test locally.

Nothing security-sensitive in this change.

Real-world evidence

evidence-bundle.md is present and substantive: it was captured on a no-inference runner and covers the CLI (gaia skill audit/import/info/list on the new skill), the live HTTP route (/api/agents returning the new tools_count: 70), real registered-tool calls with no mocks, and the prompt composition. The rewritten error path was hit for real with nothing listening:

=== generate_image() with no Lemonade Server ===
status: error
error:
Cannot reach Lemonade Server. Start it with `lemonade-server serve`, or set LEMONADE_BASE_URL to a
running server. (Request failed: HTTPConnectionPool(host='localhost', port=13305): ... Connection refused)
=== system prompt (flagship, SD tools ON) ===
length: 11667 chars
contains 'expert image generation assistant': False
bundle line: - image_gen: Generate images from a text prompt (Stable Diffusion).

That evidence supports the core claim — the capability is reachable and the persona stays out. Marked pending strix-halo lane: a real generated PNG, the chat-model eviction/reload the docs now warn about, and Agent UI pixels. Those deferrals are fine for this lane.

Two gaps do bear on the verdict. The bundle explicitly records that per-turn tool selection could not be run ("semantic selection needs the embedder, which is not up on this runner") — that is the same condition CI runs under, which is finding 1. And it shows no gaia eval agent comparison, which this repo requires for tool-registration and system-prompt changes; if that ran elsewhere, please link it. Everything not covered above rests on static review. I could not read the PR description directly (the GitHub CLI is unavailable in this review environment), so evidence is judged from the bundle alone.

🔍 Technical details

🔴 Critical

Selection tests fail without a live embedder — test_gaia_agent.yml has none (hub/agents/gaia/python/tests/test_sd_tools_do_not_rewrite_the_prompt.py:110, :123)

_select_fresh calls the real ToolLoader.select, which embeds the query via MemoryMixin._embed_text → Lemonade. tool_loader.py:266-277 catches the failure, sets _session_disabled, and returns None. The workflow runs pytest hub/agents/gaia/python/tests/ on a plain ubuntu-latest with no Lemonade (.github/workflows/test_gaia_agent.yml:85), so assert selected is not None fails for all three cases. Sibling tests in this package avoid it by injecting a fake embed_fn (test_lazy_skill_activation.py:102-109). Cheapest fix keeps the assertion meaningful where an embedder exists:

 selected = _select_fresh(flagship, query)
 if selected is None:
 pytest.skip("dynamic tool selection needs a live embedder")
 assert SD_TOOLS <= set(selected)

Injecting a deterministic embed_fn would be stronger — a skip means CI never checks the behaviour the test was written for.

🟡 Important

Remedy commands name the removed legacy CLI (src/gaia/sd/mixin.py:473-474, :479-481; hub/skills/image-gen/SKILL.md:99)

describe_client_hint/describe_start_hint exist for exactly this and resolve modern vs. legacy tooling per machine — mixin.py:34 already imports the first and uses it at :599. test_remedy_commands_are_runnable.py:113-117 asserts a modern install never sees lemonade-server load; the new text names it unconditionally, so on a modern install both branches point at a binary that isn't there.

 if "timed out" in lowered or "timeout" in lowered:
 return (
 f"Timed out waiting for {model}; the server is running but did "
 "not answer in time. First use of an SD model both downloads "
 "and loads several GB. "
 f"{describe_client_hint('pull', model).instruction} Then confirm "
 f"it loads: {describe_client_hint('load', model).instruction} ({raw})"
 )
 if "connection refused" in lowered or "failed to establish" in lowered:
 return (
 f"Cannot reach Lemonade Server. {describe_start_hint().instruction} "
 "Or set LEMONADE_BASE_URL to a running server. "
 f"({raw})"
 )

Needs describe_start_hint added to the lemonade_launcher import. Note this also changes what tests/unit/test_sd_error_messages.py:46,:37 should assert — they currently pin the literal lemonade-server serve / pull strings, so they'd need to mock resolve_lemonade the way test_remedy_commands_are_runnable.py does, or assert against the helper's output. As written those assertions are environment-dependent: with no client binary resolved, describe_client_hint emits prose containing no pull at all.

Generation history never records the seed the skill tells the model to reuse (src/gaia/sd/mixin.py:396; hub/skills/image-gen/SKILL.md:74-79)

result["seed"] = seed stores the requested seed, which is None on any call that didn't specify one — the normal case. The response is parsed for b64_json only (:379), so the server-chosen seed is discarded. get_generation_history() therefore returns "seed": null, and the skill's "keep everything else — including the seed" instruction has nothing to keep. Recording one at call time fixes it (add import random at the top):

 start_time = time.time()
 # Record a concrete seed — history-driven iteration ("same but at
 # sunset") needs one, and the server never returns the one it picked.
 if seed is None:
 seed = random.randrange(2**32)

Alternative, if pinning the seed is undesirable: drop the seed-reuse paragraph from the skill and say the second image will differ.

The absolute output dir may break the row13 golden (hub/agents/chat/python/gaia_agent_chat/agent.py:1486-1491, tests/unit/test_profilespec_characterization.py:266-271)

That harness builds a full-profile ChatAgent with enable_sd_tools=True under patch.object(Path, "home", return_value=Path("/fake/home")) and a cleared os.environ. GAIA_CONFIG_DIR is computed at import of gaia.config, which nothing in the harness's import chain pulls in at module level — so if that first import happens inside the patch, the new sd_output_dir is /fake/home/.gaia/cache/sd/images, mkdir raises PermissionError, the new except Exception swallows it, and the SD tools are missing from a golden that lists them. If instead the module was already imported, the test writes into the developer's real ~/.gaia. I could not run pytest in this review environment — please run pytest tests/unit/test_profilespec_characterization.py -k row13 both standalone and as part of the full file to confirm which.

LLM-affecting change with no eval run shown (hub/agents/gaia/python/gaia_agent/agent.py:178, src/gaia/sd/mixin.py:147)

Three tools added to the flagship registry, a changed model parameter description, and a system-prompt composition change all sit on CLAUDE.md's "requires gaia eval agent before merge" list. evidence-bundle.md contains no scorecard comparison. Link the run if it happened elsewhere.

🟢 Minor

A connect-timeout is reported as "the server is running" (src/gaia/sd/mixin.py:469) — requests renders a connect timeout as Connection to host timed out. (connect timeout=5), which hits the timeout branch and tells the user a server that never answered a SYN is healthy. Same substring-ordering class of bug the helper was written to fix. Checking "connect timeout" before the general timeout test covers it.

The characterization harness's litter guard is now dead (tests/unit/test_profilespec_characterization.py:266-269) — it redirects cwd to tmp_path solely because init_sd used to mkdir a relative .gaia/. With the absolute path that redirect does nothing; see the 🟡 above for why it may do worse than nothing.

Strengths

  • The prompt/tools split is the right call and is pinned properly. Suppressing the 5K SD persona while keeping the one-line image_gen bundle entry gets the capability without the identity, and test_the_capability_is_still_advertised_as_a_bundle stops a future "cleanup" from removing the only thing that makes it discoverable.
  • Bundle/manifest bookkeeping is complete. image_gen added to FULL_BUNDLES, the three names added to FULL_OPTIONAL_TOOLS with the reason documented, and tools_count moved in both the manifest and build_gaia() — so the existing drift guards stay meaningful rather than being worked around.
  • The starter-skill honesty guard was extended, not bypassed. Wiring init_sd into the registry fixture (test_starter_skills.py:199) and adding generate_image to the guards-the-guard assertion keeps tools_required validation from going vacuous for the new skill.
  • The image-gen skill is written for the constraint that actually matters — generation is slow, so spend the thinking before the call — and it explicitly forbids silent fallback to a smaller model, matching the repo's fail-loudly rule.

Copy link
Copy Markdown
Contributor

Approve — this PR correctly enables a feature that already existed but was unreachable: enable_sd_tools was always False on the flagship, so asking for an image got nothing even though the tools were built and registered.

Three fixes alongside the flag change are each well-reasoned:

  • The SD "expert image generation assistant" persona (≈5 K chars) now gets unconditionally stripped from _get_mixin_prompts. It was auto-discovered and landing at the front of the system prompt — wrong identity for every non-image turn. The procedure moves to the image-gen skill where it only renders on demand.
  • Timeout vs. connection-refused are now distinguished in _describe_client_error. The previous substring test for "connect" matched both (both carry HTTPConnectionPool), routing a healthy-but-slow model download into "start the server" advice.
  • The SD output directory is now absolute under ~/.gaia/cache/sd/images instead of cwd-relative, which would resolve to the package directory for a daemon-launched sidecar.

Tests pin all three behaviours, doc updates are consistent (guide, CHANGELOG, SKILL.md, starter-skills, YAML manifest all agree on 70 tools), and the arithmetic in the guide (55 +たす 7 +たす 4 +たす 3 +たす 1 = 70) checks out.

🟢 Minor follow-up — the except Exception block around init_sd in agent.py was pre-existing. The PR improved it (DEBUG → WARNING + exc_info=True), but still swallows the error without re-raising. CLAUDE.md asks for that to be fixed when touching the file. Not blocking this merge, but worth a clean-up when the file is next touched.

🔍 Technical details

Silent-fallback note (hub/agents/chat/python/gaia_agent_chat/agent.py): the except Exception as _sd_err: logger.warning(...) handler discards the error and lets the agent proceed without SD tools. init_sd in practice can only fail with filesystem errors (directory creation) or unexpected SDK exceptions — the SD client doesn't contact the server at init time, so "Lemonade not running" is not a trigger. Still, CLAUDE.md §"No Silent Fallbacks" says: "add a specific exception type, log with context, or re-raise." exc_info=True adds context; narrowing to OSError (the realistic failure) or re-raising would complete the fix.

_get_mixin_prompts is now unconditional — even a deliberately standalone SD agent would get the persona stripped. This is intentional and pinned by test_the_sd_persona_stays_out_of_the_system_prompt, but authors composing SDToolsMixin in isolation should know the prompt is now opt-in via skill, not auto-included.

Selection skip logic (_select_fresh in the new test file): if select() returns None for a reason other than session_disabled, the caller does set(None)TypeError. In practice this can't happen (the loader only returns None when disabled), but a guard would make the skip path more robust.

Copy link
Copy Markdown
Collaborator Author

Rebased onto main and addressed the except Exception finding.

The merge conflict was a false alarm in substance — main added two proactive-skill-discovery config fields immediately above the enable_sd_tools line this PR flips, so git flagged adjacent additions rather than a real disagreement. Both sides are kept.

On the swallowed error: an unexpected failure during image-tool setup now surfaces instead of quietly costing the agent its tools. Only a genuine filesystem problem is tolerated, and when that happens the log names the directory and how to fix it. A down Lemonade was never a trigger here, which I confirmed rather than assumed.

Also documented that the Stable Diffusion prompt is opt-in, so anyone composing that mixin into their own agent isn't surprised by its absence.

Not verified: LLM behaviour. This changes tool registration, which normally needs an eval run. I could not produce one — the CI gate is down (#3016) and no local backend was available. Treat selection quality as unmeasured.

🔍 Technical details

Conflicthub/agents/gaia/python/gaia_agent/agent.py only. Main's #3010 inserted skill_discovery / skill_discovery_threshold directly above the enable_sd_tools block; resolution keeps both, with enable_sd_tools: bool = True. Merged rather than rebased so nothing already pushed gets rewritten.

except Exceptionexcept OSError (gaia_agent_chat/agent.py). Verified before narrowing, not inferred:

  • LemonadeClient.__init__ is URL parsing plus attribute assignment — no network call. Constructed against a dead port in 0.2 ms, so "Lemonade not running" cannot reach this handler.
  • resolve_lemonade_api_key and _get_lemonade_config have no raise paths.
  • @tool assigns into _TOOL_REGISTRY with no duplicate check.
  • That leaves sd_output_dir.mkdir(parents=True, exist_ok=True)OSError.

Both branches exercised: injected PermissionError → caught, WARNING naming the path and remedy, agent builds without SD tools; injected RuntimeError → propagates. GAIA_CONFIG_DIR is a real env override (src/gaia/config.py:24), so the suggested remedy is honest.

_select_freshpytest.fail on None with the session enabled, instead of falling through to set(None). Preserves the property that a real selection regression fails rather than skips.

Validation — 484 passed, 16 skipped across hub/agents/gaia/python/tests/, test_chat_tool_bundles, test_sd_error_messages, test_starter_skills. util/lint.py --all: Black, isort, Flake8 pass. mypy reports 13 pre-existing errors, all in files untouched here (factory/harvest/* from #3111, ui/sse_translation.py from #3026); src/gaia/sd/mixin.py is clean.

Evalgaia eval agent not run. Lemonade was not listening on 13305 or 8000, and ANTHROPIC_API_KEY is set to the placeholder dummy, which the judge would pass straight to the SDK and 401 on rather than falling back to subscription auth. The author's own run hit the 900 s cap on every tool_selection scenario, so there is still no scorecard to diff against gemma-4-e4b-d71cd914. Separately, no generated PNG has been produced end to end — image output remains unproven.

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

🟡 The new unit test pins the wrong CLI command — fixing the prior-review finding now also requires updating this test.

tests/unit/test_sd_error_messages.py was added in this push and explicitly asserts "lemonade-server serve" in message. The prior review flagged that lemonade-server is the removed legacy binary and that the correct fix is to call describe_start_hint() / describe_client_hint() (the helpers that already exist in mixin.py and resolve the right command per install). By hardcoding the assertion, the test now locks in the wrong behaviour — passing today, but blocking any fix of the underlying 🟡 finding.

The selection-test skip logic (session_disabled guard in _select_fresh) is a clean resolution of the prior critical finding; that part is good.

🔍 Technical details

tests/unit/test_sd_error_messages.py:741:

assert "lemonade-server serve" in message

This assertion mirrors the literal string in SDToolsMixin._describe_client_error (src/gaia/sd/mixin.py:681-684). The prior review asked for both the mixin.py error messages and the SKILL.md copy to use describe_start_hint() / describe_client_hint() instead of the hardcoded binary. If that fix lands, this assertion will need to change too — but as written it pins the wrong string and will fail once the mixin is corrected, which may cause the author to revert the fix rather than update both together.

The seed-not-recorded issue (result["seed"] = seed stores None for default calls, SKILL.md promises the seed is always reusable) is unchanged from the prior review — not a new regression, but still open.

Ovtcharov added 4 commits September 2, 2026 19:12
PR #2995 removed the standalone SD agent on the grounds that image
generation stayed available on ChatAgent behind ``enable_sd_tools``.
Nothing turned that flag on, so asking the flagship to draw anything
produced a plain "I can't" — the tools were registered nowhere a user
could reach them, and no skill described how to use them.
Turning the flag on is not enough by itself. Two things had to come with
it:
- The flagship selects tools per turn, and a tool in neither CORE nor a
 bundle is never surfaced. Without an ``image_gen`` bundle the three SD
 tools would have been registered and still invisible.
- ``SDToolsMixin`` ships a ``get_sd_system_prompt`` fragment that opens
 with "You are an expert image generation assistant". Prompt composition
 auto-discovers it, so enabling SD front-loaded the flagship's prompt
 with that identity and grew it 40% (12,310 -> 17,281 chars) for every
 turn, image or not. The procedure belongs in the skill, which renders
 only when the turn calls for it; the composed prompt is now
 byte-identical to before apart from the one-line bundle menu entry.
Also fixes an error message that sent users the wrong way: a read
timeout carries "HTTPConnectionPool" in its text, so the old substring
test reported a healthy server as unreachable. The first SD generation
downloads ~6.6 GB and routinely outlasts the request window, making that
the message users hit most.
Tool descriptions claimed SD-Turbo was the default; ``init_sd`` has
defaulted to SDXL-Turbo, which the new skill documents.
...s up
The three semantic tool-selection tests failed the whole Gaia Agent job on
every PR run. They score a real query against real tool descriptions, which
needs live embeddings, but that job is a plain ubuntu runner with no Lemonade
behind it — so `select()` returned None and the assertions blew up.
They passed when written because the authoring box had Lemonade resident on
13305. Cold runner, warm dev box: the tests only ever exercised the warm one.
Skip is keyed off the loader's own `session_disabled` flag rather than a bare
`None`, so a genuine selection regression still fails here instead of being
quietly skipped. The two `assert selected is not None` lines they replaced are
now unreachable and dropped.
...D tools
Adding the three SD tools moved the flagship from 67 to 70, but three docs
still said 67 and the guide's tool table listed only the two vision tools —
so the package would have shipped a manifest and a doc set that disagree.
Fixes the count in the guide, the npm SKILL.md confirmation section, and the
unreleased 0.1.1 tool-selection entry, adds the image tools to the guide's
Images row, and gives the CHANGELOG the entry naming the capability.
Count verified against the built registry, not arithmetic: a default
GaiaAgent registers 70 tools with memory on (65 with GAIA_MEMORY_DISABLED,
which drops the 5 memory tools).
... vanishing
A bare `except Exception` around `init_sd` meant any failure cost the agent
its image tools and left only a log line. Narrowed to `OSError`, which is the
only thing that can realistically fail there — verified: the SD client makes
no network call at construction (`LemonadeClient.__init__` is URL parsing and
attribute assignment, 0.2ms against a dead port), so a down Lemonade is not a
trigger and only the output-dir `mkdir` can raise. Anything else is a bug and
now propagates rather than silently degrading the agent.
The warning also names the directory and the remedy, so a permissions problem
is fixable from the log instead of just reported.
Two smaller items from the same review:
- `SDToolsMixin` docstring now says the SD system prompt is opt-in. ChatAgent
 drops it and the guidance lives in the `image-gen` skill, which an author
 composing the mixin standalone would otherwise discover the hard way.
- `_select_fresh` fails explicitly if `select()` ever returns None while the
 session is enabled, instead of falling through to `set(None)` and a
 confusing TypeError.

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

🟡 The new _describe_client_error method still hardcodes lemonade-server pull, lemonade-server load, and lemonade-server serve — on a modern install the client binary is lemonade and the server daemon is lemond, so these commands don't exist. The prior review flagged this same issue in the old string-branching code; the refactor renamed the site but kept the wrong strings.

What makes this push worse: test_sd_error_messages.py now pins lemonade-server serve in an assertion, so fixing the underlying code will require fixing the test simultaneously. test_remedy_commands_are_runnable.py:117,138 already asserts that describe_client_hint — the helper that resolves the correct binary — never emits lemonade-server load or lemonade-server pull on a modern install. That test and the new pinned assertion are now in direct conflict.

The seed issue from the prior review (history records the requested seed, which is None for most calls, making the skill's "reuse the seed" instruction hollow) is also not addressed here, but that was flagged before and is not new to this push.

🔍 Technical details

src/gaia/sd/mixin.py:478-487 — the new _describe_client_error method:

"Pre-fetch it with `lemonade-server pull "
f"{model}`, confirm it loads with `lemonade-server load "
f"{model}`, then retry. ({raw})"
"Cannot reach Lemonade Server. Start it with "
"`lemonade-server serve`, or set LEMONADE_BASE_URL to a running "

The helper that resolves the right binary already exists and is already imported in this file (describe_client_hint, describe_start_hint from gaia.llm.lemonade_launcher; mixin.py:34 already imports describe_client_hint and uses it at line ~600 for the non-SD path). Use them here the same way.

tests/unit/test_sd_error_messages.py:741 pins assert "lemonade-server serve" in message — this will need to be updated to assert against whatever describe_start_hint().instruction returns, the same pattern test_remedy_commands_are_runnable.py uses.

hub/skills/image-gen/SKILL.md:555-558 also names lemonade-server pull <model> directly in the failure-handling section.

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

Reviewers

No reviews

Assignees

No one assigned

Labels

documentation Documentation changes tests Test changes

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /