Quorentra

Quorentra CRM Building the CRM Context Layer: Building from Zero — Part 25

Series: Building Quorentra CRM from Zero — A Modular, ChatGPT-Native AI CRM

Building unified CRM search, entity resolution, permission-aware customer context retrieval, bounded context packages, provenance, and grounded ChatGPT answers.

Building the CRM Context Layer: Building from Zero — Part 25 — Draft
Building the CRM Context Layer: Building from Zero — Part 25 — Draft

1. Introduction

Quorentra has reached an important point in its modular development.

We now have six core operational CRM domains (Part 24):

Company
Contact
Opportunity
Activity
Task
Meeting

Together they represent most of the structured information required for a useful CRM MVP.

Company
→ Who are we doing business with?
Contact
→ Who are we talking to?
Opportunity
→ What are we trying to sell?
Activity
→ What happened?
Task
→ What needs to happen?
Meeting
→ What customer interaction is scheduled?

Each module is independently useful.

But ChatGPT rarely needs information from only one module.

Consider the question:

What’s happening with Adventure Works?

There is no single database record containing the answer.

The answer may require:

Company
+
Contacts
+
Opportunities
+
Recent Activities
+
Open Tasks
+
Upcoming Meetings

ChatGPT could theoretically call six different tools.

But that creates several problems.

The model must decide:

Which tools?
Which filters?
Which records?
Which time windows?
Which result limits?
Which relationships?

More importantly, we would be delegating too much retrieval logic to the language model.

That is not the architecture we want.

Instead, Quorentra needs a server-owned:

CRM Context Layer

Its responsibility is to transform a CRM entity into a bounded, permission-aware, tenant-isolated package of relevant business context.

This becomes the evidence layer between the CRM database and ChatGPT.


2. Our Target Interaction

The user asks:

What’s happening with Adventure Works?

ChatGPT identifies:

Intent:
Retrieve CRM Context
Entity Reference:
Adventure Works

It calls something conceptually similar to:

get_crm_context(
entity_reference = "Adventure Works"
)

Quorentra resolves:

Adventure Works
Company

and builds:

CRM Context
├── Company
├── Key Contacts
├── Open Opportunities
├── Recent Activities
├── Open Tasks
└── Upcoming Meetings

ChatGPT receives structured evidence such as:

Adventure Works
Contacts
Sarah Johnson
Lisa Chen
Open Opportunities
Azure Migration
€90,000
Proposal Stage
Recent Activity
Customer requested revised pricing.
Open Tasks
Send revised proposal
Due Friday
Upcoming Meetings
Proposal Review
Tuesday at 14:00

ChatGPT can then answer:

Adventure Works currently has an active €90,000 Azure Migration opportunity in the Proposal stage. Sarah Johnson recently requested revised pricing, and you have an open task to send the revised proposal by Friday. A proposal review meeting is scheduled for Tuesday at 14:00.

Every important statement comes from authoritative CRM evidence.

That is the goal.


3. Why This Layer Matters

Without a Context Layer, the architecture looks like:

                    ChatGPT
                       │
       ┌───────────────┼───────────────┐
       │               │               │
       ▼               ▼               ▼
Companies          Contacts      Opportunities
       │               │               │
       ▼               ▼               ▼
Activities          Tasks          Meetings

ChatGPT becomes responsible for orchestrating low-level retrieval.

With a Context Layer:

ChatGPT
CRM Context Layer
├── Companies
├── Contacts
├── Opportunities
├── Activities
├── Tasks
└── Meetings

The backend owns retrieval semantics.

That is safer and more predictable.


4. ChatGPT Should Reason Over Evidence

A useful design principle for Quorentra is:

ChatGPT should reason over CRM evidence, not construct CRM evidence.

The distinction is important.

ChatGPT can decide:

What does this information mean?
What is important?
How should it be explained?

Quorentra decides:

Which tenant?
Which entity?
Which records?
Which permissions?
Which relationships?
Which time ranges?
Which result limits?

This separation gives us grounded AI behavior.


5. The Context Layer Is Not RAG Yet

It may sound similar to Retrieval-Augmented Generation.

But Part 25 is not yet building:

Embeddings
Vector Search
Semantic Chunking
pgvector
Document Retrieval
Knowledge Base RAG

Those come later.

For now, we are retrieving structured relational CRM data.

Think of this as:

Structured CRM Retrieval

rather than:

Semantic Knowledge Retrieval

Both will eventually work together.


6. Starting Checkpoint

Before implementing Part 25, verify Part 24.

Run:

cd backend
python -m pytest

Then:

cd ..\chatgpt-ui
npm run build

Test:

Schedule a meeting with Sarah Johnson at Adventure Works next Tuesday at 2 PM to review the Azure migration proposal.

Verify:

Meeting Draft
Participant Resolution
CRM Relationship Resolution
Time Resolution
Conflict Detection
Preparation
Confirmation
Meeting Creation

The Context Layer will reuse the same entity resolution infrastructure.


7. New Permissions

Introduce:

crm.search
crm.context.read

These are intentionally broader orchestration permissions.

They do not replace domain permissions.

For example:

crm.context.read
+
companies.read
+
contacts.read
+
opportunities.read
+
activities.read
+
tasks.read
+
meetings.read

determines what can appear in a context package.


8. Why Domain Permissions Still Matter

Suppose a user has:

companies.read
contacts.read

but does not have:

opportunities.read

Then:

What’s happening with Adventure Works?

must not expose Opportunity information.

The context package might contain:

Company
Contacts
Opportunities
omitted
Activities
depends on permission
Tasks
depends on permission
Meetings
depends on permission

The Context Layer must never bypass domain authorization.


9. Permission Intersection

Think of context retrieval as:

Requested Context
Tenant Scope
User Permissions
Entity Relationships
=
Returned Context

This is a critical architectural rule.


10. Unified CRM Search

Before we can build context, ChatGPT needs a consistent way to find CRM entities.

Introduce:

search_crm

This searches across supported entity types.


11. Searchable Entity Types

For the MVP:

Company
Contact
Opportunity
Task
Meeting

Activities can also be searchable, although their primary use is usually timeline retrieval.

Conceptually:

