docs(agents): document Jinja system-message constraint and session-start delivery

- Add forbidden pattern: using experimental.chat.system.transform for
  session-start injection causes Jinja 'System message must be at the
  beginning' on Qwen-family GGUF models when orchestrator spawns subagents
- Add note to session-start.sh section explaining OpenCode delivery via
  output.parts.unshift() on first chat.message turn (not system.transform)
This commit is contained in:
Brydon DeWitt 2026-05-23 00:00:40 -04:00
parent 2949981e43
commit 3738732156

View File

@ -151,6 +151,12 @@ calling the script. See `agent-infrastructure.md` item 22 for the refactor plan.
- Fires once per session. Good for: current branch, active investigations, dead - Fires once per session. Good for: current branch, active investigations, dead
ends. ends.
- Not good for: precise rule reminders (use PostToolUse or nested AGENTS.md). - Not good for: precise rule reminders (use PostToolUse or nested AGENTS.md).
- **OpenCode delivery:** injected as a synthetic `text` part via
`output.parts.unshift()` on the first `chat.message` turn. **Not** via
`experimental.chat.system.transform` — that hook fires for task-spawned
subagent sessions after a user message is already in context, which causes
Qwen-family GGUF models to abort with a Jinja "System message must be at the
beginning" error. See Forbidden Patterns below.
### `stop.sh` — End-of-session reflection ### `stop.sh` — End-of-session reflection
@ -237,6 +243,15 @@ Some things cannot be unified and live in tool-specific locations:
model may be correct. Applies to: OpenCode tool names (`read`/`edit`/`task`), model may be correct. Applies to: OpenCode tool names (`read`/`edit`/`task`),
parameter names (`offset`/`limit` not `startLine`/`endLine`), and plugin guard parameter names (`offset`/`limit` not `startLine`/`endLine`), and plugin guard
logic. logic.
- ❌ Using `experimental.chat.system.transform` to inject session-start content
in OpenCode. That hook fires for every model call — including task-spawned
subagent sessions — **after** the task prompt (a user message) is already in
the conversation. Pushing to `output.system` at that point places a system
message at a non-zero position, which Qwen-family GGUF models reject with
_"System message must be at the beginning"_ (Jinja chat template guard).
Fix: inject session-start as a synthetic `text` part via `output.parts.unshift()`
on the first `chat.message` turn (guarded by an `initializedSessions` set).
Text parts have no position constraint. Committed `f0d21e9` in dotfiles.
- ❌ Asserting that a third-party tool does **not** support a feature (config - ❌ Asserting that a third-party tool does **not** support a feature (config
mechanism, directory, option) without fetching the tool's current docs first. mechanism, directory, option) without fetching the tool's current docs first.
Training data is frequently stale. Negative claims ("X doesn't have Y") must Training data is frequently stale. Negative claims ("X doesn't have Y") must