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-0048: Memory multi-label categories
ADRs

ADR-0048: Memory multi-label categories

Architecture decision record 0048 — ibex_core.memory_labels join table with org-safe FKs, FORCE RLS, and memories.category primary sync for Phase 2.5 Track E.

ADR-0048: Memory multi-label categories

  • Status: Accepted
  • Date: 2026-08-25
  • Authors: IBEX Harness team
  • Milestone: 2.5.G5.M2 Multi-label category support

Context

A single memories.category enum cannot represent facts that span multiple taxonomy buckets (e.g. preference + factual). Track E (2.5.G5) needs the join table before Phase 3 writers and category-conditional scoring.

The milestone sketch named the table memory_categories with only REFERENCES memories(id) and no org_id. Phase 3 sketches (3.1.1, 3.C.4) use memory_labels / label. Live tenancy rules require explicit org_id filtering and composite org-safe FKs (ADR-0047).

Decision

1) Table name memory_labels (not memory_categories)

Ship ibex_core.memory_labels with column label to match Phase 3 consumers and avoid a rename migration. Document the sketch advancement on the milestone page.

2) Tenancy-first DDL

  • org_id NOT NULL + FOREIGN KEY (memory_id, org_id) → memories(id, org_id) ON DELETE CASCADE
  • PRIMARY KEY (memory_id, label)
  • Same taxonomy CHECK as memories.category
  • confidence NUMERIC(3,2) NOT NULL DEFAULT 1.00 CHECK (0..1)
  • Index (org_id, label) for tenant filters
  • FORCE ROW LEVEL SECURITY + ibex_core.rls_org_visible(org_id) (not subquery-on-memories RLS drafts)

3) Primary sync via memories.category

Keep the existing category column (do not add primary_category in M2). Trigger sync_memory_primary_category after INSERT/UPDATE/DELETE on labels:

  • When ≥1 label remains: set category to ORDER BY confidence DESC, label ASC LIMIT 1
  • When zero labels remain: leave category unchanged (NOT NULL)

Labels are source of truth once present. No bidirectional sync from category → labels in this milestone (avoids recursion). Phase 3 writers should maintain ≥1 label.

4) Backfill

INSERT … SELECT id, org_id, category, 1.00 FROM memories ON CONFLICT DO NOTHING in the same up migration.

5) No application writers

No ORM/REST/proto in this PR. Phase 3.C.4 owns write-path population beyond backfill.

6) Expand-contract with 3.1.1 / 3.C.4

Those milestones must not CREATE TABLE memory_labels. They may later rename category → primary_category if needed; M2 does not dual-column.

Consequences

Positive

  • Phase 3 scoring/extraction can assume multi-label shape
  • Tenant isolation matches live FORCE RLS pattern
  • Deterministic primary selection

Negative

  • Sketch name memory_categories diverges (documented)
  • Direct updates to memories.category while labels exist can be overwritten by the next label write

Rollout

  • Migration: infra/migrations/postgres/000015_create_memory_labels.{up,down}.sql
  • Down is dev/test only; production forward-only per ADR-0005

References

  • ADR-0005
  • ADR-0047
  • Milestone 2.5.G5.M2
  • Milestones 3.1.1, 3.C.4

Was this page helpful?

Edit on GitHub

Last updated on

PreviousADR-0047: Memory temporal validity foundationNextADR-0049: Memory relationship graph readiness

On this page

  • Context
  • Decision
  • 1) Table name memory_labels (not memory_categories)
  • 2) Tenancy-first DDL
  • 3) Primary sync via memories.category
  • 4) Backfill
  • 5) No application writers
  • 6) Expand-contract with 3.1.1 / 3.C.4
  • Consequences
  • Positive
  • Negative
  • Rollout
  • References
0%