class CRMEntityType(str, Enum):
COMPANY = "company"
CONTACT = "contact"
OPPORTUNITY = "opportunity"
ACTIVITY = "activity"
TASK = "task"
MEETING = "meeting"

12. Unified Search Input

Conceptually:

class SearchCRMInput(BaseModel):
query: str
entity_types: list[CRMEntityType] | None = None
limit: int = 10

The model provides:

query
optional entity types

The server provides:

tenant scope
permission filtering
ranking
result limits

13. Example Search

User:

Find Adventure Works.

ChatGPT calls:

search_crm(
query = "Adventure Works"
)

Quorentra may return:

1. Company
Adventure Works
2. Opportunity
Adventure Works Azure Migration
3. Task
Send Adventure Works revised proposal

Each result has a type.


14. Structured Search Results

Conceptually:

{
"results": [
{
"entity_type": "company",
"entity_id": "...",
"display_name": "Adventure Works",
"secondary_text": "Technology Services",
"score": 0.98
},
{
"entity_type": "opportunity",
"entity_id": "...",
"display_name": "Azure Migration",
"secondary_text": "Adventure Works",
"score": 0.84
}
]
}

ChatGPT does not need to know database table details.


15. Search Result IDs Are Authoritative

The model searches using human references:

Adventure Works
Sarah Johnson
Azure Migration

The server returns authoritative:

entity_type
entity_id

Subsequent context calls should use those resolved identifiers where possible.


16. Search Is Tenant-Scoped

Every query automatically includes:

organization_id =
TenantContext.organization_id

There is no global CRM search.

Even if another tenant contains an identical:

Adventure Works

it must never appear.


17. Search Is Permission-Aware

If the user lacks:

tasks.read

Task results should not appear.

If the user lacks:

meetings.read

Meeting results should not appear.

Search itself must respect the same authorization model as direct domain reads.


18. Search Ranking

For the MVP, ranking can remain simple.

Use signals such as:

Exact name match
Prefix match
Substring match
Entity importance
Recency

For example:

Adventure Works

exact Company match should rank above:

Call Adventure Works Friday

Task title substring match.


19. Do Not Start with Vector Search

We do not need embeddings to search:

Adventure Works
Sarah Johnson
Azure Migration

Relational and textual matching is sufficient for the MVP.

Vector search becomes useful later for:

documents
notes
long activity content
knowledge retrieval
semantic questions

Keep the architecture modular.


20. Define a Search Result Contract

Conceptually:

class CRMSearchResult(BaseModel):
entity_type: CRMEntityType
entity_id: UUID
display_name: str
secondary_text: str | None
score: float

This gives ChatGPT a consistent search surface.


21. Search Result Limits

Do not return hundreds of matches.

Default:

10

Maximum:

50

or another conservative server-controlled value.

The model should receive only useful candidates.


22. Ambiguous Search

Suppose:

Adventure Works Europe
Adventure Works Consulting
Adventure Works Cloud

The user asks:

What’s happening with Adventure Works?

Do not choose randomly.

Return candidates.

ChatGPT can ask:

Which Adventure Works do you mean?


23. Context Begins with Resolution

The Context Layer should normally operate on a resolved entity.

Flow:

Natural Language Reference
search_crm
Resolved Entity
get_crm_context

This cleanly separates:

Finding

from:

Context Building

24. Introduce get_crm_context

This becomes one of Quorentra’s most important ChatGPT tools.

Conceptually:

class GetCRMContextInput(BaseModel):
entity_type: CRMEntityType
entity_id: UUID

Later optional context controls can be added.

For the MVP, server defaults are preferable.


25. Why Use Resolved IDs?

Once Quorentra has resolved:

Adventure Works
company_id

do not make every subsequent query repeat fuzzy matching.

Use the authoritative ID.

This reduces ambiguity and unnecessary database work.


26. Context Types

We will initially support:

Company Context
Contact Context
Opportunity Context

These are the most useful conversational anchors.

Task and Meeting context can still be retrieved through their direct read tools.


27. Company Context

A Company context package may contain:

Company
├── Core Company Data
├── Contacts
├── Open Opportunities
├── Recent Activities
├── Open Tasks
└── Upcoming Meetings

This answers broad questions about a customer.


28. Contact Context

A Contact context package may contain:

Contact
├── Company
├── Relevant Opportunities
├── Recent Activities
├── Open Tasks
└── Upcoming Meetings

This answers:

What’s going on with Sarah Johnson?


29. Opportunity Context

An Opportunity context package may contain:

Opportunity
├── Company
├── Related Contacts
├── Recent Activities
├── Open Tasks
└── Upcoming Meetings

This answers:

What’s happening with the Azure Migration deal?


30. Define the Company Context Contract

Conceptually:

class CompanyCRMContext(BaseModel):
company: CompanySummary
contacts: list[ContactSummary]
opportunities: list[OpportunitySummary]
recent_activities: list[ActivitySummary]
open_tasks: list[TaskSummary]
upcoming_meetings: list[MeetingSummary]
metadata: CRMContextMetadata

The context package is structured.

It is not a giant text blob.


31. Why Structured Context?

Structured context lets ChatGPT distinguish:

Company fact
Contact fact
Opportunity fact
Activity
Task
Meeting

instead of trying to parse an unstructured server-generated paragraph.

This improves grounding.


32. Context Summaries Versus Full Records

Do not return full ORM models.

Instead define purpose-built summaries.

For example:

class OpportunitySummary(BaseModel):
id: UUID
name: str
stage: str
amount: Decimal | None
probability: int | None
expected_close_date: date | None

Only expose what the context needs.


33. Contact Summary

Conceptually:

class ContactSummary(BaseModel):
id: UUID
full_name: str
job_title: str | None
company_id: UUID

Avoid returning unnecessary personal data.


34. Activity Summary

Conceptually:

class ActivitySummary(BaseModel):
id: UUID
activity_type: str
occurred_at: datetime
subject: str | None
summary: str | None

Do not automatically include huge note bodies.


35. Task Summary

Conceptually:

class TaskSummary(BaseModel):
id: UUID
title: str
priority: str
due_at: datetime
assigned_to_name: str

