Skip to main content
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

version
number
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

data
array
Array of change records
data[].id
string
Change record UUID
data[].version
number
Version number this change created
data[].changeType
string
Type of changeValues: CREATE, UPDATE, DELETE, ACTIVATE, DEACTIVATE
data[].changes
object
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"]
  }
}
data[].comment
string
Optional comment explaining the change
data[].author
object
User who made the change
  • id (string): User UUID
  • email (string): User email
data[].createdAt
string
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:
  1. Fetch the historical version: GET /api/admin/prompts/:id?version=2
  2. Extract all fields from the response
  3. 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)"

2. Change Comments

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

Build docs developers (and LLMs) love