The CGIAR Risk Intelligence Tool implements comprehensive version control for AI prompts, allowing full audit trails, rollback capabilities, and change tracking.
Version Strategy
Current Version Pattern
Prompts follow a snapshot-on-update versioning strategy:
- The
prompts table stores the latest version only (no JOINs needed for reads)
- On every update, the current state is snapshotted to
prompt_versions table
- Version number is auto-incremented on each update
- Version 1 is created automatically when the prompt is first created
Database Schema
model Prompt {
id String @id @default(uuid())
name String
version Int @default(1) // Current version
// ... other fields
versions PromptVersion[] // Historical snapshots
changes PromptChange[] // Audit trail
}
model PromptVersion {
id String @id @default(uuid())
promptId String
version Int
// Snapshot of all prompt fields at this version
name String
section AgentSection
systemPrompt String
userPromptTemplate String
// ... all other prompt fields
createdAt DateTime @default(now())
@@unique([promptId, version])
}
model PromptChange {
id String @id @default(uuid())
promptId String
version Int
changeType PromptChangeType // CREATE, UPDATE, DELETE, ACTIVATE, DEACTIVATE
changes Json // { field: { old: value, new: value } }
comment String?
authorId String
createdAt DateTime @default(now())
}
Endpoints
Get Prompt (Current or Historical Version)
GET /api/admin/prompts/:id
Retrieve the current version or a specific historical version.
Query Parameters
Historical version number to retrieve (omit for current version)Example: 2
Example: Get Current Version
curl -X GET https://api.example.com/api/admin/prompts/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
Example: Get Historical Version
curl -X GET "https://api.example.com/api/admin/prompts/550e8400-e29b-41d4-a716-446655440000?version=2" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
Response
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Gap Detection — Market Risk v2",
"version": 2,
"systemPrompt": "You are an expert agricultural risk analyst...",
// ... all prompt fields
"createdAt": "2026-02-20T10:00:00.000Z",
"updatedAt": "2026-03-01T14:30:00.000Z"
}
}
Get Change History
GET /api/admin/prompts/:id/history
Returns the full version history of a prompt, including diffs between versions, who made each change, and when.
Response Fields
Array of change recordsVersion number this change created
Type of changeValues: CREATE, UPDATE, DELETE, ACTIVATE, DEACTIVATE
Field-level diff objectFormat: { field: { old: value, new: value } }Example:{
"systemPrompt": {
"old": "You are an expert risk analyst.",
"new": "You are an expert agricultural risk analyst with 15+ years of experience."
},
"tags": {
"old": ["v2", "production"],
"new": ["v3", "production", "reviewed"]
}
}
Optional comment explaining the change
User who made the change
id (string): User UUID
email (string): User email
ISO 8601 timestamp of the change
Example Request
curl -X GET https://api.example.com/api/admin/prompts/550e8400-e29b-41d4-a716-446655440000/history \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
Example Response
{
"data": [
{
"id": "change-uuid-3",
"version": 3,
"changeType": "UPDATE",
"changes": {
"systemPrompt": {
"old": "You are an expert risk analyst.",
"new": "You are an expert agricultural risk analyst with 15+ years of experience."
},
"tags": {
"old": ["v2", "production"],
"new": ["v3", "production", "reviewed"]
}
},
"comment": "Enhanced system prompt with experience level",
"author": {
"id": "user-uuid-456",
"email": "[email protected]"
},
"createdAt": "2026-03-04T14:30:00.000Z"
},
{
"id": "change-uuid-2",
"version": 2,
"changeType": "UPDATE",
"changes": {
"name": {
"old": "Gap Detection — Market Risk v1",
"new": "Gap Detection — Market Risk v2"
},
"userPromptTemplate": {
"old": "Identify gaps for: {{categories}}",
"new": "Given the following business plan section:\n\n{{document_excerpt}}\n\nIdentify gaps in risk coverage for the following categories: {{categories}}"
}
},
"comment": null,
"author": {
"id": "user-uuid-123",
"email": "[email protected]"
},
"createdAt": "2026-03-01T10:00:00.000Z"
},
{
"id": "change-uuid-1",
"version": 1,
"changeType": "CREATE",
"changes": {},
"comment": "Initial creation",
"author": {
"id": "user-uuid-123",
"email": "[email protected]"
},
"createdAt": "2026-02-20T10:00:00.000Z"
}
]
}
Version Lifecycle
1. Creation (v1)
When a prompt is created via POST /api/admin/prompts/create:
// Prisma transaction:
1. Create prompt record with version: 1
2. Create initial version snapshot in prompt_versions
3. Create change record with changeType: CREATE
2. Update (v2, v3, …)
When a prompt is updated via PUT /api/admin/prompts/:id/update:
// Prisma transaction:
1. Read current prompt state
2. Snapshot current state to prompt_versions (preserves previous version)
3. Compute field-level diffs (old vs new)
4. Increment version number
5. Update prompt record with new values
6. Create change record with changeType: UPDATE and diffs
3. Toggle Active State
When toggling via POST /api/admin/prompts/:id/toggle-active:
// Prisma transaction:
1. Snapshot current state to prompt_versions
2. Increment version
3. Flip isActive flag
4. Create change record with changeType: ACTIVATE or DEACTIVATE
4. Deletion
When deleting via DELETE /api/admin/prompts/:id:
// Option A: Delete entire prompt
1. Create change record with changeType: DELETE
2. Delete prompt (cascades to versions, comments, changes)
// Option B: Delete specific version
1. Delete version snapshot from prompt_versions
2. Create change record documenting version deletion
Diff Computation
The changes field in PromptChange records contains field-level diffs:
type ChangesDiff = {
[field: string]: {
old: any;
new: any;
}
};
// Example:
{
"systemPrompt": {
"old": "Original text...",
"new": "Updated text..."
},
"tags": {
"old": ["v1"],
"new": ["v2", "reviewed"]
},
"isActive": {
"old": false,
"new": true
}
}
Only changed fields are included in the diff.
Rollback Pattern
To rollback to a previous version:
- Fetch the historical version:
GET /api/admin/prompts/:id?version=2
- Extract all fields from the response
- Update the prompt with those values:
PUT /api/admin/prompts/:id/update
// Fetch version 2
const { data: v2 } = await fetch('/api/admin/prompts/550e8400-e29b-41d4-a716-446655440000?version=2');
// Rollback by updating with v2 values
await fetch('/api/admin/prompts/550e8400-e29b-41d4-a716-446655440000/update', {
method: 'PUT',
body: JSON.stringify({
name: v2.name,
systemPrompt: v2.systemPrompt,
userPromptTemplate: v2.userPromptTemplate,
// ... all other fields
})
});
// This creates version 4 (a copy of version 2)
Version Retention
All versions are retained indefinitely unless:
- The entire prompt is deleted (CASCADE deletes all versions)
- A specific version is explicitly deleted:
DELETE /api/admin/prompts/:id?version=2
Recommendation: Implement a cleanup policy for old versions (e.g., keep last 10 versions per prompt).
Conflict Detection
The update endpoint implements optimistic locking to detect concurrent modifications:
// Pseudo-code in PromptsService.update():
const currentPrompt = await prisma.prompt.findUnique({ where: { id } });
if (currentPrompt.updatedAt > clientLastReadTimestamp) {
throw new ConflictException('Prompt was modified by another user');
}
// Proceed with update...
This prevents lost updates in multi-user scenarios.
Best Practices
1. Version Naming
Include version numbers in the prompt name for easy identification:
✅ "Gap Detection — Market Risk v3"
❌ "Gap Detection — Market Risk (updated)"
Use the comment field in change records to explain significant updates:
await promptsService.update(id, dto, userId, {
comment: 'Refined output format based on user feedback'
});
3. Version Review
Before activating a new version, review the change history:
# Get change history
curl /api/admin/prompts/:id/history
# Compare current vs previous version
curl /api/admin/prompts/:id?version=2 # Old
curl /api/admin/prompts/:id # New
# If satisfied, activate
curl -X POST /api/admin/prompts/:id/toggle-active
4. Version Cleanup
Periodically delete old version snapshots to reduce database size:
# Delete version 1 (keep versions 2-5)
curl -X DELETE "/api/admin/prompts/:id?version=1"
Source Code
- Versioning logic:
packages/api/src/prompts/prompts.service.ts
- Change history service:
packages/api/src/prompts/change-history.service.ts
- Get prompt endpoint:
packages/api/src/prompts/prompts.controller.ts:141
- Get history endpoint:
packages/api/src/prompts/prompts.controller.ts:244