Only open Tasks belong in the default Company context.


36. Meeting Summary

Conceptually:

class MeetingSummary(BaseModel):
id: UUID
title: str
start_at: datetime
end_at: datetime
participant_names: list[str]

Only future scheduled Meetings belong in the default context.


37. Context Is Not a Database Dump

This is extremely important.

Avoid:

Company
+
all Contacts
+
all Opportunities
+
all Activities ever
+
all Tasks ever
+
all Meetings ever

A large customer could generate thousands of records.

That is not useful context.


38. Context Must Be Bounded

For example:

Contacts:
10
Open Opportunities:
10
Recent Activities:
20
Open Tasks:
20
Upcoming Meetings:
10

These are example limits, not immutable rules.

The key requirement is:

Every collection has a bounded server-controlled maximum.


39. Context Budget

Introduce the concept of a:

Context Budget

The Context Builder must decide how much CRM evidence to return.

For the MVP, this can be record-count based.

Later it may become token-aware.


40. Example Context Budget

DEFAULT_CONTEXT_LIMITS = {
"contacts": 10,
"opportunities": 10,
"activities": 20,
"tasks": 20,
"meetings": 10,
}

The model should not arbitrarily increase these values beyond server limits.


41. Why Context Budgets Matter

Without them:

Large Customer
Thousands of Activities
Huge Tool Response
Large ChatGPT Context
Higher Cost
Lower Signal

With budgets:

Large Customer
Relevant Recent Evidence
Bounded Context
Better Reasoning

42. Activity Recency Window

For the default Company context, perhaps retrieve:

last 90 days

or:

latest 20 Activities

whichever limit is reached first.

The exact policy should be configurable.


43. Tasks Are State-Oriented

For default context:

status = open

is usually more relevant than completed historical Tasks.

Therefore:

Open Tasks

belong in the context.

Completed Tasks remain available through Task search/history.


44. Meetings Are Future-Oriented

Default Meeting context should retrieve:

status = scheduled
AND
start_at >= now

ordered by:

start_at ASC

Historical Meetings are represented through Activities or explicit Meeting history queries.


45. Opportunities Are State-Oriented

Default Company context should usually prioritize:

Open Opportunities

rather than closed historical deals.

Later we can optionally include recent wins/losses.

For the MVP, focus on current work.


46. Contacts Need Prioritization

A Company may have 200 Contacts.

Returning arbitrary Contacts is not useful.

For the MVP, use deterministic ranking such as:

Contacts referenced by open Opportunities
Contacts referenced by recent Activities
Contacts referenced by open Tasks
Contacts referenced by upcoming Meetings
Recently updated Contacts

This produces more useful context.


47. Key Contacts Are Derived

Do not necessarily store:

is_key_contact

yet.

Instead derive contextual importance from CRM relationships.

Later the domain can support explicit stakeholder roles.


48. Company Context Builder

Introduce:

build_company_context

as an internal application service.

Conceptually:

async def build_company_context(
*,
company_id: UUID,
tenant_context: TenantContext,
actor: UserContext,
) -> CompanyCRMContext:
...

This should not depend on ChatGPT-specific logic.


49. Context Builder Flow

Load Company
Verify Tenant
Verify crm.context.read
Determine Domain Permissions
Load Permitted Contacts
Load Permitted Opportunities
Load Permitted Recent Activities
Load Permitted Open Tasks
Load Permitted Upcoming Meetings
Apply Limits
Rank Results
Build Metadata
Return Structured Context

50. Context Retrieval Is Read-Only

Part 25 introduces no new business mutation.

Therefore:

get_crm_context

must never:

create
update
delete
complete
cancel

anything.

It is purely retrieval.


51. Context Metadata

Every context package should contain metadata.

Conceptually:

class CRMContextMetadata(BaseModel):
generated_at: datetime
organization_id: UUID
root_entity_type: CRMEntityType
root_entity_id: UUID
limits: dict[str, int]
truncated_sections: list[str]
provenance: list["ContextProvenance"]

This makes the package inspectable.


52. Do We Expose Organization ID to ChatGPT?

Internally, metadata may contain it.

But tool response schemas should expose only what is actually useful.

The important point is that tenant scope is recorded and enforced server-side.

Do not make ChatGPT responsible for supplying it.


53. Context Provenance

Every context item should be traceable to its authoritative CRM entity.

For example:

{
"entity_type": "activity",
"entity_id": "...",
"source": "crm_database"
}

This is the beginning of evidence provenance.


54. Why Provenance Matters

Suppose ChatGPT says:

Sarah requested revised pricing.

We should be able to trace that statement to:

Activity
ID: ...
Occurred: 2 Aug 2026

This becomes essential as AI features become more sophisticated.


55. Context Item References

A useful pattern is to give each context item a stable reference.

For example:

company:cmp_001
contact:con_004
opportunity:opp_012
activity:act_203
task:tsk_077
meeting:mtg_031

These are conceptual examples.

Internally, UUIDs can remain authoritative.


56. Evidence IDs

We can also generate context-local evidence labels:

E1
E2
E3
E4

Example:

E1 — Azure Migration Opportunity
E2 — Customer requested revised pricing
E3 — Send revised proposal Task
E4 — Proposal Review Meeting

ChatGPT can use these when reasoning.


57. Do Not Expose Internal Database Complexity

ChatGPT should not need:

SQLAlchemy relationship names
join tables
foreign-key structures
repository implementation details

The Context Layer abstracts those details.


58. Example Company Context Response

Conceptually:

{
"root": {
"type": "company",
"id": "...",
"name": "Adventure Works"
},
"contacts": [
{
"id": "...",
"name": "Sarah Johnson",
"job_title": "IT Director"
}
],
"opportunities": [
{
"id": "...",
"name": "Azure Migration",
"stage": "proposal",
"amount": 90000,
"currency": "EUR"
}
],
"recent_activities": [
{
"id": "...",
"type": "call",
"occurred_at": "2026-08-02T...",
"summary": "Customer requested revised pricing."
}
],
"open_tasks": [
{
"id": "...",
"title": "Send revised proposal",
"due_at": "2026-08-07T...",
"priority": "high"
}
],
"upcoming_meetings": [
{
"id": "...",
"title": "Azure Migration Proposal Review",
"start_at": "2026-08-11T...",
"participants": [
"Sarah Johnson"
]
}
],
"metadata": {
"generated_at": "...",
"truncated_sections": []
}
}

