Data Model¶
Precis¶
This document defines every entity in Thinklio, the relationships between them, and the rules governing data ownership, visibility, and lifecycle. It is the single source of truth for "what things exist in this system and how they relate."
Thinklio's data model spans several functional domains. Core platform entities (accounts, teams, users) provide the multi-tenancy and membership substrate. Agent entities (agents, assignments, tools, templates) define what agents are, what they can do, and where they operate. Execution entities (interactions, steps, jobs, subjobs, observers) track all agent work from a single message through to multi-step deferred workflows. Core data structures (tasks, contacts, items, notes, tags) give agents structured objects to create, query, and update on behalf of users. Knowledge facts store structured information extracted from interactions, while the media and library system handles file storage, processing, and document-grounded retrieval. Communications entities manage outbound notification dispatch across channels. Platform services, billing, and access-control entities round out the model.
Every entity follows a consistent scoping model: user, team, or account. Visibility is governed by scope, with account policies acting as the ceiling that no lower layer can loosen. The type system for tasks, items, and notes provides extensibility: platform-reserved system types give consistent behaviour out of the box, while accounts can create custom types for domain-specific workflows.
The conceptual model described here is implementation-aware but not implementation-bound. The current implementation targets Convex (see doc 02 System Architecture); legacy references to Supabase Auth, PostgreSQL, and Redis reflect the original design and are retained where they provide useful context for migration. The Convex schema lives at /convex/schema.ts. Singular table names are used throughout per project convention.
For storage implementation details see doc 05 Persistence, Storage & Ingestion. For agent architecture and the execution contract see doc 03 Agent Architecture & Extensibility. For security and governance policies see doc 07 Security & Governance.
Table of contents¶
- Purpose
- Naming and type conventions
- Scope and visibility model
- Entity relationship overview
- Core platform entities
- Agent entities
- Agent execution entities
- Core data structures
- Knowledge
- Learned workflows
- Events
- Communications and channels
- Planning and execution scoring
- Platform services
- Media, storage and libraries
- Access and security
- Billing and resource management
- Agent tool integration
- API endpoints
- Agent usage patterns
- Data lifecycle
- Open questions
- Revision history
1. Purpose¶
This document serves three audiences:
Developers use it as the canonical reference for every table, field, relationship, and constraint in the system. When implementing a feature that touches data, this document answers "what exists, what are the rules, and where does it connect."
Architects use it to understand the full entity landscape, identify where new features land, and ensure consistency across the model.
Integration partners use the core data structures section (section 8), agent tool integration (section 18), and API endpoints (section 19) to build against a stable, published interface.
2. Naming and type conventions¶
Table names are singular throughout: account, agent, tool, task, note. Join tables use both singular names: account_user, team_member, agent_tool. Exception: user_profile instead of user (reserved word in PostgreSQL/Supabase).
Primary keys are UUIDs in the legacy Supabase model. In the Convex implementation, Convex's built-in document IDs serve the same purpose.
Timestamps use TIMESTAMPTZ in the legacy model. Convex stores timestamps as epoch milliseconds (v.number()).
Enums are expressed as string unions in Convex (v.union(v.literal(...), ...)). The conceptual model uses enum notation for clarity.
JSONB fields map to unvalidated or validated objects in Convex. Where the schema is known, Convex validators enforce structure at write time.
Embeddings use vector(1536) in the legacy model. Convex vector search uses the built-in vector index mechanism.
3. Scope and visibility model¶
All scoped entities follow the platform's standard three-tier model:
| Scope | Visible to | Typical use |
|---|---|---|
user |
The owning user only | Personal tasks, private contacts, personal notes |
team |
All members of the team | Shared task lists, team contacts, team items |
account |
All account members | Organisation-wide items, shared contact database, company notes |
Exceptions and refinements:
- Items are never user-scoped. They are organisational by nature; the minimum scope is
team. - Contacts carry an additional
visibilityfield (private,team,account) that can restrict access below the scope level. A private contact in an account scope is visible only to its creator. - Admins and owners can see all entities regardless of visibility, for administration and data management purposes.
- Notes can be shared beyond their scope via
note_sharerecords without changing the note's scope. - RLS policies (in the legacy Supabase model) enforce scope membership via
account_userandteam_member. In Convex, equivalent enforcement runs in middleware (see doc 07).
Policy layering: Account policies set the ceiling. Team policies can tighten within account bounds. User preferences can tighten further. No layer can loosen a restriction set above it. This is consistent across the entire platform, not just the data model.
4. Entity relationship overview¶
┌──────────────┐
│ account │
└──────┬───────┘
│ has many
├──────────────┐
│ │
┌──────▼───────┐ ┌────▼──────────┐
│ team │ │ account_user │ (membership + role)
└──────┬───────┘ └───────────────┘
│ has many
┌──────▼───────┐
│ team_member │ (membership + role)
└──────────────┘
┌──────────────┐
│ agent │ (first-class, independent)
└──────┬───────┘
│ assigned via
┌──────▼────────────────┐
│ agent_assignment │ (agent → user | team | account, with scope
│ │ and per-assignment tool restrictions)
└───────────────────────┘
┌──────────────┐
│ user_profile │ (platform-global identity)
└──────────────┘
┌──────────────┐ ┌──────────────┐
│ interaction │───────▶│ step │ (1:many)
│ │ └──────────────┘
│ │
│ │───────▶ parent_interaction_id (delegation chain)
└──────────────┘
┌──────────────┐ ┌──────────────┐
│ job │───────▶│ subjob │ (1:many)
│ │ └──────────────┘
│ │
│ │───────▶┌──────────────┐
│ │ │ job_observer │ (many observers per job)
└──────────────┘ └──────────────┘
┌──────────────────────────────────────┐
│ Core Data Structures │
├──────────┐ ┌──────────┐ ┌────────┐│
│ task │ │ contact │ │ item ││
└────┬─────┘ └────┬─────┘ └───┬────┘│
│ │ │ │
└─────────────┼─────────────┘ │
▼ │
note │
│ │
└──────▶ tag ◀──────┘
entity_tag
(task/contact/item/note)
└──────────────────────────────────────┘
┌──────────────┐
│knowledge_fact│ (scoped: agent | account | team | user)
└──────────────┘
┌──────────────┐
│ tool │ (type: internal | external | mcp | agent)
└──────┬───────┘
│ assigned via
┌──────▼───────┐
│ agent_tool │ (agent → tool, with permission level)
└──────────────┘
┌──────────────┐
│ workflow │ (learned, first-class)
└──────────────┘
┌──────────────┐
│ event │ (immutable, append-only)
└──────────────┘
Additional entity groups (abbreviated in the diagram for clarity): platform services (platform_service, llm_model, account_service_config, account_llm_preference), communications (channel_config, user_channel, user_comm, notification), planning (canonical_plan, execution_outcome, plan_score), media and storage (storage_bucket, account_storage_bucket, media, media_processor, media_processing_rule, media_processing_job, library, library_item, agent_library), access and security (invitation, api_key, oauth_token, webhook_subscription), and billing (credit_ledger, budget_limit, usage_record, quality_rating, platform_config).
5. Core platform entities¶
5.1 user_profile¶
A person on the platform. Users have a single global identity and can participate in multiple accounts and teams. In the legacy model, authentication is managed by Supabase Auth and user_profile is synced from auth.users via a database trigger on signup. In the Convex model, authentication is managed by Clerk; the user_profile record is created on first login via a webhook.
| Field | Type | Description |
|---|---|---|
| id | UUID | Primary key (synced from auth provider) |
| display_name | string | How the user appears in the platform |
| status | enum | active, suspended, deleted |
| settings | JSONB | Personal preferences and configuration |
| created_at | timestamp | Account creation |
| updated_at | timestamp | Last modification |
Rules:
- A user exists independently of any account.
- A user can create agents directly for personal use.
- A user can belong to multiple accounts with different roles.
- Deleting a user triggers data portability and retention procedures (section 21).
- Credentials are never stored in this table.
5.2 account¶
A billing and governance entity. Accounts own policies, budgets, and administrative control. In the Convex model, accounts map to Clerk organisations.
| Field | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| name | string | Display name |
| slug | string | URL-safe identifier, unique |
| plan | enum | free, team, account, enterprise |
| settings | JSONB | Account-wide settings and preferences |
| policies | JSONB | Governance policies (tool restrictions, content rules, delegation limits) |
| budget_monthly | decimal | Monthly spending limit |
| status | enum | active, suspended, archived |
| created_at | timestamp | |
| updated_at | timestamp |
Rules:
- An account is the anchor for billing.
- Account policies override all lower-level settings.
- Budget enforcement happens at the account level first, then team, then user.
- Account policies include delegation governance:
max_delegation_depth, delegation approval requirements.
5.3 account_user¶
Links users to accounts with roles.
| Field | Type | Description |
|---|---|---|
| account_id | UUID | FK to account |
| user_id | UUID | FK to user_profile |
| role | enum | owner, admin, editor, viewer |
| joined_at | timestamp | When the user joined |
Role descriptions:
- owner: Full access including billing, account deletion, and ownership transfer.
- admin: Manage teams, members, agents, and settings. No billing or account deletion.
- editor: Create and edit agents, manage knowledge, use all agent features.
- viewer: Read-only access. Can interact with assigned agents but cannot create or modify configuration.
Rules:
- Every account has at least one owner.
- Owners can manage admins and members; admins can manage editors and viewers.
- A user's role determines what they can configure within the account.
5.4 team¶
A group of users within an account who share context and agents.
| Field | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| account_id | UUID | FK to account (nullable for personal teams) |
| name | string | Display name |
| slug | string | URL-safe, unique within account |
| settings | JSONB | Team-specific settings |
| budget_monthly | decimal | Team spending limit (within account budget) |
| status | enum | active, archived |
| created_at | timestamp | |
| updated_at | timestamp |
Rules:
- Teams belong to accounts, or are standalone for personal use.
- Team knowledge is isolated: Team A's knowledge is invisible to Team B.
- Team budgets must not exceed the parent account budget.
5.5 team_member¶
Links users to teams with roles.
| Field | Type | Description |
|---|---|---|
| team_id | UUID | FK to team |
| user_id | UUID | FK to user_profile |
| role | enum | admin, member, readonly |
| joined_at | timestamp | When the user joined |
6. Agent entities¶
6.1 agent¶
A first-class platform entity: the AI assistant. Agents exist independently and are assigned to contexts. For the full agent architecture, execution contract, and composition model see doc 03.
| Field | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| name | string | Display name |
| slug | string | URL-safe identifier |
| description | string | What this agent does |
| system_prompt | text | Core personality and instructions |
| capability_level | enum | tools_only, workflow, experimental, learning |
| llm_provider | string | Which LLM provider to use |
| llm_model | string | Which model to use |
| settings | JSONB | Agent-specific configuration |
| status | enum | active, paused, archived |
| created_by | UUID | FK to user_profile |
| template_id | UUID | FK to agent_catalog (nullable) |
| created_at | timestamp | |
| updated_at | timestamp |
Rules:
- Agents are created by users (personally) or by account/team admins.
- An agent can be assigned to multiple contexts simultaneously.
- Agent knowledge (domain expertise, learned workflows) travels with the agent.
- The capability_level determines what the agent is allowed to do.
- Pausing stops new interactions; archiving preserves the audit trail.
- An agent can be registered as a tool (type
agent) for other agents to invoke via delegation.
6.2 agent_assignment¶
Links agents to contexts (users, teams, or accounts) with scope configuration and per-assignment tool restrictions.
| Field | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| agent_id | UUID | FK to agent |
| context_type | enum | user, team, account |
| context_id | UUID | FK to user_profile, team, or account |
| access_level | enum | interact, configure, admin |
| knowledge_scope | JSONB | Which knowledge layers are active |
| budget_monthly | decimal | Budget limit for this assignment |
| tool_restrictions | JSONB | Per-tool permission overrides that narrow the agent's configured capabilities |
| status | enum | active, suspended |
| created_at | timestamp | |
| updated_at | timestamp |
Rules:
- An agent can have multiple assignments (to a team and to individual users, for example).
- Each assignment has its own knowledge scope and budget.
- Knowledge isolation is per-assignment: Team A's knowledge is separate from Team B's, even for the same agent.
- Removing an assignment does not delete the agent.
tool_restrictionscan only narrow, never widen, the agent's configured tool permissions. An assignment cannot grant tool access that the agent does not already have.- Tool restrictions apply equally to regular tools and agent-as-tool delegations. A scheduler agent assigned to a team can be restricted to read-only calendar operations for that specific assignment.
Tool restrictions format:
{
"scheduler_agent": {
"allowed_actions": ["find_free_time", "check_conflicts"],
"denied_actions": ["create_event"]
},
"search_web": {
"max_calls_per_interaction": 3
},
"send_email": {
"blocked": true
}
}
Policy evaluation order for tool access:
- Agent configuration: does the agent have this tool assigned? (Maximum capability.)
- Assignment restrictions: does the assignment narrow the tool's permissions for this context?
- Account policies: do account-level policies impose further restrictions?
Each layer can only restrict, never expand.
6.3 tool¶
A capability available to agents. Tools can be platform-provided, external webhook/API integrations, MCP-discovered, or other agents (agent-as-tool delegation).
| Field | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| slug | string | Unique identifier |
| name | string | Display name |
| description | text | What the tool does (used in LLM tool definitions) |
| type | enum | internal, external, mcp, agent |
| trust_level | enum | read, low_risk_write, high_risk_write |
| parameter_schema | JSONB | JSON Schema for parameters (also serves as the invocation contract for agent-type tools) |
| return_schema | JSONB | JSON Schema for return values |
| config | JSONB | Endpoint URL, auth, timeout for external tools. Agent ID for agent-type tools. MCP server reference for MCP tools. |
| rate_limit | JSONB | Rate limiting configuration |
| default_execution_mode | enum | immediate, deferred (default: immediate) |
| version | string | Tool version |
| status | enum | active, deprecated, disabled |
| registered_by | UUID | FK to user_profile or account (nullable for platform tools) |
| created_at | timestamp | |
| updated_at | timestamp |
Rules for agent-type tools:
- When type is
agent, the config field contains{"agent_id": "uuid"}identifying the delegate agent. - The parameter_schema defines the invocation contract: what structured input the delegate accepts.
- The return_schema defines what structured output the delegate produces.
- The trust_level reflects the delegate agent's most sensitive configured capability.
- Cycle detection is enforced at configuration time (when registering an agent-as-tool, the system checks for cycles in the delegation graph) and at runtime (each delegation carries a chain of agent IDs; invoking an agent already in the chain is denied).
Rules for dynamic registration (Integration API):
- External systems can register tools through the Integration API with governance approval.
- Dynamically registered tools go through the same policy evaluation as statically configured tools.
- The
registered_byfield tracks who registered the tool for audit purposes. - Health monitoring applies to external and dynamically registered tools; unhealthy tools are circuit-broken.
6.4 agent_tool¶
Links agents to tools with permission levels.
| Field | Type | Description |
|---|---|---|
| agent_id | UUID | FK to agent |
| tool_id | UUID | FK to tool |
| permission | enum | read, readwrite |
| config_override | JSONB | Agent-specific tool configuration |
| created_at | timestamp |
6.5 agent_catalog¶
Pre-configured templates for creating agents, including delegation relationships for composed agents. In doc 03 this entity is referred to as the platform catalogue; in the legacy model it was agent_template.
| Field | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| name | string | Template name |
| description | text | What kind of agent this creates |
| system_prompt | text | Default system prompt |
| capability_level | enum | Default capability level |
| tool_assignments | JSONB | Default tool assignments (including agent-as-tool delegations) |
| delegation_config | JSONB | Pre-configured delegation relationships and per-delegation restrictions |
| knowledge_seeds | JSONB | Initial knowledge facts |
| library_assignments | JSONB | Array of {library_id, priority} objects; when an agent is deployed these become agent_library records |
| scope | enum | platform, account |
| scope_id | UUID | FK to account (for account-specific templates) |
| created_by | UUID | FK to user_profile |
| created_at | timestamp |
Rules:
- Templates for composed agents (Personal Assistant with Scheduler and Research, for example) pre-configure delegation relationships and tool assignments.
- Cycle detection runs when a template is created or modified that includes agent-as-tool delegations.
- Platform templates (
scope = platform) are available to all accounts. Account templates are scoped to a single account. - Platform templates may reference platform-scoped libraries; account templates may reference account-scoped libraries within their scope.
7. Agent execution entities¶
7.1 interaction¶
A unit of work initiated by a user message. Contains one or more steps. Supports delegation chains through parent interaction linking. For the full execution contract and harness design see doc 03.
| Field | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| agent_id | UUID | FK to agent |
| user_id | UUID | FK to user_profile |
| team_id | UUID | FK to team (nullable) |
| account_id | UUID | FK to account (nullable) |
| session_id | UUID | Groups interactions into conversations |
| channel | string | Which channel initiated this interaction |
| state | enum | pending, running, success, failed |
| total_cost | decimal | Sum of all step costs |
| parent_interaction_id | UUID | FK to interaction (nullable). Set when this interaction is a delegation from another agent's act step. |
| delegation_depth | int | Current depth in the delegation chain. 0 for top-level interactions. |
| started_at | timestamp | When the interaction began |
| completed_at | timestamp | When the interaction finished |
| metadata | JSONB | Trace ID, channel-specific data |
Rules:
- State is derived from constituent steps (see harness design in doc 03).
- Cost is the sum of all step costs.
- The account_id and team_id provide cost attribution context.
- When parent_interaction_id is set, this interaction's cost becomes the act step cost in the parent interaction. Cost rolls up through the delegation chain to the originating user, team, and account.
- Delegation depth is checked against the account policy's
max_delegation_depthsetting (default: 3). The policy engine denies delegations that would exceed this limit.
7.2 step¶
An individual unit of execution within an interaction. Steps carry an execution mode that determines how the harness orchestrates post-step flow.
| Field | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| interaction_id | UUID | FK to interaction |
| step_type | enum | context, think, act, observe, respond, extract |
| step_order | int | Sequence within the interaction |
| state | enum | created, running, success, failed |
| execution_mode | enum | immediate, deferred, interactive (nullable, only relevant for act steps) |
| input_data | JSONB | What went into this step |
| output_data | JSONB | What came out of this step |
| error_data | JSONB | Failure reason and details (if failed) |
| cost | decimal | Cost of this step |
| cost_detail | JSONB | Breakdown: tokens in, tokens out, API costs |
| job_id | UUID | FK to job (nullable). Set when a deferred act step creates a job. |
| started_at | timestamp | When execution began |
| completed_at | timestamp | When execution finished |
Rules:
- Steps follow the state machine:
createdthenrunningthensuccessorfailed. - Failed steps include error_data with reason:
timeout,error,governance,budget,cancellation,delegation_depth_exceeded,delegation_cycle_detected. - Steps are persisted before execution begins (created state). Results are persisted on completion.
- Resumption: find steps in
createdorrunningstate and re-execute. - For act steps, execution_mode determines harness behaviour:
- immediate: step executes synchronously, harness waits for result.
- deferred: step dispatches work and creates a Job, succeeds on dispatch (not on work completion).
- interactive: step result feeds back into a new think step for further reasoning.
- A deferred act step's job_id links to the job entity for tracking.
7.3 job¶
A unit of work that outlives a single interaction. Created when a deferred act step dispatches work to an external execution engine or a delegate agent with unpredictable completion time. For the full job system design see doc 03 section 8.
| Field | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| type | string | Descriptor: research, handoff, workflow, delegation, etc. |
| created_by_agent | UUID | FK to agent that created this job |
| created_by_interaction | UUID | FK to interaction that spawned this job |
| session_id | UUID | Conversational context for follow-up interactions |
| state | enum | pending, dispatched, in_progress, resolved, failed, cancelled, timed_out |
| has_useful_output | boolean | Whether partial output is available and worth notifying about |
| dispatch_target | JSONB | Webhook URL, agent ID, external system config |
| dispatch_payload | JSONB | Structured brief/parameters for the executor |
| context_bundle | JSONB | Accumulated state from prior steps, carried forward for follow-up interactions |
| usefulness_rule | string | Rule identifier (default: any_completed_subjob) |
| timeout_at | timestamp | Deadline for the timeout monitor |
| created_at | timestamp | |
| updated_at | timestamp | Last state change |
State machine:
Rules:
- Jobs inherit the governance context (budget, policies, audit trail) of the creating interaction. Deferred execution is not an escape hatch from governance.
- Active jobs (non-terminal states) live in Redis for fast reads and writes in the legacy model. Terminal jobs are flushed asynchronously to PostgreSQL for durable storage and audit. In the Convex model, all job state lives in the Convex database with real-time subscriptions replacing polling.
- The context_bundle carries forward state that follow-up interactions need. When a PA checks a calendar (immediate step) then dispatches research (deferred step), the calendar result is stored in the job's context_bundle so the follow-up interaction has access to both the research output and the earlier calendar result.
resolvedmeans all subjobs have reached a terminal state and at least one succeeded. The observing agent evaluates whether the result set constitutes full success, acceptable partial success, or effective failure.failedmeans all subjobs terminated and none succeeded.- Retention follows the same policies as interactions and events (configurable per account, default 90 days active, then archived).
7.4 subjob¶
A discrete unit of work within a job. A simple deferred job has a single subjob. A complex job (generate five research articles, for example) has multiple subjobs, enabling granular progress tracking and partial output notification.
| Field | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| job_id | UUID | FK to job |
| label | string | Human-readable description |
| order | int | Sequence within the job |
| state | enum | pending, running, completed, failed |
| result_data | JSONB | Output from this subjob |
| error_data | JSONB | Failure details (if failed) |
| started_at | timestamp | When execution began |
| completed_at | timestamp | When execution finished |
Rules:
- When a subjob completes, the parent job evaluates its usefulness rule.
- The default rule (
any_completed_subjob): at least one subjob completed with non-null result_data while other subjobs are still in progress setshas_useful_output = trueon the job and notifies qualifying observers. - When all subjobs reach terminal states: the job transitions to
resolved(if any succeeded) orfailed(if none succeeded). - Subjobs within a timed-out or cancelled job are transitioned to
failedwith appropriate error reasons.
7.5 job_observer¶
An entity registered to receive notifications about a job's state changes. Decouples job creation from job consumption.
| Field | Type | Description |
|---|---|---|
| job_id | UUID | FK to job |
| observer_type | enum | agent, system |
| observer_id | UUID | Agent ID or system process identifier |
| assignment_id | UUID | Which assignment context governs this observer's permissions |
| notify_on | enum | completion_only, failure_only, partial_and_completion, all_changes |
| callback_metadata | JSONB | Context the observer needs when notified |
| registered_at | timestamp | When the observer was registered |
Notification filtering:
| notify_on | Receives |
|---|---|
completion_only |
Terminal states only: resolved, failed, cancelled, timed_out |
failure_only |
Failed, cancelled, timed_out only |
partial_and_completion |
Terminal states plus partial output availability |
all_changes |
Every state transition |
Rules:
- The creating agent is automatically registered as an observer when a job is created.
- Additional observers can be added by any agent with visibility into the job (governed by assignment context and account policies).
- Terminal state notifications bypass observer preferences: all observers are notified when a job reaches a terminal state.
- A periodic cleanup process removes observer registrations pointing to deleted agents.
- Observer types include
agent(internal) andsystem(for monitoring processes and, in future, external webhook subscribers via the Platform API).
8. Core data structures¶
These four universal data structures give agents structured objects to work with. They are not app features; they are the substrate that agents create, query, and update on behalf of users. The UI surfaces them, but the primary interface is the agent tool system (section 18).
Design principles:
- Agent-first. These structures exist so agents can operate on structured data. The UI is a secondary read/write interface.
- Scope-aware. Every entity follows the standard scoping model (section 3). Visibility is governed by scope and the role model.
- Lightweight. These are not replacements for Jira, Salesforce, or Notion. They provide just enough structure for agents to work with. Accounts that need deeper functionality use integrations (Todoist, HubSpot) with optional two-way sync.
- Extensible via metadata. Every entity carries a
metadataJSONB field for domain-specific data without schema changes. A support agent stores SLA data in item metadata. A CRM agent stores deal stage in contact metadata. - Published API. The tool and REST interfaces are public. Custom agents and external integrations use these structures exactly as built-in agents do.
- Typed and customisable. Tasks, items, and notes have a type system: platform-reserved system types provide consistent behaviour, while accounts can create custom types for domain-specific workflows.
8.1 task_list¶
A named group of tasks. Lists provide simple organisation without the overhead of projects or boards.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account |
| scope | text | user, team, account |
| scope_id | UUID | Scope entity ID |
| name | text | Display name (e.g. "Shopping", "Q2 Goals", "Sprint 14") |
| description | text | Optional |
| position | integer | Ordering among lists in the same scope |
| is_archived | boolean | Soft-delete for completed lists |
| created_by | UUID | FK to user_profile |
| created_at | timestamp | |
| updated_at | timestamp |
Visibility: User-scoped lists are private. Team-scoped lists are visible to team members. Account-scoped lists are visible to all account members.
8.2 task¶
An individual unit of work. Can belong to a list or be unassigned (inbox).
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account |
| list_id | UUID | FK to task_list (nullable for unassigned tasks) |
| task_type_id | UUID | FK to task_type (nullable, defaults to standard task) |
| title | text | |
| description | text | Optional, supports markdown |
| status | text | todo, in_progress, done, cancelled |
| priority | text | urgent, high, normal, low |
| due_date | date | Optional |
| due_time | time | Optional, for time-specific deadlines |
| assigned_to | UUID | FK to user_profile (nullable) |
| created_by | UUID | FK to user_profile (nullable for agent-created) |
| created_by_agent | UUID | FK to agent (nullable for user-created) |
| completed_at | timestamp | Set when status transitions to done |
| position | integer | Ordering within list |
| scope | text | user, team, account |
| scope_id | UUID | |
| source_item_id | UUID | FK to item (nullable, if task was generated from a ticket/request) |
| recurrence | text | RRULE string for recurring tasks (nullable). RFC 5545 format, e.g. FREQ=WEEKLY;BYDAY=MO |
| recurrence_parent_id | UUID | FK to task (self-referencing, nullable). Points to the original recurring task definition. |
| next_occurrence | timestamp | Next scheduled occurrence for the parent recurring task (nullable) |
| metadata | JSONB | Domain-specific fields |
| created_at | timestamp | |
| updated_at | timestamp |
Key indexes:
(account_id, assigned_to, status, due_date)for "what's due for this user?"(account_id, list_id, position)for ordered list view(account_id, scope, scope_id, status)for scoped task queries(account_id, recurrence_parent_id)for finding generated occurrences of a recurring task(next_occurrence)whererecurrence IS NOT NULLfor the recurring task scheduler
Relationship to items: When an item (ticket/request) generates work for a user, a task is created with source_item_id linking back. The item tracks the request lifecycle; the task tracks the user's work. Completing the task may or may not resolve the item.
Recurring tasks: A task with a recurrence RRULE is a recurring task definition. The platform generates individual task instances as children (linked via recurrence_parent_id) when each occurrence is due. The parent task's next_occurrence is updated after each generation. Completing or cancelling a child task does not affect the parent or future occurrences. Modifying or deleting the parent stops future generation.
8.3 task_type¶
Categorises tasks into system-reserved and account-custom types.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account (nullable: NULL for system types) |
| slug | text | Machine-readable identifier, e.g. standard, reminder, follow_up |
| name | text | Display name |
| description | text | Optional |
| is_system | boolean | true for platform-reserved types, false for account-created |
| icon | text | Optional icon identifier for UI |
| default_status | text | Default initial status for tasks of this type |
| metadata | JSONB | Type-specific configuration (e.g. allowed statuses, default priority) |
| created_at | timestamp |
System-reserved vs account-custom pattern: System types (is_system = true, account_id = NULL) are seeded by the platform and available to all accounts. They cannot be modified or deleted. Account-custom types (is_system = false, account_id set) are created by account admins for domain-specific workflows. System types include: standard, reminder, follow_up, onboarding, review, recurring.
Unique constraint: (account_id, slug). No duplicate slugs per account. System types use (NULL, slug).
8.4 contact¶
A person or organisation that the account interacts with.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account |
| type | text | person, organisation |
| name | text | Full name or organisation name |
| text | Primary email (nullable) | |
| phone | text | Primary phone (nullable) |
| title | text | Job title or role, persons only (nullable) |
| website | text | (nullable) |
| organisation_id | UUID | FK to contact (self-referencing, for "person works at org") |
| visibility | text | private, team, account |
| scope | text | user, team, account |
| scope_id | UUID | |
| source | text | manual, agent, import, integration |
| source_ref | text | External ID if imported (e.g. HubSpot contact ID) |
| metadata | JSONB | Flexible fields: address, social profiles, custom fields, deal info |
| created_by | UUID | FK to user_profile |
| created_by_agent | UUID | FK to agent |
| created_at | timestamp | |
| updated_at | timestamp |
Visibility model:
private: only the creating user can see this contact. Personal contacts (friends, family, personal network).team: visible to all members of the contact's team scope. Team-level business contacts.account: visible to all account members. Shared business contacts, customers, partners.
Admins and owners can see all contacts regardless of visibility (for data management purposes).
Organisation linking: A person contact can reference an organisation contact via organisation_id. This enables "show me everyone at Acme Corp" queries without a separate join table.
Integration sync: When an account connects HubSpot or another CRM, contacts can be synced bidirectionally. source = 'integration' and source_ref tracks the external ID to prevent duplicates and enable sync.
8.5 contact_interaction¶
A lightweight log of interactions with a contact. Not a full activity stream, just enough for agents to answer "when did we last talk to this person?"
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| contact_id | UUID | FK to contact |
| type | text | call, email, meeting, note, message, other |
| summary | text | One-line summary |
| detail | text | Optional longer description |
| occurred_at | timestamp | When the interaction happened |
| logged_by | UUID | FK to user_profile (nullable) |
| logged_by_agent | UUID | FK to agent (nullable) |
| metadata | JSONB | Duration, channel, linked email ID, etc. |
| created_at | timestamp |
8.6 item¶
An externally-initiated thing that needs processing. Items differ from tasks: tasks are "I need to do X", items are "X arrived and needs handling."
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account |
| item_type_id | UUID | FK to item_type (nullable, defaults to generic item) |
| type | text | support_ticket, request, bug, inquiry, approval, other |
| title | text | |
| description | text | Supports markdown |
| status | text | open, in_progress, waiting, resolved, closed |
| priority | text | urgent, high, normal, low |
| source | text | email, web, api, agent, manual, channel |
| source_ref | text | External reference (email message ID, form submission ID, etc.) |
| assigned_to_user | UUID | FK to user_profile (nullable) |
| assigned_to_team | UUID | FK to team (nullable) |
| assigned_to_agent | UUID | FK to agent (nullable, for auto-processing) |
| contact_id | UUID | FK to contact (who submitted, nullable) |
| resolved_at | timestamp | |
| closed_at | timestamp | |
| scope | text | team, account (items are never user-scoped) |
| scope_id | UUID | |
| metadata | JSONB | SLA data, form fields, channel context, etc. |
| created_by | UUID | FK to user_profile (nullable) |
| created_by_agent | UUID | FK to agent (nullable) |
| created_at | timestamp | |
| updated_at | timestamp |
Key indexes:
(account_id, status, priority)for queue management(account_id, assigned_to_user)for "my items"(account_id, contact_id)for "items from this contact"(account_id, type, status)for filtered views
Item to task relationship: When an item is assigned to a user, the assigning agent (or admin) may create a task linked via task.source_item_id. The item tracks the external request lifecycle. The task tracks the internal work. An item may generate zero, one, or many tasks.
Item workflow:
waiting = blocked on external input (waiting for customer reply, for example). resolved = work complete, pending confirmation. closed = confirmed done (or auto-closed after timeout).
8.7 item_type¶
Categorises items into system-reserved and account-custom types. Same pattern as task_type.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account (nullable: NULL for system types) |
| slug | text | Machine-readable identifier, e.g. support_ticket, request, bug |
| name | text | Display name |
| description | text | Optional |
| is_system | boolean | true for platform-reserved types |
| icon | text | Optional icon identifier |
| default_status | text | Default initial status |
| default_priority | text | Default priority |
| allowed_statuses | JSONB | Array of valid statuses for this type (nullable, uses default workflow if not set) |
| metadata | JSONB | Type-specific configuration |
| created_at | timestamp |
System types include: support_ticket, request, bug, inquiry, approval, other. Accounts can create custom types for domain-specific workflows (e.g. rfp, compliance_review, warranty_claim).
8.8 note¶
Freeform captured information. Meeting summaries, research, observations, ideas. Stored in markdown format.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account |
| note_type_id | UUID | FK to note_type (nullable, defaults to general note) |
| title | text | Optional |
| content | text | Markdown-format freeform text |
| scope | text | user, team, account |
| scope_id | UUID | |
| linked_type | text | task, contact, item, agent (nullable) |
| linked_id | UUID | FK to linked entity (nullable) |
| created_by | UUID | FK to user_profile (nullable) |
| created_by_agent | UUID | FK to agent (nullable) |
| metadata | JSONB | |
| created_at | timestamp | |
| updated_at | timestamp | |
| edited_at | timestamp | Set when content is modified after creation (nullable). Distinct from updated_at which changes on any field update. Enables "edited" indicators in the UI. |
Linking: A note can be linked to one primary entity. "Notes from call with John" uses linked_type = 'contact'. "Research for task X" uses linked_type = 'task'. Notes without links are standalone (ideas, observations).
Knowledge extraction: Notes are candidates for automatic knowledge fact extraction. When a note is created, the platform can optionally run the fact_extract processor to derive structured knowledge facts from the freeform content.
Storage format: Note content is stored as markdown. The API accepts and returns markdown. Client rendering is the responsibility of the UI layer.
8.9 note_share¶
Granular sharing of individual notes beyond their scope visibility.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| note_id | UUID | FK to note |
| shared_with_user | UUID | FK to user_profile (nullable, one of user/team must be set) |
| shared_with_team | UUID | FK to team (nullable) |
| permission | text | view, edit |
| shared_by | UUID | FK to user_profile |
| created_at | timestamp |
Purpose: Allows a user-scoped note to be shared with specific users or teams without changing its scope. A user can share their private meeting notes with a colleague without making them team-visible. The note's scope remains user but the note_share records grant additional access.
8.10 note_mention¶
Tracks @mentions within notes for cross-entity linking and notification.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| note_id | UUID | FK to note |
| mentioned_type | text | user, contact, task, item, agent |
| mentioned_id | UUID | ID of the mentioned entity |
| position | integer | Character offset in the note content (for UI highlighting) |
| created_at | timestamp |
Purpose: When a note contains @John or @Task:Fix login bug, the platform extracts mentions and stores them. This enables notifications to mentioned users, "notes mentioning me" queries, cross-entity discovery ("find all notes that mention this contact"), and UI autocomplete for @mentions.
8.11 note_type¶
Categorises notes into system-reserved and account-custom types. Same pattern as task_type and item_type.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account (nullable: NULL for system types) |
| slug | text | Machine-readable identifier, e.g. general, meeting, research |
| name | text | Display name |
| description | text | Optional |
| is_system | boolean | true for platform-reserved types |
| icon | text | Optional icon identifier |
| metadata | JSONB | Type-specific configuration |
| created_at | timestamp |
System types include: general, meeting, research, decision, standup, retrospective, idea. Accounts can create custom types for domain-specific note categories.
8.12 tag¶
Cross-cutting categorisation for any entity.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account |
| name | text | Display name (e.g. "Urgent", "VIP", "Q2", "Follow-up") |
| colour | text | Hex colour for UI display |
| created_at | timestamp |
Unique constraint: (account_id, name). No duplicate tag names per account.
8.13 entity_tag¶
Polymorphic join table linking tags to any entity.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| tag_id | UUID | FK to tag |
| entity_type | text | task, contact, item, note |
| entity_id | UUID | |
| created_at | timestamp |
Unique constraint: (tag_id, entity_type, entity_id). No duplicate tag assignments.
8.14 Entity attachments¶
Media files (images, documents, etc.) can be attached to any core data entity using the media table's polymorphic linking fields (linked_type and linked_id). This uses the existing media entity (section 15) rather than a separate attachment table. When a user attaches a file to a task or note, a media record is created with linked_type and linked_id pointing to the entity. Querying attachments is a simple filter on media by those two fields.
9. Knowledge¶
9.1 knowledge_fact¶
A structured piece of knowledge with scope and ownership. For the four-layer knowledge architecture (agent, account, team, user) and runtime resolution see doc 03 section 11.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| scope | enum | agent, account, team, user |
| scope_id | UUID | FK to agent, account, team, or user_profile |
| agent_id | UUID | FK to agent (which agent this knowledge is for) |
| subject | string | What the fact is about |
| predicate | string | The relationship or attribute |
| value | text | The fact content |
| category | string | Classification: identity, preference, project, procedure, etc. |
| confidence | float | Extraction confidence (0.0 to 1.0) |
| source_interaction_id | UUID | FK to interaction that produced this fact |
| embedding | vector(1536) | Semantic embedding for similarity search |
| access_count | int | How often this fact has been retrieved |
| last_accessed | timestamp | Last retrieval time |
| created_at | timestamp | |
| updated_at | timestamp |
Rules:
scopeandscope_idtogether determine visibility:agentscope: visible whenever this agent is used.accountscope: visible to all account members using this agent.teamscope: visible to team members only.userscope: visible only to that specific user.- Precedence for conflicts: account > agent > team > user.
- User facts are portable: they follow the user across accounts.
- Team facts stay with the team when a user leaves.
- When an agent delegates to another agent, knowledge extracted during the delegate's interaction is scoped to the delegate agent's assignment context. The invoking agent does not automatically receive knowledge extracted by the delegate. Cross-agent knowledge sharing happens through normal interaction flow.
10. Learned workflows¶
10.1 workflow¶
A codified problem-solving pattern learned by an agent. Distinct from the Convex Workflow component (which provides durable execution infrastructure); this entity captures reusable reasoning patterns.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| agent_id | UUID | FK to agent that learned this workflow |
| name | string | Human-readable name |
| description | text | What problem this workflow solves |
| trigger_pattern | text | What kind of request triggers this workflow |
| steps | JSONB | Ordered list of step definitions |
| scope | enum | agent, team, account |
| scope_id | UUID | FK to scope entity (for promoted workflows) |
| success_count | int | Times used successfully |
| version | int | Workflow version |
| status | enum | draft, active, approved, deprecated |
| created_at | timestamp | |
| updated_at | timestamp |
Rules:
- Workflows start in agent scope (private to the agent).
- Admins can promote workflows to team or account scope.
- Promotion creates a copy; the original remains with the agent.
- Workflows require approval before becoming active at team or account level.
11. Events¶
11.1 event¶
The append-only event store. For the event system design and harness integration see doc 06.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| type | string | Event type (e.g. message.received, job.state_changed) |
| source | string | Originating service |
| agent_id | UUID | FK to agent |
| user_id | UUID | FK to user_profile (nullable) |
| team_id | UUID | FK to team (nullable) |
| account_id | UUID | FK to account (nullable) |
| session_id | UUID | Session context |
| parent_id | UUID | Parent event for chains |
| direction | enum | inbound, outbound, internal |
| content | text | Event content (message text, etc.) |
| payload | JSONB | Type-specific structured data |
| metadata | JSONB | Trace ID, version, priority |
| created_at | timestamp | Event timestamp |
Rules:
- Events are immutable: never updated or deleted.
- Partitioned by created_at (monthly) for performance in the legacy model.
- The system of record: all state can be derived from events.
- Subject to data retention policies (archival after configurable period).
- Job-related events (
job.created,job.dispatched,job.state_changed,job.resolved,job.failed,job.cancelled,job.timed_out) follow the same immutability and retention rules.
12. Communications and channels¶
12.1 channel_config¶
Channel type configurations. For the full channel architecture see doc 06.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account |
| channel_type | enum | telegram, email, web, api |
| config | JSONB | Configuration (encrypted bot tokens, API keys, etc.) |
| status | enum | active, disabled |
| created_at | timestamp | |
| updated_at | timestamp |
Rules:
- Credentials in config are encrypted in the secrets vault; only references are stored here.
- Each account can have multiple channels of the same type (multiple email accounts, for example).
12.2 user_channel¶
User-to-channel identity links. Connects a user's external identity (Telegram ID, email address) to their Thinklio account so messages from any linked channel are attributed to the correct user.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| user_id | UUID | FK to user_profile |
| channel_type | string | Channel identifier (e.g. telegram, email) |
| external_id | string | User's ID in the external system (Telegram user ID, email address) |
| display_name | string | How the user appears in that channel |
| status | enum | pending_verification, active, disabled |
| verified_at | timestamp | When verification completed |
| last_active_at | timestamp | Last message or activity |
| created_at | timestamp |
12.3 user_comm¶
Outbound notification dispatch queue. Handles messages that the platform needs to deliver to users outside of agent conversations.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account |
| user_id | UUID | FK to user_profile |
| comm_type | text | task_reminder, task_due, item_assigned, mention, share, digest, system |
| channel | text | Preferred delivery channel: email, push, in_app, telegram |
| subject | text | Message subject/title |
| body | text | Message content (markdown) |
| entity_type | text | Related entity type (nullable): task, item, note, etc. |
| entity_id | UUID | Related entity ID (nullable) |
| status | text | pending, sent, delivered, failed, cancelled |
| scheduled_for | timestamp | When to send (nullable: NULL = send immediately) |
| sent_at | timestamp | When actually sent (nullable) |
| error | text | Error message if delivery failed (nullable) |
| metadata | JSONB | Channel-specific data, retry count, etc. |
| created_at | timestamp |
Dispatch pattern: A background worker polls user_comm for pending messages, resolves the user's preferred delivery channel, and dispatches via the appropriate transport (email via Postmark, push notification, in-app notification record, or Telegram message). Failed deliveries are retried with exponential backoff. A reminders module generates task reminder and due-date notifications by scanning tasks and creating user_comm records.
Relationship to notifications: The notification table stores in-app notifications (visible in the UI notification centre). user_comm is the dispatch queue for all outbound channels. When channel = 'in_app', the worker creates a notification record. For other channels, it dispatches externally.
12.4 notification¶
In-app notification records, visible in the UI notification centre.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| user_id | UUID | FK to user_profile |
| account_id | UUID | FK to account |
| title | string | Notification title |
| body | text | Notification body |
| notification_type | string | Type: task_assigned, item_update, mention, etc. |
| reference_type | string | Entity type referenced |
| reference_id | UUID | Entity ID referenced |
| read_at | timestamp | When the user read it (nullable) |
| created_at | timestamp |
13. Planning and execution scoring¶
13.1 canonical_plan¶
Reusable plan definitions for agent reasoning. Plans codify problem-solving approaches that agents can evaluate and select based on effectiveness history.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| name | string | Plan name |
| description | text | What this plan does |
| plan_type | string | Category: research, analysis, coordination, etc. |
| steps | JSONB | Ordered step definitions |
| scope | enum | platform, account, agent |
| scope_id | UUID | FK to agent or account (for scoped plans) |
| created_at | timestamp |
13.2 execution_outcome¶
Recorded outcomes of plan executions, providing the raw data for effectiveness scoring.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| canonical_plan_id | UUID | FK to canonical_plan |
| agent_id | UUID | FK to agent |
| interaction_id | UUID | FK to interaction |
| success | boolean | Whether the execution succeeded |
| cost | decimal | Execution cost |
| duration_ms | int | Execution time in milliseconds |
| context_hash | string | Hash of execution context for grouping similar executions |
| created_at | timestamp |
13.3 plan_score¶
Bayesian scores for plan effectiveness. Recalculated as new execution outcomes are recorded.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| canonical_plan_id | UUID | FK to canonical_plan |
| agent_id | UUID | FK to agent |
| context_hash | string | Hash of execution context |
| score | float | Effectiveness score (0.0 to 1.0) |
| sample_count | int | Number of executions scored |
| last_updated | timestamp | When the score was last recalculated |
14. Platform services¶
14.1 platform_service¶
External service registry. Tracks all third-party services the platform integrates with.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| slug | string | Unique identifier (e.g. openai, anthropic, github) |
| name | string | Display name |
| description | text | What this service is |
| service_type | enum | llm, integration, external_tool |
| base_url | string | Service base URL |
| status | enum | active, disabled |
| created_at | timestamp |
14.2 llm_model¶
Available LLM models with pricing.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| platform_service_id | UUID | FK to platform_service |
| slug | string | Model identifier (e.g. claude-3-opus) |
| name | string | Display name |
| provider | string | Provider name |
| input_cost_per_1k | decimal | Cost per 1000 input tokens (in account currency) |
| output_cost_per_1k | decimal | Cost per 1000 output tokens |
| context_window | int | Maximum context window in tokens |
| capabilities | JSONB | Model capabilities: vision, tool_use, etc. |
| status | enum | active, deprecated, disabled |
| created_at | timestamp |
14.3 account_service_config¶
Account-level service configuration overrides. Allows accounts to bring their own API keys.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account |
| platform_service_id | UUID | FK to platform_service |
| api_key_ref | string | Secrets vault reference for API key |
| config | JSONB | Account-specific configuration |
| created_at | timestamp |
Rules:
- Allows accounts to use their own API keys with external services.
api_key_refpoints to an encrypted vault entry; keys are never stored directly in this table.
14.4 account_llm_preference¶
Account-level LLM model preferences by performance tier.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account |
| tier | enum | fast, balanced, powerful |
| llm_model_id | UUID | FK to llm_model |
| created_at | timestamp |
Rules:
- Each account specifies a preferred model for each performance tier.
- Agents use these preferences unless they override at the agent level.
15. Media, storage and libraries¶
These entities support file storage, processing, and document-grounded knowledge retrieval. They are distinct from the knowledge_fact layer: facts are small, structured, accumulated through interaction; media-derived content is large, document-sourced, loaded deliberately by operators and agents. For the full ingestion pipeline see doc 05 Persistence, Storage & Ingestion.
15.1 storage_bucket¶
A registry of all storage buckets available to the platform, covering three tiers: Thinklio-managed shared buckets, Thinklio-managed dedicated enterprise buckets, and account-supplied BYOB (bring your own bucket) buckets.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| name | string | Internal name (e.g. thinklio-au-01, acme-corp-r2) |
| bucket_type | enum | platform_shared, enterprise_dedicated, account_supplied |
| provider | string | r2, s3, minio, gcs: any S3-compatible provider |
| endpoint | string | S3-compatible endpoint URL |
| bucket_name | string | Bucket name at the provider |
| jurisdiction | string | AU, EU, US, OTHER: declared data residency region |
| account_id | UUID | FK to account; null for platform_shared; set for dedicated and account-supplied |
| credentials_ref | string | Reference to secrets vault entry; null for platform and enterprise buckets (credentials held by Thinklio ops) |
| validation_status | enum | pending, valid, invalid: result of last connectivity test |
| validated_at | timestamp | Timestamp of last successful validation |
| is_active | boolean | Whether this bucket accepts new uploads |
| created_at | timestamp |
Bucket types:
platform_shared: Managed by Thinklio operations; available to all accounts; credentials held in platform infrastructure. Thinklio guarantees data residency within the declared jurisdiction.enterprise_dedicated: Managed by Thinklio operations; provisioned exclusively for one enterprise account. Used where dedicated infrastructure is required without the account managing the bucket themselves.account_supplied: Registered by the account admin. Any S3-compatible provider. Credentials encrypted into the platform secrets vault at registration;credentials_refis the vault lookup key. Thinklio accepts the account's jurisdiction declaration on trust.
Credential security: Access key and secret key for account-supplied buckets are never stored in this table. They are stored in the secrets vault; credentials_ref is the only field written here.
Rules:
- At account creation, the system assigns the nearest-jurisdiction active
platform_sharedbucket as the default. - Enterprise customers may have one or more
enterprise_dedicatedbuckets assigned by Thinklio ops. - Any account can register one or more
account_suppliedbuckets via account settings; each undergoes a validation job (PUT/GET/DELETE test) before becoming active. - Bucket selection at upload time uses the account's current default bucket; no silent fallback between buckets.
15.2 account_storage_bucket¶
Links accounts to storage buckets. An account can have multiple active buckets; exactly one is the default for new uploads at any time.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account |
| bucket_id | UUID | FK to storage_bucket |
| is_default | boolean | Whether this is the account's active default bucket |
| assigned_by | enum | system (auto at account creation), ops (Thinklio operations), account_admin (account-registered BYOB) |
| assigned_at | timestamp |
Rules:
- Exactly one default bucket per account at any time (enforced by partial unique index).
systemassignment happens at account creation;opsassignment is for enterprise-dedicated buckets;account_adminassignment follows a successful BYOB validation.- Changing the default does not migrate existing media. New uploads go to the new default; existing media stays in its original bucket (each media record carries
bucket_id).
15.3 media¶
The central entity for all files stored in Thinklio. Any file uploaded by a user or created as an artefact by an agent is a media record. Media is a general-purpose service: agent knowledge ingestion, report outputs, user uploads, and agent-generated documents all use the same entity.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account (nullable for personal or platform media) |
| scope | enum | agent, account, team, user |
| scope_id | UUID | FK to scope entity |
| agent_id | UUID | FK to agent (nullable: not all media is agent-related) |
| uploaded_by | UUID | FK to user_profile |
| original_filename | string | Filename as provided at upload or creation |
| stored_filename | string | UUID-based filename used in the bucket (avoids collisions and path traversal) |
| content_type | string | MIME type |
| file_size | bigint | Size in bytes |
| content_hash | string | SHA-256 hash for deduplication and integrity verification |
| bucket_id | UUID | FK to storage_bucket |
| bucket_key | string | Full object key/path within the bucket |
| status | enum | pending, processing, ready, failed, archived |
| keywords | text[] | Populated by enrichment processors |
| metadata | JSONB | Variable; populated by processors (image analysis results, sentiment, content flags, etc.) |
| summary | text | Nullable; plain-text summary generated by the summarise processor (capped at ~300 words) |
| summary_embedding | vector(1536) | Nullable; embedding of summary for lightweight single-vector retrieval |
| linked_type | text | Polymorphic entity type if linked: task, contact, item, note, etc. |
| linked_id | UUID | ID of the linked entity (nullable) |
| created_at | timestamp | |
| updated_at | timestamp |
Processing tiers:
- Level 1 (automatic): The media record itself: filename, size, type, hash, bucket location. Created for every upload.
- Level 2 (enrichment): Optional processors governed by account/agent rules. Outputs go to
metadata,keywords,summary, andsummary_embedding. Includes image analysis, content moderation, PDF text extraction, and summarisation. - Level 3 (full indexing): Chunking, embedding, and optional fact extraction. Creates library_item records. Triggered explicitly by intent at upload time, by processing rules, or by a
recommended_for_indexingsignal from the summarise processor.
Rules:
- The original file in the bucket is immutable. All processing is derived and re-derivable from the stored blob.
content_hashenables deduplication: if the same hash already exists within scope, reference the existing blob rather than storing a duplicate.summaryandsummary_embeddingtogether enable useful retrieval without full indexing. Any summarised document is discoverable via semantic search even if never chunked.- Deleting a media record removes the bucket object, all derived library_item records, and any knowledge_fact records with
source_media_idpointing to this record. - Media scope follows the same visibility rules as knowledge_fact.
linked_typeandlinked_idenable polymorphic attachment to entities. A user can attach a document to a task, item, contact, or note.
15.4 media_processor¶
Platform-registered processor definitions. Each processor is a discrete, independently runnable unit of work that can be applied to a media record. Processors are managed by Thinklio; accounts and users select which processors run via rules, but cannot define new processor types.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| slug | string | Unique identifier: image_analysis, pdf_extract, summarise, chunk_and_index, content_moderation, virus_scan |
| name | string | Display name |
| description | text | What this processor does and what it outputs |
| applicable_mime_types | text[] | MIME type patterns: application/pdf, image/*, * |
| processing_tier | enum | level_2_enrichment or level_3_indexing |
| output_targets | text[] | Where output is written: metadata, keywords, summary, summary_embedding, library_items, knowledge_facts |
| depends_on | text[] | Slugs of processors that must complete successfully before this one can run |
| config_schema | JSONB | JSON Schema for processor-specific configuration options |
| status | enum | active, deprecated |
| version | string | Processor version |
| created_at | timestamp |
Rules:
depends_onis evaluated at job dispatch time. A processor whose dependencies have not completed is held inpendinguntil they do.processing_tierdetermines which governance layer controls enablement: Level 2 processors are available by default subject to account policy; Level 3 processors require explicit intent or a qualifying rule.- Processors with
output_targetsincludingsummaryorsummary_embeddingmust only run against text-extractable content types.
15.5 media_processing_rule¶
Governs which processors run on which media, under what conditions. Rules are evaluated when a media record is created or reprocessed. The three-tier governance model mirrors the account/team/agent structure.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| scope | enum | platform, account, agent |
| scope_id | UUID | FK to scope entity (null for platform rules) |
| processor_id | UUID | FK to media_processor |
| name | string | Human-readable rule name |
| mime_type_pattern | string | MIME type glob pattern this rule applies to |
| conditions | JSONB | Additional trigger conditions (e.g. file_size_min, recommended_for_indexing: true) |
| processor_config | JSONB | Configuration passed to the processor (e.g. summary_max_words: 300, target_library_id) |
| priority | int | Evaluation order; lower numbers are evaluated first |
| status | enum | active, disabled |
| created_at | timestamp | |
| updated_at | timestamp |
Rules:
- Platform rules apply to all accounts unless overridden at account or agent scope.
- Account rules apply to all agents within that account.
- Agent rules apply only to that specific agent.
- Rules can only narrow processor usage at lower scopes. An account cannot enable a processor that platform governance has disabled.
- The
conditionsfield supportsrecommended_for_indexing: trueas a trigger condition, enabling the summarise processor to signal Level 3 readiness and have a chunking rule fire automatically, subject to other conditions and governance approval.
15.6 media_processing_job¶
Tracks each processor run against each media record. One record per processor per media file, updated on retry.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| media_id | UUID | FK to media |
| processor_id | UUID | FK to media_processor |
| rule_id | UUID | FK to media_processing_rule (nullable for manually triggered runs) |
| status | enum | pending, running, completed, failed, skipped |
| input_snapshot | JSONB | Parameters passed to the processor |
| output | JSONB | Processor result prior to writing to output targets |
| error_data | JSONB | Failure reason and details |
| started_at | timestamp | |
| completed_at | timestamp | |
| created_at | timestamp |
Rules:
- At most one active (pending or running) job per processor per media record.
- Failed jobs can be retried; the existing record is updated, not replaced.
skippedindicates that the processor's MIME type pattern or conditions were not met, or a dependency failed.- Output is retained for audit, debugging, and reprocessing purposes.
15.7 library¶
A named, curated collection of media-derived content available for semantic retrieval. Libraries are the primary knowledge corpus for agents that depend on document-grounded responses (Coach Agent, HR Agent, Knowledge Base Agent, and others).
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| name | string | Display name (e.g. "General Coaching Methodology", "Acme Corp HR Policies") |
| description | text | What this library contains and what it is for |
| scope | enum | platform (Thinklio-managed, reusable across all accounts) or account (customer-owned) |
| account_id | UUID | FK to account (null for platform libraries) |
| embedding_model | string | Embedding model used for all items in this library. Must be consistent for valid similarity search. |
| chunk_strategy | JSONB | Chunking configuration: target_tokens, overlap_tokens, boundary_strategy |
| status | enum | active, archived |
| created_by | UUID | FK to user_profile |
| created_at | timestamp | |
| updated_at | timestamp |
Rules:
- Platform libraries are Thinklio-managed and available to all accounts, useful for reusable domain corpora (general coaching methodology, standard HR frameworks, etc.).
- Account libraries are customer-owned and visible only within that account.
- All items in a library must use the same embedding model. Changing the model requires reprocessing all items.
- Archiving a library prevents new items from being added but does not remove existing items; agents connected to an archived library can still retrieve from it.
15.8 library_item¶
A chunk of content from a media file, stored with an embedding for semantic retrieval. The atomic unit of library knowledge.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| library_id | UUID | FK to library |
| media_id | UUID | FK to media (the source file) |
| chunk_index | int | Position of this chunk within the source document |
| content | text | The chunk text |
| token_count | int | Approximate token count |
| embedding | vector(1536) | Semantic embedding for similarity search |
| metadata | JSONB | Source location (page number, section heading, etc.) and other attributes |
| created_at | timestamp |
Rules:
- Created by the
chunk_and_indexprocessor; not manually inserted. - If the source media record is deleted, all derived library_item records are cascade-deleted.
- Reprocessing a media file within a library deletes and recreates its items for that library.
- A media file can contribute items to multiple libraries (a policy document added to both the account library and a specialist compliance library, for example).
15.9 agent_library¶
Links agents to libraries, governing which libraries are searched during context assembly.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| agent_id | UUID | FK to agent |
| library_id | UUID | FK to library |
| priority | int | Search order when multiple libraries are attached. Lower number = higher priority. |
| created_at | timestamp |
Rules:
- An agent can be connected to multiple libraries.
- Priority determines search order in context assembly. Higher-priority library results are retrieved first and weighted higher when the token budget forces truncation.
agent_catalog.library_assignmentspre-configures these associations; when an agent is deployed from a template the library connections are created automatically.
16. Access and security¶
For the full security model, secrets vault design, and governance policy framework see doc 07.
16.1 invitation¶
Account and team invitations.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account |
| team_id | UUID | FK to team (nullable) |
| invited_email | string | Email address being invited |
| invited_by | UUID | FK to user_profile |
| role | string | Role being offered: owner, admin, editor, viewer |
| token | string | One-time invitation token |
| status | enum | pending, accepted, expired, revoked |
| expires_at | timestamp | When the invitation expires |
| created_at | timestamp |
16.2 api_key¶
API keys for programmatic access.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account |
| name | string | Key name for identification |
| key_hash | string | Hashed key (never store plaintext) |
| scope | JSONB | Allowed endpoints and methods |
| status | enum | active, revoked |
| last_used_at | timestamp | When the key was last used |
| created_by | UUID | FK to user_profile |
| created_at | timestamp |
16.3 oauth_token¶
OAuth tokens for external service integrations.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account |
| platform_service_id | UUID | FK to platform_service |
| access_token_ref | string | Vault reference (never stored plaintext) |
| refresh_token_ref | string | Vault reference (nullable) |
| expires_at | timestamp | Token expiration |
| created_at | timestamp | |
| updated_at | timestamp | Last refresh |
16.4 webhook_subscription¶
Webhook subscription configurations for outbound event delivery.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account |
| url | string | Webhook URL |
| events | text[] | Event types to subscribe to |
| secret_ref | string | Vault reference for webhook signing secret |
| status | enum | active, disabled |
| created_at | timestamp |
17. Billing and resource management¶
17.1 credit_ledger¶
Credit transaction log. Every credit and debit is recorded as an immutable ledger entry.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account |
| amount | decimal | Credit amount (positive for credits, negative for usage) |
| balance_after | decimal | Running balance after transaction |
| description | string | Reason for transaction |
| reference_type | string | Type of entity referenced: interaction, job, etc. |
| reference_id | UUID | ID of referenced entity |
| created_at | timestamp | Transaction date |
17.2 budget_limit¶
Budget limits per scope.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| account_id | UUID | FK to account |
| scope | enum | account, team, user |
| scope_id | UUID | FK to team or user_profile (nullable for account scope) |
| monthly_limit | decimal | Monthly spending limit |
| current_spend | decimal | Current month's spending |
| period_start | timestamp | Start of current billing period |
| created_at | timestamp | |
| updated_at | timestamp |
17.3 usage_record¶
Resource consumption records. One record per billable unit of work.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| interaction_id | UUID | FK to interaction (nullable) |
| step_id | UUID | FK to step (nullable) |
| agent_id | UUID | FK to agent |
| user_id | UUID | FK to user_profile |
| team_id | UUID | FK to team (nullable) |
| account_id | UUID | FK to account |
| cost | decimal | Cost of this usage |
| cost_detail | JSONB | Breakdown: tokens, API calls, duration, etc. |
| created_at | timestamp |
17.4 quality_rating¶
User feedback ratings on agent interactions.
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| interaction_id | UUID | FK to interaction |
| user_id | UUID | FK to user_profile |
| rating | int | Score from 1 to 5 |
| comment | text | Optional feedback comment |
| created_at | timestamp |
17.5 platform_config¶
Global platform configuration (singleton).
| Field | Type | Description |
|---|---|---|
| id | UUID | PK |
| config | JSONB | Platform configuration: feature flags, defaults, limits, etc. |
| updated_at | timestamp | Last update |
18. Agent tool integration¶
The core data structures are exposed as native tools in the tool registry. Every agent can use them based on its tool assignments.
18.1 Task tools¶
| Tool slug | Type | Trust level | Description |
|---|---|---|---|
native_task_create |
internal | low_risk_write | Create a task (optionally in a list) |
native_task_update |
internal | low_risk_write | Update status, priority, assignment, due date |
native_task_list |
internal | read | List tasks with filters (status, assigned_to, due_date, list) |
native_task_search |
internal | read | Search tasks by keyword |
native_task_complete |
internal | low_risk_write | Mark task as done |
18.2 Contact tools¶
| Tool slug | Type | Trust level | Description |
|---|---|---|---|
native_contact_create |
internal | low_risk_write | Create a person or organisation |
native_contact_update |
internal | low_risk_write | Update contact details |
native_contact_search |
internal | read | Search contacts by name, email, organisation |
native_contact_log |
internal | low_risk_write | Log an interaction with a contact |
native_contact_history |
internal | read | Get interaction history for a contact |
18.3 Item tools¶
| Tool slug | Type | Trust level | Description |
|---|---|---|---|
native_item_create |
internal | low_risk_write | Create a ticket/request/inquiry |
native_item_update |
internal | low_risk_write | Update status, assignment, priority |
native_item_list |
internal | read | List items with filters (type, status, assigned) |
native_item_assign |
internal | low_risk_write | Assign to user, team, or agent |
18.4 Note tools¶
| Tool slug | Type | Trust level | Description |
|---|---|---|---|
native_note_create |
internal | low_risk_write | Create a note, optionally linked to an entity |
native_note_search |
internal | read | Search notes by keyword |
18.5 Tag tools¶
| Tool slug | Type | Trust level | Description |
|---|---|---|---|
native_tag_apply |
internal | low_risk_write | Apply a tag to any entity |
native_tag_remove |
internal | low_risk_write | Remove a tag from an entity |
18.6 Relationship to external integration tools¶
The existing external tools (tasks_create, tasks_list, crm_search_contacts, etc.) continue to work with external providers (Todoist, HubSpot). The native_* tools operate on the platform's own data.
Accounts can configure sync between native structures and external services:
- Todoist sync: native tasks to/from Todoist tasks (two-way).
- HubSpot sync: native contacts to/from HubSpot contacts (two-way).
When sync is active, both the native tool and the external tool write to the same underlying data, with conflict resolution based on last-modified timestamp.
When sync is not active, the native and external tools operate independently. An agent can use native_task_create for internal tasks and tasks_create (Todoist) for externally-managed tasks.
19. API endpoints¶
All endpoints require authentication and respect scoping rules. In the legacy model, authentication uses Supabase JWT. In the Convex model, authentication uses Clerk session tokens validated by Convex middleware.
19.1 Tasks¶
GET /v1/tasks?scope=user&scope_id=...&status=todo&list_id=...
POST /v1/tasks Create task
GET /v1/tasks/{id} Get task
PATCH /v1/tasks/{id} Update task
DELETE /v1/tasks/{id} Delete task
GET /v1/task-lists?scope=user&scope_id=...
POST /v1/task-lists Create list
PATCH /v1/task-lists/{id} Update list
DELETE /v1/task-lists/{id} Archive list
19.2 Task types¶
GET /v1/task-types?account_id=... List task types (system + account-custom)
POST /v1/task-types Create custom task type (admin only)
GET /v1/task-types/{id} Get task type
PATCH /v1/task-types/{id} Update custom task type (admin only, system types immutable)
DELETE /v1/task-types/{id} Delete custom task type (admin only, system types immutable)
19.3 Contacts¶
GET /v1/contacts?scope=account&scope_id=...&type=person&q=search
POST /v1/contacts Create contact
GET /v1/contacts/{id} Get contact with recent interactions
PATCH /v1/contacts/{id} Update contact
DELETE /v1/contacts/{id} Delete contact
POST /v1/contacts/{id}/interactions Log an interaction
GET /v1/contacts/{id}/interactions List interactions
19.4 Items¶
GET /v1/items?scope=account&scope_id=...&type=support_ticket&status=open
POST /v1/items Create item
GET /v1/items/{id} Get item with linked tasks
PATCH /v1/items/{id} Update item
POST /v1/items/{id}/assign Assign to user/team/agent
19.5 Item types¶
GET /v1/item-types?account_id=... List item types (system + account-custom)
POST /v1/item-types Create custom item type
GET /v1/item-types/{id} Get item type
PATCH /v1/item-types/{id} Update custom item type
DELETE /v1/item-types/{id} Delete custom item type
19.6 Notes¶
GET /v1/notes?scope=user&scope_id=...&linked_type=contact&linked_id=...
POST /v1/notes Create note
GET /v1/notes/{id} Get note
PATCH /v1/notes/{id} Update note
DELETE /v1/notes/{id} Delete note
POST /v1/notes/{id}/share Share note with user or team
DELETE /v1/notes/{id}/share/{share_id} Revoke a share
GET /v1/notes/{id}/mentions List mentions in a note
19.7 Note types¶
GET /v1/note-types?account_id=... List note types (system + account-custom)
POST /v1/note-types Create custom note type
GET /v1/note-types/{id} Get note type
PATCH /v1/note-types/{id} Update custom note type
DELETE /v1/note-types/{id} Delete custom note type
19.8 Mentions¶
Returns matching entities across users, contacts, tasks, items, and agents. Used by the UI for autocomplete when typing @ in a note. Results are scoped to the caller's visibility.
19.9 Tags¶
GET /v1/tags?account_id=... List account's tags
POST /v1/tags Create tag
POST /v1/tags/apply { tag_id, entity_type, entity_id }
POST /v1/tags/remove { tag_id, entity_type, entity_id }
19.10 Agent events¶
The POST /v1/agent-events endpoint provides bidirectional agent communication. External systems and agents can push structured events into the platform, and the platform routes them to the appropriate agent for processing.
Event structure:
{
"agent_id": "uuid",
"event_type": "deployment.completed",
"payload": { ... },
"source": "github-actions",
"correlation_id": "optional-tracking-id"
}
Processing: The gateway receives the event, validates the agent exists and the caller has permission, then routes it through the standard event bus. The agent processes it as a non-conversational interaction: the agent can take action (create tasks, update items, notify users via user_comm) without requiring a user conversation.
Use cases: CI/CD pipeline notifies a DevOps agent of deployment status. Monitoring system alerts an ops agent of threshold breaches. External CRM pushes contact updates to a CRM agent. Scheduled cron triggers a daily digest agent.
20. Agent usage patterns¶
The following scenarios illustrate how agents use the core data structures in combination.
20.1 Personal assistant¶
The canonical use case. A personal assistant agent uses all four structures:
- Tasks: "Remind me to call John tomorrow" creates a task via
native_task_createwith due_date. - Contacts: "I just met Sarah from Acme at the conference" creates a contact via
native_contact_createand logs the meeting vianative_contact_log. - Notes: "Here are my notes from today's meeting" creates a note via
native_note_createlinked to attendee contacts. - Items: Less common, but "I got a request from the finance team" creates an item via
native_item_create.
Daily briefing: the agent queries tasks due today, overdue items, recent contact interactions, and surfaces them in the briefing page.
20.2 Support agent¶
Primarily works with items and contacts:
- Inbound email triggers the agent to create an item of type
support_ticketwithsource = 'email'and link to the contact. - Agent triages: sets priority, assigns to team or user.
- Assignment creates a task for the assignee: "Investigate billing issue for Acme".
- Agent logs interactions: "Replied to customer with workaround".
- Resolution: item transitions through
in_progresstoresolvedtoclosed.
20.3 CRM agent¶
Primarily works with contacts and contact interactions:
- "Log a call with John at Acme, discussed renewal, he's interested in the enterprise plan."
- Agent creates a contact interaction, updates contact metadata with deal context.
- Agent can query: "Who haven't we spoken to in 30 days?" via
native_contact_searchwith last interaction filter. - Syncs with HubSpot if configured.
20.4 Task management agent¶
The dedicated task organiser:
- Creates and manages task lists: "Create a list for the product launch."
- Reprioritises: "What's the most important thing I should work on today?"
- Reports: "Give me a summary of what the team completed this week."
- Reminders: surfaces overdue tasks and approaching deadlines.
- Reorganisation: move tasks between lists, batch-update priorities.
20.5 HR / onboarding agent¶
Uses items for requests and tasks for onboarding workflows:
- New hire notification creates an item of type
request. - Generates a task list "Onboarding: Jane Smith" with steps (IT setup, badge, training, etc.).
- Tracks progress, reminds responsible parties, reports to HR admin.
20.6 Meeting agent¶
Primarily creates notes and tasks:
- Post-meeting: agent creates a note with meeting summary linked to attendee contacts.
- Extracts action items and creates tasks assigned to the responsible people.
- Logs the meeting as a contact interaction for each attendee.
20.7 Custom and external agents¶
Third-party agents built on the Thinklio platform use the same tools:
- A custom "Legal Review" agent creates items of type
approvaland tracks document review tasks. - A custom "Inventory" agent uses tasks to track restock actions and contacts to manage supplier relationships.
- An external integration creates items via the REST API when events occur in their system.
The tool and API interfaces are identical for built-in and custom agents. No special access or different data model is needed.
21. Data lifecycle¶
21.1 Retention policies¶
| Data type | Default retention | Configurable | Archive strategy |
|---|---|---|---|
| Events | 90 days active, then archived | Per account | Move to archive table |
| Interactions | 90 days active | Per account | Move to archive table |
| Steps | 90 days active | Per account | Move to archive table |
| Jobs | 90 days active (from terminal state) | Per account | Move to archive table |
| Subjobs | 90 days active (follows parent job) | Per account | Move to archive table |
| Tasks | Indefinite (unless deleted) | Per account | Soft delete with TTL |
| Contacts | Indefinite (unless deleted) | Per account | Soft delete with TTL |
| Items | Indefinite (unless deleted) | Per account | Soft delete with TTL |
| Notes | Indefinite (unless deleted) | Per account | Soft delete with TTL |
| Knowledge facts | Indefinite | Per account | Soft delete with TTL |
| Audit logs | 1 year | Per account (min 90 days) | Cold storage |
| User data | Until deletion request | N/A | Hard delete with compliance log |
21.2 Data deletion (GDPR right to erasure)¶
When a user requests deletion:
- User knowledge facts: hard deleted.
- User's messages in events: anonymised (content replaced, user_id nullified).
- User's interactions and steps: anonymised.
- User's job observer registrations: removed.
- Team knowledge contributions: remain, attributed to "former member".
- User's tasks, items, notes: marked as deleted and retained per retention policy.
- Deletion logged in compliance audit trail.
21.3 Sync architecture (future)¶
For accounts that use external services alongside native structures:
- Native task to/from Todoist task.
- Native contact to/from HubSpot contact.
- Native item to/from Zendesk ticket.
Sync principles:
- Last-write-wins with configurable conflict resolution.
- Sync is per-account, per-service, opt-in.
- External ID stored in
source_refto prevent duplicates. - Sync runs on a schedule (polling) or via webhooks (push).
- Sync failures are logged but do not block the native operation.
Implementation: A sync worker subscribes to change events on native entities and pushes to the external service. Inbound changes arrive via webhooks or polling and update the native entity. The metadata field stores sync state (last sync timestamp, external version).
This is a Phase 2+ feature. Phase 1 operates native and external independently.
22. Open questions¶
- Convex schema mapping. The conceptual model uses UUIDs, enums, JSONB, and vector fields. Convex uses document IDs, string unions, validated objects, and vector indexes. A formal mapping document for the Convex schema may be useful as a companion to this conceptual model, or the conceptual model should be updated to use Convex-native types throughout once the migration is complete.
- Type system extensibility. The system-reserved vs account-custom type pattern is consistent across task_type, item_type, and note_type. Should there be a single generic
entity_typetable with adomaindiscriminator (task, item, note), or is the per-domain table approach preferable for query simplicity and schema clarity? - Contact deduplication. With contacts arriving from multiple sources (manual, agent, import, integration), deduplication logic needs design. Options: exact email match, fuzzy name match with human confirmation, agent-assisted merge suggestions.
- Note versioning. The current model tracks
edited_atbut not a full version history. Should note edits be versioned (git-style diffs, snapshot-based), and if so, what is the storage and query cost? - Media processing cost attribution. Level 2 and Level 3 media processing consume LLM tokens and compute. Should processing costs be attributed to the uploading user/agent, or spread across the account as infrastructure cost?
- Tag hierarchy. The current tag model is flat. Should tags support hierarchy (parent/child) or namespacing for large accounts with many tags?
23. Revision history¶
| Date | Change |
|---|---|
| 2026-03-14 | Original Data Model (doc 05 v0.1.0) published |
| 2026-03-21 | Core Data Structures: Tasks, Contacts, Items & Notes (doc 32 v0.2.0) published |
| 2026-03-21 | Data Model updated to v0.3.0 incorporating doc 32 entities |
| 2026-04-16 | Consolidated docs 05 and 32 into this document (v1.0.0). All sources archived. |