Data Model
Canonical schema, constraints, and path contracts.
This page is the reference for Clapilot's PostgreSQL schema: the core tables, the constraints worth knowing before writing queries or migrations, and the file-path/sync contracts that connect rows to the shared workspace. It is written for developers touching db/migrations/ or any data-access code.
Canonical schema lives in db/migrations/*.sql and is applied by scripts/db-migrate.mjs (or startup auto-migrate via entrypoint.sh). Native runtime tables (agent_*) are documented in depth in the Clapilot-Agent docs; this page focuses on the app-owned schema.
Product telemetry
product_events stores privacy-aware product usage metadata for pilot reporting. It records event name, route, module, optional internal user/session IDs, coarse platform metadata, duration, success/failure state, error code, and sanitized properties_json.
The table must not store chat, email, document, tenant, phone, token, secret, or sensitive free-text content. Use POST /api/telemetry/events or recordProductEvent() so server-side validation and property filtering are applied consistently.
Core tables
| Domain | Table | Purpose |
|---|---|---|
| Identity | users | auth identity with email and password hash |
| Identity | user_profiles | role, display name, user mailbox credentials (kanzlei_email*) |
| Clients | mandanten | client/contact master records plus optional enrichment metadata (website_url, logo_url, profile_image_url, enrichment_*); Contacts settings VCF imports create rows here and skip exact email duplicates instead of maintaining a parallel address-book table |
| Documents | dokument_kategorien | document category dictionary |
| Documents | document_folders | optional document organization folders with parent/child nesting |
| Documents | dokumente | document metadata and relative path contract |
| Steuer-Manager | tax_manager_generations | owner-scoped UStVA/EÜR generation configuration, deterministic result JSON, rendered HTML, and lifecycle state |
| Steuer-Manager | tax_manager_generation_documents | generation source-document snapshots and per-document extraction/classification line items |
| Tasks | aufgaben_boards | kanban/list board containers for tasks |
| Tasks | aufgaben | board/task records for the Aufgaben feature, including Issue Reporter-origin tasks when reports are routed to the Agent Orchestrator board |
| Tasks | aufgaben_kommentare | task comments |
| Automations | scheduled_tasks | automation metadata rows; schedule-backed rows are synchronized against the native runtime job layer, while event-triggered rows are app-managed and dispatch direct native runs |
| Livestream | livestream_channels, livestream_assets, livestream_queue_items, livestream_runs, livestream_events | Live Stream Studio channel config, YouTube chat ingest status, agent top-up controls, generated media provenance, approved playback queue, FFmpeg run state, and audit events |
| Livestream | livestream_chat_messages, livestream_audience_requests | YouTube Live Chat messages and classified viewer questions/music/video/topic requests for the livestream agent loop |
| Media | generated_images, generated_videos, media_generation_provider_configs | User-owned generated image/video assets plus AI media video/music provider defaults. Normal chat text-to-video and image-to-video generation writes generated_videos; image-to-video rows record generationMode, sourceImageId, sourceImagePath, and source MIME lineage in metadata. Live Stream Studio provider jobs continue to write livestream_assets. |
| Video Studio | video_studio_characters | Workspace-global reusable character identity, appearance prompt, optional source image, and canonical portrait image; owner_user_id is creator attribution only. |
| Video Studio | video_studio_ai_projects | Workspace-global AI storyboard project configuration, selected provider/model, output paths, storyboard model, and project lifecycle state; owner_user_id is creator attribution only. |
| Video Studio | video_studio_ai_project_characters | Many-to-many project-to-character links with cascading cleanup. |
| Video Studio | video_studio_ai_scenes | Ordered AI/HTML storyboard scenes with prompts, dialogue, character ids, frame/video references, source metadata, and scene lifecycle state. |
| Chat | chat_sessions, chat_nachrichten, chat_messages | chat persistence |
| Chat | profile_agent_pets | per-user custom Profile Pets registered from generated image assets; rows store a stable key, display labels, generated-image references for preview/activity rendering, and the still/animation prompts used by the Pet Creator. When a registration lacks an animated activity asset, Clapilot writes derived transparent preview/GIF assets into generated_images and stores the GIF as the rendered pet asset |
| Specialists | specialized_agents | shared admin-managed specialist catalog for scoped chat/automation agents |
| Specialists | specialized_agent_embed_deployments | optional per-specialist public/embed deployment metadata |
| Specialists | specialized_agent_embed_api_keys | per-deployment publishable external API keys, stored as hashes only, plus a stable key-owned external session_id |
| Specialists | specialized_agent_embed_request_logs | append-only public embed guard and abuse log rows keyed by deployment/session/IP hash |
| Notes/Wiki | notizen | client notes |
| Notes/Wiki | wiki_pages | fixed Wiki module Markdown pages with unique slugs, summaries, tags, source type, provenance refs, and archive state |
| Memory | user_memories | long-form user memory |
email_drafts | draft/sent email artifacts, including source_language and reply_language (de, en, it) for generated reply review | |
agent_email_jobs | legacy poller queue and retries | |
| Calendar | calendar_entries | user calendar |
| Sync | unified_sync_items | source-agnostic task/event records used as the zero-risk sync/export base layer; stores Clapilot proposals, Outlook mirrors, later confirmed synced rows, external identity, core dates, status, and linked entity references without making Outlook the internal model |
| Integrations | google_user_integrations | per-user Google OAuth integration, tokens, and service toggles |
| Integrations | microsoft_user_integrations | per-user Microsoft 365 OAuth integration, tokens, and service toggles |
| Media | media_generation_provider_configs | model/endpoint configuration for non-chat media generation providers such as Google Gemini/Veo, Gemini/Lyria, xAI Grok Video, and Kie.ai, plus the shared encrypted Kie.ai API key stored on Kie.ai rows |
| Settings | app_settings | global singleton system settings |
| Learning | agent_learning_objects, agent_learning_approvals, agent_learning_audit_events | native runtime learning ledger for durable facts, procedure proposals, hot memory snapshots, approval decisions, extraction/curation audit events, and prompt-use audit events |
| Telemetry | product_events | privacy-aware internal usage events for pilot reports; stores metadata only and sanitized JSON properties |
| Demo data | demo_seed_runs | admin-triggered demo seed execution log per user/action |
| Demo data | demo_seed_records | registry of seeded demo artifacts used for full and partial resets |
Column-level note: user_profiles.agent_pet_key stores the signed-in user's selected chat activity style. It defaults to bubbles, which renders the compact animated three-dot bubble; users can explicitly select clapilot, pilot_rooster, or a registered custom Pet instead. Web and Apple chat clients share this behavior while generic loading indicators remain unchanged.
High-value constraints
app_settings.id = TRUEenforces singleton rowaufgaben.board_idreferencesaufgaben_boards.id(required)- tasks can be aggregated across all boards in the Aufgaben UI, but each task still belongs to exactly one board row
aufgaben_boards.visibility_scopeseparates shared boards from per-user private boards; private rows carryowner_user_id, and task reads/mutations only expose shared boards plus the signed-in user's own private boards. The built-in private board is namedPrivat.aufgabenandcalendar_entriesshare a zero-risk sync model:external_id,sync_source,sync_status, andlinked_entities. Local Clapilot records default tosync_source='clapilot'andsync_status='local'; imported Outlook/Microsoft and Google calendar rows are mirrored records, not destructive writes back to the source system.aufgaben.source_typeincludesemail,document,manual, andissue_reporter; Issue Reporter tasks use the report or Hub issue as source traceability and are assigned to the agent when they target the Agent Orchestrator board.hub_reported_issues.app_keystores the normalized repository basename used by Issue Reporter routing and review filtering. Existing rows and callers without anappparameter default toclapilot; the full repository and task board remain resolved throughapp_settings.agent_orchestrator_repo_automation_config.aufgabenincludes optional CRM/follow-up fields for acquisition, partner, talent, and deal workflows:naechster_schritt,kontaktperson,organisation,branche,athlet,projekt,deal_name,kommunikationskanal,letzter_kontakt_at,letzter_kontakt_notiz,wiedervorlage_at,abschlussart,abschlussnotiz,tags,umsatzrelevant, anddeal_wert. These fields stay nullable/defaulted so existing simple tasks remain compatible;tagsis indexed with GIN, follow-up dates are indexed with task status, and revenue/deal value has a dedicated sort/filter index.aufgaben.schedule_moderemains for legacy task-board scheduling rows during migration/importscheduled_tasks.providerisopenclawfor native-job-backed rows andclapilotfor direct event-triggered rows (openclawis a legacy-named enum value kept for compatibility; those rows execute in the nativeclapilot-agentjob layer)scheduled_tasks.trigger_kindinschedule|new_mail|new_document|new_calendar_entry|webhookscheduled_tasks.specialized_agent_idoptionally referencesspecialized_agents.id; when present, the automation executes inside that specialist's isolated runtime scope instead of the generic main-agent automation pathscheduled_tasks.created_byrecords the human creator for audit;scheduled_tasks.service_principal_idindependently selects the runtime identity. Team-facing and ownerless automations use the built-inglobal_team_serviceprincipal, while personal automations execute with their creator's user context.notify_target_jsonremains a separate delivery decision.scheduled_tasks.provider_snapshot.mailboxScopestoresall|personal|agentfornew_mailevent automations; missing values are treated asallscheduled_tasks.provider_snapshot.payload.specializedAgentIdmirrors the current specialist binding into direct/event automation metadata and native job sync snapshotsscheduled_tasks.provider_snapshot.webhookTokenstores the bearer-style token forwebhooktrigger URLs;idx_scheduled_tasks_webhook_tokenkeeps tokens uniquescheduled_task_event_queuedurably stores webhook request envelopes, delivery/hash dedupe keys, the pre-reservedagent_runsforeign key, retry state, locks, anddelivered_at.reserve_scheduled_task_webhook_run(...)creates the run and queue row in one transaction.agent_runs.actor_user_idsnapshots the verified initiating user per execution; unlike mutable Team Chat session state, it is safe for audit and personal-export ownership decisions.scheduled_tasks.workflow_config_jsonstores the optional node-editor graph (schema_version, trigger/agent/output nodes, edges, and positions). Compatibility columns still hold the primary trigger/agent/output; additional event/webhook trigger nodes can dispatch the same automation, and output nodes can fan out result delivery to multiple validated targets. Agent nodeconfigsupportsspecialized_agent_id,model,prompt(per-step work order, max 8000 chars), andskill_keys(up to 12 installed-skill keys injected additively into that step's run — for default and specialized agents alike). Multiple agent nodes form a linear chain alongagent -> agentedges (normalization drops fan-in/fan-out beyond one edge per node and cycle-creating edges); the first chain agent stays mirrored into the flatprompt/specialized_agent_id/modelcolumns, downstream agents are graph-only. Output nodes wired from an intermediate agent deliver that step's result; unwired outputs deliver the final chain result.scheduled_tasks.schedule_modeinonce|interval|weekdays|cronfor schedule-backed rows andNULLfor event-triggered rowsscheduled_tasks.provider_job_idmaps to the native job id for schedule-backed rows and to the direct automation session key for event-triggered rows- webhook runs append
:event:<queue-id>to the task session key and disable stored-history replay, preventing context from accumulating between unrelated deliveries while retaining each event's auditable run transcript scheduled_tasks.notify_target_jsonstores the assigned automation target (main_session, optionalmain_session.sessionIdfor a concrete web chat session,team_chatfor Teamchat#general, optionalteam_chat.roomIdfor a selected Teamchat channel/group, or approvedchannel_approvaltarget). Rows without an explicit target are backfilled tomain_sessionfor user-owned automations andteam_chatfor system-owned automationsscheduled_tasks.notify_with_resultis a legacy compatibility column and is no longer part of the active automation contractlivestream_assets.statusfollowsdraft|generating|review_ready|approved|queued|playing|played|archived|failed; queueing requiresapprovedorqueuedplus a non-emptymedia_path- Live Stream Studio also exposes scanned video files from
CLAPILOT_MEDIA_OUTPUT_DIRas a filesystem-backed media library; dropping a file into the queue creates or re-approves anuploadedlivestream asset that points at that file. Deleting a media-library file is blocked while it is pending or playing, removes the filesystem file, archives linked assets, and clears theirmedia_path. livestream_queue_items.loop_countstores how many times a row should appear in each loop playlist cycle.0disables looping and the row is markedplayedafter its normal playback; values1..100keep the row active by returning it fromplayingtopendingafter each cycle. If the streamer restarts ungracefully and leaves loop rows inplaying, the next stale-heartbeat recovery moves them back topendingso the selected loop queue survives restarts until an admin skips/removes it.loop_enabledremains as the UI/tool compatibility boolean derived fromloop_count > 0;loop_remainingis retained only for migration compatibility.livestream_channels.stream_desired_stateis the operator intent (running|stopped), whilestreamer_statusandstreamer_heartbeat_atare written by the separateclapilot-streamerprocesslivestream_channels.stream_key_*stores the YouTube stream key encrypted withCLAPILOT_LIVESTREAM_SECRETor the normal runtime secret fallback; APIs only returnstream_key_hintlivestream_channels.agent_topup_*controls the seconds-based queue refill loop. The streamer writes wake timing/status and calls/api/internal/livestream/topupwhen queue buffer is below target.livestream_channels.youtube_chat_*controls YouTube chat ingest. The streamer calls/api/internal/livestream/youtube-chat/poll, which uses the linked Google OAuth user to poll the active broadcast chat and stores deduplicated messages plus derived audience requests.media_generation_provider_configs.api_key_*stores the shared Kie.ai key encrypted withCLAPILOT_MEDIA_GENERATION_SECRETor the normal runtime secret fallback; Gemini media generation reuses the existinggoogle_geminiRuntime provider key, and xAI Grok Video reuses the existingxaiRuntime provider OAuth/API credential.video_studio_ai_projects.statusfollowsdraft|storyboard_generating|storyboard_ready|generating|concatenating|ready|failed; each project stores the exactprovider_slug,model, and thestoryboard_modelthat produced its storyboard.video_studio_ai_scenes.kindisai|html;statusfollowspending|frame_ready|clip_generating|clip_ready|failed.(project_id, scene_index)is unique, project deletion cascades to scenes, and generated image/video deletion clears the corresponding optional reference instead of deleting the scene.video_studio_ai_project_charactershas primary key(project_id, character_id)and cascades when either side is deleted.app_settings.video_storyboard_modeloptionally selects the native/compatible text model used for strict Video Studio storyboard JSON; an empty value falls back toagent:main.chat_nachrichten.message_origininuser_turn|assistant_automation|assistant_specialist|assistant_specialist_delegated|assistant_async_callbackchat_roomsstores/team-chatchannels, group DMs, user direct rooms, and specialist direct rooms; specialist direct room ids useagent-dm:<user-id>:<specialized-agent-handle>and carry a one-human membership row for access control. Channel rows also store the opt-inagent_to_agent_enabledflag plusmain_agent_enabledandmain_agent_reply_mode. Existing and new channels default to an invited main agent withall_messages; admins may remove it or require explicit mentions. Direct and group-DM rooms do not use these channel controls.chat_room_membersis the authoritative room-scoped access list for people.generalis backfilled and auto-joined for compatibility; every other channel contains only its creator and explicitly invited people. Leaving/removal retains the row withleft_atfor safe re-invitation.chat_group_room_settings.modelstores the room's main-agent runtime model override; team rooms no longer useagent:<specialized-agent-handle>model refs as hidden specialist defaults.chat_room_specialized_agentsstores room-scoped specialist invitations withreply_mode=mention_only|all_messages; only invited specialists can be mentioned in that room, andall_messagesspecialists receive unmentioned room turns as detached specialist tasks. The main agent uses the same reply-mode vocabulary through itschat_roomscolumns. In an agent-conversation-enabled channel the specialist modes govern agent-authored messages, while lineage, depth, source-deduplication, and the shared reaction count are persisted inchat_group_messages.message_meta.agentConversation*fields.specialized_agents.handleis unique and backs the shared@agentHandlemention surface in personal and team chatspecialized_agents.skill_keysandspecialized_agents.allowed_tool_namesare persisted separately so skill injection and runtime tool exposure stay independently configurablespecialized_agents.default_model_refoptionally pins one provider/model for that specialist; when empty, the runtime falls back to the active chat/session model before using the global provider defaultspecialized_agent_embed_deployments.specialized_agent_idis unique, so each specialist currently has at most one external/embed deploymentspecialized_agent_embed_deployments.public_slugis unique and is intended to back future public/embed routing for that specialistspecialized_agent_embed_deployments.runtime_access_modeis forced topublic_safe; public website/API-key specialist runs keep public guardrails and cannot expose the normal internal specialist runtime envelopespecialized_agent_embed_deployments.public_allowed_tool_namesis the separate public website/API-key tool allowlist. It is filtered to public-safe tools at save/run time and does not inherit fromspecialized_agents.allowed_tool_names; an empty array means public runs get zero tools.specialized_agent_embed_deployments.service_principal_idreferences a restrictedagent_service_principalsrow dedicated to that public specialist deploymentspecialized_agent_embed_api_keys.key_hashis unique and only the hash is persisted; raw publishable keys are returned once at creation time and never stored in plaintextspecialized_agent_embed_api_keys.session_idmaps each key to a stable external specialist session fallback used when public/embed or OpenAI-compatible clients omit their own session idspecialized_agent_embed_api_keys.deployment_idscopes each key to exactly one specialist deployment; keys can be soft-revoked viarevoked_atspecialized_agent_embed_request_logspersists public website-agent request outcomes, including blocked safety/rate-limit decisions, so the public embed can enforce per-IP message/session limits without relying on signed-in chat historyspecialized_agent_taskspersists detached specialist work for both direct@agentHandlementions and main-agent delegation, including task status, specialist target, visible pending message ids, scoped chat linkage, stored attachments, specialist output/error, and whether an async main-agent callback should be posted after completionaufgabenassignment rules:zugewiesen_typinuser|agentorNULLzugewiesen_typ='user'requireszugewiesen_an(users.id)zugewiesen_typ='agent'requireszugewiesen_an IS NULL
agent_email_jobs.statusinpending|processing|processed|failedagent_learning_objects.statuscontrols prompt eligibility; only approved/active/promoted objects can be considered, and retrieval still enforces expiry, validity windows, latest approval state, visibility scope, subject/session/channel matching, and a per-run token budget before writinglearning.used_in_promptaudit rows. The opt-out activation policy appendsauto_approved_by_policyfor canonical, evidence-backed facts and preferences. Low-risk procedure proposals and other exceptions start in review states such asneeds_evidence. Admin curation and the model-backed Learning Curator append decision rows and audit events rather than overwriting approval history. The curator records a content hash and model provenance so only new or changed facts are checked, while manual decisions remain authoritative.- Memory v2 separates
agent_memory_sources, canonicalagent_memory_assertions, exactagent_memory_assertion_evidence, append-only reviews, explicit conflicts, versioned embedding generations/vectors, retrieval feedback, and the idempotent ingestion outbox. Assertion identity/semantics are immutable; corrections create linked revisions and terminal/tombstoned identities cannot be silently recreated. Safe fact/preference assertions can be policy-approved automatically, while conflicts, corrections, policies, procedures, weak evidence, and other exceptions receive Learning review objects. Only approved/current/audience-visible assertions are eligible for prompt retrieval, active durable-memory projections, prompt-eligible Learning state, and the automatically maintained Knowledge Graph.agent_memoriesand every graph build/entity/claim/edge/evidence row carryinstance_key; write guards and read filters prevent cross-instance projection or recall. User profiles are built with the same instance filter; onlydefaultuses the legacy persisted profile cache, while other instances are computed on demand. Wiki proposal state remains separately human-reviewed. email_drafts.statusindraft|sent|discardedemail_message_cache.detected_language,email_drafts.source_language,email_drafts.reply_language,email_thread_automations.source_language, andemail_thread_automations.reply_languageuse the supported UI language keysde|en|itwiki_pages.slugis unique kebab-case and can be used anywhere a Wiki page id is accepted;source_typeismanual|agent|dream|imported,statusisactive|archived,tagsis GIN-indexed, and exact source/assertion/evidence refs preserve provenance.semantic_signature_version='wiki-semantic-v1'identifies the shared web/native/backfill dedup contract; migration196preserves previous derived values inwiki_semantic_signature_migration_auditfor reversible restoration.chat_sessions.user_idscopes sessions per user;chat_sessions.is_mainmarks the single protected main personal session per user;chat_sessions.is_pinnedpersists user-pinned custom sessions for the web session lists;chat_sessions.title_manually_setprevents first-turn auto-title generation from overwriting manual renames;chat_nachrichten.session_idbinds each message pair to a session- update triggers maintain
updated_at unified_sync_items.item_kindistask|event;sourceisclapilot|outlook|clapilot_synced; event rows requirestart_at,end_at, andend_at > start_at.unified_sync_items.external_provider/external_idare either both set or both empty, and(user_id, item_kind, external_provider, external_id)is unique for imported Outlook rows so mirror imports can upsert without duplicates.unified_sync_items.linked_entitiesis a JSON array for references such as source email, document, Mandant, match, task, or event records. Export and future sync flows should read this canonical model rather than reconstructing relationships from UI payloads.calendar_entriescan store Google sync metadata ingoogle_event_id,google_sync_source,google_calendar_id,google_etag,google_synced_at.calendar_entriescan also store Microsoft sync metadata inmicrosoft_event_id,microsoft_sync_source,microsoft_calendar_id,microsoft_etag, andmicrosoft_synced_at.calendar_entries.conference_urlandconference_providerstore provider-agnostic online meeting links for Kalender detail views; Google Meet rows mirrorgoogle_conference_url, and Microsoft Calendar sync stores Teams join URLs from Graph when available.calendar_entries.external_idmirrors the active provider event id for imported calendar records, withsync_source='outlook'for Microsoft/Outlook rows andsync_source='google'for Google rows.google_user_integrationsstores token rows per(user_id, provider)and keeps token state for re-connect and refresh.microsoft_user_integrationsstores token rows per(user_id, provider)and keeps token state for re-connect and refresh.x_user_integrationsstores token rows per(user_id, provider)and keeps token state plus X profile identity for re-connect and refresh.calendar_entries.google_event_idis unique per user for synced Google event rows (google_sync_source='google').calendar_entries.microsoft_event_idis unique per user for synced Microsoft event rows (microsoft_sync_source='microsoft').app_settingsstores Google OAuth client credentials (google_oauth_client_id,google_oauth_client_secret), Microsoft OAuth app registration values (microsoft_oauth_client_id,microsoft_oauth_client_secret,microsoft_oauth_tenant_id), and X OAuth client credentials (x_oauth_client_id,x_oauth_client_secret); redirect targets are derived frompublic_base_urlas/api/integrations/google/oauth/complete,/api/integrations/microsoft/oauth/complete, and/api/integrations/x/oauth/complete.app_settings.mandant_profile_web_crawl_enabledis the admin-owned feature toggle for Mandanten website/logo/profile enrichment.- Mandanten web-profile enrichment treats a name as only the first identity signal. Automatic master-data writes require an organization plus at least one independent match (non-generic email domain, location, industry/company context, or an already confirmed website). Private-person and otherwise ambiguous results are stored only in
mandanten.enrichment_suggestionwith confidence, evidence, and source for explicit review; accepting a suggestion fills only emptywebsite_url,logo_url, andprofile_image_urlfields and never overwrites existing values. app_settings.developer_mode_enabledis the admin-owned feature toggle for the hidden root-levelSettings -> Developerarea and defaults tofalse. It gates instance API-key management and API-key authentication, in-instance E2E suites that may use live runtime credentials and subscription-backed model access, and developer-only modules such as Agent Orchestrator and the web Terminal.instance_api_keys.allowed_repositoriesstores the immutable full-repository allowlist for isolatedissue_reports:writepublic-client keys.instance_api_key_rate_limit_bucketsstores short-lived atomic per-key scope counters for the Memory, Tool Execution, and stateless Inference APIs and per-key/hashed-IP counters for public issue intake; buckets older than two days are pruned during successful requests.instance_api_keysstores instance-level external API credentials.key_hashis the SHA-256 digest of a high-entropyclp_live_...secret that is returned only once;key_prefixis safe display metadata.scopesis the explicit permission list, whileexpires_at,revoked_at, andlast_used_atsupport lifecycle control and operational review. Supported private scopes includesubscription_usage:read, the creating user'snotifications:readinbox, creator-boundmemory:read/memory:write, stateless provider passthrough throughinference:execute, and high-privilege creator-boundtools:execute; the isolated publicissue_reports:writescope usesclp_public_...keys.user_notificationsis the durable per-recipient message-notification inbox shared by APNs delivery and the polling API. Rows use the samechat_response/team_chat_message, title/body, message, session/room, sender, and author metadata delivered to Apple devices. The(user_id, clapilot_type, message_id)uniqueness boundary makes producer retries idempotent.hub_instance_subscription_usage_snapshotsstores the latest normalized subscription-usage snapshot reported for each connected instance host.checked_atcomes from the sender snapshot, while Hub-controlledreceived_atdrives the 45-minute stale indicator. The JSON payload contains display-safe provider windows, plan/account labels, errors, and credit summaries; subscription credentials and arbitrary metadata are rejected by both sender and Hub normalization.subscription_usage_hub_sync_stateis the one-row spoke-side lease and outcome record for Hub usage reporting. It enforces the 15-minute successful-report interval and five-minute failure retry without adding another cache or credential store.agent_provider_configssupports the first-classelevenlabsprovider type (migration 269) for TTS, STT, and the Video Studio speech-to-speech voice changer; it uses the normal encrypted API-key columns and defaults tohttps://api.elevenlabs.io/v1.agent_provider_configssupports the first-classollamaprovider type. Its normal encrypted API-key columns hold a versioned Ollama credential bundle containing the optional Cloud API key and optional ollama.com browser session used for Subscription Usage. Public/admin reads expose only separate presence flags and masked hints; the plaintext browser session is never returned.app_settings.document_processing_use_native_pdf_toolmay still exist as a transition artifact, but document inbox processing no longer uses a user-facing PDF mode toggle.document_folders.nameis unique case-insensitively per parent folder, so sibling names cannot collide but the same name can exist in different branches.document_folders.parent_idoptionally links a folder to anotherdocument_folders.id, enabling nested document trees.document_folders.visibility_scopeanddokumente.visibility_scopeseparate shared documents from per-user private documents; private rows carryowner_user_id. The built-in personal folder is namedPersönlich.dokumente.folder_idoptionally links a document todocument_folders.id; deleting a folder lifts its documents and child folders to the deleted folder's parent instead of deleting them.dokumente.original_file_name,dokumente.suggested_file_name, anddokumente.auto_renamedpreserve the upload filename, the post-analysis filename suggestion, and whether the stored file path was actually renamed when the document indexer detects a generic name such asDownload_1.pdf,scan.pdf, orIMG_1234.jpg. After content extraction, the canonical schema isYYYY-MM-DD_Type_Counterparty_Amount-or-Subject.ext; unsafe characters and umlauts are normalized. High-confidence suggestions rename the stored file and title, while lower-confidence suggestions remain reviewable metadata. Concurrent or pre-existing targets receive deterministic_1,_2, … suffixes under a database advisory lock, so neither archive paths nor files are overwritten.dokumente.source_idis optional and unique together withsource_typewhen present. Email attachment imports usesource_type='email_attachment'plus a stable mailbox/message/attachment key so opening an email detail can safely create or reuse the linked Dokumente row.dokumente.amount_cents,dokumente.currency, anddokumente.datumare canonical document tax fields extracted during processing. Steuer-Manager sources accessiblerechnung,beleg, andustrows fromdokumenteby selected Mandant and period; uploaded files also becomedokumenterows before generation.tax_manager_generationsstores the owner, optional Mandant, source (mandant|upload), output kind (ustva|euer), normalized period, lifecycle status (draft|extracting|extracted|finalized|failed), agent task, deterministic totals/Kennzahlen/Prüfhinweise JSON, and rendered HTML. UStVA is month/quarter only; EÜR is year only at the API layer.tax_manager_generation_documentssnapshots each source filename and stores status (pending|classified|unreadable|excluded|duplicate), direction, EÜR category, UStVA Kennzahl, integer-cent amounts, VAT rate, date, counterparty/invoice number, AfA marker, confidence, issues, extraction source, and notes.(generation_id, document_id)is unique while the document link is present; generation deletion cascades, while document deletion retains the snapshot with a null link.- Tax-manager migrations
001through004and their legacy tables remain for deployed-data compatibility, but the active module handler and UI use onlytax_manager_generationsandtax_manager_generation_documents. document_mandantenstores additional Mandanten related to a document.dokumente.mandant_idremains the primary compatibility link, whiledocument_mandantensupports recipient, sender, issuer, mentioned, and related parties so outgoing invoices can stay primarily assigned to the customer while retaining the sender/issuer context.document_workflow_automations(one row perdokument_id) is the document-side analogue ofemail_thread_automations: after extraction,src/lib/document-auto-flow.tsanalyzes the document and persists asummary,context_label,document_kind, matchedmandant_id, the executed actions inaction_payload(tasksarray,calendar,follow_up,highlights), and acontent_signatureso the model only re-runs (and tasks are only re-created) when extracted content changes.statusisanalyzed|no_action|executed|failed. Actions are auto-executed, not gated: each detected task is created inaufgaben(source_type='document'), or, when it strongly matches an existing open task for the same client / a task already linked to the document, that task is updated instead of duplicated (action_kinddistinguishescreatedvsupdated). A single document can produce multiple tasks (action_payload.tasks[]);task_idholds the primary one,calendar_event_idandfollow_up_task_idback-reference the calendar/follow-up rows. Linkedaufgabenrows usesource_type='document'and linkedcalendar_entriesrows usesource_origin='document'.demo_seed_runs.statusinrunning|completed|faileddemo_seed_recordsstoresrecord_kind, optionalrecord_id, andexternal_refmetadata so resets can target only tagged demo data instead of wiping unrelated live data
Google Drive sync contract
- Synced Drive files are written under:
${GOOGLE_DRIVE_STORAGE_ROOT || /app/workspace/mandanten}/google/drive/{user_id}/- dedicated Agent Google account files use the isolated
google/drive/{user_id}/agent/subtree
- If a
targetPathis provided to the sync endpoint, files are placed in the requested subfolder. - Downloaded files are stored with restricted local permissions (best-effort) so access is limited to the runtime user.
- Background imports also upsert
dokumenterows with relative paths likegoogle/drive/{user_id}/..., so the documents UI can expose them in the virtualGoogle Drivefolder.
Microsoft 365 file sync contract
- Synced OneDrive files are written under:
${MICROSOFT_DRIVE_STORAGE_ROOT || /app/workspace/mandanten}/microsoft/onedrive/{user_id}/
- If a
targetPathis provided to the sync endpoint, files are placed in the requested subfolder. - The sync endpoint walks nested OneDrive folders, not only root-level files, until the requested
max_fileslimit is reached. - Downloaded files are stored with restricted local permissions (best-effort) so access is limited to the runtime user.
- Imports also upsert
dokumenterows with relative paths likemicrosoft/onedrive/{user_id}/..., so the documents UI can expose them in the virtualMicrosoft 365folder.
Background sync (Google)
- Runtime worker:
scripts/google-sync-poller.mjs - Worker runs when
GOOGLE_SYNC_ENABLED=true(default in compose). - Calendar and Drive are enabled per user through
google_user_integrations.calendar_enabled/drive_enabled, managed fromSettings -> App Verbindungen. google_user_integrations.account_typeseparates the user's Workspace connection from the dedicatedagentWorkspace identity. The agent row can enable Calendar, Gmail, Drive, Docs/Sheets, and Meet independently;calendar_entries.google_account_typerecords the credential that owns a Google event so later ETag-protected patches use the same account.- Docs, Sheets, and Contacts also have per-user service toggles in
google_user_integrations, even though the current background worker only acts on Calendar and Drive. - Scope constraints apply: sync actions only run when the user-granted OAuth scopes cover the required Google API scopes.
Microsoft 365 integration behavior
- Microsoft 365 currently ships as a user-managed OAuth integration under
Settings -> App Verbindungen. - Mail, Calendar, OneDrive, Word, Excel, and Contacts are enabled per user through
microsoft_user_integrations.*_enabled; OneDrive defaults to enabled when the user has grantedFiles.Read. - Enabled Microsoft Calendar imports events into
calendar_entrieswhen the calendar view loads or when the calendar service toggle is enabled. - The current implementation exposes authenticated HTTP endpoints for connect/list/sync flows, but does not yet ship dedicated live/native Microsoft agent tools; chat receives connection-status context only.
X integration behavior
- X currently ships as a user-managed OAuth integration under
Settings -> App Verbindungen. - The current implementation stores per-user token state and basic profile identity in
x_user_integrations. - The current implementation exposes authenticated HTTP endpoints for connect/disconnect/status flows, but does not yet ship dedicated live/native X agent tools; chat and native context reads receive connection-status context only.
Document path contract
dokumente.file_pathis relative to/app/workspace/mandanten- valid examples:
_inbox/file.pdf<mandant>/dokumente/<typ>/file.pdf
- invalid examples:
- absolute paths
mandanten/...prefixed values
Implementation: src/lib/document-paths.ts and document APIs.
