Skip to content

Extract declared output fields with a strict schema #46

Open

Description

@Silthus

Parent

Spec: #22 — Generate text, an LLM action node for workflows.

What to build

The second pass. An author declares named output fields with plain-language instructions, and gets back one string per field instead of one blob of text.

Pass two runs only when fields are declared. Zero fields skips it entirely and returns the text with an empty field map, which is the path the previous ticket already ships.

A fixed extraction model, never the author's pick. The author's model choice governs the writing, not the extraction. Pass two is hardcoded to one model chosen for transport fidelity: strict structured output is unreliable through the gateway's Anthropic route, and the whole extraction contract rests on strict mode. Reasoning is set from the answer recorded on the probe ticket.

A strict schema. Every declared field is a required property named after the workflow variable it targets, typed to allow a null, with additional properties forbidden. Nulls are coerced to empty strings by the endpoint. All fields are strings, because the variables panel only ever creates string variables.

The anti-fabrication rule is the point. Strict mode forces a value for every property, and a model made to produce a value will invent one. The extraction system prompt must order rules first, then field names, then the generated text inside a delimiter, and it must tell the model to return an empty string rather than guess when the text does not support a field. An author must never send a fabricated claim to their customer because the schema demanded a string.

Pass two is also an injection surface. Person properties reach it through pass one's text, so the same treat-content-as-data guard applies here, and untrusted values get their whitespace collapsed before being embedded. Pass two receives only the generated text, the field names, and the instructions. It does not receive the original prompt and it does not receive person properties.

An envelope, not a merge. Fields live under their own key so an author's field named text cannot collide with the raw generation. The raw generation is always reachable by result path, which is how an author maps a whole generation when they do not need fields.

A flat size budget on the returned payload, set below the executor's variable cap and deliberately not derived from available headroom, so the same prompt never succeeds in one workflow and fails in another because of what its sibling steps happen to hold. The cap itself is not raised.

One retry, inside the endpoint, for model-fixable failures only — oversized output, a malformed response, a refusal. Never for transport, which has its own handling.

A deadline check between the passes, against the task's wall-clock budget, so a slow first pass fails cleanly instead of starting a second pass that cannot finish.

Acceptance criteria

  • Declared fields come back as one string per field, keyed by the workflow variable name.
  • Zero declared fields skips the second pass entirely and returns an empty field map.
  • The extraction model is fixed and is not affected by the author's model choice.
  • The schema is strict with additional properties forbidden and every field required.
  • A null value is returned as an empty string.
  • A field the generated text does not support comes back empty rather than invented.
  • Generated text containing instruction-shaped content does not redirect the extraction.
  • A field named text does not collide with the raw generation, and the raw generation stays reachable by result path.
  • A payload exceeding the flat size budget fails as an oversized-output error, and the budget does not vary with what other steps hold.
  • A model-fixable failure is retried exactly once; a transport failure is not retried here.
  • A first pass that consumes the wall-clock budget fails before the second pass starts.
  • Tested at the task level with the gateway calls stubbed, covering envelope assembly, coercion, the skip, the retry, the budget, and the deadline check.

Blocked by

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentFully specified, ready for an AFK agent

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions