feat: system prompt prefix/suffix settings joined into every session
Two new per-user preferences, `promptPrefix`/`promptSuffix`, hold free text
joined at the very start / end of the harness's assembled system prompt for
all agents (subagents included) — a per-deployment persona or behavioral rule
riding the harness's own prompt registry instead of session-log surgery:
- src/host/promptInjection.ts registers two global prompt sections through a
deferred `ctx.inject(['systemPrompt'], …)` (a composition without the
registry degrades to a no-op; the registration unwinds with this plugin's
fiber). Orders anchor on the harness's own section-order registry, so a
central renumbering keeps the prefix first and the suffix last. Text is
registered `interpolate: false`, so `{{...}}` in user text stays literal.
- The section text providers read the volatile config references the loader
rewrites in place on preference edits, so an edit applies from the next
turn without remounting the entry; new sessions carry the text from their
first turn through the harness's own system-prompt projection.
- The settings card grows two free-text rows (local draft, committed on blur
and on unmount; a host push never clobbers in-progress typing), zh/en
strings, and the served-value sanitize/degrade path for the string fields.
Verified against a running harness: the committed text persists in the
profile layer, and a new session's system prompt starts with the prefix and
ends with the suffix. docs/compatibility.md documents the seams, and the
compat matrix probes them on both supported lines.
This commit is contained in:
1 parent
425fd2e7b8
commit
d75b539419
18 files changed
+463
-13
No files matched your search
@@ -51,8 +51,12 @@ The fold never branches on a detected harness version. The version probe is best
|
||||
- **Developer messages**: `developer/message` tool-registry changes are nested under `data.message` and fold into injected context, including request composition, live surface, and removed-node archives. They never count as human inputs; empty-content events keep their surface position at zero tokens, matching the harness's message projection (its `deriveEventMessage` projects an empty message to null — the fold's structural read keeps the node).
|
||||
- **Tool-result error mark**: the event-level `data.error` metadata is admitted only alongside the message mark (the generation's own validation refuses a row whose `data.error` outlives `message.isError`), so the fold reads `message.isError` alone; a hostile row carrying only the envelope field degrades to unflagged, never to a throw.
|
||||
- **Image offload accounting (deferred)**: when a route rejects with `IMAGE_OFFLOAD_REQUIRED`, the harness offloads images out of the context through a message projection. The plugin's fold reads raw durable events, so an offloaded message keeps its pre-offload image count and price until compaction removes it — an over-count on a rare recovery path, not a crash. Revisit alongside the harness's own token-meter treatment if it shows up in practice.
|
||||
- **User preferences**: plugin configuration lives on the Plugins page, derived from the loader entry's own Config schema (`src/host/config.ts`): cordis validates the `config:` block through the schema's Standard Schema face, and the SAME schema is what the settings `describe` projects — the namespace is the entry id (`dsh-context`) and the nine preference fields (`.volatile()`-marked) are the served, live-editable form; the fold bounds stay ordinary entry config (their edit remounts the entry, a preference edit commits volatile-only and never remounts). The browser half registers its card on the Plugins page's keyed `plugins.bundle.config` seat over the `configForms` transport (`ctx.configForms.whileServed` keeps the card alive only while the Host serves the namespace). The `.volatile()` modifier is feature-detected at schema build time (`volatileField`), so the same bundle still loads — preferences inert — on a below-floor harness whose schemastery predates it (the gate's fallback composition).
|
||||
- **User preferences**: plugin configuration lives on the Plugins page, derived from the loader entry's own Config schema (`src/host/config.ts`): cordis validates the `config:` block through the schema's Standard Schema face, and the SAME schema is what the settings `describe` projects — the namespace is the entry id (`dsh-context`) and the preference fields (`.volatile()`-marked) are the served, live-editable form; the fold bounds stay ordinary entry config (their edit remounts the entry, a preference edit commits volatile-only and never remounts). The browser half registers its card on the Plugins page's keyed `plugins.bundle.config` seat over the `configForms` transport (`ctx.configForms.whileServed` keeps the card alive only while the Host serves the namespace). The `.volatile()` modifier is feature-detected at schema build time (`volatileField`), so the same bundle still loads — preferences inert — on a below-floor harness whose schemastery predates it (the gate's fallback composition).
|
||||
- Preference values persisted on a retired line (the `settings.yaml` section `dsh-context`) are moved into the profile entry of the same name by the harness's own legacy import on first boot, so existing values carry into the current surface.
|
||||
- **System-prompt prefix/suffix injection**: the `promptPrefix`/`promptSuffix` preferences are joined around the harness's assembled system prompt by two globally registered prompt sections (`src/host/promptInjection.ts`), not by string-editing session events. The seams this rides, probed per supported line in the compat matrix:
|
||||
- `ctx.systemPrompt.section()` — the ordered-section registry face (`packages/core/system-prompt`). The sections are registered through a deferred `ctx.inject(['systemPrompt'], …)`, so a composition that starts the registry later still gets them, one without it degrades to a no-op, and the registration unwinds with this plugin's fiber. The orders anchor on the harness's own registry (`HARNESS_IDENTITY - 1000`, `DEPLOYMENT_PERSONA_SUFFIX + 1000`), keeping the prefix first and the suffix last even if the central order table is renumbered.
|
||||
- The volatile-only commit — `vendor/loader/src/config/entry.ts` rewrites the running config references on a preference edit without remounting the entry. The section text providers read those references per assembly, so an edit applies from the next model step; the harness's own system-prompt projection then updates the session log (`system/message` replace or append, per its routing), which is how a new session carries the text from its first turn.
|
||||
- Scope: global sections participate in every assembly — every session, subagents included (the same semantics as the harness's own deployment `personaPrefix`/`personaSuffix`). A deployment that registers a `complete` prompt section keeps ONLY that section, by the registry's design; the configured text is dropped there. The text is registered `interpolate: false`, so `{{...}}` in user text stays literal and can never trip the strict variable interpolation.
|
||||
|
||||
## Parsing resilience [IMPORTANT!]
|
||||
|
||||
|
||||
Reference in new issue
Block a user