Tools · Quality & Media¶
Code analysis and validation, plus ComfyUI image generation. See Tool Reference for how to read these signatures.
QualityTools¶
Group quality. All eight tools are tier Standard (the class comment claiming validate_web_app/capture_api_schema are Elevated is stale — see tier note). File ops use the workspace executor; the validation tools reach a running app inside the container.
analyze_code¶
analyze_code(path, focus?) → analysis report
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
path |
string | yes | — | File or directory to analyze |
focus |
string | no | all |
bugs, performance, security, style, or all |
Auto-selects a linter by extension — dotnet format (.cs), ESLint (JS/TS), pylint/flake8 (.py) — and reads the file itself, so the agent should not call read_file first. Returns ## Code Analysis: <path> with the tool used, focus, and linter output (truncated 4000 chars). Requires container context.
code_review¶
code_review(path, context_info?) → structured review
| Param | Type | Req | Description |
|---|---|---|---|
path |
string | yes | File to review |
context_info |
string | no | What the code should do |
The LLM-backed quality tool: hands the file to a model through the scheduler and returns a severity-tagged review (severity / line / category / description), not linter output. 50 KB file cap; falls back to a stub line on scheduler failure.
run_tests¶
run_tests(test_pattern?, verbose?) → test results
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
test_pattern |
string | no | — | Pattern to filter which tests run |
verbose |
bool | no | false |
Exposed but the method does not branch on it today |
Detects the framework (npm, pytest, or dotnet test; a Python suite with no config file is recognised by test_*.py or *_test.py at the root or under tests/) and reads the pass/fail counts from the runner's summary line, including node:test's # pass N / ℹ pass N form. Returns ## Test Results: FAILED when any test failed or the tests could not be collected (a pytest collection error, kept in the output), PASSED when tests passed and none failed, and otherwise NO TESTS RAN — <reason> (no test script defined, no framework detected, no tests found, a test script that names test files that do not exist, a non-zero exit with no summary, or no counts at all), with the framework, counts, and raw output (truncated 5000 chars). A runner that never ran anything is not a pass. A Node project runs its own test script with npm test; the pattern is passed through (as a Jest regex) only when that script runs Jest or Vitest, and otherwise the whole suite runs and the report says the pattern was not applied. A project with no test script and no test code file returns ## Test Results: NO TEST SUITE, which is not a verdict. Requires container context.
validate_web_app¶
validate_web_app(url, checks) → PASS/FAIL report
| Param | Type | Req | Description |
|---|---|---|---|
url |
string | yes | Base URL of the web app (e.g. http://localhost:3100) |
checks |
JSON array | yes | Each: { type:"http"\|"html"\|"process", path, expect_status, expect_fields:[…], expect_contains:[…], pattern } (array or stringified array accepted) |
Runs each check inside the container (HTTP via node -e fetch(...), process via ps aux). An http check with no expect_status passes on 2xx/3xx only; a named status must match exactly. Returns ## Validation Report: P/T PASSED with one [PASS]/[FAIL] <type>: <detail> line per check (truncated 3500 chars). When a check could not connect, the report ends with the last 12 lines of /workspace/.server.log, where a background run_shell command's output goes, so a server that crashed on start shows why; nothing is added when there is no log.
capture_api_schema¶
capture_api_schema(url, endpoints) → discovered schema
| Param | Type | Req | Description |
|---|---|---|---|
url |
string | yes | API base URL |
endpoints |
JSON array | yes | Endpoint paths to probe (array or stringified array; first 15 only) |
Probes live endpoints and infers response field names and types. Returns ## API Schema: <url> with per-endpoint type and fields (or a raw snippet when not JSON, truncated 3500 chars). Requires container context.
record_style¶
record_style(direction, rules, stylesheet?) → confirmation + token count
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
direction |
string | yes | — | The approved design direction and why, in prose (Markdown) |
rules |
string | yes | — | What a later maintenance run must not break, in prose (Markdown) |
stylesheet |
string | no | largest .css in the workspace |
Stylesheet to read the tokens from |
Writes finishing/style-record.md and knowledge/style-record.md. The split is the point: the agent supplies the judgement (direction, rules), and the tool reads every CSS custom property the stylesheet declares and writes the token tables itself, grouped into colour, typography, spacing and other. A record written from memory can document a palette the stylesheet does not contain — this one cannot, because it is generated from the file. The default picks the largest stylesheet, on the reasoning that the design system lives in the sheet that defines it rather than in a page-specific override. When the sheet declares no custom properties the record says so, and reports that as a finding rather than an empty table.
check_finishing¶
check_finishing(root?) → one PASS/FAIL row per check
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
root |
string | no | the whole workspace | Workspace subdirectory to check |
The deterministic marks of a finished project, checked against what actually ships. The first row names the project's kind, read from what the workspace holds: an app (a manifest of something that runs, server code, or a package.json with scripts or dependencies), a site (pages and nothing that builds or serves them), or a set of documents. Then the marks that apply to that kind: a favicon every page links, as an image file the link reaches from the page (app, site; an inline data:image/ icon or an off-machine URL counts), no leftover scaffold markers such as placeholder titles, lorem ipsum or a stock palette (every kind), a README carrying Overview / Run / Config / Screenshots whose images exist on disk and a version matching the changelog head (apps only), and an empty state on every screen that renders a list (app, site). The finishing pass's own finishing/ folder is not read by these rows. After those come the anti-slop rows, each named slop/<family>: <tell> [hard|purpose|lock]: copy tells such as chatbot residue, stand-in people and unsourced numbers; interface tells such as dead in-page links, a removed focus outline, or a default gradient or typeface with no written reason; banner and emoji comments in code; and a record that names an image, clip or track the workspace does not hold. A purpose row passes once the style record or brief gives the reason. Dead in-page links and unsourced numbers are the two rows that fail the tool's verdict, so a step is sent back on them; the rest report. Returns ## Finishing checks: N/M passed followed by a | check | result | detail | table, each FAIL naming the file and what was looked for.
Only deliverable files are read. Dot-directories, node_modules, dist and build are skipped, so a build's own leftovers — an archived previous run sitting in the workspace, say — can neither fail the project nor pass it.
play_test¶
play_test(path?) → PASS or FAIL for the page, with every error its script threw
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
path |
string | no | the project's entry page | Workspace path of the page, e.g. site/index.html |
Opens the page in headless Chrome the way a visitor does, served from the workspace, clicks each visible button and control once, and reports every error its script throws, on load and for each click. FAIL <page>: N error(s) lists them; a PASS says how many controls were clicked and how many changed the page, and adds a note when none did. Files the page asked for that did not load are listed after the verdict for the checker to judge; they do not by themselves make it a FAIL. Each run leaves a record in .play-test/ bound to a hash of the page and its local scripts, and a role that holds play_test and states a verdict on a page it has not played as it stands now is sent back. A page that sets up window.game (itself, through a local script, or through the starter kit's Kit.run) is also put through the keyboard play test, and holding the keys must move the player: an unplayable game reports FAIL <page>: the keyboard play test fails: …. Five roles in roles.json hold it (api-qa-engineer, fullstack-qa-engineer, qa-validator, qa-reporter, finishing-ops), as do the web roles of the role library.
ComfyUITools¶
Group comfyui. All three tools tier Standard (the class comment claiming Elevated is stale). Generation is asynchronous — submit, then poll.
comfyui_generate¶
comfyui_generate(workflow, wait?, save_dir?, filename?, ratio?, transparent?, reference_images?) → workspace image paths or queue receipt
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
workflow |
string | yes | — | A plain-English image description (builds a default SDXL graph on the checkpoint from the ComfyUI_Checkpoint setting, else the first checkpoint the ComfyUI install lists, sampled with that checkpoint's entry in Templates/composition/image-models.json: sampler, scheduler, steps, CFG and size; a checkpoint with no entry renders at euler / normal, 25 steps, CFG 8, 1024 × 1024), or a full ComfyUI workflow JSON string. When workflow is absent the router reads the description from prompt instead |
wait |
bool | no | true |
Wait for completion and return image paths (default enforced at the router) |
save_dir |
string | no | images |
Workspace directory the generated PNGs are saved into; a leading /workspace/ is tolerated |
filename |
string | no | — | Name to save the image as (cs-01.png), so the file lands where it is owed without a rename. A directory part is discarded — directories are chosen with save_dir — and a name with no extension inherits the source's. When a call produces more than one image only the first takes the name; the rest keep ComfyUI's own. A name that reduces to nothing falls back to ComfyUI's. |
ratio |
string | no | 1:1 |
Qwen-Image and Z-Image teams only. Aspect ratio of the picture: 3:2, 2:3, 1:1, 16:9 |
transparent |
bool | no | false |
A game piece or cut-out: a PNG of the object alone on a transparent background; describe only the object. Qwen-Image paints the transparency itself. On SDXL the description asks for the object on flat magenta (#FF00FF) and the Engine cuts the magenta out of the saved PNG; a picture whose background did not come back magenta is left as painted, and the reply says so |
reference_images |
string | no | — | Qwen-Image teams only. Workspace paths of existing images to use, comma-separated or an array, referred to in the description as <image1>, <image2>. Without a ratio the result keeps <image1>'s frame (an edit of it); with one it is a new picture that uses them |
When wait=true, each finished image is downloaded from the ComfyUI backend into the run's workspace under save_dir and the tool returns ## Generated Images (N) with the prompt ID and one /workspace/<save_dir>/<file>.png line per image — the path the agent cites and other agents read. An image that could not be saved is listed by its ComfyUI file name with the reason. When wait=false the tool returns "Workflow queued. Prompt ID: <id>…" and nothing is saved. 5-minute completion timeout. A step that generated counts as delivered even if it wrote no text file. Each saved image is also appended to images-manifest.md at the workspace root, with its path and the prompt that produced it — the next step never sees this tool's reply, and the manifest is how it learns the file exists instead of inventing a path. capture_reference records its screenshots in the same manifest. The manifest and the .image-meta sidecars below are the Engine's records: an agent's write_file, edit_file or revert_file on either is refused and changes nothing.
Qwen-Image teams. A team whose config sets image_model: "qwen" renders on Qwen-Image 2.1 instead of SDXL. The shipped image templates set it, and so does a generated team that makes images, including one that also renders speech (until 2026-10-11 that team stayed on SDXL). The description goes to a separate leased GPU service, comfyui-qwen, and the two Qwen-only parameters above (ratio, reference_images) apply; for any other team they are ignored. When such a team is resolved, a declared comfyui GPU service is swapped for comfyui-qwen, and at run start the lease starts whichever ComfyUI matches the team's image_model, so a team switched between models in the designer gets the right one. Each saved image's request is recorded beside it in an .image-meta sidecar, which is what the finishing pass reads. The result of a Qwen picture made with no reference_images ends with one sentence naming its saved path: pass it as reference_images with a ratio and call it <image1> for later pictures of the same subject, so they keep its design in a new scene; a different subject needs no reference.
Z-Image teams. A team whose config sets image_model: "z-image" renders on Z-Image Turbo (Apache-2.0) through its own leased GPU service, comfyui-zimage, at about one megapixel in the requested ratio. It renders from the description alone: a call with reference_images is refused with a reason and queues nothing. transparent works as on SDXL — the object is painted on flat magenta and the Engine cuts the magenta out. The service's address is the ComfyUI_ZImageEndpoint setting (default http://127.0.0.1:8190); a declared comfyui service is swapped for comfyui-zimage when the team is resolved.
comfyui_video¶
comfyui_video(description, image?, seconds?, ratio?, resolution?, save_dir?, filename?) → the saved clip's path
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
description |
string | yes | — | What happens in the clip: subject, action, setting, light and camera movement |
image |
string | no | — | Workspace path of an image to animate; the clip starts from that picture |
seconds |
int | no | 3 |
Length, 1-5 seconds at 24 fps |
ratio |
string | no | 16:9 |
16:9, 9:16 or 1:1 |
resolution |
string | no | 544p |
544p (960x544 at 16:9) or 720p (1280x704, the model's native size, about 2.5x slower) |
save_dir |
string | no | videos |
Workspace directory the clip is saved into |
filename |
string | no | — | Name to save as, e.g. intro.mp4 |
Renders Wan 2.2 TI2V-5B (Apache-2.0) through ComfyUI on the comfyui-video GPU service, which the run's lease starts — a node holds the tool through the video profile, and the composition resolver then adds comfyui-video to the team's gpu_services. The tool waits for the clip (up to an hour), converts ComfyUI's VP9 WebM to an H.264 MP4 with ffmpeg on the Engine's host, records it in images-manifest.md and returns ## Generated Video with the /workspace/<save_dir>/<file>.mp4 path, its size, frame count and length. Without ffmpeg the WebM is kept and the reply says so. It refuses before queueing anything when the session has no workspace, the image to animate is not in it, or the service does not answer (the reply names the gpu_services entry to declare). A clip takes minutes: on an integrated GPU with unified memory, 5 s at 544p took about 15 minutes and 2 s at 720p about 7.
comfyui_status¶
comfyui_status(prompt_id?) → queue status
| Param | Type | Req | Description |
|---|---|---|---|
prompt_id |
string | no | Optional prompt ID to check a specific generation |
Returns ## ComfyUI Status with running/pending counts; if prompt_id is given, appends that prompt's status string and the output file names ComfyUI holds for it (or a "not found in history" note). Those names are ComfyUI's own — comfyui_generate is what copies an image into the workspace and reports the path there, so an agent collecting an image does not need this tool.
finish_image¶
finish_image(images) → receipt
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
images |
string | yes | — | Workspace paths of the images to finish, comma-separated or an array, e.g. /workspace/images/hero.png. At most 4 |
Queues the final upscale of pictures a Qwen-Image team made: Qwen's edit mode re-renders each image at about four times the pixels, keeping its composition, text and colours, into <dir>/final/<name>. It is slow — about 25 minutes an image — and runs after the run, outside its lease, so the tool returns at once and the final/ files do not exist yet in the run that asked for them. In the shipped roles only the finishing team's Designer holds it.
Separately, when the re-render is switched on, after every Completed run of a Qwen-Image team the Engine re-renders that run's images from their recorded requests — a prompt-enhancer rewrite, then a fresh 2K render at 40 steps, about 9 minutes an image — into the same final/ directories. Transparent images and images built from references are skipped. Both passes are configured under Aetherium:ImageFinishing (Configuration); the re-render is off by default since 2026-10-12: 2K and larger finished images are made in a finishing run, by request, where the finishing team queues them with finish_image.
AudioTools¶
Group audio. Both tools tier Standard. The voice is the Engine's qwen-tts GPU service (Aetherium:VramOrchestration:Services), which the run's lease starts when the template declares gpu_services: ["qwen-tts"]; the tools refuse with that hint when it is down. Where the service answers and where its reference clips live come from Aetherium:Tts (see Configuration).
tts_render¶
tts_render(script_path, output_path, voices?, default_voice?) → the MP3's workspace path, line and word counts, seconds of speech, duration
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
script_path |
string | yes | — | Workspace-relative script to speak. Speaker lines in any of: [HOST] on one line then PIXEL: words on the next; NAME: words; **NAME (direction):** words; [LABEL] or **NAME:** (direction) on its own line then the words below it. A tag is all caps or one or two capitalised words (Host, Narrator), and may sit in parentheses on its own line (**(NARRATOR - deep voice)**); bare paragraphs after a tag belong to that speaker until the next tag, label or cue. Bracketed cues, (directions), headings, list and table lines, and front matter such as Tone: or Spoken words: are not spoken |
output_path |
string | yes | — | Workspace-relative MP3 to write (.mp3 appended if missing) |
voices |
JSON object as a string | no | the node's tts_voices |
Speaker label → reference voice name or voice description, e.g. {"PIXEL":"pp_pixel","BYTE":"design: gravelly man in his fifties, slow and dry"}; added on top of the node's map, an explicit entry wins; labels compare case-insensitively. A node key naming the file being rendered (its path, name or stem) gives that file one voice in place of the node's speaker keys |
default_voice |
string | no | the node's tts_default_voice, else Aetherium:Tts:DefaultVoice |
Reference voice, or description, for speakers the map does not name |
A reference voice is a clip <name>.wav with its transcript <name>.txt beside it under Aetherium:Tts:RefsDir; a clip without a transcript cannot be used. Each spoken line is one POST {BaseUrl}/audio/speech (Base task, in-context clone), the WAVs are joined with GapSeconds of silence and encoded to 44.1 kHz mono MP3 by ffmpeg on the host, and the file is recorded in the workspace's images manifest so the next step can find it. Every voice the cast uses must exist before anything renders: a reference voice with no clip and transcript refuses the whole call, naming each missing voice once with the speakers that use it, a reference voice whose name differs only in case, and the reference voices that do exist (the first 40; tts_status lists them all), then offers a cast that gives each speaker a different existing voice, written as the voices JSON (none when there are fewer voices than speakers). A line whose synthesis fails is skipped and named in the reply; the render fails only when no line rendered. The result line says where the voices came from. A render writes a sidecar beside the MP3 with a fingerprint of every spoken line and its resolved voice; a repeat call with an unchanged script and cast answers from the sidecar and renders nothing.
A voice written as a description — design: followed by the description, or any value of three or more words, since a reference name never contains a space — is designed once per template. The first render that needs it asks the VoiceDesign server (Aetherium:Tts:DesignBaseUrl, the qwen-tts-design GPU service) to speak, in that voice, the first line of 12 to 40 words the script gives it (a fixed sample when it has none), saves the clip and that line as its transcript under {RefsDir}/designed/<template id>/ (the team's name for a team no template made), and then clones it for every line like any reference voice. Only voices the script speaks are designed. Within a template the same description reuses the same clip, so every project made from that template keeps its cast; a changed description changes the render's fingerprint. A template that designs voices declares gpu_services: ["qwen-tts", "qwen-tts-design"].
tts_status¶
tts_status() → Voice service UP|DOWN at <url>. Voice design service UP|DOWN at <url> (…). Reference voices (N): …. Default: <name>.
Where to go next¶
- §13 — Integrations — the ComfyUI backend.
- MCP Server Classes —
QualityToolsandComfyUIToolsdependencies. - API · Containers & Snapshots — the container the validation tools reach into.