This is excellent input for grounded reasoning.


59. Context Ordering

Collections should have deterministic ordering.

For example:

Contacts
→ contextual importance DESC
Opportunities
→ open first
→ expected close date ASC
Activities
→ occurred_at DESC
Tasks
→ due_at ASC
Meetings
→ start_at ASC

Deterministic context is easier to test.


60. Contact Context Builder

Introduce:

build_contact_context

The root becomes:

Contact

and related evidence includes:

Company
Relevant Opportunities
Recent Activities
Open Tasks
Upcoming Meetings

61. Relevant Opportunities for a Contact

Our current domain may not yet have explicit Opportunity stakeholder relationships.

Therefore only include Opportunities where the relationship can be supported.

Possible evidence:

Contact referenced by Opportunity
Contact referenced by Opportunity Activities
Contact referenced by Opportunity Tasks
Contact participating in Opportunity Meetings

Do not fabricate relationships.


62. Opportunity Context Builder

Introduce:

build_opportunity_context

This package contains:

Opportunity
Company
Relevant Contacts
Recent Activities
Open Tasks
Upcoming Meetings

This will become especially important for future Opportunity Intelligence.


63. Opportunity Context Example

User:

What’s happening with the Azure Migration deal?

Quorentra returns:

Azure Migration
Company
Adventure Works
Stage
Proposal
Value
€90,000
Probability
60%
Recent Activity
Customer requested revised pricing.
Open Task
Send revised proposal Friday.
Upcoming Meeting
Proposal Review Tuesday.

ChatGPT now has a coherent evidence package.


64. Grounded Answers

We can now establish another important principle:

Every CRM-specific factual claim should be supportable by returned CRM context.

ChatGPT may summarize or interpret.

But it should not invent missing CRM facts.


65. Example Grounded Answer

Evidence:

Opportunity:
€90,000
Proposal
60%
Activity:
Customer requested revised pricing.
Task:
Send revised proposal Friday.
Meeting:
Proposal review Tuesday.

Good answer:

The Azure Migration opportunity is currently in the Proposal stage at €90,000 with a 60% probability. The customer recently requested revised pricing, which is due to be sent Friday, and a proposal review meeting is scheduled for Tuesday.

Every statement is grounded.


66. Unsupported Answer

Bad answer:

Adventure Works is highly likely to sign because Sarah is enthusiastic about the proposal.

Why?

Because:

highly likely
enthusiastic

are not present in the evidence.

Later AI intelligence may infer risk or sentiment.

But those must be clearly identified as analysis, not CRM fact.


67. Facts Versus Analysis

This distinction will become critical.

We should conceptually separate:

CRM Facts

from:

AI Analysis

For now, Part 25 focuses on facts.

Later:

CRM Context
AI Analysis

can generate recommendations.


68. Context Completeness

Sometimes context is truncated.

Suppose Adventure Works has:

87 Activities

but we return:

20

Metadata should indicate:

activities_truncated = true

ChatGPT then knows it does not have the entire history.


69. Why Truncation Metadata Matters

Without it, ChatGPT might say:

These are all interactions with Adventure Works.

That would be false.

With metadata, it can say:

Based on the most recent activity…

This is more precise.


70. Context Time Range

Metadata can also expose:

activity_window_start
activity_window_end

when useful.

For example:

Recent Activities:
last 90 days

This improves interpretability.


71. Context Freshness

Every package should include:

generated_at

Context is a snapshot.

It should not be treated as permanently current.


72. Do Not Persist Context Packages Initially

For the MVP:

Request
Build Context
Return Context
Discard

Do not create a new persistent Context table yet.

The authoritative data already exists in the domain tables.


73. Why Not Cache Yet?

Caching introduces:

Invalidation
Staleness
Permission Changes
Tenant Changes
Record Updates

Unless performance requires it, avoid premature caching.

Build correctly first.


74. Query Efficiency

Although context is assembled from multiple domains, avoid classic N+1 query problems.

Use deliberate repository queries.

For example:

1 Company query
1 Contacts query
1 Opportunities query
1 Activities query
1 Tasks query
1 Meetings query

rather than hundreds of per-record queries.


75. Repository Boundaries

Each domain repository remains responsible for its own data.

For example:

CompanyRepository
ContactRepository
OpportunityRepository
ActivityRepository
TaskRepository
MeetingRepository

The Context Builder orchestrates them.

It does not replace them.


76. Application Service Composition

Conceptually:

CRMContextService
├── CompanyRepository
├── ContactRepository
├── OpportunityRepository
├── ActivityRepository
├── TaskRepository
└── MeetingRepository

This is a clean application-layer orchestration service.


77. Avoid Cross-Domain ORM Magic

Do not create one enormous ORM relationship graph and serialize everything recursively.

That leads to:

Unbounded loading
Circular relationships
Unexpected queries
Large responses
Security mistakes

Explicit context queries are safer.


78. Context Policy

Introduce a configuration object:

class CRMContextPolicy:
contact_limit = 10
opportunity_limit = 10
activity_limit = 20
task_limit = 20
meeting_limit = 10
activity_lookback_days = 90

Later this can vary by context type.


79. Company Context Policy

For example:

Company
Contacts:
10 most relevant
Opportunities:
10 open
Activities:
20 most recent within 90 days
Tasks:
20 open
Meetings:
10 upcoming

80. Opportunity Context Policy

An Opportunity may need:

Contacts:
10 relevant
Activities:
30 recent
Tasks:
20 open
Meetings:
10 upcoming

Different context types can have different policies.


81. Context Sections

Each section should indicate whether it was:

included
empty
not_permitted
truncated

This is more informative than simply omitting everything.


82. Example Section Metadata

{
"section": "opportunities",
"status": "not_permitted",
"returned_count": 0
}

This can remain internal if exposing permission details is undesirable.

The important thing is that the Context Builder knows why data is absent.


83. Empty Versus Unauthorized

These are not the same.

