Files
Nicolò Boschi 9e6d9e76cc feat(api,control-plane): a prompt tester for retain, and prompt preview for every operation (#4140)
* feat(api,control-plane): a prompt tester for retain, and prompt preview for every operation

Closes the "render prompts without calling an LLM" half of #3774.

A bank's missions only mean something once you can see the prompt they land in,
and today that means tracing Python constants and format calls.

`POST /banks/{id}/prompts/preview` returns the messages retain, consolidation or
reflect would send — in send order, no LLM call, no writes. The operation is the
whole request: everything comes from the bank, and the runtime data an operation
would be given is a fixed placeholder.

A message arrives as `blocks`. The active ones concatenate back to the exact text
sent — enforced by a test against what the extraction path itself builds. Each is
identified by machine values only (`field`, a `section` slug, or the `heading` the
prompt text carries); the response ships no display copy, so names and
explanations live in the UI that localises them. An inactive block has no text and
marks a setting switched off at the point it would land, so an unset mission is
still visible where it would go.

Both messages always come back: retain and consolidation keep their system prompt
bank-agnostic so one provider-side cache serves every bank, and carry the mission
in the user message instead. Only reflect puts its mission in the system prompt.

**Two bugs found on the way, both pre-existing in dry-run extraction:**

- Neither dry-run nor the preview applied retain strategies. Both resolved config
  directly instead of through `_resolve_retain_config`, so they ignored the bank's
  `retain_default_strategy` too — silently extracting and previewing under
  settings a real retain would never use. Both now resolve the way retain does and
  take an optional `strategy`.
- The preview read the bank's config without running `validate_bank_read`, so a
  tenant extension denying `GET_BANK_CONFIG` was bypassed by an endpoint that
  renders that config as prompt text. Both paths now share
  `_authorize_bank_config_read`, which also carries the bank-existence check.

**Control plane.** The dry-run dialog is gone; its work moved into the prompt
tester on the bank Configuration tab, because changing a mission and seeing what
it extracts is one loop that was split across two dialogs. Blocks re-render for
free as settings change; a sample-text box and a Run button spend the LLM call on
demand. A strategy picker renders any of the bank's named strategies. Editing a
block saves that setting to the bank; `editable` comes from the config layer's own
allowlist, so server-level fields say so rather than offering an edit that would
collect a 400.

`PromptBlockModel.kind` is required with no default: progenitor rejects a default
on an inline enum with TypeError(InvalidValue), which breaks the Rust client.

* chore(cli): skip preview_prompt in the OpenAPI coverage manifest
2026-09-07 17:27:51 +02:00
..
…
2026-08-25 12:19:47 +02:00