Last Updated: 2026-04-03 Status: Draft
1. Core Entities & Aggregates
1.1 Agent Domain
1.1.1 AgentConfig
Defines a specialist agent's identity and behavior. One per domain per tenant (blog specialist, content specialist, etc.), plus a shared router config.
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | ✅ | Primary key |
tenant_id |
UUID | ✅ | Owning tenant |
agent_type |
Enum | ✅ | router, blog, content, analytics, chat |
display_name |
String | ✅ | Human-readable name (e.g., "Blog Assistant") |
system_prompt |
Text | ✅ | Domain-specific system prompt |
model_tier |
Enum | ✅ | haiku, sonnet, opus — default model for this agent |
temperature |
Float | ✅ | Creativity (0.0–1.0), default 0.3 for most, 0.7 for content generation |
max_tokens |
Integer | ✅ | Max response tokens, default 1000 |
tools |
Text[] | ✅ | List of tool names this agent can call |
enabled |
Boolean | ✅ | Whether this specialist is active |
created_at |
Timestamp | ✅ | Record creation |
updated_at |
Timestamp | ✅ | Last modification |
1.1.2 ToolDefinition
A registered tool that agents can call. Maps to a service action.
| Field | Type | Required | Description |
|---|---|---|---|
name |
String | ✅ | Unique identifier (e.g., blog.create_draft) |
service |
Enum | ✅ | blog, content, chat, media, analytics, billing |
description |
Text | ✅ | What this tool does — used by the LLM to decide when to call it |
parameters |
JSONB | ✅ | JSON Schema of accepted parameters |
requires_confirmation |
Boolean | ✅ | If true, user must approve before execution (destructive actions) |
required_permission |
String | ✅ | PBAC permission needed (e.g., blog.write) |
rate_limit |
Integer | ❌ | Max calls per minute per tenant (null = unlimited) |
1.2 Conversation Domain
1.2.1 Conversation
A conversation thread between a user and the AI copilot.
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | ✅ | Primary key |
tenant_id |
UUID | ✅ | Owning tenant |
user_id |
UUID | ✅ | The user who started this conversation |
title |
String | ❌ | Auto-generated summary of the conversation topic |
status |
Enum | ✅ | active, idle, closed |
context_snapshot |
JSONB | ❌ | Frozen context at conversation start (tenant profile, plan) |
total_tokens |
Integer | ✅ | Cumulative tokens used across all messages |
total_cost_usd |
Float | ✅ | Cumulative cost in USD |
message_count |
Integer | ✅ | Total messages in this conversation |
started_at |
Timestamp | ✅ | When conversation began |
last_message_at |
Timestamp | ✅ | Last activity timestamp |
closed_at |
Timestamp | ❌ | When conversation was closed |
1.2.2 Message
An individual message within a conversation.
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | ✅ | Primary key |
conversation_id |
UUID | ✅ | FK → Conversation |
role |
Enum | ✅ | user, assistant, tool_call, tool_result, system |
content |
Text | ✅ | Message text (or tool call/result JSON) |
agent_type |
Enum | ❌ | Which specialist generated this (null for user messages) |
model |
String | ❌ | Model used (e.g., claude-haiku-4-5-20251001) |
input_tokens |
Integer | ❌ | Tokens consumed for input |
output_tokens |
Integer | ❌ | Tokens generated for output |
tool_calls |
JSONB | ❌ | Array of tool calls made in this turn |
latency_ms |
Integer | ❌ | Time to first token in milliseconds |
created_at |
Timestamp | ✅ | When this message was created |
1.3 Memory Domain
1.3.1 Memory
A stored piece of knowledge about a tenant, linked to entities.
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | ✅ | Primary key |
tenant_id |
UUID | ✅ | Owning tenant — NEVER cross-tenant |
memory_type |
Enum | ✅ | fact, preference, episode, pattern, entity |
content |
Text | ✅ | The memory itself (natural language) |
embedding |
vector(1536) | ✅ | Semantic embedding for similarity search |
importance |
Float | ✅ | 0.0–1.0, boosted by outcomes, decayed by time |
access_count |
Integer | ✅ | How many times this memory has been retrieved |
decay_rate |
Float | ✅ | How fast importance decays (default 0.01 per day) |
entity_ids |
Text[] | ❌ | Linked entities (e.g., ["blog", "traffic", "recipe"]) |
source_type |
Enum | ✅ | conversation, observation, consolidation, insight |
source_id |
UUID | ❌ | FK to conversation or insight that created this |
expires_at |
Timestamp | ❌ | Auto-pruned after this date (null = permanent) |
accessed_at |
Timestamp | ✅ | Last time this memory was retrieved |
created_at |
Timestamp | ✅ | Record creation |
1.3.2 MemoryEntity
An entity node in the memory graph. Represents a concept the AI tracks.
| Field | Type | Required | Description |
|---|---|---|---|
id |
String | ✅ | Entity identifier (e.g., blog, traffic, priya, diwali-post) |
tenant_id |
UUID | ✅ | Owning tenant |
entity_type |
Enum | ✅ | service, topic, person, content, campaign, metric |
display_name |
String | ✅ | Human-readable name |
metadata |
JSONB | ❌ | Arbitrary structured data about this entity |
memory_count |
Integer | ✅ | Number of memories linked to this entity |
created_at |
Timestamp | ✅ | When this entity was first recognized |
updated_at |
Timestamp | ✅ | Last modification |
1.4 Decision Domain
1.4.1 Decision
A logged AI decision — what the agent did, why, and what happened.
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | ✅ | Primary key |
tenant_id |
UUID | ✅ | Owning tenant |
conversation_id |
UUID | ❌ | FK → Conversation (null for proactive insights) |
agent_type |
Enum | ✅ | Which specialist made this decision |
action |
String | ✅ | What the agent decided to do (e.g., diagnosed_traffic_drop) |
reasoning |
Text | ✅ | Chain of thought — why this action |
tool_calls |
JSONB | ❌ | Tools called as part of this decision |
outcome |
JSONB | ❌ | Tracked result (populated later by outcome tracker) |
feedback_score |
Float | ❌ | -1.0 to 1.0, computed from outcome |
outcome_tracked_at |
Timestamp | ❌ | When outcome was measured |
created_at |
Timestamp | ✅ | When the decision was made |
1.5 Insight Domain
1.5.1 Insight
A proactive insight generated by the insight engine.
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | ✅ | Primary key |
tenant_id |
UUID | ✅ | Owning tenant |
insight_type |
Enum | ✅ | opportunity, anomaly, win, suggestion, reminder |
priority |
Enum | ✅ | high, medium, low |
title |
String | ✅ | Short headline (shown in dashboard card) |
body |
Text | ✅ | Detailed explanation |
suggested_action |
String | ❌ | Tool name to execute if user clicks CTA |
action_params |
JSONB | ❌ | Parameters for the suggested action |
seen_at |
Timestamp | ❌ | When user viewed this insight (null = unseen) |
acted_on |
Boolean | ✅ | Whether user clicked the CTA |
dismissed_at |
Timestamp | ❌ | When user dismissed this insight |
expires_at |
Timestamp | ✅ | Insights are time-sensitive — auto-hide after this date |
created_at |
Timestamp | ✅ | When the insight was generated |
1.6 Usage Domain
1.6.1 UsageRecord
Per-interaction cost tracking for the cost governor.
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | ✅ | Primary key |
tenant_id |
UUID | ✅ | Owning tenant |
conversation_id |
UUID | ✅ | FK → Conversation |
message_id |
UUID | ✅ | FK → Message |
model |
String | ✅ | Model used (e.g., claude-sonnet-4-6) |
input_tokens |
Integer | ✅ | Tokens sent to LLM |
output_tokens |
Integer | ✅ | Tokens received from LLM |
cost_usd |
Float | ✅ | Computed cost for this call |
billing_period |
String | ✅ | YYYY-MM format for monthly aggregation |
created_at |
Timestamp | ✅ | When the LLM call was made |
2. Entity Relationships
3. State Machines in Detail
3.1 Conversation.status Lifecycle
3.2 Memory.importance Lifecycle
3.3 Insight.status Lifecycle
4. Key Domain Rules & Invariants
-
Tenant isolation is non-negotiable. Every entity has a
tenant_id. Every query filters by it. Row-Level Security is the safety net, not the primary mechanism — application code MUST scope all queries. A bug that leaks cross-tenant data is a P0 security incident. -
Memories are never deleted by agents. Only the periodic consolidation cron can prune memories (when importance drops below 0.1). Agents can create memories and boost importance, but never delete. This prevents an AI hallucination from destroying learned knowledge.
-
Destructive tool calls require user confirmation. If a
ToolDefinitionhasrequires_confirmation: true, the agent MUST pause execution and ask the user to approve before calling the tool. The UI shows a confirmation dialog. No implicit destructive actions. -
Usage is checked before LLM calls, not after. The cost governor checks
usage_recordsfor the current billing period before sending any request to an LLM provider. If the tenant has exceeded their plan limit, the request is rejected with a graceful message — no tokens are consumed. -
Conversations auto-close after 24 hours of inactivity. When closed, the system extracts memories from the conversation (immediate consolidation), logs final usage, and destroys the Durable Object. The conversation history remains in PostgreSQL permanently for audit and training.
-
One active conversation per user per tenant. A user cannot have two simultaneous copilot sessions. Opening a new tab resumes the existing conversation. This is enforced by the Durable Object (keyed by
tenant_id:user_id). -
Insights expire. Every insight has an
expires_attimestamp. Stale insights (e.g., "Your traffic dropped last week" when it's now recovered) are hidden after expiry. The insight engine generates fresh insights every 6 hours. -
Memory importance is bounded [0.0, 1.0]. Boosting and decaying never push importance outside this range. A memory at 1.0 importance still decays — nothing is immune to time.