No open Opportunities

means something different from:

User cannot read Opportunities

The backend must preserve that distinction.

Whether ChatGPT sees it depends on the response policy.


84. Data Minimization

Only return data necessary for the conversational use case.

For example, Company context may not need:

internal database timestamps
technical audit fields
password-related fields
raw authentication identifiers
internal integration secrets

Obviously those must never enter ChatGPT context.


85. Sensitive Fields

Even within CRM records, some fields may require future field-level policies.

Examples:

personal phone numbers
private notes
legal information
commercially restricted data

The MVP can begin with domain-level permissions, but the Context Layer should be designed so field filtering can be added later.


86. Context Sanitization

Introduce a final server-side sanitization step:

Domain Records
Permission Filter
Field Projection
Context Sanitization
Context Package

Do not serialize ORM objects directly.


87. Tool Response Stability

Because ChatGPT depends on these schemas, treat context contracts like APIs.

Avoid casually changing:

field names
entity type names
section meanings
date formats
status semantics

Version them deliberately if needed.


88. ChatGPT Tool Surface

Our read tool surface now begins to look like:

search_crm
get_crm_context
get_company
get_contact
get_opportunity
get_activity
get_task
get_meeting

The first two handle broad conversational retrieval.

The specific tools handle precise detail.


89. Broad Versus Precise Retrieval

User:

What’s happening with Adventure Works?

Use:

get_crm_context

User:

What is Sarah’s email address?

Use:

get_contact

User:

What is the due date of the revised proposal task?

Use:

get_task

Do not retrieve large context packages when a precise read is sufficient.


90. Context Selection Principle

Use:

smallest authoritative retrieval
that fully answers the question

This reduces latency, cost, and unnecessary data exposure.


91. ChatGPT Context Widget

Create:

chatgpt-ui/src/context/
├── CRMContextSummary.tsx
├── CompanyContextCard.tsx
├── ContactContextCard.tsx
└── OpportunityContextCard.tsx

These widgets can provide visual grounding.


92. Company Context Widget

Example:

┌──────────────────────────────────────────────┐
│ ADVENTURE WORKS │
│ │
│ Active Opportunity │
│ Azure Migration │
│ €90,000 · Proposal · 60% │
│ │
│ Recent Activity │
│ Customer requested revised pricing. │
│ │
│ Next Task │
│ Send revised proposal │
│ Friday │
│ │
│ Next Meeting │
│ Proposal Review │
│ Tuesday · 14:00 │
│ │
│ [View Company] [View Opportunity] │
└──────────────────────────────────────────────┘

This gives ChatGPT answers a useful visual companion.


93. Opportunity Context Widget

Example:

┌──────────────────────────────────────────────┐
│ AZURE MIGRATION │
│ Adventure Works │
│ │
│ €90,000 │
│ Proposal │
│ 60% Probability │
│ │
│ Latest Activity │
│ Revised pricing requested │
│ │
│ Open Task │
│ Send revised proposal · Friday │
│ │
│ Upcoming Meeting │
│ Proposal Review · Tuesday 14:00 │
└──────────────────────────────────────────────┘

94. Do Not Let Widgets Become Authoritative

Widgets render server-returned data.

They do not calculate:

Opportunity stage
Task status
Meeting time
Amounts
Probabilities

The backend remains authoritative.


95. Context and Conversation

Suppose the user asks:

What’s happening with Adventure Works?

ChatGPT retrieves the context.

Then the user asks:

When is the meeting?

The conversation may already contain the Meeting context.

But for authoritative answers, we should be careful about stale state.


96. Freshness Strategy

For the MVP:

same immediate conversational turn
→ context can be reused
later authoritative request
→ reread if freshness matters

Especially before any mutation, always reread current state.


97. Context Is Never Mutation Authority

Suppose context says:

Task version = 1

Then the user says:

Complete it.

Do not execute directly from cached conversational context.

The mutation preparation step reloads the Task and verifies current state.

This preserves our architecture.


98. Read Path Versus Write Path

We now have a strong separation:

READ PATH
User
ChatGPT
Search / Context
Authorized CRM Data
Grounded Answer

and:

WRITE PATH
User
ChatGPT
Draft
Validation
Preparation
Confirmation
Execution
Audit

This is one of the most important architectural patterns in Quorentra.


99. Context Error Model

Introduce structured errors such as:

CRM_ENTITY_NOT_FOUND
CRM_ENTITY_AMBIGUOUS
CRM_CONTEXT_FORBIDDEN
CRM_CONTEXT_TYPE_UNSUPPORTED
CRM_CONTEXT_BUILD_FAILED

ChatGPT can respond appropriately.


100. Entity Not Found

User:

What’s happening with Northstar Dynamics?

No match.

Return:

{
"code": "CRM_ENTITY_NOT_FOUND",
"query": "Northstar Dynamics"
}

Do not invent a customer.


101. Ambiguous Entity

Return candidates:

{
"code": "CRM_ENTITY_AMBIGUOUS",
"candidates": [
{
"type": "company",
"id": "...",
"name": "Northstar Dynamics Europe"
},
{
"type": "company",
"id": "...",
"name": "Northstar Dynamics US"
}
]
}

ChatGPT asks for clarification.


102. Unsupported Context Root

If someone requests full CRM context around:

Activity

but the MVP only supports:

Company
Contact
Opportunity

return:

CRM_CONTEXT_TYPE_UNSUPPORTED

Do not silently invent semantics.


103. Search Audit

Searches generally do not need business AuditEvents like mutations do.

But operational logs may record:

search type
tenant
actor
result count
duration

without storing unnecessary query content.


104. Context Access Logging

Because context retrieval may expose multiple CRM records, consider structured access logging.

For the MVP:

context_type
root_entity_id
organization_id
actor_user_id
sections_returned
duration_ms

This is useful for security and diagnostics.


105. Metrics

Useful metrics include:

crm_search_requests_total
crm_search_zero_results_total
crm_search_ambiguous_total
crm_context_requests_total
crm_context_failures_total
crm_context_truncated_total
crm_context_build_duration_ms

Later we can measure which context sections are most useful.


106. Search Performance

Indexes should support common search fields.

