Skip to main content

Memory API

Compliance memory system endpoints for signal capture, system status, state management, memory block CRUD, archival passage operations, and confidence scoring. The memory system captures officer interaction signals and (optionally) stores them in Letta archival memory for RAG-based agent personalization.

All memory endpoints are under the /api prefix.

Endpoints Summary

MethodPathStatusDescription
POST/api/cases/{workflow_id}/signals202Capture an officer action signal
GET/api/memory/status200Memory system status and signal statistics
POST/api/memory/reset200Reset all memory state (demo/showcase only)
GET/api/memory/case/{workflow_id}200Memory story for a specific case
GET/api/memory/blocks/{officer_id}/{label}200Read a memory block
PUT/api/memory/blocks/{officer_id}/{label}200Update a memory block
GET/api/memory/passages/{officer_id}200List archival memory passages
POST/api/memory/passages/{officer_id}/search200Semantic search over archival passages
DELETE/api/memory/passages/{officer_id}/{passage_id}200Delete a specific passage
GET/api/memory/confidence/{officer_id}200Calculate memory confidence score
GET/api/memory/company-context200Get company context configuration
PUT/api/memory/company-context200Update company context configuration

Capture Signal

POST /api/cases/{workflow_id}/signals

Captures an officer action signal for the compliance memory system. The signal is classified deterministically, persisted to PostgreSQL, and (if Letta is enabled) asynchronously ingested into the officer's archival memory.

This endpoint always returns 202 -- signal capture is best-effort and non-blocking. Failures in classification, persistence, or Letta ingestion are logged but never propagated to the caller.

Path Parameters

ParameterTypeDescription
workflow_idstringThe workflow/case identifier

Request Body

{
"signal_type": "case_approved",
"source_component": "DecisionActions",
"iteration": 1,
"officer_id": "officer-001",
"context_data": {
"decision": "approve",
"reason": "All documents verified, no adverse findings"
}
}
FieldTypeRequiredDefaultDescription
signal_typestringYes--Signal type identifier (see Signal Types)
source_componentstringYes--UI component that generated the signal
iterationintegerNo1Current case iteration number
officer_idstringNo""Officer identifier (used for per-officer Letta agents)
context_dataobjectNo{}Arbitrary JSON context payload for the signal

Response 202

{
"status": "captured",
"signal_category": "judgment",
"safety_class": "non_suppressible"
}
FieldTypeDescription
statusstringAlways "captured"
signal_categorystringClassified category: judgment, preference, or behavioral
safety_classstringSafety classification: non_suppressible or preference_only

Example: Capture a case approval signal

curl -X POST http://localhost:8002/api/cases/wf_abc123/signals \
-H "Content-Type: application/json" \
-d '{
"signal_type": "case_approved",
"source_component": "DecisionActions",
"iteration": 1,
"officer_id": "officer-001",
"context_data": {"decision": "approve", "reason": "Clean KYB"}
}'

Example: Capture a task dismissal signal

curl -X POST http://localhost:8002/api/cases/wf_abc123/signals \
-H "Content-Type: application/json" \
-d '{
"signal_type": "suggestion_dismissed",
"source_component": "FollowUpTasks",
"iteration": 2,
"officer_id": "officer-001",
"context_data": {"task_id": "task_xyz", "finding_id": "minor-address-mismatch"}
}'

Signal Types

Signals are classified into three categories based on their type:

Judgment signals (non_suppressible)

Signal TypeTrigger
suggestion_dismissedOfficer dismisses an AI-suggested follow-up task
case_approvedOfficer approves a case
case_rejectedOfficer rejects a case
case_escalatedOfficer escalates a case
followup_requestedOfficer requests follow-up iteration
finding_overriddenOfficer overrides an OSINT finding
risk_level_changedOfficer changes the risk level assessment

Preference signals (preference_only)

Signal TypeTrigger
suggestion_acceptedOfficer accepts an AI-suggested task
suggestion_modifiedOfficer modifies an AI-suggested task before accepting
custom_task_addedOfficer creates a custom follow-up task
chat_correctionOfficer corrects AI assistant in chat
chat_positive_feedbackOfficer gives positive feedback to AI assistant
template_customizedOfficer customizes a workflow template

Behavioral signals (preference_only)

Signal TypeTrigger
section_time_spentTime spent viewing a report section
report_section_expandedOfficer expands a report section for detail
evidence_downloadedOfficer downloads evidence artifacts
chat_questionOfficer asks a question in AI assistant chat

Unknown signal types are classified as ("behavioral", "preference_only").


Memory Status

GET /api/memory/status

Returns the current state of the compliance memory system, including feature flag status, signal statistics, category breakdown, recent signals, and Letta connection status.

This endpoint always returns 200, even when the database or Letta is unreachable (returns default/zero values).

