phase 2 single provider

The directive content must be injected into the LLM request's messages array. This sounds trivial, but it is not — different injection strategies have different effects on model behaviour, and different models have different rules about system message placement and count. The three injection modes (from the `directiv

Milestone 2.3.3 — System Prompt Injection Strategy

Status: Planned
Goal: 2.3 — Directive resolution and prompt injection
Phase: 2 — Single Provider End-to-End
Estimated effort: 2 days
ADR required: ADR-0028 — System prompt injection strategy


Why This Milestone Exists

The directive content must be injected into the LLM request's messages array. This sounds trivial, but it is not — different injection strategies have different effects on model behaviour, and different models have different rules about system message placement and count.

The three injection modes (from the directives.injection_mode column):

  • system_first: Directive becomes the first system message; all existing system messages follow it. This is the strongest enforcement mode — the directive is always first in context.
  • system_append: Directive is appended as a system message after existing system messages. Less likely to override client-supplied instructions.
  • user_prepend: Directive is prepended to the first user message as [DIRECTIVE]: <content>\n\n<original>. For models that don't support multiple system messages.

This logic belongs in the proxy, not in the provider client. The provider client sees the already-injected messages array. The injection strategy is agent-level configuration — the provider doesn't know about it.


Branch

feature/m2-3-3-prompt-injection

PR Title

feat(proxy): system prompt injection middleware with configurable strategy (m2.3.3)


ADR-0028 — Prompt injection strategy

Document: why IBEX controls injection (not the client SDK), why three modes, what OpenAI's guidance says about multiple system messages, why user_prepend exists for compatibility, and how Phase 3 memory injection will follow the same pattern.


Deliverables

Go
// packages/injection/injector.go
 
// InjectionMode controls how the directive is inserted into the messages array.
type InjectionMode string
 
const (
    // SystemFirst: directive inserted as the first system message.
    // Existing system messages follow it. Recommended for GPT-4o.
    ModeSystemFirst InjectionMode = "system_first"
 
    // SystemAppend: directive appended as the last system message.
    // Existing system messages precede it.
    ModeSystemAppend InjectionMode = "system_append"
 
    // UserPrepend: directive prepended to the first user message.
    // For models that do not support multiple system messages.
    ModeUserPrepend InjectionMode = "user_prepend"
)
 
// Inject returns a new messages slice with the directive injected according to mode.
// If directive is empty, returns messages unchanged.
// The input messages slice is never mutated — a new slice is returned.
func Inject(messages []provider.Message, directive string, mode InjectionMode) []provider.Message

Acceptance Criteria

  • system_first mode: directive is first message; role="system"; original messages follow
  • system_append mode: directive is last system message; user messages follow
  • user_prepend mode: directive is prepended to the first user message content
  • Empty directive: messages returned unchanged
  • Input slice is never mutated (pure function — safe for concurrent use)
  • Injection is tested with realistic GPT-4o message arrays including multi-turn history

Edit on GitHub

Last updated on

On this page

0%