For example:

Company.name
Contact.first_name
Contact.last_name
Contact.email
Opportunity.name
Task.title
Meeting.title

Tenant ID should be part of the relevant index strategy.


107. Example Company Index

Conceptually:

CREATE INDEX idx_company_org_name
ON companies (organization_id, name);

Equivalent indexes can support other domain searches.


108. PostgreSQL Text Search

If simple ILIKE becomes insufficient, PostgreSQL gives us several options before vector search:

pg_trgm
GIN indexes
Full-text search

These are excellent tools for structured CRM search.


109. pg_trgm

For fuzzy name matching:

Adventure Wroks

can still potentially match:

Adventure Works

using trigram similarity.

This may be a useful incremental improvement.


110. Keep Search Explainable

For CRM entity resolution, deterministic search is valuable.

We want to understand why:

Adventure Works

matched a particular Company.

This is easier with explicit matching rules than opaque semantic similarity.


111. Context Builder Tests

Test:

Company context
Contact context
Opportunity context
tenant isolation
permission filtering
limits
ordering
truncation
empty sections

112. Unified Search Tests

Verify:

exact Company match
prefix Company match
Contact match
Opportunity match
Task match
Meeting match
multiple entity types
entity type filter
result limit

113. Tenant Isolation Search Test

Tenant A:

Adventure Works

Tenant B:

Adventure Works

User in Tenant A searches.

Expected:

Tenant A result only

114. Permission-Aware Search Test

User lacks:

opportunities.read

Search:

Azure

Expected:

Opportunity results omitted

even if matching Opportunities exist.


115. Company Context Permission Test

User has:

companies.read
contacts.read
tasks.read

but lacks:

opportunities.read
activities.read
meetings.read

Context must contain only permitted sections.


116. Company Context Limit Test

Create:

100 Contacts
50 Opportunities
500 Activities
80 Tasks
30 Meetings

Context must respect configured limits.


117. Activity Ordering Test

Activities returned:

newest
oldest

within the context window.


118. Task Ordering Test

Open Tasks returned:

earliest due
latest due

119. Meeting Ordering Test

Upcoming Meetings returned:

nearest
furthest

120. Opportunity Filtering Test

Default Company context should include:

open Opportunities

and exclude closed historical Opportunities unless policy says otherwise.


121. Activity Window Test

If policy is:

90 days

an Activity from:

120 days ago

should not appear in default context.


122. Truncation Test

If there are:

40 Activities

and limit is:

20

return:

20 Activities
metadata:
activities_truncated = true

123. Empty Context Section Test

Company has no Meetings.

Return:

upcoming_meetings = []

not an invented placeholder Meeting.


124. Context Provenance Test

Every returned item must retain enough information to identify:

entity type
entity ID
source

125. No Mutation Test

Call:

get_crm_context

Verify:

0 database business mutations
0 MutationRequests
0 AuditEvents

unless access auditing is separately implemented.


126. Query Count Test

Prevent accidental N+1 behavior.

For a Company context containing ten Contacts, the system should not perform dozens of repeated queries unnecessarily.

Set performance expectations.


127. Context Serialization Test

Verify ORM-only or sensitive fields never appear in the tool response.

For example:

password hashes
internal secrets
raw security metadata

must never be serialized.


128. Grounding Tests

Given known context, ask ChatGPT:

What’s happening with Adventure Works?

Verify the answer does not claim unsupported:

sentiment
commitment
risk
customer intention
future actions

unless present in evidence.


129. Missing Evidence Test

No Task exists.

ChatGPT must not say:

You need to send the proposal Friday.

unless that obligation is present elsewhere in authoritative context.


130. Truncated Context Language

If Activity history is truncated, responses should prefer language such as:

Based on the recent activity...

rather than:

The complete history shows...

This is an important grounding behavior.


131. Context Refresh Test

Retrieve context.

Modify Opportunity.

Retrieve context again.

Expected:

new authoritative state

because context is built dynamically.


132. Context Before Mutation

Retrieve context showing:

Task open
version 1

Change Task elsewhere.

Then attempt completion.

Mutation preparation must reload the Task rather than trust the earlier context.


133. Context Security Test

Attempt to request:

entity_id

belonging to another tenant.

Expected:

not found / forbidden according to security policy

Never return cross-tenant context.


134. Logging

Useful structured fields:

context_request_id
root_entity_type
root_entity_id
organization_id
actor_user_id
sections_requested
sections_returned
result_counts
truncated_sections
duration_ms

Avoid logging full CRM context payloads.


135. Context Request ID

Introduce:

context_request_id

for operational traceability.

This is not a persistent business entity.

It simply helps correlate:

Tool request
Context Builder
Repository calls
Response

136. Future AI Traceability

Later, an AI recommendation may be associated with:

context_request_id

or a richer evidence snapshot identifier.

This helps answer:

What evidence produced this recommendation?

We are preparing for that future now.


137. Version Update

Part 25 introduces:

Unified CRM Search
CRM Context Layer
Company Context
Contact Context
Opportunity Context
Context Budgets
Permission-Aware Retrieval
Context Provenance
Grounded CRM Answers

Update:

app/core/constants.py

from:

APP_VERSION = "0.11.0"

to:

APP_VERSION = "0.12.0"

138. Quorentra 0.12.0

Our modular MVP now looks like:

