Skip to main content

Prompt Management

Atlas ships 31 named AI agent prompt templates (plus 5 reusable _-prefixed partials) in app/prompts/templates/, spanning ~25 distinct agents. The Prompt Management system centralizes these prompts with version control, DB-backed storage, and EU AI Act traceability — every AI output is linked to the exact prompt version that produced it.

Architecture Evolution

Prompt Registry

The PromptRegistry singleton (app/prompts/registry.py) manages all named prompts with a layered loading strategy. Filesystem .jinja2 templates form the baseline; active rows from the prompt_versions table are overlaid on top (DB-first, FS fallback) and refreshed on a 60-second TTL:

Prompt Inventory (31 named templates)

Each template's name and agent are declared in its YAML frontmatter. The name is the key passed to registry.render(name, ...). Files prefixed with _ are partials included via {% include %} and are not directly renderable.

Prompt NameAgentDomain
synthesissynthesis_agentInvestigation synthesis
task_generatortask_generatorFollow-up task creation
registry_investigationregistry_agentCompany registry OSINT
belgian_investigationbelgian_agentBelgian data collection
belgian_gazettebelgian_scraping_agentBelgian Gazette scrape
belgian_gazette_detailbelgian_scraping_agentGazette detail extraction
belgian_inhoudingsplichtbelgian_scraping_agentBelgian tax/social debt
adverse_mediaadverse_media_agentAdverse media scan
social_intelligencesocial_intelligence_agentSocial intelligence
sanctions_resolversanctions_resolver_agentSanctions FP resolution
person_validationperson_validation_agentNatural-person validation
mcc_classifiermcc_classifierMerchant categorization
document_extractordocument_extractorDocument field extraction
document_validatordocument_validatorDoc quality / validation
finding_debuggerfinding_debuggerSignal / finding analysis
case_intelligencecase_intelligence_agentDecision support
quality_scorerquality_scorerLLM-as-judge quality
scan_synthesisscan_synthesis_agentDocument scan synthesis
dashboarddashboard_agentCopilot dashboard tools
dashboard_statsdashboard_stats_agentDashboard statistics
memory_adminmemory_admin_agentCompliance memory admin
osint_legacyosint_agentLegacy OSINT prompt
precious_metals_risk_assessmentrisk_classifierHVG/precious-metals risk
customs_guarantee_extractionguarantee_validatorCustoms guarantee extraction
customs_poa_classificationpoa_classifierCustoms power-of-attorney
shipment_document_classifiershipment_classifierShipment doc classification
shipment_invoice_extractionshipment_extractorShipment invoice extraction
shipment_packing_list_extractionshipment_extractorPacking list extraction
shipment_bill_of_lading_extractionshipment_extractorBill-of-lading extraction
shipment_certificate_origin_extractionshipment_extractorCertificate-of-origin extraction
shipment_transit_document_extractionshipment_extractorTransit document extraction

Partials (5): _eea_registries, _guardrails, _minimum_sources, _regulatory_basis, _severity_matrix — shared fragments included by other templates; they carry no frontmatter.

Database Model

Key constraints:

  • UNIQUE(name, version_number) — no duplicate versions
  • Partial unique index: only one active version per prompt name
  • status IN ('active', 'draft', 'archived') — check constraint
  • Immutable rows — once created, template_body never changes (append-only pattern)

EU AI Act Traceability

Every AI agent execution now links to the exact prompt version that produced it:

This satisfies EU AI Act Article 12 (automatic logging) — every AI output is traceable to the exact prompt text, model, and input that produced it.

Admin API

Implemented in app/api/admin_prompts.py, mounted at prefix /api/admin/prompts:

GET /api/admin/prompts # List all prompts with active versions
GET /api/admin/prompts/{name} # Get prompt (active version)
GET /api/admin/prompts/{name}/versions # List all versions
GET /api/admin/prompts/{name}/versions/{version} # Get a specific version
POST /api/admin/prompts/{name}/draft # Create a new draft version
POST /api/admin/prompts/{name}/activate/{version} # Activate a version (deactivates previous)
POST /api/admin/prompts/{name}/archive/{version} # Archive a version
GET /api/admin/prompts/{name}/diff # Diff two versions

Authorization: the entire router is guarded by Depends(require_role("super_admin"))super_admin role only.

Jinja2 Template Format

Templates use Jinja2 with YAML frontmatter:

---
name: synthesis
version: 3
agent: synthesis_agent
description: Main synthesis prompt for investigation analysis
variables:
- company_name
- country
- risk_context
- regulatory_context
---
You are a compliance investigation analyst for {{ company_name }}.
Country: {{ country }}

{% if risk_context %}
Risk context from prior investigations:
{{ risk_context }}
{% endif %}

{% if regulatory_context %}
Applicable regulations:
{{ regulatory_context | truncate_json }}
{% endif %}

Custom Jinja2 filters (registered in PromptRegistry.__init__): truncate_json (serialize + truncate large context dicts) and inhoudingsplicht_status (build Belgian tax/social debt status text). The tojson policy is also overridden with a datetime-aware serializer so KBO/NBB date objects render cleanly.

Parity Testing

Every prompt migration from inline strings to templates includes a parity test that proves zero behavioral change:

def test_synthesis_parity():
"""Template renders identically to the original inline prompt."""
original = _get_original_inline_prompt()
rendered = registry.render("synthesis", test_vars)
assert rendered.strip() == original.strip()

The parity suite (tests/test_prompts/test_prompt_parity.py) holds one test per prompt that was migrated from an inline string, ensuring the migration is invisible to the AI agents. Prompts authored as templates from the start (e.g. the customs/shipment extractors) have no inline original and so carry no parity test.