IBEX Harness
DocsBenchmarksBlogChangelogRoadmap
GitHub
IBEX Harness

Documentation

Architecture Decision RecordsADR-0002: Repository foundation bootstrapADR-0003: Branch protection and merge policyADR-0004: Protobuf and code generation policyADR-0005: Postgres migration strategyADR-0006: Auth protobuf contract (`ibex.auth.v1`)ADR-0007: Auth token validation implementationADR-0008: Security scanning and CI quality gatesADR-0009: Permission bitmap layoutADR-0010: Cryptography policyADR-0011: Proxy auth gRPC client and middlewareADR-0012: Proxy request normalization (OpenAI chat)ADR-0013: Proxy input validation and stable error envelopeADR-0014: Core domain migration sequencingADR-0015: Proxy rate limit skeleton (Phase 1)ADR-0016: Proxy agent identity verification (Phase 1)ADR-0017: Request ID and trace context strategy (Phase 1)ADR-0018: Graceful shutdown contract (Phase 1)ADR-0019: OpenTelemetry provider configuration (Phase 1)ADR-0020: Shared package boundaries — `packages/config` and `packages/apierror`ADR-0021: Prometheus Metric Catalog (Phase 1)ADR-0022: Health check contract (Phase 1)ADR-0023: Docs site architecture (Phase 1.5)ADR-0024: Benchmark data publishing modelADR-0025: LLM provider abstractionADR-0026: OpenAI client designADR-0027: Streaming dual-write strategyADR-0028: Auth cache designADR-0029: Token revocation propagation via Redis pub/subADR-0030: Directive versioning strategyADR-0031: System prompt injection strategyADR-0032: Session data model and retentionADR-0033: ClickHouse llm_traces schema and retentionADR-0034: Proxy overhead performance measurement methodologyADR-0035: Chat Idempotency-Key Redis dedupeADR-0038: Context assembly service design and gRPC contractADR-0039: Proxy Postgres ownership for session and directive storesADR-0040: Anthropic provider adapterADR-0041: Model capability registryADR-0042: Self-hosted OpenAI-compatible LLM adapterADR-0043: Tokenizer registry architectureADR-0044: Non-streaming response pipelineADR-0045: Streaming response transformationADR-0046: Embedder interface and profile registryADR-0047: Memory temporal validity foundationADR-0048: Memory multi-label categoriesADR-0049: Memory relationship graph readinessADR-0050: MCP server skeleton (transport, auth, audit)ADR-0051: Local LGTM observability stack (Phase 2.5 exit pull-forward)ADR-0052: Memory schema v2 expand (HNSW, quality columns)ADR-0053: Vector store abstraction and composite scoring v2
ADRs›ADR-0031: System prompt injection strategy
ADRs

ADR-0031: System prompt injection strategy

Accepted — proxy-owned Inject with system_first, system_append, and user_prepend; pure packages/injection; no directive content in logs.

ADR-0031: System prompt injection strategy

  • Status: Accepted
  • Date: 2026-07-24
  • Authors: IBEX Harness team
  • Milestone: 2.3.3 System prompt injection

Context

Milestone 2.3.2 resolves an agent's active directive and stashes content + injection_mode on the request context. Without writing that content into the LLM messages array, directives have no effect on model behaviour.

Injection strategy is agent-level configuration (directives.injection_mode from ADR-0030). Different modes change how strongly the directive competes with client-supplied system messages. OpenAI models accept multiple system messages; some other providers historically preferred a single system or instruction channel — user_prepend exists for that compatibility path.

ADR-0025 and ADR-0026 already require that provider clients do not implement directive injection. The proxy owns the messages array before provider.Complete.

Decision

  1. Proxy owns injection: Apply injection in the chat forward path after llm.ToProviderRequest, using ResolvedDirectiveFromContext. Provider clients receive already-injected []provider.Message.
  2. Shared pure package: packages/injection.Inject(messages, directive, mode) returns a new slice and never mutates the input. Safe for concurrent use. Latency budget <0.5ms (in-memory only).
  3. Three modes (aligned with Postgres enum):
    • system_first — directive is the first message with role=system; originals follow (strongest enforcement; default).
    • system_append — directive is inserted after the leading contiguous system block; if none, insert as first system message.
    • user_prepend — first user message content becomes [DIRECTIVE]: <content>\n\n<original>. If no user message exists, leave messages unchanged (no-op copy).
  4. Empty content: If directive content is empty (or resolve fail-open left no context), return messages unchanged (still allocate a defensive copy when calling Inject with empty content, or skip Inject entirely in the proxy).
  5. Unknown / empty mode: Treat as system_first (matches DB default and directive.DefaultInjectionMode).
  6. Privacy: Never log or trace directive content. Metrics/logs may record mode or presence only.
  7. Phase 3 reuse: Memory / context assembly should call the same Inject (or a thin wrapper) rather than re-implementing message splicing inside provider clients.

Consequences

  • Chat path order remains: auth → agent verify → rate limit → directive resolve → parse → provider routing → inject → Complete.
  • OpenAI multi-system guidance: multiple system messages are valid for GPT-4o-class models; system_first / system_append rely on that.
  • Clients that already send system prompts keep them; IBEX never silently drops client system messages.
  • Benchmark synthetic stagePromptInject remains a follow-on for Goal 2.6 (real wiring), not a blocker for 2.3.3.

Alternatives considered

  1. Inject inside OpenAI client — couples one provider to agent config; rejected by ADR-0025/0026.
  2. Middleware that mutates llm.ChatCompletionRequest before conversion — possible, but the public Inject API is defined on provider.Message; converting then injecting keeps one splice implementation.
  3. Single forced system message only — simpler; loses append / prepend strategies required by the schema enum.

Was this page helpful?

Edit on GitHub

Last updated on

PreviousADR-0030: Directive versioning strategyNextADR-0032: Session data model and retention

On this page

  • Context
  • Decision
  • Consequences
  • Alternatives considered
0%