Response 200

{
"signal_capture_enabled": true,
"letta_enabled": false,
"signals": {
"total": 42,
"by_category": {
"judgment": 15,
"preference": 20,
"behavioral": 7
},
"recent": [
{
"signal_type": "case_approved",
"category": "judgment",
"safety_class": "non_suppressible",
"source": "DecisionActions",
"created_at": "2026-03-01T14:30:00+00:00"
}
]
},
"letta": {
"connected": false,
"officer_agents": 0
}
}
FieldTypeDescription
signal_capture_enabledbooleanWhether signal capture is active (config flag)
letta_enabledbooleanWhether Letta memory is enabled (config flag)
signals.totalintegerTotal number of signal events in the database
signals.by_categoryobjectSignal count per category (judgment, preference, behavioral)
signals.recentarrayLast 10 signals ordered by creation time (most recent first)
signals.recent[].signal_typestringThe signal type identifier
signals.recent[].categorystringSignal category
signals.recent[].safety_classstringSafety classification
signals.recent[].sourcestringSource UI component
signals.recent[].created_atstringISO 8601 timestamp
letta.connectedbooleanWhether the Letta client is connected and functional
letta.officer_agentsintegerNumber of officer agents currently cached

Example

curl http://localhost:8002/api/memory/status
info

When the database is unreachable, the response still returns 200 with signals.total = 0, signals.by_category = {}, and signals.recent = []. This is by design -- the status endpoint is intended for debugging and demos and should never fail.


Memory Reset

POST /api/memory/reset

Resets all compliance memory state. This endpoint is intended for demo showcases where the operator needs to clear state between demonstrations.

Clears:

  1. All rows from the signal_events PostgreSQL table
  2. The in-memory Letta agent cache (does not delete Letta agents from the server)

Response 200

{
"status": "reset_complete",
"cleared": {
"signal_events": 42,
"letta_agents": 2
}
}
FieldTypeDescription
statusstringAlways "reset_complete"
cleared.signal_eventsintegerNumber of signal events deleted from PostgreSQL
cleared.letta_agentsintegerNumber of officer agent entries cleared from the in-memory cache

Example

curl -X POST http://localhost:8002/api/memory/reset
caution

This endpoint deletes all signal event data from the database. It is intended for demo and showcase purposes only. In production, consider implementing soft-delete or archival instead of hard deletion.


Case Memory

GET /api/memory/case/{workflow_id}

Returns the complete memory story for a specific case: all signals, AI context used during investigation, and sync status.

Path Parameters

ParameterTypeDescription
workflow_idstringThe workflow/case identifier

Response 200

{
"workflow_id": "wf_abc123",
"signals": {
"total": 5,
"by_category": {
"judgment": 3,
"preference": 2
},
"items": [
{
"signal_type": "case_approved",
"category": "judgment",
"safety_class": "non_suppressible",
"source": "DecisionActions",
"letta_synced": true,
"created_at": "2026-03-01T14:30:00+00:00"
}
]
},
"ai_context": {
"rules_applied": 3,
"passages_matched": 5,
"confidence_at_review": null,
"synthesis_summary": "Company has clean KBO records...",
"model_used": "openai/gpt-4o"
},
"learned": {
"total_captured": 5,
"synced_to_letta": 4,
"pending_sync": 1,
"categories": {
"judgment": 3,
"preference": 2
}
}
}
FieldTypeDescription
workflow_idstringThe queried workflow ID
signals.totalintegerTotal signals for this case
signals.by_categoryobjectCategory breakdown
signals.itemsarrayAll signals in chronological order
signals.items[].letta_syncedbooleanWhether this signal has been synced to Letta
ai_context.rules_appliedintegerNumber of learned rules from learned_procedures block
ai_context.passages_matchedintegerNumber of archival passages matching this case
ai_context.synthesis_summarystringLast synthesis agent output summary (truncated to 500 chars)
ai_context.model_usedstringLLM model used for the last synthesis
learned.total_capturedintegerTotal signals captured
learned.synced_to_lettaintegerSignals successfully ingested to Letta
learned.pending_syncintegerSignals awaiting Letta ingestion
learned.categoriesobjectPer-category signal breakdown

Example

curl http://localhost:8002/api/memory/case/wf_abc123

Read Memory Block

GET /api/memory/blocks/{officer_id}/{label}

Read a Letta memory block for the specified officer. Memory blocks are structured JSON data that persist officer profile, preferences, learned rules, and company context.

Path Parameters

ParameterTypeDescription
officer_idstringOfficer identifier (e.g., default)
labelstringBlock label. Must be one of: active_preferences, learned_procedures, officer_profile

Response 200

