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 v2ADR-0054: In-process Presidio PII detection in memoryADR-0055: Memory write-path exact and near-duplicate dedupADR-0056: Temporal-interval-aware conflict detectionADR-0057: Memory write-path persistence and orchestrationADR-0058: Semantic search FTS fallback triggerADR-0059: Hot cache read pathADR-0060: Memory service tenant isolation architectureADR-0061: Retrieval-quality benchmark methodologyADR-0062: Worker task observability and dead-letter handlingADR-0063: Extraction output schema v2ADR-0064: Extraction provider registry and batch executionADR-0065: Extraction batch idempotency and turn pointer orderingADR-0066: Extraction quality evaluation harnessADR-0067: Context capability catalog generate-and-diff for token budgetsADR-0068: Composite scoring-time relevance gateADR-0069: Context packer bounded DP knapsackADR-0070: Context formatting locked ordering and session nonceADR-0071: Context assembly gRPC degradation ladder and retrieval deadlineADR-0072: Extraction enqueue via worker HTTP (not Celery Redis wire format)ADR-0073: Organization Lifecycle and Cascading SuspensionADR-0074 Provider Credential StorageADR-0075 Per-Org Model Routing PolicyADR-0076: Circuit Breaker per ProviderADR-0077: Provider Fallback RoutingADR-0078: Operator runtime topology & environment contract (4.P.0)ADR-0079: Operator session RS256 dual-verify and TOTP step-upIBEX Console service rename and boundaryADR-0081: Fail-closed Proxy runtime controlsD2 metadata-only trace read contractD2 publication, identity, query, and lifecycle contract
ADRs›ADR-0070: Context formatting locked ordering and session nonce
ADRs

ADR-0070: Context formatting locked ordering and session nonce

Architecture decision record 0070 — locked context injection order (directive → history → memories by category → tools), XML-style ibex_memory delimiters, and per-assembly secrets.token_urlsafe nonce.

ADR-0070: Context formatting locked ordering and session nonce

  • Status: Accepted
  • Date: 2026-09-05
  • Authors: IBEX Harness team
  • Milestone: 3.5.C.5 Context Formatter
  • Depends on: ADR-0069 (PackedMemories / ScoredMemory), ADR-0038 (proto assembled_context string — wiring in 3.5.C.6)

Context

Track C needs a formatter that turns a resolved directive, recent messages, and packed memories into a single assembled_context string. Discovery (main @ post-C.4) found:

  • No formatter module existed under services/context.
  • Planning sketches incorrectly named ADR-0042 (self-hosted OpenAI-compatible adapter) as the amend target for ordering — unrelated.
  • ADR-0038 covers the gRPC contract only and has no ordering/delimiter section; Phase 3.5 exit criteria cite it for indexing, not for formatter decisions.
  • ARCHITECTURE.md “Format and inject” previously said History last (Directive → Procedural → Declarative → Episodic → Tools → History), while phase index / goals / blog already locked history at position 2.
  • SECURITY.md §7.3 requires memories wrapped as data with a per-session nonce (e.g. <ibex_memory nonce="...">).

Decision

1) Locked injection order

1. Directive (plain text + untrusted-data instruction naming the assembly nonce)
2. Conversation history (native `role: content` lines — not XML-wrapped)
3. Memories by category: procedural → factual → preference → behavioral → episodic
4. Tool schemas (optional; omitted when empty)

History @ 2: mitigates “lost in the middle” — recent turns stay near the start where attention is reliable; putting history last buried it after memory blocks.

Unknown categories (if any) append after the five known buckets, sorted by (category, memory_id).

2) Module

  • services/context/app/formatter.py — ContextFormatter, FormatRequest, FormattedContext, CATEGORY_ORDER
  • Consumes one FormatRequest (directive, recent messages, PackedMemories, optional tool schemas / injected nonce)
  • Does not orchestrate budget → retrieve → pack (3.5.C.6)

3) Per-assembly nonce + XML-escaped memory bodies

  • Generate with Python stdlib secrets.token_urlsafe(nbytes) (OS CSPRNG) — no home-grown crypto (SECURITY.md §6.4 / AGENTS §5.4).
  • Default nbytes=16 (~128 bits of entropy); env IBEX_CONTEXT_FORMATTER_NONCE_BYTES (allowed 1..64).
  • Scope: one nonce per format() call (one assembly). Open tags carry nonce="{assembly}"; consumers must only trust tags whose nonce matches this assembly.
  • Boundary contract: memory markup is serialized only (never parsed) via html.escape for attribute values (quote=True) and body text. Untrusted content cannot forge </ibex_memory> or inject raw markup. The per-assembly nonce on the open tag authenticates genuine delimiters; escaping prevents close-tag / attribute breakout from memory bodies. Stdlib xml / defusedxml are intentionally unused — XXE applies to parsers, not serializers, and SAST often false-positives on serialize-only ElementTree imports.

4) Doc supersession

This ADR supersedes the stale ARCHITECTURE.md “Format and inject” history-last ordering. ARCHITECTURE.md is updated to match this decision and links here. Do not amend ADR-0038 or ADR-0042.

Consequences

  • Golden-output and nonce spoof unit tests gate regressions in CI (context-test).
  • C.6 can map FormattedContext.assembled_context → AssembleContextResponse.assembled_context.
  • Proxy / directive injection modes (ADR-0031) remain separate: this ADR formats the assembly string, not the proxy message-array inject strategy.

Alternatives considered

  1. Amend ADR-0042 — rejected: wrong document (Phase 2.5 self-hosted adapter).
  2. Amend ADR-0038 — rejected: mixes gRPC contract with formatting policy; 0038 has no ordering section.
  3. Nonce alone without body escaping — rejected for close-tag forgery: raw </ibex_memory> in content could terminate the block; escaping is required in addition to the per-assembly open-tag nonce.
  4. XML-wrap history — rejected: milestone success signal requires native role messages for trusted history.
  5. xml.etree.ElementTree / defusedxml for markup — rejected for this module: we never parse XML (no XXE surface); defusedxml does not provide an Element builder API, and importing stdlib xml trips serialize-only SAST noise. Prefer html.escape serialization.

Was this page helpful?

Edit on GitHub

Last updated on

PreviousADR-0069: Context packer bounded DP knapsackNextADR-0071: Context assembly gRPC degradation ladder and retrieval deadline

On this page

  • Context
  • Decision
  • 1) Locked injection order
  • 2) Module
  • 3) Per-assembly nonce + XML-escaped memory bodies
  • 4) Doc supersession
  • Consequences
  • Alternatives considered
0%