Lem/Docs

Generate figures from the manuscript, not a prompt.

Both figure commands derive one validated brief from manuscript context and accept no free-form description. An optional Probierz rubric loop regenerates a candidate until it passes.

Generate and convert#

figure generate asks Codex through Brama for a standalone SVG intermediate. figure tikz creates that SVG first and converts it into editable TikZ; --source-image instead converts an already approved intermediate. The result JSON contains asset_path for the intermediate and tikz_path for the editable result, and figures are written into the paper's own figures/ directory.

The backend's `POST /papers/<id>/figures/codex-tikz` writes Codex's TikZ to the paper's figures/ directory. A request without a caption is captioned by its description, and one without a label is labelled `fig:<file name>`. The file is named `lem-` and the words of the first the request states of output_name, label and section_title, else of its description; when that file already exists, the name carries a fresh UUID so no figure is overwritten. The figure's id is its file name.

For raster generation and editing through Brama's image route, Lem refuses missing or empty data arrays, missing b64_json strings, invalid base64, and base64 that decodes to no bytes. The failed figure result names the response field and cause; Lem does not publish an empty figure asset or report it as generated.

Feedback that selects a raster source stays an edit: a missing or unreadable source reports its file-read failure instead of generating a different image without it. SVG preview configuration errors likewise remain failures rather than selecting another alias after discarding the error.

The backend refuses figure manifests, brief proposals, generation, TikZ conversion, the paper context (`GET` and `POST /papers/<id>/context`), the paper plan (`GET` and `POST /papers/<id>/plan`) and the roadmap for an unknown paper with HTTP 404 and a Paper not found cause. It never substitutes an empty manifest, an empty context or plan, or a newly invented paper ID for a missing paper.

Figure clients use the same announced backend address and HTTP transport as other paper operations. A backend refusal preserves its HTTP status and returned cause; an unreachable backend includes the requested address and connection error instead of a generic bad-server-response message.

In the desktop Figures panel, a failed manifest refresh clears the unavailable list and selection and displays the backend cause. Locally extracted manuscript figures do not silently replace a failed backend response. Operation messages are displayed in full without a fixed line-count cutoff.

The workspace gives each paper its own Figures panel identity and state. Switching papers replaces that state rather than clearing a shared panel while old requests finish, so prior-paper completions do not repopulate the new paper's panel. Already submitted provider work for the previous paper is not rolled back.

Within a Figures panel, manifest loads carry a request identity and ignore cancelled or superseded results. Controls and action handlers prevent figure operations during a manifest load, and an active operation prevents starting that read. Main-actor state isolation keeps a prior read from replacing an action's result or message.

Brief proposal, generation, editing and TikZ actions check cancellation again after the backend returns or throws. A cancelled action does not apply returned figures, briefs or errors to the panel; this does not roll back work already submitted to the provider.

Shell
swift run LemCLI figure generate <id-or-title> [--output-name <n>]
swift run LemCLI figure tikz <id-or-title> [--label <l>] [--caption <c>] [--output-name <n>] [--source-image <path>]

The quality check#

figure tikz uses the Probierz LEM_PROBIERZ_BIN names, else the first probierz on PATH; Lem searches no directory of its own. A LEM_PROBIERZ_BIN that is not an executable file refuses as `LEM_PROBIERZ_BIN names <path>, which is not an executable file` instead of falling back to another Probierz. Probierz runs with Lem's own environment (without STADO_MODEL_ROUTER_TOKEN), so TeX must be on that PATH. When Probierz is available and a reference image exists, Lem sends the reference and the one candidate to probierz evaluate figure; with a gateway named in `~/.lem/brama.json` it passes that `--router-url` and hands over its Brama bearer and signing secret on standard input, and with none Probierz hands its own request to Stado's inference alternatives and is handed no credential. A hand-over that fails ends the run with its cause. The report goes to figures/probierz/<candidate>.json, or <candidate>-<uuid>.json when an earlier evaluation of that candidate is there. Each invocation generates exactly one candidate and records exactly one verdict; Lem chooses no attempt count. A rejected candidate returns a failed figure result whose message is Probierz's verdict turned into revision feedback, and running figure tikz again with that text as --feedback generates the revision. When Probierz is absent, Lem writes the candidate and states that no quality check ran.

Each evaluation writes an immutable JSON report and reference/candidate PNG renders under the paper's figures/probierz/ directory. The result exposes quality_report_path and quality_passed; a rejected candidate is never published as an approved one.

Two routes, two capabilities#

~/.lem/brama.json states only what a machine overrides; every key it leaves out keeps Lem's declaration. It names two Brama routes, because drawing a figure and judging one are different capabilities. figure_model converts the reference into TikZ and is declared as the role alias wisent-backend/chat/primary; figure_evaluation_model scores the render and is declared as the capability selector any-vision-capable, so Brama picks an active vision route instead of Lem naming a vendor's model. Neither declaration is a model id, so a new or retired model changes nothing here; through Stado's inference alternatives the same aliases are asked. Addresses are not declared: `url` names a gateway in place of Stado's alternatives and `skarbiec.url` a broker in place of Stado's secrets resolution; with neither and no Stado, every model or credential call refuses naming the command that could not run and the file.