{
"officer_id": "default",
"label": "learned_procedures",
"value": {
"rules": [
"Belgian BVs always need gazette verification",
"PSP merchants with crypto exposure require EDD"
],
"last_updated": "2026-03-01T14:30:00+00:00",
"consolidation_count": 0
}
}
FieldTypeDescription
officer_idstringThe queried officer
labelstringBlock label
valueobject or nullParsed JSON content of the block, or null if the block has not been initialized

Example

curl http://localhost:8002/api/memory/blocks/default/learned_procedures

Error Responses

StatusCondition
400Invalid block label (not in allowed set)
tip

The value field returns null when Letta is disabled or the officer agent has not been provisioned yet. This is the expected default state.


Update Memory Block

PUT /api/memory/blocks/{officer_id}/{label}

Update a Letta memory block with new JSON content. This overwrites the entire block value.

Path Parameters

ParameterTypeDescription
officer_idstringOfficer identifier (e.g., default)
labelstringBlock label. Must be one of: active_preferences, learned_procedures, officer_profile

Request Body

{
"value": {
"rules": [
"Belgian BVs always need gazette verification",
"PSP merchants with crypto exposure require EDD",
"Always verify LinkedIn profiles for directors"
],
"last_updated": "2026-03-02T10:00:00+00:00",
"consolidation_count": 0
}
}
FieldTypeRequiredDescription
valueobjectYesThe new block content as a JSON object

Response 200

{
"status": "updated",
"officer_id": "default",
"label": "learned_procedures"
}

Error Responses

StatusCondition
400Invalid block label
503Memory service unavailable (Letta disabled or connection error)

Example

curl -X PUT http://localhost:8002/api/memory/blocks/default/learned_procedures \
-H "Content-Type: application/json" \
-d '{
"value": {
"rules": ["Always require gazette check for Belgian companies"],
"last_updated": "2026-03-02T10:00:00+00:00",
"consolidation_count": 0
}
}'

List Passages

GET /api/memory/passages/{officer_id}

List archival memory passages for the specified officer, optionally filtered by tag.

Path Parameters

ParameterTypeDescription
officer_idstringOfficer identifier (e.g., default)

Query Parameters

ParameterTypeDefaultDescription
tagstring--Optional tag filter (e.g., category:judgment)
limitinteger50Maximum passages to return (capped at 100)

Response 200

{
"officer_id": "default",
"passages": [
{
"id": "passage_abc123",
"text": "Officer approved case demo-001 (BE, psp_merchant_onboarding). Reason: Clean investigation.",
"tags": ["signal:case_approved", "category:judgment", "safety:non_suppressible", "country:BE"],
"created_at": "2026-03-01T14:30:00+00:00"
}
],
"count": 1
}
FieldTypeDescription
officer_idstringThe queried officer
passagesarrayList of passage objects
passages[].idstringUnique passage identifier (used for deletion)
passages[].textstringPassage content (narrative text)
passages[].tagsarrayStructural tags for filtering
passages[].created_atstringISO 8601 creation timestamp
countintegerNumber of passages returned

Example

# List all passages
curl http://localhost:8002/api/memory/passages/default

# Filter by tag
curl "http://localhost:8002/api/memory/passages/default?tag=category:judgment&limit=20"

Search Passages

POST /api/memory/passages/{officer_id}/search

Perform semantic search over the officer's archival memory passages. Uses Letta's vector similarity search to find passages relevant to the query.

Path Parameters

ParameterTypeDescription
officer_idstringOfficer identifier (e.g., default)

Request Body

{
"query": "Belgian PSP merchant cases with sanctions risk",
"tags": ["category:judgment"]
}
FieldTypeRequiredDescription
querystringYesNatural language search query (must not be empty)
tagsarrayNoOptional tag filters to narrow results

Response 200

{
"officer_id": "default",
"passages": [
{
"id": "passage_xyz789",
"text": "Officer rejected case demo-003 (NL, psp_merchant_onboarding). Reason: Sanctions match confirmed.",
"tags": ["signal:case_rejected", "category:judgment", "safety:non_suppressible", "country:NL"],
"created_at": "2026-03-01T12:00:00+00:00"
}
],
"count": 1
}

Error Responses

StatusCondition
400Empty or whitespace-only query

Example

curl -X POST http://localhost:8002/api/memory/passages/default/search \
-H "Content-Type: application/json" \
-d '{"query": "sanctions match", "tags": ["category:judgment"]}'
tip

Semantic search returns passages ranked by vector similarity. It finds conceptually related passages even when exact keywords do not match. For example, searching "shell company risk" may return passages about "complex multi-jurisdictional ownership structures."


Delete Passage

DELETE /api/memory/passages/{officer_id}/{passage_id}

Delete a specific archival memory passage. This permanently removes the passage from Letta's archival memory.

Path Parameters