Platform
├── FastAPI ✓
├── PostgreSQL ✓
├── SQLAlchemy ✓
└── Alembic ✓
Identity
├── Organizations ✓
├── Users ✓
├── Memberships ✓
├── Authentication ✓
└── JWT ✓
Security
├── TenantContext ✓
├── Tenant Isolation ✓
├── RBAC ✓
├── Domain Permissions ✓
└── Context-Aware Authorization ✓
CRM
├── Companies ✓
├── Contacts ✓
├── Opportunities ✓
├── Activities ✓
├── Tasks ✓
└── Meetings ✓
Sales
├── Pipeline ✓
├── Opportunity Stages ✓
├── Probability ✓
└── Weighted Pipeline ✓
Work Management
├── Tasks ✓
├── Due Dates ✓
├── Priorities ✓
├── Assignments ✓
└── Completion ✓
Scheduling
├── Meetings ✓
├── Participants ✓
├── Conflict Detection ✓
├── Cancellation ✓
└── Completion ✓
CRM Memory
├── Activity Timeline ✓
├── Calls ✓
├── Emails ✓
├── Meeting Activities ✓
└── Notes ✓
Interfaces
├── REST ✓
├── MCP ✓
└── ChatGPT ✓
Mutation Governance
├── Drafts ✓
├── Mutation Requests ✓
├── Confirmation ✓
├── Expiry ✓
├── Cancellation ✓
├── Idempotency ✓
├── Transactions ✓
├── Optimistic Concurrency ✓
└── Audit Events ✓
Conversational Operations
├── Entity Resolution ✓
├── Member Resolution ✓
├── Participant Resolution ✓
├── Relative Time Resolution ✓
├── Relationship Validation ✓
├── Missing-Field Detection ✓
├── Field Provenance ✓
├── Multi-Turn Completion ✓
└── Safe Execution ✓
CRM Retrieval
├── Unified CRM Search ✓
├── Tenant-Scoped Search ✓
├── Permission-Aware Search ✓
├── Search Ranking ✓
└── Bounded Search Results ✓
CRM Context Layer
├── Company Context ✓
├── Contact Context ✓
├── Opportunity Context ✓
├── Permission Filtering ✓
├── Tenant Filtering ✓
├── Context Budgets ✓
├── Recency Windows ✓
├── Result Ranking ✓
├── Truncation Metadata ✓
├── Context Provenance ✓
├── Data Minimization ✓
└── Structured Context Packages ✓
ChatGPT UI
├── Pipeline ✓
├── CRM Entity Detail ✓
├── Activity Timeline ✓
├── Task Views ✓
├── Meeting Views ✓
├── Mutation Confirmation ✓
└── CRM Context Widgets ✓
ChatGPT CRM Operations
├── Search CRM ✓
├── Retrieve CRM Context ✓
├── Read Companies ✓
├── Read Contacts ✓
├── Read Opportunities ✓
├── Read Activities ✓
├── Read Tasks ✓
├── Read Meetings ✓
├── Create Company ✓
├── Create Contact ✓
├── Create Opportunity ✓
├── Update Opportunity Stage ✓
├── Record Activity ✓
├── Create Task ✓
├── Complete Task ✓
├── Schedule Meeting ✓
├── Cancel Meeting ✓
└── Complete Meeting ✓
Grounding
├── Structured Evidence ✓
├── Evidence Provenance ✓
├── Context Freshness ✓
├── Context Completeness Metadata ✓
└── Fact/Analysis Separation ✓
Semantic Knowledge Retrieval -
AI Intelligence -

139. Acceptance Criteria

Part 25 is complete when:

✓ Part 24 regression suite remains green
✓ crm.search exists
✓ crm.context.read exists
✓ domain permissions remain authoritative
✓ context retrieval cannot bypass domain permissions
✓ search_crm exists
✓ search supports Company
✓ search supports Contact
✓ search supports Opportunity
✓ search supports Activity where appropriate
✓ search supports Task
✓ search supports Meeting
✓ search is tenant-scoped
✓ search is permission-aware
✓ search results are bounded
✓ search results are ranked deterministically
✓ search returns entity type
✓ search returns authoritative entity ID
✓ exact matches rank appropriately
✓ ambiguous matches are not silently resolved
✓ zero-result searches do not invent entities
✓ CRMSearchResult contract exists
✓ search schemas expose no unnecessary internal data
✓ get_crm_context exists
✓ context operates on resolved entity IDs
✓ Company context exists
✓ Contact context exists
✓ Opportunity context exists
✓ unsupported context roots return structured errors
✓ Company context can include permitted Contacts
✓ Company context can include permitted Opportunities
✓ Company context can include permitted Activities
✓ Company context can include permitted Tasks
✓ Company context can include permitted Meetings
✓ Contact context includes Company where permitted
✓ Contact context includes only supported related Opportunities
✓ Contact context includes recent Activities
✓ Contact context includes open Tasks
✓ Contact context includes upcoming Meetings
✓ Opportunity context includes Company
✓ Opportunity context includes supported Contacts
✓ Opportunity context includes recent Activities
✓ Opportunity context includes open Tasks
✓ Opportunity context includes upcoming Meetings
✓ every context query is tenant-scoped
✓ cross-tenant IDs never return context
✓ every context section respects domain permissions
✓ unauthorized sections do not leak data
✓ purpose-built summary schemas exist
✓ ORM models are not serialized directly
✓ unnecessary fields are excluded
✓ sensitive technical fields never enter context
✓ context budgets exist
✓ Contacts are bounded
✓ Opportunities are bounded
✓ Activities are bounded
✓ Tasks are bounded
✓ Meetings are bounded
✓ default Activity recency window exists
✓ default Company context prioritizes open Opportunities
✓ default Task context returns open Tasks
✓ default Meeting context returns future scheduled Meetings
✓ Contact ranking is deterministic
✓ Activity ordering is deterministic
✓ Task ordering is deterministic
✓ Meeting ordering is deterministic
✓ truncation is detected
✓ truncation metadata exists
✓ generated_at exists
✓ context freshness is explicit
✓ empty sections remain empty rather than invented
✓ context provenance exists
✓ context items retain authoritative entity identity
✓ evidence can be traced to CRM records
✓ context packages are structured
✓ context is not returned as one unstructured text blob
✓ ChatGPT receives enough type information to distinguish evidence
✓ CRMContextService exists
✓ Context Builder orchestrates domain repositories
✓ domain repositories remain independent
✓ Context Builder avoids recursive ORM serialization
✓ Context Builder avoids obvious N+1 behavior
✓ context retrieval is read-only
✓ get_crm_context creates no business mutation
✓ get_crm_context creates no MutationRequest
✓ broad questions can use CRM Context
✓ precise questions can use specific read tools
✓ smallest sufficient authoritative retrieval is preferred
✓ Company context widget exists
✓ Contact context widget exists
✓ Opportunity context widget exists
✓ widgets render authoritative server data
✓ context snapshots are not trusted for later mutations
✓ mutation preparation reloads current authoritative state
✓ context does not bypass optimistic concurrency
✓ CRM_ENTITY_NOT_FOUND exists
✓ CRM_ENTITY_AMBIGUOUS exists
✓ CRM_CONTEXT_FORBIDDEN exists
✓ CRM_CONTEXT_TYPE_UNSUPPORTED exists
✓ CRM_CONTEXT_BUILD_FAILED exists
✓ context access logging avoids full payload logging
✓ context metrics exist
✓ context request correlation exists
✓ grounded answers use returned CRM evidence
✓ unsupported CRM facts are not invented
✓ facts and AI analysis remain conceptually separate
✓ truncated context does not produce claims of completeness
✓ Quorentra reports version 0.12.0

