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: Planned
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 by agent_id, with a 60-second TTL. On a miss, the proxy queries Postgres directly (the proxy gets its own read-only Postgres connection for directive and session data — distinct from the auth service's pool). On a directive update, the cache entry is invalidated via pub/sub (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
Deliverables
1. packages/directive — resolver interface and cached implementation
// Package directive provides directive resolution for the proxy hot path.
package directive
// Resolver resolves the active directive for an agent.
// Returns ("", nil) when the agent has no active directive.
// Returns an error only for infrastructure failures (DB/Redis down).
type Resolver interface {
Resolve(ctx context.Context, agentID uuid.UUID) (content string, err error)
Invalidate(agentID uuid.UUID) // called by cache invalidation subscriber
}
// Redis key format: directive:{agent_id} (agent_id is already org-scoped via the agents table)
// TTL: 60 seconds (configurable)
// On miss: query Postgres for active directive content and populate cache2. Postgres query for directive resolution
const resolveDirectiveQuery = `
SELECT dv.content
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.is_active = true
AND dv.id IS NOT NULL
LIMIT 1`
// Returns pgx.ErrNoRows when agent has no active directive — resolved to ("", nil)3. Cache invalidation subscriber
When a directive is updated (via Phase 3 API service), publish to ibex:directive:updates:{agent_id}. The proxy subscriber calls Resolver.Invalidate(agentID) which deletes the Redis key and clears any in-process pointer.
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
("", nil)— not an error - Directive content is NOT logged (privacy)
- Cache TTL is configurable via
IBEX_DIRECTIVE_CACHE_TTL(default: 60s)
Last updated on