Skip to content

Prompting boundary

backend/prompting/ owns deterministic, provider-independent model-facing construction. A module belongs there only when it:

  1. deterministically transforms caller-supplied data;
  2. constructs model-facing content or a canonical projection required by it;
  3. is shared by multiple upper-layer consumers or the stable workflow toolkit;
  4. performs no I/O, model call, persistence, or feature/pass enablement decision.

Instruction prose and result contracts used by one pipeline pass stay with that pass. Feature enablement stays in its feature slice. Provider adaptation, retries, structured-output normalization, cache mechanics, and local runtimes stay in inference/.

Allowed dependency graph

api        -> pipeline, features, workflows, prompting, inference, analysis, database, core
pipeline   -> features, workflows, prompting, inference, analysis, database, core
features   -> prompting, inference, analysis, database, core
workflows  -> prompting, inference, analysis, database, core
prompting  -> core
inference  -> core
analysis   -> database, core
database   -> core
core       -> (nothing)

features/ and workflows/ are siblings and do not import one another. Feature slices do not import peer slices. The workflows row applies to the host framework modules directly under backend/workflows/; plug-in slices under backend/workflows/<id>/ may import only their own package and the public workflow toolkit, never these lower layers, host internals, or peer plug-ins. The exact rules are enforced by scripts/check_backend_layers.py; every Python-bearing top-level backend package must be classified there.

Shared sentence boundaries, quotation scanning, and protected-markup primitives belong in core/text_segmentation.py, which both inference and analysis use. Roleplay block segmentation (speech, emphasis, narration, and OOC asides) belongs in analysis/text/roleplay_segmentation.py and builds on those primitives. Consumers import core helpers directly; the analysis module does not re-export them. Workflow plug-ins access shared capabilities through the stable toolkit.

Byte and order contracts

Prompt text, whitespace, delimiters, message order, parameter order, macro resolution timing, seeds, and stored random choices are public behavior and must remain byte-for-byte stable.

Built-in tools use this order:

(
    "direct_scene",
    "editor_apply_patch",
    "editor_rewrite",
    "editor_find_replace",
    "give_feedback",
    "update_state",
    "select_lorebook",
    "propose_world_changes",
)

Enabled built-ins preserve that order. Workflow tools append in registration order, which is plug-in package-name order, and re-registering a workflow tool preserves its position. Schema property order and insertion-order-preserving JSON serialization are part of the transport contract.

Card message projections

History formatting resolves identity macros, then applies caller-supplied CardScripts for the prompt channel to the message body, then appends attachment annotations and group speaker labels. Stored bodies, system instructions, and card definitions remain canonical. Group history uses the original speaker's card scripts, independent of the currently generating speaker; unattributed rows have no owning card. Pipeline and off-turn workflow prefixes share this contract. See Card render scripts.

Workflow-owned text composed outside that prefix -- saved configuration a workflow reuses in every conversation -- resolves against the same identity through toolkit.conversation_macros, which shares the prefix's persona-lock precedence so one call cannot name the user two ways. Such text rides a per-call tail rather than the shared prefix, so it may pass seed="" and roll {{random}} fresh each call without affecting prefix reuse.