Skip to Content
Developer DocsServicesAgent Config Service

Agent Config Service

The Agent Config Service manages prompt templates, versioning, lifecycle, and runtime resolution for all AEGIS AI agents. It provides a centralized, auditable system for creating, reviewing, and deploying prompt templates with Jinja2 templating, role-based access control, and Redis-cached runtime resolution.

Overview

Instead of hardcoding system prompts in the orchestration engine, prompts are managed as versioned templates organized into namespaces. Each template goes through a controlled lifecycle:

  1. Draft — Authors create and edit drafts privately. One draft per user per template.
  2. Pre-production — Promoted drafts are immutable and can be targeted to specific users for testing.
  3. Active — The production version served to all users at runtime.
  4. Archived — Superseded versions preserved for audit and rollback.

Every lifecycle transition is recorded in an HMAC-signed, append-only audit trail.

The orchestration engine calls this service at conversation start to render the agent’s persona (when it uses a prompt_template_ref) and each loaded skill’s context_templates (R35 P3b), which system_prompt_node assembles into the base system message. These runtime calls send X-Tenant-Id (always) and X-User-Id (when a real end user is present) as headers; /resolve + /render accept identity optionally (get_optional_user). A best-effort soft-fail degrades prompt context (but never the skill’s executable surface) if the service is unreachable.

Port & Language

PropertyValue
Port8010
LanguagePython 3.12
FrameworkFastAPI
Entry pointsrc/agent_config/main.py

Key Endpoints

MethodPathDescription
GET/prompts/namespacesList namespaces (filtered by user access)
POST/prompts/namespacesCreate a namespace (tenant admin only)
GET/PUT/prompts/namespaces/{id}Get or update namespace config
GET/POST/prompts/namespaces/{id}/tiersList or create budget tiers
GET/POST/prompts/templatesList or create prompt templates
GET/PUT/DELETE/prompts/templates/{id}Template CRUD
GET/POST/prompts/templates/{id}/versionsList versions or create a draft
GET/prompts/templates/{id}/my-draftGet current user’s draft
PUT/prompts/versions/{id}/save-draftUpdate draft in place, re-validate
DELETE/prompts/versions/{id}/discardHard-delete a draft
POST/prompts/versions/{id}/promote-pre-prodPromote draft to pre-production
POST/prompts/versions/{id}/promote-activePromote pre-prod to active
POST/prompts/versions/{id}/approveApprove a pending promotion
POST/prompts/versions/{id}/rejectReject with reason
POST/prompts/versions/{id}/restoreRestore an archived version
GET/prompts/resolve/{namespace}:{slug}Resolve best version (hot path)
POST/prompts/render/{namespace}:{slug}Resolve + render with variables
GET/prompts/pending-approvalsList pending approvals for current user
POST/prompts/audit/verifyVerify HMAC signatures on audit entries
GET/POST/PUT/DELETE/prompts/namespaces/{id}/accessNamespace access control CRUD
GET/POST/skillsList or create skill definitions
GET/PUT/DELETE/skills/{id}Skill detail / update / soft-delete. Create/update accept context_mode (reason_alongside|compute_and_return, R35 §11.4).
GET/POST/skills/{id}/code-blocksList or create code blocks. Create/update accept llm_visible (R35 §11.3, default true).
GET/PUT/DELETE/skills/{id}/code-blocks/{block_key}Code block CRUD
GET/POST/skills/{id}/personasR35 P2 — list or create skill personas (voice/identity; exactly one of prompt_text / prompt_template_ref; at most one is_default)
GET/PUT/DELETE/skills/{id}/personas/{persona_key}R35 P2 — skill persona CRUD
GET/healthHealth check

Architecture

Module Breakdown

src/agent_config/ ├── main.py # FastAPI app, lifespan, route registration ├── config.py # Settings from environment variables ├── database.py # AsyncPG connection pool (shared PostgresPool) ├── dependencies.py # get_current_user (X-User-Id required), get_optional_user (user optional, tenant required — used by /resolve + /render), get_redis ├── seed.py # Namespace/tier/prompt seeder + migration runner ├── models/ │ ├── namespaces.py # Pydantic models for namespace CRUD │ ├── tiers.py # Budget tier models │ ├── templates.py # Template CRUD models │ ├── versions.py # Version CRUD + lifecycle models │ └── audit.py # Audit log models ├── routes/ │ ├── namespaces.py # Namespace + tier endpoints │ ├── templates.py # Template CRUD endpoints │ ├── versions.py # Version CRUD + lifecycle + pending approvals │ ├── resolution.py # Runtime resolve + render (hot path) │ ├── audit.py # Audit log queries + HMAC verification │ └── access.py # Namespace access control ├── services/ │ ├── validation.py # 5-step save-time validation pipeline │ ├── injection_scan.py # Prompt injection pattern detector │ ├── lifecycle.py # Promote, approve, reject, restore, conflict │ ├── renderer.py # Jinja2 SandboxedEnvironment with timeout │ ├── resolution.py # Redis-cached runtime resolution │ └── audit_service.py # HMAC signing + audit log writes └── migrations/ └── 001_initial_schema.sql

Template Organization

Templates are organized in a two-level hierarchy: Namespace > Slug. Each namespace has its own budget tiers, access control, and approval settings.

NamespacePurposeAuto-Approve
agentsAgent system prompts (Rule 37, Rule 32, etc.)No
ontologyKnowledge graph ontology generationYes
detectionEvent detection rule formulasYes
notificationsNotification text generationYes

Version Lifecycle

