phase 2 single provider

The proxy needs the agent's active directive content on every request. It cannot query Postgres on every request without adding 2–10ms to the proxy overhead. The solution is a Redis cache of directive content, keyed by agent_id, with a 60-second TTL. On a miss, the proxy queries Postgres directly (the proxy gets its ow

Milestone 2.3.2 — Directive Resolver (Redis Cache → Postgres Fallback)

Status: Complete
Goal: 2.3 — Directive resolution and prompt injection
Phase: 2 — Single Provider End-to-End
Estimated effort: 2–3 days
ADR required: None (follows established cache pattern)


Why This Milestone Exists

The proxy needs the agent's active directive content on every request. It cannot query Postgres on every request without adding 2–10ms to the proxy overhead. The solution is a Redis cache of directive content, keyed {org_id}:directive:{agent_id}, with a 60-second TTL (configurable via IBEX_DIRECTIVE_CACHE_TTL). On a miss, the proxy queries Postgres directly (read-only sql.DB pool — distinct from the auth service's pool). On a directive update, the cache entry is invalidated via pub/sub on directive_updates:{org_id} (same pattern as 2.2.2).


Branch

feature/m2-3-2-directive-resolver

PR Title

feat(proxy): directive resolver with Redis cache and Postgres fallback (m2.3.2)


Prerequisites

  • 2.3.1 merged — directive tables exist
  • 1.4.2 merged — packages/config available

Deliverables

1. packages/directive — resolver interface and cached implementation

Go
// Package directive provides directive resolution for the proxy hot path.
package directive
 
// Resolver resolves the active directive for an agent.
// Returns zero Resolved (empty Content) when the agent has no active directive.
// Returns an error only for infrastructure failures (DB/Redis down).
type Resolver interface {
    Resolve(ctx context.Context, orgID, agentID uuid.UUID) (Resolved, error)
    Invalidate(orgID, agentID uuid.UUID)
}
 
// Redis key format: {org_id}:directive:{agent_id}
// TTL: IBEX_DIRECTIVE_CACHE_TTL (default 60s)
// On miss: query Postgres with org_id in WHERE + RLS app.current_org_id

2. Postgres query for directive resolution

Go
const resolveDirectiveQuery = `
    SELECT dv.content, d.injection_mode, dv.id
    FROM ibex_core.directives d
    JOIN ibex_core.directive_versions dv ON dv.id = d.active_version_id
    WHERE d.agent_id = $1
      AND d.org_id   = $2
      AND d.is_active = true
    LIMIT 1`
// Returns sql.ErrNoRows when agent has no active directive — resolved to empty Resolved

3. Cache invalidation subscriber

When a directive is updated (via Phase 3 API service), publish to directive_updates:{org_id}. The proxy subscriber calls Resolver.Invalidate(orgID, agentID) which deletes the Redis key.


Acceptance Criteria

  • Directive resolved from Redis on cache hit in < 2ms
  • Postgres fallback on Redis miss populates cache for subsequent requests
  • Agent with no directive returns empty content — not an error
  • Directive content is NOT logged (privacy)
  • Cache TTL is configurable via IBEX_DIRECTIVE_CACHE_TTL (default: 60s)

Edit on GitHub

Last updated on

On this page

0%