Most importantly:

ChatGPT can now ask Quorentra for a bounded, permission-aware, tenant-isolated package of authoritative customer evidence instead of manually reconstructing customer state through unrelated low-level queries.


140. What We Have Achieved

The question:

What’s happening with Adventure Works?

can now flow through:

Natural Language
Search CRM
Resolve Adventure Works
Company
CRM Context Builder
┌─────────────────────────────┐
│ Company │
│ Contacts │
│ Opportunities │
│ Recent Activities │
│ Open Tasks │
│ Upcoming Meetings │
└─────────────────────────────┘
Permission Filtering
Tenant Filtering
Context Budget
Provenance
Structured Evidence
ChatGPT
Grounded Answer

This is a major architectural milestone.


141. Quorentra Now Has an Evidence Layer

Our architecture has evolved from:

ChatGPT
CRM Tools
CRM Domains
PostgreSQL

to:

                     ChatGPT
                        │
             ┌──────────┴──────────┐
             │                     │
             ▼                     ▼
       CRM Context             CRM Mutations
          Layer                    │
             │                     │
             ▼                     ▼
       CRM Read Model        Governed Write Path
             │                     │
             └──────────┬──────────┘
                        ▼
                   CRM Domains
                        │
                        ▼
                   PostgreSQL

The read and write paths now have different responsibilities.


142. The Read Path

The read path is optimized for:

Retrieval
Context
Grounding
Explanation

It looks like:

User Question
ChatGPT
Entity Resolution
Context Builder
Authorized Evidence
Grounded Answer

143. The Write Path

The write path remains optimized for:

Safety
Validation
Confirmation
Consistency
Auditability

It looks like:

User Intent
ChatGPT
Draft
Validation
Preparation
Confirmation
Mutation
Audit

This separation is deliberate.


144. Why This Is Important for ChatGPT-Native CRM

Traditional CRM systems usually optimize around:

Screens
Forms
Tables
Navigation

Quorentra must also optimize around:

Questions
Context
Intent
Evidence
Actions

That requires a different application architecture.

The Context Layer is part of that difference.


145. We Can Now Ask More Natural Questions

For example:

What’s happening with Adventure Works?

What was my last interaction with Sarah?

What do I need to do next for the Azure Migration deal?

Do we have anything scheduled with Contoso?

Which open opportunities does Adventure Works have?

What customer work do I have today?

These questions can increasingly be answered through structured Quorentra evidence.


146. But We Still Have Only Structured CRM Memory

The Context Layer retrieves:

Companies
Contacts
Opportunities
Activities
Tasks
Meetings

But real CRM knowledge also exists inside:

Proposal documents
Contracts
Meeting notes
Emails
Technical documents
Customer requirements
Statements of work
Presentations
PDFs
DOCX files

Those cannot always be represented as ordinary relational fields.


147. The Next Architectural Layer

To make ChatGPT genuinely knowledgeable about customer relationships, Quorentra eventually needs:

Structured CRM Context
+
Unstructured Customer Knowledge

That means introducing a document and knowledge architecture.

But we should continue following the same modular principle.

Do not jump directly to:

RAG

First establish the authoritative document domain.


148. Documents Before Embeddings

Before asking:

How should we chunk documents?

we first need to answer:

What is a Document?
Who owns it?
Which tenant does it belong to?
Which Company does it belong to?
Which Opportunity does it belong to?
Who uploaded it?
What is its MIME type?
Where is the file stored?
What is its processing state?
Can the user access it?
How do we audit it?

Only after those foundations exist should we introduce extraction and embeddings.


149. Why This Fits the Modular Strategy

We continue adding one coherent capability at a time.

So far:

Identity
Security
Companies
Contacts
Opportunities
Activities
Tasks
Meetings
CRM Context

Next:

Documents

Then later:

Text Extraction
Chunking
Embeddings
Vector Search
RAG

Each stage produces a working system.


150. Next Article

In Part 26, we will build:

Adding CRM Documents and Attachments — Uploads, Storage, Metadata, Entity Relationships, Permissions, and Secure Retrieval

We will introduce:

documents.read
documents.create
documents.delete
Document
Document Status
Document Type
Original Filename
MIME Type
File Size
Storage Key
Checksum
Uploaded By
Company Relationship
Contact Relationship
Opportunity Relationship
Activity Relationship
Task Relationship
Meeting Relationship
Tenant Isolation
Secure Upload
File Validation
File Size Limits
MIME Validation
Extension Validation
Storage Abstraction
Local Development Storage
Future Object Storage Compatibility
Document Metadata
Document Search
Document Listing
Document Download
Signed / Authorized Retrieval
Document Deletion
Audit Events
ChatGPT Document Tools
Document Widgets

Our target interaction will eventually become:

User:
"Attach the revised Azure migration proposal
to the Adventure Works opportunity."

Quorentra will resolve:

Adventure Works
Company
Azure Migration
Opportunity

and associate the uploaded file with the correct CRM context.

Later, once text extraction and RAG are added, the user will be able to ask:

What pricing did we propose to Adventure Works?

Quorentra will combine:

Structured CRM Context
+
Relevant Document Knowledge
Grounded ChatGPT Answer

That will begin the next major phase of Quorentra — A Modular, ChatGPT-Native AI CRM: moving from structured CRM records toward a customer knowledge platform.

Quorentra

Contact

Menu

Designed with WordPress

Discover more from Quorentra

Subscribe now to keep reading and get access to the full archive.

Continue reading