Prompting boundary¶
backend/prompting/ owns deterministic, provider-independent model-facing
construction. A module belongs there only when it:
- deterministically transforms caller-supplied data;
- constructs model-facing content or a canonical projection required by it;
- is shared by multiple upper-layer consumers or the stable workflow toolkit;
- 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.