ParameterTypeDescription
officer_idstringOfficer identifier (e.g., default)
passage_idstringThe unique passage ID (obtained from list or search)

Response 200

{
"status": "deleted",
"passage_id": "passage_abc123"
}

Error Responses

StatusCondition
503Memory service unavailable (Letta disabled or connection error)

Example

curl -X DELETE http://localhost:8002/api/memory/passages/default/passage_abc123
warning

Passage deletion is permanent. There is no undo. In the Memory Admin UI, delete buttons are hidden by default and appear on hover to prevent accidental deletions.


Confidence Score

GET /api/memory/confidence/{officer_id}

Calculate the memory confidence score for an officer, optionally scoped to a specific case type (template + country). The score reflects how much the system has learned about the officer's decision patterns for similar cases.

Path Parameters

ParameterTypeDescription
officer_idstringOfficer identifier (e.g., default)

Query Parameters

ParameterTypeDefaultDescription
template_idstring""Scope to a specific template (e.g., psp_merchant_onboarding)
countrystring""Scope to a specific country code (e.g., BE)

Response 200

{
"score": 55,
"level": "learning",
"label": "I'm building experience with this type of case.",
"rules_count": 3,
"relevant_passages": 5
}
FieldTypeDescription
scoreintegerConfidence score from 0 to 100
levelstringMaturity level: novice (0-29), learning (30-69), or experienced (70-100)
labelstringHuman-readable confidence description
rules_countintegerNumber of rules in the learned_procedures block
relevant_passagesintegerNumber of archival passages matching the case type

Scoring Formula

The score combines two factors:

  • Rules contribution (max 40%): min(rules_count * 10, 40) -- 4 rules reaches the cap
  • Passages contribution (max 60%): min(relevant_passages * 3, 60) -- 20 passages reaches the cap

When template_id or country are provided, only passages matching those tags are counted. Without filters, all passages contribute.

Example

# Global confidence
curl http://localhost:8002/api/memory/confidence/default

# Scoped to Belgian PSP cases
curl "http://localhost:8002/api/memory/confidence/default?template_id=psp_merchant_onboarding&country=BE"
info

When Letta is disabled, the endpoint returns score: 0, level: "novice", and label: "Memory system disabled". This allows the frontend to always render the confidence meter without conditional logic.


Get Company Context

GET /api/memory/company-context

Returns the company context configuration from the Letta memory block. Used for tenant onboarding and organizational policy display.

Query Parameters

ParameterTypeDefaultDescription
officer_idstring"default"Officer identifier

Response 200

{
"company_context": {
"status": "configured",
"company_type": "Payment Service Provider",
"jurisdiction": "Belgium",
"primary_regulator": "NBB (National Bank of Belgium)",
"services": ["merchant acquiring", "payment processing"],
"regulatory_framework": "PSD2, AML6",
"customer_segments": ["e-commerce merchants", "SaaS platforms"],
"risk_appetite": "moderate",
"prohibited_jurisdictions": ["DPRK", "Iran", "Syria"],
"additional_notes": ""
}
}

When the company context has not been configured, the response returns {"company_context": {"status": "not_configured"}}.

Example

curl http://localhost:8002/api/memory/company-context

Update Company Context

PUT /api/memory/company-context

Update the company context configuration. Marks the context as configured and persists it to the Letta memory block.

Query Parameters

ParameterTypeDefaultDescription
officer_idstring"default"Officer identifier

Request Body

{
"company_type": "Payment Service Provider",
"jurisdiction": "Belgium",
"primary_regulator": "NBB (National Bank of Belgium)",
"services": ["merchant acquiring", "payment processing"],
"regulatory_framework": "PSD2, AML6",
"customer_segments": ["e-commerce merchants"],
"risk_appetite": "moderate",
"prohibited_jurisdictions": ["DPRK", "Iran", "Syria"],
"additional_notes": ""
}
FieldTypeRequiredDefaultDescription
company_typestringNo""Type of company (e.g., PSP, Bank, Consultancy)
jurisdictionstringNo""Primary jurisdiction
primary_regulatorstringNo""Primary regulatory body
servicesarrayNo[]List of services offered
regulatory_frameworkstringNo""Applicable regulations
customer_segmentsarrayNo[]Target customer segments
risk_appetitestringNo""Risk appetite level
prohibited_jurisdictionsarrayNo[]Jurisdictions the company does not operate in
additional_notesstringNo""Free-form notes

Response 200

{
"success": true
}

Error Responses

StatusCondition
503Memory service unavailable

Example

curl -X PUT http://localhost:8002/api/memory/company-context \
-H "Content-Type: application/json" \
-d '{
"company_type": "Payment Service Provider",
"jurisdiction": "Belgium",
"primary_regulator": "NBB",
"services": ["merchant acquiring"],
"risk_appetite": "moderate"
}'