Draft (private, mutable) ├── Promote to Pre-prod ──┬── No conflict → Pre-production │ └── Conflict (409) → Override or Merge Pre-production (immutable, targeted) ├── auto_approve = true → Active (immediate) ├── auto_approve = false → Pending approval │ ├── Approver approves → Active │ └── Approver rejects → Stays Pre-prod (author revises) Active (one per template) └── Superseded → Archived (restorable)

Validation Pipeline

Every draft creation and save runs a 5-step validation pipeline:

  1. Jinja2 Syntax Check — Catches template syntax errors with line numbers
  2. Restricted Subset Enforcement — Blocks macro, import, set, call constructs and non-whitelisted filters
  3. Variable Extraction — Detects undeclared variables, warns on unknowns
  4. Injection Scan — Regex-based detection of instruction override, role confusion, sandbox escape, and data exfiltration patterns
  5. Token Budget Check — Estimates rendered tokens using tiktoken, rejects if over budget tier limit

Conflict Resolution

When promoting to pre-production and another user’s pre-prod already exists, the API returns 409 with two options:

OptionBehavior
OverrideArchives the existing pre-prod, promotes your draft
MergeArchives both, creates a new version from a merged body

Runtime Resolution (Hot Path)

The resolve endpoint checks in order:

  1. Redis cache for pre-prod version targeting this user (60s TTL)
  2. Redis cache for active version (5-min TTL)
  3. Database query on cache miss

Access Control

Namespace access uses a four-level role hierarchy:

RolePermissions
viewerRead templates and versions
authorCreate templates, drafts, promote
approverApprove/reject promotions (cannot approve own versions)
adminManage tiers, access control, namespace settings

Unauthorized access returns 404 (not 403) to avoid leaking namespace existence.

Database Tables

TablePurpose
prompt_namespacesNamespace definitions with approval config
prompt_budget_tiersToken budget limits per namespace
prompt_templatesTemplate metadata with active version pointer
prompt_versionsVersion bodies with status, validation, targeting
prompt_audit_logAppend-only, HMAC-signed lifecycle events
namespace_access_controlRole-based access per namespace per user
skill_definitionsSkill metadata (domain tags, context_templates, required_capabilities, context_mode)
skill_code_blocksSandboxed code blocks per skill (input_binding, llm_visible, mode)
skill_personasR35 P2 — a skill’s voice/identity personas (one default per skill)
agent_definitionsR35 P1 — agent → root skill + persona + model config (read by the loader from R35 P3a)

Key constraints:

  • One draft per user per template (partial unique index on status = 'draft')
  • One pre-prod per template (partial unique index)
  • One active per template (partial unique index)
  • Audit log is append-only (triggers reject UPDATE/DELETE)
  • One default persona per skill (idx_one_default_persona_per_skill, partial unique on is_default); each persona sets exactly one of prompt_text / prompt_template_ref (app-enforced)

Dependencies

Python Packages

PackageVersionPurpose
fastapi^0.115Web framework
uvicorn^0.34ASGI server
asyncpg^0.29PostgreSQL async driver
redis^5.0Redis client for cache
jinja2^3.1Template rendering (SandboxedEnvironment)
tiktoken^0.7Token estimation (cl100k_base encoding)
aegis-sharedlocalShared DB helpers (PostgresPool)

Infrastructure Dependencies

DependencyPurpose
PostgreSQL 15Template/version storage, audit trail
Redis 7Runtime resolution cache

Configuration

Environment VariableDefaultDescription
AGENT_CONFIG_HOST0.0.0.0Bind address
AGENT_CONFIG_PORT8010Bind port
DATABASE_URLpostgresql://aegis:aegis_local@localhost:5432/aegisPostgreSQL connection
REDIS_URLredis://localhost:6379Redis connection
HMAC_SIGNING_KEYaegis-local-hmac-key-change-in-productionKey for audit trail HMAC signatures
DEFAULT_TENANT_ID00000000-0000-0000-0000-000000000001Default tenant for local dev

The HMAC_SIGNING_KEY must be kept secret and consistent. Changing the key invalidates all existing audit log signatures. Use HashiCorp Vault in production.

Running Locally

cd services/agent-config-service poetry install poetry run uvicorn agent_config.main:app --reload --port 8010

Seed Data

Run the seeder to create default namespaces, budget tiers, and migrate agent prompts from the orchestration engine:

cd services/agent-config-service poetry run python -m agent_config.seed

This creates:

  • 4 namespaces (agents, ontology, detection, notifications)
  • 5 budget tiers
  • 4 agent system prompt templates with active version 1

The service lifespan additionally self-applies its numbered SQL migrations (001009) and runs idempotent seeders on every boot, including the R35 P2 skill seeders: seed_rrc_prompts (the skills namespace + two regulatory-context templates), seed_rrc_skills (rrc_rule37 + rrc_rule32 with 8 sandboxed code blocks + a default persona each), and seed_rrc_skill_rules (3 HITL require_hitl skill rules, register-only until R35 P5). These are additive — nothing on the live agent path reads them until R35 P3a.

Gateway Access

All endpoints are proxied through the API gateway at http://localhost:8000/api/v1/prompts/*.

Integration with Orchestration Engine

The orchestration engine’s system_prompt_node calls this service at conversation start:

result = await render_prompt(namespace="agents", slug=slug, variables={}, user_id=user_id)

If the service is unreachable, it falls back to hardcoded prompts in fallback_prompts.py. The resolved prompt_version_id is stored in the conversation state for audit trail purposes.

Last updated on