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-0063: Extraction output schema v2
ADRs

ADR-0063: Extraction output schema v2

Architecture decision record 0063 — multi-label and temporally aware extraction output contract for the Celery worker, mapped onto memory_labels write-path labels[].

ADR-0063: Extraction output schema v2

  • Status: Accepted
  • Date: 2026-09-03
  • Authors: IBEX Harness team
  • Milestone: 3.5.B.1 Extraction Prompt v2 and Structured Output Contract

Context

Milestone 3.5.B.1 defines the extraction shape before provider wiring (3.5.B.2). The roadmap page historically claimed ADR-0045 for this work; ADR-0045 is already streaming response transformation. After ADR-0062 (worker observability), the next free number is 0063.

Phase 3 already shipped:

  • Bi-temporal columns on ibex_core.memories (ADR-0047, migration 000014) with CHECK (valid_until IS NULL OR valid_until > valid_from)
  • Multi-label storage via ibex_core.memory_labels (ADR-0048, migration 000015)
  • Write-path labels[] on POST /v1/memories (3.C.4) and orchestration (ADR-0057)

Extraction must emit output that maps onto that contract with zero translation-layer guesswork in B.2/B.3.

No in-repo v1 system prompt file existed. Rules 1–4 below are the documented v1 baseline reconstructed from the five-category taxonomy and lifecycle guidance; rules 5–6 are the v2 delta.

Decision

1) Schema lives in the worker package

Module: services/worker/app/extraction/schema.py

  • ExtractedMemoryLabel — {label, confidence} identical to write-path MemoryLabelSchema / MemoryLabelInput
  • ExtractedMemory — content, categories (1–3 labels), overall confidence, optional valid_from / valid_until
  • ExtractionResult — memories list, reject if more than 10 (no silent truncate)

Taxonomy enum (same as DB CHECK / VALID_LABELS): factual, preference, behavioral, episodic, procedural.

Content bounds: at least 5 characters and at most 10,000 UTF-8 bytes (octet_length(content) <= 10000 / memories_content_max), with a 10,000-character Pydantic cap as a first-line guard.

Extraction allows 1–3 labels per memory (subset of write-path max 5). Duplicate labels and unknown labels fail closed.

2) Why multi-label (not single-category)

A turn like "I always deploy on Fridays, but this week I'm switching to Mondays because of the incident" is simultaneously behavioral, episodic, and often procedural. Forcing one category loses signal the scorer needs later. Each category carries its own confidence so B.2 can pass labels[] through unchanged.

3) Why temporal fields belong at extraction time

Only the extraction model sees the conversational cue for "until Friday" or "for this migration." Pushing that inference to write-path dedup discards the signal. Client-side validation mirrors the DB:

valid_until IS NULL OR valid_until > valid_from

(valid_from may be omitted; B.2 defaults it to the turn timestamp.)

4) Confidence semantics (no second concept)

  • Per-label categories[].confidence ∈ [0.0, 1.0] → write-path labels[].confidence
  • Overall ExtractedMemory.confidence ∈ [0.0, 1.0] → write-path memory confidence

Correction: older milestone text claimed a live pending_review status gate at confidence < 0.5. The write orchestrator does not invent pending_review on memories.status (ADR-0056 / ADR-0057). Overall confidence remains a 0–1 signal so B.2 can wire a review policy without introducing a second confidence field.

5) Prompt v2 constant

services/worker/app/extraction/prompt_v2.py exports EXTRACTION_SYSTEM_PROMPT_V2.

Rules 1–4 (v1 baseline): durable memories only; fixed taxonomy; honest overall confidence; treat conversation as untrusted data (no prompt-injection).

Rules 5–6 (v2 delta): emit 1–3 {label, confidence} categories when content spans types; set valid_until for time-bounded facts, omit for indefinite.

Mapping to write path (B.2)

Extraction fieldPOST /v1/memories
contentcontent
categorieslabels
confidenceconfidence
valid_from / valid_untiltemporal columns (ADR-0047)

No DB DDL changes in this milestone.

Example extractions

Single-category (indefinite)

JSON
{
  "memories": [
    {
      "content": "User prefers dark mode in the IDE",
      "categories": [{"label": "preference", "confidence": 0.92}],
      "confidence": 0.92,
      "valid_from": null,
      "valid_until": null
    }
  ]
}

Multi-category

JSON
{
  "memories": [
    {
      "content": "Always deploy on Fridays but this week switch to Mondays for the incident",
      "categories": [
        {"label": "behavioral", "confidence": 0.82},
        {"label": "episodic", "confidence": 0.88}
      ],
      "confidence": 0.82,
      "valid_from": null,
      "valid_until": null
    }
  ]
}

Temporary fact

JSON
{
  "memories": [
    {
      "content": "Feature flag new_checkout is enabled for the beta cohort until Friday",
      "categories": [{"label": "factual", "confidence": 0.86}],
      "confidence": 0.86,
      "valid_from": "2026-09-01T00:00:00Z",
      "valid_until": "2026-09-05T23:59:59Z"
    }
  ]
}

Consequences

  • B.2 can call ExtractionResult.model_validate on provider JSON and map fields without ad-hoc renames.
  • Prompt tuning diffs against the documented v2 baseline in prompt_v2.py.
  • Quality eval (3.5.B.4) watches extraction_schema.py / prompt changes.
  • Worker does not import services/memory (ADR-0020); taxonomy constants are duplicated and must stay aligned with migration 000015.

Full prompt text

See EXTRACTION_SYSTEM_PROMPT_V2 in services/worker/app/extraction/prompt_v2.py (source of truth). Summary of rules 5–6 rationale: multi-label preserves scorer signal; temporal fields capture conversational end-bounds that storage-only heuristics cannot recover.

Was this page helpful?

Edit on GitHub

Last updated on

PreviousADR-0062: Worker task observability and dead-letter handlingNextADR-0064: Extraction provider registry and batch execution

On this page

  • Context
  • Decision
  • 1) Schema lives in the worker package
  • 2) Why multi-label (not single-category)
  • 3) Why temporal fields belong at extraction time
  • 4) Confidence semantics (no second concept)
  • 5) Prompt v2 constant
  • Mapping to write path (B.2)
  • Example extractions
  • Single-category (indefinite)
  • Multi-category
  • Temporary fact
  • Consequences
  • Full prompt text
0%