Multica 的一项工作不会只落在一张表里。创建一个 Issue 会涉及工作区、成员、项目、标签和附件;把它交给 Agent 后,又会产生 Runtime、队列任务、运行消息和用量记录。数据库模型要解决的核心问题,是让这些记录各自保存清晰的事实,同时还能沿着同一个工作区和同一个 Issue 找回完整上下文。
本文先从核心数据抽象建立坐标,再列出当前 schema 的全部业务表,随后按领域解释模型如何组织业务事实和生命周期,最后回到 Go 服务端说明 SQL、事务和业务 Service 如何访问数据库。
核心数据抽象
读数据库模型时,最容易陷入字段清单:看到一百多个表,却不知道它们共同描述了什么。Multica 的数据可以先压缩成四个抽象,再回到具体表。
工作区是边界。 user 是全局账户,workspace 是团队的隔离空间,member 把用户加入某个空间并赋予角色。任务、Agent、项目和集成数据大多带有 workspace_id,服务层也会在读取和写入时重复校验这个边界。
Issue 是协作事实。 issue 描述要完成的工作,project 提供更大的目标范围,comment 保存讨论和触发信息,标签、状态、依赖和订阅表补充 Issue 的组织方式。Issue 保存当前状态,活动日志和评论保存变化过程。
Agent、Runtime 和 Task 是执行事实。 agent 保存“谁来做”的配置,agent_runtime 保存“在哪台机器上做”,agent_task_queue 保存“一次具体运行”。三者分开后,同一个 Agent 可以更换 Runtime,一个 Issue 也可以有多次运行。
Chat、Autopilot 和 Channel 是入口。 Chat 将消息映射到 Task,Autopilot 按计划或 webhook 产生运行,Channel 表把外部聊天平台的身份、会话和投递状态接到同一条执行链路上。它们都复用 Issue、Task 和事件记录,而不是各自建立一套任务模型。
这四个抽象解释了表之间的层次:工作区提供范围,Issue 提供协作对象,Task 提供执行实例,外围表保存入口、投影和审计信息。下面按领域列出完整表名,并在同一张表中补充字段、主键、关系和阅读提示。
数据库表一览
当前 SQL schema 由迁移文件持续演进,sqlc 最终生成了 123 个业务模型。下面按领域列出当前模型表,并在同一张表中说明主要字段、主键、与其他表的关系以及阅读时需要注意的地方。schema_migrations 是迁移器自己的版本账本,不属于业务模型。
关系一栏使用业务语言描述字段的用途;由于项目遵循由应用层校验和清理关系的设计,文字中的“应用层”表示这不是数据库级联行为。核心字段 是从 sqlc 生成模型中挑出的主要字段,完整字段仍以对应的 models.go 和查询文件为准。
认证、管理与运行支撑
| 表 | 核心功能 | 核心字段 | 主键 | 与其他表的关系 |
|---|---|---|---|---|
verification_code |
邮箱验证码及尝试次数 | id, email, code, expires_at, used, created_at, attempts |
id |
独立的认证记录,不直接关联其他业务表。 |
personal_access_token |
用户 API token 的哈希、过期和撤销状态 | id, user_id, name, token_hash, token_prefix, expires_at, last_used_at, revoked, created_at |
id |
字段 user_id 用来关联 user 表中的记录。 |
workspace_invitation |
工作区邀请、角色、状态和过期时间 | id, workspace_id, inviter_id, invitee_email, invitee_user_id, role, status, created_at, updated_at, expires_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录。字段 inviter_id 用来关联 user 表中的记录;字段 invitee_user_id 用来关联 user 表中的记录。 |
workspace_share_link |
可重复使用的工作区分享链接及使用次数 | id, workspace_id, code, created_by, role, expires_at, max_uses, use_count, is_active, created_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录。 |
notification_preference |
用户在工作区内的通知偏好 | id, workspace_id, user_id, preferences, updated_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录。字段 user_id 用来关联 user 表中的记录。 |
daemon_token |
daemon 认证 token 的哈希和有效期 | id, token_hash, workspace_id, daemon_id, expires_at, created_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录。 |
daemon_connection |
Agent 与 daemon 的连接、心跳和运行时信息 | id, agent_id, daemon_id, status, last_heartbeat_at, runtime_info, created_at, updated_at |
id |
字段 agent_id 用来关联 agent 表中的记录。 |
agent_builder_draft |
Agent Builder 会话中的草稿 | chat_session_id, workspace_id, draft, updated_at |
chat_session_id |
表结构未声明数据库外键;删除由 Chat session 清理流程负责。 |
quick_action |
评论或聊天中可复用的快捷 Prompt | id, workspace_id, name, description, assignee_type, assignee_id, prompt, visibility, status, last_used_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录;字段 assignee_id 用来关联 主体 表中的记录,具体有效性由应用层校验。 |
seat_capacity_outbox |
席位容量操作的可靠投递队列 | workspace_id, operation_token, action, subject_id, member_id, invitation_id, share_link_id, user_id, expires_at, delivered_at |
模型无单独 id;见复合唯一键 |
字段 workspace_id 用来关联 workspace 表中的记录,投递关系由应用层维护。 |
contact_sales_inquiry |
站点销售联系表单 | id, first_name, last_name, business_email, company_name, company_size, country_region, use_case, goals, consent_outreach |
id |
独立的销售线索记录,不直接关联其他业务表。 |
feedback |
用户对工作区的反馈 | id, user_id, workspace_id, message, metadata, created_at |
id |
字段 user_id 用来关联 user 表中的记录。字段 workspace_id 用来关联 workspace 表中的记录。 |
instance_telemetry_state |
自托管实例遥测发送状态 | singleton, instance_id, last_successful_day, pending_day, pending_body, next_attempt_at, attempt_count, updated_at |
singleton |
单例遥测状态,不直接关联其他业务表。 |
核心业务表
| 表 | 核心功能 | 核心字段 | 主键 | 与其他表的关系 |
|---|---|---|---|---|
user |
全局用户账户和个人资料 | id, name, email, avatar_url, created_at, updated_at, onboarded_at, onboarding_questionnaire, cloud_waitlist_email, cloud_waitlist_reason |
id |
全局账户基础表;其他表通过用户 ID 在应用层引用它。 |
workspace |
租户边界、设置、Issue 编号计数器和仓库上下文 | id, name, slug, description, settings, created_at, updated_at, context, repos, issue_prefix |
id |
工作区基础表;其他业务表通过工作区 ID 在应用层归属到它。 |
member |
用户加入工作区的成员关系和角色 | id, workspace_id, user_id, role, created_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录。字段 user_id 用来关联 user 表中的记录。 |
issue |
待完成的工作及其当前状态、负责人和上下文 | id, workspace_id, title, description, status, priority, assignee_type, assignee_id, creator_type, creator_id |
id |
字段 workspace_id 用来关联 workspace 表中的记录。字段 parent_issue_id 用来关联 issue 表中的记录。字段 project_id 用来关联 project 表中的记录。 |
issue_label |
工作区标签定义 | id, workspace_id, name, color, created_at, updated_at, resource_type, description |
id |
字段 workspace_id 用来关联 workspace 表中的记录。 |
issue_to_label |
Issue 与标签的多对多连接 | issue_id, label_id |
issue_id, label_id |
字段 issue_id 用来关联 issue 表中的记录。字段 label_id 用来关联 issue_label 表中的记录。 |
issue_dependency |
Issue 之间的阻塞或关联关系 | id, issue_id, depends_on_issue_id, type |
id |
字段 issue_id 用来关联 issue 表中的记录;字段 depends_on_issue_id 用来关联 issue 表中的记录。 |
comment |
Issue 讨论、线程和 Agent 触发信息 | id, issue_id, author_type, author_id, content, type, created_at, updated_at, parent_id, workspace_id |
id |
字段 issue_id 用来关联 issue 表中的记录。字段 parent_id 用来关联 comment 表中的记录。字段 workspace_id 用来关联 workspace 表中的记录。 |
inbox_item |
面向成员或 Agent 的通知投影 | id, workspace_id, recipient_type, recipient_id, type, severity, issue_id, title, body, read |
id |
字段 workspace_id 用来关联 workspace 表中的记录。字段 issue_id 用来关联 issue 表中的记录。 |
activity_log |
工作区和 Issue 的审计时间线 | id, workspace_id, issue_id, actor_type, actor_id, action, details, created_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录。字段 issue_id 用来关联 issue 表中的记录。 |
agent |
Agent 的指令、模型、权限和运行时配置 | id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id |
id |
字段 workspace_id 用来关联 workspace 表中的记录。字段 owner_id 用来关联 user 表中的记录。字段 runtime_id 用来关联 agent_runtime 表中的记录。 |
agent_runtime |
daemon 提供的一次实际执行环境 | id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录。字段 owner_id 用来关联 user 表中的记录。 |
agent_task_queue |
一次 Agent 运行的状态和执行上下文 | id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error |
id |
字段 agent_id 用来关联 agent 表中的记录。字段 issue_id 用来关联 issue 表中的记录。字段 runtime_id 用来关联 agent_runtime 表中的记录。字段 parent_task_id 用来关联 agent_task_queue 表中的记录。 |
agent_invocation_target |
public_to Agent 的调用授权对象 | id, agent_id, target_type, target_id, created_by, created_at |
id |
字段 agent_id 用来关联 agent 表中的记录;字段 target_id 用来关联 授权对象 表中的记录,具体有效性由应用层校验。 |
agent_skill |
Agent 与 Skill 的多对多连接 | agent_id, skill_id, created_at, enabled |
agent_id, skill_id |
字段 agent_id 用来关联 agent 表中的记录。字段 skill_id 用来关联 skill 表中的记录。 |
skill |
工作区级可复用技能包 | id, workspace_id, name, description, content, config, created_by, created_at, updated_at, plugin_installation_id |
id |
字段 workspace_id 用来关联 workspace 表中的记录。字段 created_by 用来关联 user 表中的记录。 |
skill_file |
Skill 的附属文件 | id, skill_id, path, content, created_at, updated_at |
id |
字段 skill_id 用来关联 skill 表中的记录。 |
project |
一组 Issue 的目标和负责人 | id, workspace_id, title, description, icon, status, lead_type, lead_id, created_at, updated_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录。 |
project_resource |
项目关联的仓库、目录或外部资源 | id, project_id, workspace_id, resource_type, resource_ref, label, position, created_at, created_by |
id |
字段 project_id 用来关联 project 表中的记录。字段 workspace_id 用来关联 workspace 表中的记录。 |
squad |
由 Leader 协调的成员或 Agent 小队 | id, workspace_id, name, description, leader_id, creator_id, created_at, updated_at, archived_at, archived_by |
id |
字段 workspace_id 用来关联 workspace 表中的记录。字段 leader_id 用来关联 agent 表中的记录。 |
squad_member |
Squad 与成员/Agent 的多态连接 | id, squad_id, member_type, member_id, role, created_at |
id |
字段 squad_id 用来关联 squad 表中的记录。 |
runtime_profile |
自定义 Runtime 的协议和启动命令 | id, workspace_id, display_name, protocol_family, command_name, description, fixed_args, visibility, created_by, enabled |
id |
字段 workspace_id 用来关联 workspace 表;created_by 记录创建者用户,关系由应用层校验。 |
chat_session |
用户与 Agent 的持续会话 | id, workspace_id, agent_id, creator_id, title, session_id, work_dir, status, created_at, updated_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录。字段 agent_id 用来关联 agent 表中的记录。字段 creator_id 用来关联 user 表中的记录。字段 runtime_id 用来关联 agent_runtime 表中的记录。 |
chat_message |
Chat 中的一条用户、助手或系统消息 | id, chat_session_id, role, content, task_id, created_at, failure_reason, elapsed_ms, message_kind, channel_media_pending_until |
id |
字段 chat_session_id 用来关联 chat_session 表中的记录。字段 task_id 用来关联 agent_task_queue 表中的记录。 |
chat_draft_restore |
可恢复的聊天草稿 | id, chat_session_id, task_id, content, attachment_ids, created_at |
id |
字段 chat_session_id 用来关联 chat_session 表;task_id 用来关联对应的运行记录,关系由应用层校验。 |
chat_pinned_agent |
用户置顶的 Agent 及顺序 | id, workspace_id, user_id, agent_id, position, created_at |
id |
字段 workspace_id、user_id 和 agent_id 分别关联工作区、用户和智能体,关系由应用层校验。 |
task_message |
一次运行的工具调用、输入和输出事件 | id, task_id, seq, type, tool, content, input, output, created_at, output_truncated |
id |
字段 task_id 用来关联 agent_task_queue 表中的记录。 |
task_usage |
单个 Task 的模型 token 和成本 | id, task_id, provider, model, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, created_at, updated_at |
id |
字段 task_id 用来关联 agent_task_queue 表中的记录。 |
task_usage_hourly |
按小时和维度聚合的模型用量 | bucket_hour, workspace_id, runtime_id, agent_id, project_id, provider, model, input_tokens, output_tokens, cache_read_tokens |
无显式主键;上述维度有 UNIQUE NULLS NOT DISTINCT 约束 |
表结构未声明数据库外键;聚合维度由 Task/Issue 的应用逻辑写入。 |
task_usage_hourly_dirty |
等待重算的小时用量桶 | bucket_hour, workspace_id, runtime_id, agent_id, project_id, provider, model, enqueued_at |
无显式主键;上述维度有 UNIQUE NULLS NOT DISTINCT 约束 |
表结构未声明数据库外键;由数据库触发器写入失效桶。 |
task_usage_hourly_rollup_state |
小时用量聚合水位和错误状态 | id, watermark_at, last_run_started_at, last_run_finished_at, last_run_rows, last_error |
id |
维护用量聚合进度,不直接关联业务对象;聚合任务由应用层驱动。 |
自动化与集成主表
| 表 | 核心功能 | 核心字段 | 主键 | 与其他表的关系 |
|---|---|---|---|---|
autopilot |
自动化规则本身和负责人 | id, workspace_id, title, description, assignee_id, status, execution_mode, issue_title_template, created_by_type, created_by_id |
id |
字段 workspace_id 用来关联 workspace 表中的记录。字段 project_id 用来关联 project 表中的记录。字段 assignee_id 用来关联 agent 表中的记录。 |
autopilot_trigger |
自动化的 Cron、Webhook 或事件触发器 | id, autopilot_id, kind, enabled, cron_expression, timezone, next_run_at, webhook_token, label, last_fired_at |
id |
字段 autopilot_id 用来关联 autopilot 表中的记录。 |
autopilot_run |
一次自动化触发的运行及其结果 | id, autopilot_id, trigger_id, source, status, issue_id, task_id, triggered_at, completed_at, failure_reason |
id |
字段 autopilot_id 用来关联 autopilot 表中的记录。字段 trigger_id 用来关联 autopilot_trigger 表中的记录。字段 issue_id 用来关联 issue 表中的记录。字段 task_id 用来关联 agent_task_queue 表中的记录。 |
autopilot_rule_version |
发布时的规则配置快照和发布者 | id, autopilot_id, workspace_id, published_by_type, published_by_id, config_summary, created_at |
id |
字段 autopilot_id 和 workspace_id 分别关联自动化规则和工作区,关系由应用层校验。 |
autopilot_subscriber |
自动化规则订阅者 | autopilot_id, user_type, user_id, created_at |
autopilot_id, user_type, user_id |
通过 autopilot_id 关联自动化规则;user_type 与 user_id 表示订阅者,具体主体由应用层校验。 |
autopilot_collaborator |
自动化规则协作者和授权人 | autopilot_id, user_type, user_id, granted_by, created_at |
autopilot_id, user_type, user_id |
通过 autopilot_id 关联自动化规则;user_type 与 user_id 表示协作者,授权人由应用层校验。 |
autopilot_quota_period |
工作区时间段的自动化配额计数 | workspace_id, period_start, period_end, used_count, reserved_count, blocked_counts, created_at, updated_at, rejection_notified_at |
workspace_id, period_start, period_end |
字段 workspace_id 用来关联 workspace 表中的记录。 |
autopilot_quota_reservation |
一次自动化配额的预留和结算 | id, workspace_id, period_start, period_end, policy_revision, subscription_version, source, idempotency_key, state, created_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录。 |
webhook_delivery |
外部 Webhook 的验签、去重、租约和重试 | id, workspace_id, autopilot_id, trigger_id, provider, event, dedupe_key, dedupe_source, signature_status, status |
id |
字段 workspace_id 用来关联 workspace 表中的记录;字段 autopilot_id 用来关联 autopilot 表中的记录;字段 trigger_id 用来关联 autopilot_trigger 表中的记录。字段 autopilot_run_id 用来关联 autopilot_run 表中的记录。 |
vcs_connection |
通用版本控制连接和加密凭据 | id, workspace_id, provider, instance_url, account_login, access_token_encrypted, webhook_secret_encrypted, connected_by_id, created_at, updated_at |
id |
字段 workspace_id 表示连接所属的工作区,关系由应用层校验。 |
vcs_pull_request |
通用 VCS Pull Request 镜像 | id, workspace_id, connection_id, provider, repo_owner, repo_name, pr_number, title, state, html_url |
id |
字段 connection_id 用来关联 vcs_connection 表中的记录。 |
issue_vcs_pull_request |
Issue 与通用 VCS PR 的连接 | issue_id, pull_request_id, close_intent, linked_by_type, linked_by_id, linked_at |
issue_id, pull_request_id |
字段 issue_id 用来关联 issue 表中的记录。字段 pull_request_id 用来关联 vcs_pull_request 表中的记录。 |
vcs_commit_status |
提交检查状态 | connection_id, sha, context, state, target_url, description, updated_at |
connection_id, sha, context |
字段 connection_id 关联 vcs_connection;SHA 和 context 标识对应的提交检查。 |
通用渠道表
| 表 | 核心功能 | 核心字段 | 主键 | 与其他表的关系 |
|---|---|---|---|---|
channel_installation |
工作区 Agent 的外部聊天渠道安装 | id, workspace_id, agent_id, channel_type, config, status, ws_lease_token, ws_lease_expires_at, installer_user_id, installed_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录;字段 agent_id 用来关联 agent 表中的记录,具体有效性由应用层校验。 |
channel_user_binding |
外部渠道用户与 Multica 用户的绑定 | id, workspace_id, multica_user_id, installation_id, channel_type, channel_user_id, config, bound_at |
id |
字段 installation_id 用来关联 channel_installation 表中的记录,具体有效性由应用层校验。 |
channel_chat_session_binding |
外部聊天会话与 Chat session 的绑定 | id, chat_session_id, installation_id, channel_type, channel_chat_id, chat_type, last_message_id, last_thread_id, config, created_at |
id |
字段 chat_session_id 用来关联 chat_session 表中的记录;字段 installation_id 用来关联 installation 表中的记录,具体有效性由应用层校验。 |
channel_inbound_message_dedup |
外部入站消息去重 | installation_id, message_id, received_at, processed_at, claim_token |
installation_id, message_id |
通过 installation_id 关联渠道安装;message_id 用于判断同一入站消息是否已经处理,关系由应用层校验。 |
channel_inbound_audit |
外部事件接收和丢弃审计 | id, installation_id, channel_type, channel_chat_id, event_type, channel_event_id, channel_message_id, drop_reason, received_at |
id |
通过 installation_id 关联渠道安装;事件和消息 ID 用于记录外部渠道的接收审计,关系由应用层校验。 |
channel_task_delivery |
Task 到外部聊天的投递上下文 | task_id, binding_id, installation_id, channel_type, channel_chat_id, chat_type, channel_message_id, channel_thread_id, route_revision, config |
task_id |
字段 task_id 用来关联 task 表中的记录;字段 binding_id 用来关联 channel binding 表中的记录;字段 installation_id 用来关联 installation 表中的记录,具体有效性由应用层校验。 |
channel_outbound_message |
外部出站消息与路由版本 | installation_id, channel_type, channel_message_id, binding_id, route_revision, task_id, outbound_kind, created_at |
无显式主键;(installation_id, channel_message_id) 唯一索引 |
表结构未声明数据库外键;绑定和 Task 关系由应用层校验。 |
channel_outbound_card_message |
外部卡片消息及补丁状态 | id, chat_session_id, task_id, channel_type, channel_chat_id, channel_card_message_id, status, last_patched_at, created_at |
id |
字段 chat_session_id 和 task_id 分别关联 Chat 会话和运行记录,外部渠道身份由应用层维护。 |
channel_reply_delivery |
一次回复分块发送的租约和结算 | turn_id, task_id, binding_id, installation_id, channel_type, chat_id, phase, send_state, message_id, chunks_sent |
无显式主键;turn_id 唯一索引 |
表结构未声明数据库外键;Task、Binding 和 Installation 关系由应用层校验。 |
channel_binding_token |
一次性渠道绑定 token | token_hash, workspace_id, installation_id, channel_type, channel_user_id, expires_at, consumed_at, created_at |
token_hash |
表结构未声明数据库外键;installation 和用户范围由应用层校验。 |
channel_chat_context_generation |
外部会话上下文重建版本 | chat_session_id, revision, history_start_message_id, history_end_message_id, history_boundary_pending, pending_fresh, initiator_user_id, created_at, last_message_id, last_thread_id |
无显式主键;(chat_session_id, revision) 唯一索引 |
表结构未声明数据库外键;Chat session 关系由应用层校验。 |
channel_media_pending_object |
待解析渠道媒体对象的重试账本 | storage_key, workspace_id, chat_message_id, storage_url, installation_id, state, lease_token, lease_expires_at, attempt, next_attempt_at |
storage_key |
字段 workspace_id、chat_message_id 和 installation_id 分别指向工作区、Chat 消息和渠道安装,关系由应用层校验。 |
协作辅助表
| 表 | 核心功能 | 核心字段 | 主键 | 与其他表的关系 |
|---|---|---|---|---|
comment_reaction |
评论表情反应 | id, comment_id, workspace_id, actor_type, actor_id, emoji, created_at |
id |
字段 comment_id 用来关联 comment 表中的记录。字段 workspace_id 用来关联 workspace 表中的记录。 |
issue_reaction |
Issue 表情反应 | id, issue_id, workspace_id, actor_type, actor_id, emoji, created_at |
id |
字段 issue_id 用来关联 issue 表中的记录。字段 workspace_id 用来关联 workspace 表中的记录。 |
agent_to_label |
Agent 与标签的连接 | agent_id, label_id, created_at |
agent_id, label_id |
字段 agent_id 和 label_id 分别关联智能体与标签,是两者的多对多连接,关系由应用层校验。 |
skill_to_label |
Skill 与标签的连接 | skill_id, label_id, created_at |
skill_id, label_id |
字段 skill_id 和 label_id 分别关联 skill 与标签,是两者的多对多连接,关系由应用层校验。 |
issue_subscriber |
Issue 订阅者及退订状态 | issue_id, user_type, user_id, reason, created_at, unsubscribed_at, opt_out_scope |
issue_id, user_type, user_id |
字段 issue_id 用来关联 issue 表中的记录。 |
issue_view |
保存的 Issue 筛选和展示定义 | id, workspace_id, owner_id, name, scope_type, scope_id, scope_variant, visibility, definition_version, query |
id |
字段 workspace_id 用来关联 workspace 表中的记录;字段 owner_id 用来关联 user 表中的记录,具体有效性由应用层校验。 |
issue_view_preference |
用户对 Issue 视图的偏好 | workspace_id, user_id, scope_type, scope_id, prefs, updated_at |
模型无单独 id;见复合唯一键 |
字段 workspace_id 用来关联 workspace 表中的记录;字段 user_id 用来关联 user 表中的记录,具体有效性由应用层校验。 |
pinned_item |
用户固定的工作区对象 | id, workspace_id, user_id, item_type, item_id, position, created_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录。字段 user_id 用来关联 user 表中的记录。 |
attachment |
Issue、评论、Chat 或 Task 的文件引用 | id, workspace_id, issue_id, comment_id, uploader_type, uploader_id, filename, url, content_type, size_bytes |
id |
字段 workspace_id 用来关联 workspace 表中的记录。字段 issue_id 用来关联 issue 表中的记录;字段 comment_id 用来关联 comment 表中的记录。字段 chat_session_id 用来关联 chat_session 表中的记录;字段 chat_message_id 用来关联 chat_message 表中的记录。 |
调度、维护与搜索
| 表 | 核心功能 | 核心字段 | 主键 | 与其他表的关系 |
|---|---|---|---|---|
sys_cron_executions |
系统 Cron 作业的租约、重试和结果 | id, job_name, scope_kind, scope_id, plan_time, status, attempt, max_attempts, next_retry_at, runner_id |
id |
表结构未声明数据库外键;相关 ID 的有效性和清理由应用层负责。 |
maintenance_job |
长时间维护作业的检查点和进度 | id, job_type, job_version, scope_key, idempotency_key, request_hash, status, revision, dry_run, options |
id |
表结构未声明数据库外键;相关 ID 的有效性和清理由应用层负责。 |
search_index_change |
异步搜索索引变更队列 | entity_type, entity_id, workspace_id, change_xid, changed_at |
entity_type, entity_id |
表结构未声明数据库外键;workspace_id 由触发器写入并由索引消费者校验。 |
search_index_prune_mark |
搜索变更队列清理水位 | singleton, pruned_through_xid |
singleton |
表结构未声明数据库外键;相关 ID 的有效性和清理由应用层负责。 |
client_usage_daily |
客户端每日活跃和 Runtime 探测统计 | user_id, client_type, install_id, activity_date, workspace_id, client_version, os, first_active_at, last_active_at, runtime_probed_at |
user_id, client_type, install_id, activity_date, workspace_id |
字段 user_id 用来关联 user 表中的记录;字段 workspace_id 用来关联 workspace 表中的记录,具体有效性由应用层校验。 |
Issue 扩展与来源
| 表 | 核心功能 | 核心字段 | 主键 | 与其他表的关系 |
|---|---|---|---|---|
issue_property |
工作区自定义 Issue 属性定义 | id, workspace_id, name, type, description, config, position, archived_at, created_at, updated_at |
id |
字段 workspace_id 表示自定义属性所属的工作区,关系由应用层校验。 |
issue_status |
工作区 Issue 状态目录 | id, workspace_id, key, name, description, category, color, is_system, position, archived_at |
id |
字段 workspace_id 表示状态目录所属的工作区,关系由应用层校验。 |
issue_child_event |
子 Issue 状态传播事件 | id, workspace_id, parent_id, child_id, kind, source_task_id, created_at, claimed_at, processed_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录;字段 parent_id 用来关联 issue 表中的记录;字段 child_id 用来关联 issue 表中的记录,具体有效性由应用层校验。 |
issue_source_context |
从 Issue 或评论捕获的不可变上下文快照 | id, workspace_id, issue_id, origin_task_id, source_issue_id, anchor_comment_id, captured_by_user_id, snapshot_version, snapshot, capture_digest |
id |
字段 issue_id 用来关联 issue 表中的记录。 |
issue_source_context_object_intent |
来源上下文对象存储清理意图 | storage_key, workspace_id, source_context_id, attachment_id, object_url, state, lease_token, lease_expires_at, next_attempt_at, last_error |
模型无单独 id;见复合唯一键 |
字段 source_context_id 用来关联 issue_source_context 表中的记录。字段 attachment_id 用来关联 attachment 表中的记录。 |
issue_wakeup |
Issue 的延迟或事件唤醒规则 | id, workspace_id, issue_id, agent_id, created_by, source_task_id, parent_comment_id, instruction, kind, mode |
id |
字段 workspace_id 用来关联 workspace 表中的记录;字段 issue_id 用来关联 issue 表中的记录;字段 agent_id 用来关联 agent 表中的记录,具体有效性由应用层校验。 |
issue_wakeup_receipt |
唤醒事件去重和处理凭据 | id, wakeup_id, revision, event_key, event_type, payload, task_id, processed_at, created_at, coalesce_key |
id |
字段 wakeup_id 用来关联 issue_wakeup 表中的记录。 |
task_supplement |
Task 补充请求的生命周期 | task_id, workspace_id, issue_id, comment_id, author_id, client_request_id, status, failure_reason, attempt_count, created_at |
task_id |
字段 workspace_id 用来关联 workspace 表中的记录;字段 issue_id 用来关联 issue 表中的记录;字段 comment_id 用来关联 comment 表中的记录,具体有效性由应用层校验。 |
task_supplement_capability |
补充请求所需能力 | task_id, workspace_id, issue_id, capability, created_at |
task_id, capability |
字段 workspace_id 用来关联 workspace 表中的记录;字段 issue_id 用来关联 issue 表中的记录,具体有效性由应用层校验。 |
task_token |
一次运行的短期访问 token | id, token_hash, task_id, agent_id, workspace_id, user_id, expires_at, created_at |
id |
字段 task_id 用来关联 agent_task_queue 表中的记录。字段 agent_id 用来关联 agent 表中的记录。字段 workspace_id 用来关联 workspace 表中的记录。字段 user_id 用来关联 user 表中的记录。 |
渠道与历史兼容
| 表 | 核心功能 | 核心字段 | 主键 | 与其他表的关系 |
|---|---|---|---|---|
dingtalk_group_route |
钉钉群到 Agent 的路由 | id, workspace_id, installation_id, conversation_id, conversation_title, agent_id, revision, discovered_at, updated_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录;字段 installation_id 用来关联 channel_installation 表中的记录;字段 agent_id 用来关联 agent 表中的记录,具体有效性由应用层校验。 |
dingtalk_group_presence |
钉钉群发现和活跃状态 | workspace_id, installation_id, conversation_id, conversation_title, bot_name, bot_identity_issue, first_seen_at, last_active_at, mention_count, created_at |
模型无单独 id;见复合唯一键 |
字段 workspace_id 和 installation_id 分别关联工作区与渠道安装,关系由应用层校验。 |
dingtalk_bot_identity |
钉钉机器人身份诊断 | workspace_id, installation_id, bot_name, bot_identity_issue, created_at, updated_at |
模型无单独 id;见复合唯一键 |
字段 workspace_id 和 installation_id 分别关联工作区与渠道安装,关系由应用层校验。 |
lark_installation |
旧 Feishu 安装记录 | id, workspace_id, agent_id, app_id, app_secret_encrypted, tenant_key, bot_open_id, installer_user_id, status, ws_lease_token |
id |
字段 workspace_id 用来关联 workspace 表中的记录;字段 agent_id 用来关联 agent 表中的记录。字段 installer_user_id 用来关联 user 表中的记录。 |
lark_user_binding |
旧 Feishu 用户绑定 | id, workspace_id, multica_user_id, installation_id, lark_open_id, union_id, bound_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录;字段 multica_user_id 用来关联 member 表中的记录;字段 installation_id 用来关联 lark_installation 表中的记录。 |
lark_chat_session_binding |
旧 Feishu 会话绑定 | id, chat_session_id, installation_id, lark_chat_id, lark_chat_type, created_at, last_lark_message_id, last_lark_thread_id |
id |
字段 chat_session_id 用来关联 chat_session 表中的记录;字段 installation_id 用来关联 lark_installation 表中的记录。 |
lark_inbound_message_dedup |
旧 Feishu 入站去重 | installation_id, message_id, received_at, processed_at, claim_token |
模型无单独 id;见复合唯一键 |
通过 installation_id 关联旧 Feishu 安装;message_id 用于判断入站消息是否重复。 |
lark_inbound_audit |
旧 Feishu 入站审计 | id, installation_id, lark_chat_id, event_type, lark_event_id, lark_message_id, drop_reason, received_at |
id |
通过 installation_id 关联旧 Feishu 安装;事件和消息 ID 用于保存接收审计。 |
lark_outbound_card_message |
旧 Feishu 卡片消息 | id, chat_session_id, task_id, lark_chat_id, lark_card_message_id, status, last_patched_at, created_at |
id |
字段 chat_session_id 和 task_id 分别关联 Chat 会话和运行记录,旧 Feishu 身份由应用层维护。 |
lark_binding_token |
旧 Feishu 绑定 token | token_hash, workspace_id, installation_id, lark_open_id, expires_at, consumed_at, created_at |
模型无单独 id;见复合唯一键 |
字段 workspace_id 和 installation_id 分别关联工作区与旧 Feishu 安装,绑定用户由应用层校验。 |
GitHub 辅助表
| 表 | 核心功能 | 核心字段 | 主键 | 与其他表的关系 |
|---|---|---|---|---|
github_installation |
GitHub App 安装和账号 | id, workspace_id, installation_id, account_login, account_type, account_avatar_url, connected_by_id, created_at, updated_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录。字段 connected_by_id 用来关联 user 表中的记录。 |
github_pull_request |
GitHub Pull Request 镜像 | id, workspace_id, installation_id, repo_owner, repo_name, pr_number, title, state, html_url, branch |
id |
字段 workspace_id 用来关联 workspace 表中的记录。 |
github_pull_request_check_suite |
GitHub 检查套件 | pr_id, suite_id, head_sha, app_id, conclusion, status, updated_at |
模型无单独 id;见复合唯一键 |
字段 pr_id 用来关联 github_pull_request 表中的记录。 |
github_pull_request_check_run |
GitHub 检查运行 | pr_id, head_sha, ordinal, name, status, conclusion, details_url, is_status_context |
模型无单独 id;见复合唯一键 |
字段 pr_id 用来关联 github_pull_request 表中的记录。 |
github_pending_check_suite |
尚未合并的 GitHub 检查结果 | workspace_id, installation_id, repo_owner, repo_name, pr_number, suite_id, head_sha, app_id, conclusion, status |
模型无单独 id;见复合唯一键 |
保存尚未归档的外部检查结果;工作区和安装信息由应用层校验。 |
github_pending_installation |
尚未绑定工作区的 GitHub 安装 | installation_id, account_login, account_type, account_avatar_url, received_at, updated_at |
模型无单独 id;见复合唯一键 |
保存尚未绑定工作区的 GitHub 安装,不直接关联业务表。 |
issue_pull_request |
Issue 与 GitHub PR 的连接 | issue_id, pull_request_id, linked_by_type, linked_by_id, linked_at, close_intent |
issue_id, pull_request_id |
字段 issue_id 用来关联 issue 表中的记录。字段 pull_request_id 用来关联 github_pull_request 表中的记录。 |
issue_pull_request_exclusion |
Issue 明确排除的 GitHub PR | issue_id, pull_request_id, workspace_id, excluded_by_type, excluded_by_id, created_at |
issue_id, pull_request_id |
字段 issue_id 用来关联 issue 表中的记录。字段 pull_request_id 用来关联 github_pull_request 表中的记录。 |
issue_pr_automation |
Issue 的 PR 自动完成设置 | issue_id, workspace_id, auto_complete_disabled, updated_by_type, updated_by_id, updated_at |
模型无单独 id;见复合唯一键 |
字段 issue_id 和 workspace_id 分别关联任务与工作区,关系由应用层校验。 |
插件与外部服务辅助表
| 表 | 核心功能 | 核心字段 | 主键 | 与其他表的关系 |
|---|---|---|---|---|
plugin_package |
工作区插件包 | id, workspace_id, plugin_key, name, created_by, created_at, updated_at |
id |
字段 workspace_id 表示插件包所属工作区,创建者由应用层校验。 |
plugin_package_version |
插件包的不可变版本 | id, package_id, workspace_id, version, manifest, digest, size_bytes, published_by, created_at |
id |
字段 package_id 用来关联 plugin_package 表中的记录。 |
plugin_package_file |
插件版本中的文件 | id, version_id, path, content, size_bytes, sha256, created_at |
id |
字段 version_id 用来关联 plugin_package_version 表中的记录。 |
plugin_installation |
插件在工作区的安装实例 | id, workspace_id, plugin_key, version, manifest, granted_scopes, config, enabled, installed_by, created_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录。字段 package_version_id 用来关联 plugin_package_version 表中的记录。 |
plugin_storage |
插件按作用域保存的 key/value | id, installation_id, scope_type, scope_id, key, value, created_at, updated_at |
id |
字段 installation_id 用来关联 plugin_installation 表中的记录。 |
plugin_secret |
插件加密密钥 | id, installation_id, key, ciphertext, created_at, updated_at |
id |
字段 installation_id 用来关联 plugin_installation 表中的记录。 |
plugin_hook_schedule |
插件 Hook 的定时计划 | id, installation_id, workspace_id, hook_key, cron_expression, timezone, generation, activated_at, next_run_at, enabled |
id |
字段 installation_id 用来关联 plugin_installation 表中的记录。 |
plugin_invocation |
插件 Hook 的执行记录 | id, installation_id, workspace_id, hook_key, trigger, status, event_type, attempt, latency_ms, error |
id |
字段 installation_id 用来关联 plugin_installation 表中的记录。 |
user_composio_connection |
用户的 Composio 工具连接 | id, user_id, toolkit_slug, auth_config_id, connected_account_id, composio_user_id, status, connected_at, last_used_at, created_at |
id |
字段 user_id 用来关联 user 表中的记录。 |
workspace_mcp_server |
工作区 MCP Server 配置 | id, workspace_id, name, config, created_by, created_at, updated_at |
id |
字段 workspace_id 用来关联 workspace 表中的记录。 |
agent_mcp_server |
Agent 与 MCP Server 的连接 | agent_id, server_id, enabled, created_at |
agent_id, server_id |
字段 agent_id 用来关联 agent 表中的记录。字段 server_id 用来关联 workspace_mcp_server 表中的记录。 |
下面不再重复逐表列字段,而是按领域说明这些表如何组织业务事实、关系和生命周期。需要核对具体字段时,以前面的表格和 server/pkg/db/generated/models.go 为准。
身份与工作区
用户与成员
user 是全局账户,workspace 是隔离团队数据的边界;用户只有通过 member 加入工作区后,才拥有该工作区中的角色和权限。这样同一个用户可以参与多个工作区,而任务、智能体和集成数据仍然在各自的工作区内隔离。member 的唯一性约束保证同一用户不会在同一工作区重复入组。
workspace_invitation 和 workspace_share_link 是两种加入工作区的入口:前者面向特定邮箱或用户,后者面向可重复使用的邀请链接。验证邮箱、个人访问 token 等认证数据与成员关系分开保存;token 只保存哈希和生命周期信息,原文不会进入数据库。
通知偏好、固定对象和客户端活跃记录属于成员使用工作区时产生的个性化投影,分别由 notification_preference、pinned_item 和 client_usage_daily 保存。它们不会改变成员权限,也不承担工作区身份判断;feedback 则保留用户对工作区的反馈上下文。
工作区配置
workspace 不只是租户名称,还承载 Issue 编号、仓库上下文和默认设置等工作区级事实。Issue 编号计数器是并发控制点:创建任务时,服务在同一事务中锁定并递增计数器,再写入新的可读编号,避免两个请求得到相同编号。工作区条件也会贯穿业务查询,使一个对象的 UUID 不能绕过租户边界。
Issue 与协作对象
Issue 主表
issue 是协作模型的中心,保存一项待完成工作的当前状态;项目、评论、标签、依赖、附件和运行记录都围绕它展开。表中的内容、看板状态和负责人属于当前投影,状态变化过程则交给 comment、activity_log 和运行事件记录。这样读取列表时不必重放全部历史,审计时又能追溯状态如何变化。
Issue 的负责人和创建者使用 *_type + *_id 表达多态主体,因此可以指向成员、智能体、小队或系统主体。metadata 适合保存来源和自动化产生的机器信息,工作区自定义字段则由 issue_property 定义、由 Issue 的属性值引用;服务层负责检查定义和任务是否属于同一工作区,并验证属性类型和大小。
状态目录由 issue_status 独立建模,系统状态和工作区自定义状态共用同一解析路径。看板可以调整显示名称和顺序,而 Issue 仍使用稳定的状态 key,避免展示层变化破坏历史数据。
标签、依赖与层级
标签定义保存在 issue_label,issue_to_label 只保存任务与标签的连接,因此标签可以独立创建、归档和复用。相同的标签命名空间也服务于 agent_to_label 和 skill_to_label,让任务、智能体和 skill 可以使用一致的筛选方式。
issue_dependency 表达阻塞、被阻塞和关联等任务间关系;父子任务使用 issue.parent_issue_id,子任务状态变化则通过 issue_child_event 形成待处理事件。连接关系和事件分开后,层级结构负责表达当前组织方式,事件表负责保证异步状态传播不会因为重复投递而丢失或重复执行。
评论、活动与收件箱
comment 同时承担讨论记录和触发智能体的入口:它可以形成线程,也可以追溯到产生它的运行。评论和任务上的表情反应分别由 comment_reaction 与 issue_reaction 保存,避免把反应状态塞进正文。
activity_log 是审计时间线,记录谁在何时对工作区或任务做了什么;它不取代 Issue 的当前状态。inbox_item 是面向成员或智能体的通知投影,表示一条已经产生的提醒;issue_subscriber 表示希望继续接收变化的订阅意愿。一个描述“订阅谁”的表和一个描述“通知什么”的表分开,才能分别处理退订、已读和通知重建。
项目、视图与附件
project 为一组任务提供目标和负责人,project_resource 把仓库、目录或外部资源挂到项目上。项目是组织范围,任务仍然是实际协作对象,二者不会互相复制内容。
issue_view 保存筛选与展示的查询定义,issue_view_preference 只保存某个用户对该视图的显示偏好。视图是查询描述,不是任务缓存;任务列表仍由查询层根据当前事实重新计算。
attachment 是统一的文件引用索引,可以挂到任务、评论、Chat 或运行上;文件本体留在本地或对象存储,数据库只管理引用和生命周期。issue_source_context 保存从任务、评论或运行捕获的不可变上下文快照,issue_source_context_object_intent 则记录对象存储清理意图和重试进度,使快照删除可以异步完成而不丢记录。
唤醒规则
issue_wakeup 把“以后什么时候再次唤醒智能体”从当前任务状态中分离出来,支持定时、事件过滤、间隔、过期和最大触发次数等策略。每次事件处理都会在 issue_wakeup_receipt 留下去重凭据,并记录实际创建的运行;即使同一事件重复投递,也不会重复启动运行。
Agent 与执行链路
Agent 配置
agent 描述智能体“应该怎样工作”:指令、模型、权限、可见性和并发限制属于配置;agent_runtime 描述“在哪里执行”:它代表某个守护进程提供的实际环境。配置与执行环境分开后,同一个智能体可以更换运行时,一个运行时也可以承载多个智能体。
系统级智能体可以作为 Agent Builder 等内部流程的隐藏执行载体,普通列表会将其过滤。agent_invocation_target 保存公开调用授权,agent_mcp_server 连接智能体与工作区 MCP Server;这些授权关系依靠智能体所属工作区确定范围,不重复复制工作区字段。
Runtime 与 Profile
runtime_profile 描述某类运行时如何启动,agent_runtime 描述某个守护进程当前提供的在线实例。前者是可复用的协议和启动定义,后者是带有心跳、设备信息和在线状态的运行对象。daemon_connection 记录连接变化,daemon_token 负责守护进程认证,三者共同覆盖“定义、在线实例、连接凭据”三个生命周期。
任务队列
agent_task_queue 是 Issue、Agent、Runtime 和一次运行之间的连接点。它既保存排队和执行状态,也保存来源、重试、重跑、审计归因、小队协作和派发时冻结的上下文。这样一次运行可以从任务、Chat、自动化或唤醒规则进入同一条执行链路,而不会为每个入口建立独立的任务模型。
队列领取使用行锁和 SKIP LOCKED,多个守护进程可以并行扫描而不会领取同一条记录。运行完成后,结果不会直接覆盖 Issue 的描述或状态;服务会分别写入运行消息、评论、活动和事件,让当前状态、过程记录和审计时间线各自保持清晰。
消息与用量
task_message 是一次运行的事件流,适合重建工具调用和输出过程;大输出可以保留截断标记,而不必把所有内容塞进任务主表。task_usage 保存单次运行的模型用量,小时聚合表则面向统计和配额查询。task_usage_hourly_dirty 表示需要重算的聚合桶,task_usage_hourly_rollup_state 记录聚合水位和最近一次处理结果,原始运行记录因此不必承担报表查询压力。
Skills 与 Squads
skill 是工作区级能力包,skill_file 保存其附属文件,agent_skill 决定某个智能体是否启用某项能力。能力定义和智能体配置分离后,同一个 skill 可以被多个智能体复用。
squad 把多个成员或智能体组织成由 Leader 协调的小队,squad_member 只记录成员类型、成员身份和角色。任务和自动化可以把小队作为负责人,具体执行时再由任务队列记录 Leader Task,组织关系与运行实例因此保持分离。
Chat 与渠道数据
Chat 核心表
chat_session 表示用户与智能体的持续会话,chat_message 保存会话中的消息和与运行的关联。一条用户消息可以创建一个统一的运行,再进入 agent_task_queue;因此 Web Chat、移动端 Chat 和外部聊天渠道共享同一条执行链路。chat_draft_restore 提供取消或失败后的恢复入口,chat_pinned_agent 只保存用户在工作区内的置顶顺序。
Channel 表
通用 channel_* 表把外部平台拆成四层:安装记录负责工作区和渠道配置,用户与会话绑定负责外部身份映射,入站表负责去重和审计,投递表负责把运行结果送回外部聊天。卡片补丁、分块回复、上下文重建和媒体解析各自拥有独立的状态账本,因此某一阶段重试不会重复发送或破坏会话游标。
这些表大多由应用层维护工作区、安装、绑定和 Chat 的生命周期。早期的 lark_* 表仍保留用于历史数据和迁移兼容,新业务路径统一使用带 channel_type 的通用模型;这是一种数据模型演进,而不是两套并行的业务入口。
自动化与外部集成
Autopilot
autopilot 保存自动化规则,autopilot_trigger 把规则连接到 Cron、Webhook 或事件,autopilot_run 则记录一次实际触发。规则配置、触发条件和运行结果分开保存,使规则修改后仍能解释历史运行;autopilot_rule_version 保存发布时的快照,订阅者和协作者保存访问关系。
配额表把“本时间段还能启动多少次”和“这一次是否已经预留额度”分开建模:autopilot_quota_period 维护工作区时间段的计数,autopilot_quota_reservation 通过幂等键记录预留、执行和结算。webhook_delivery 则把验签、去重、租约和重试从自动化规则中分离出来,便于可靠地重新投递外部事件。
VCS 与 GitHub
通用 VCS 模型用 vcs_connection 保存连接,用 vcs_pull_request 保存 Pull Request 镜像,再由 issue_vcs_pull_request 建立任务与 PR 的关联,vcs_commit_status 保存提交检查。连接、外部对象和任务关联分开后,同一套任务模型可以接入不同版本控制服务。
GitHub 专用表保留 GitHub 安装、PR、检查套件和检查运行等更细的对象;待处理安装和检查结果用于处理异步回调尚未完成绑定的中间状态。通用 VCS 与 GitHub 专用模型并存,是为了兼容不同集成能力,任务关联仍由各自的 link 表维护。
插件与 MCP
插件模型按“包、版本、文件、工作区安装”分层:版本和文件是不可变发布物,安装记录决定某个工作区启用哪个版本以及授予哪些权限。plugin_storage 和 plugin_secret 分别保存插件运行数据与加密密钥,Hook 调度和调用记录则独立记录计划、重试和结果。
workspace_mcp_server 保存工作区级工具配置,agent_mcp_server 决定某个智能体可以连接哪些 Server;user_composio_connection 保存用户授权的外部工具连接。插件、MCP 和 Composio 都是执行能力的来源,但它们的安装、授权和运行记录分别建模,避免把外部凭据直接混入智能体或任务主表。
数据访问层
SQL 与 sqlc
数据访问代码集中在 server/pkg/db/queries/。每个 SQL 文件按领域拆分,例如 issue.sql、agent.sql、chat.sql、autopilot.sql 和 runtime.sql。server/sqlc.yaml 把 migrations/ 作为 schema,把这些查询生成到 server/pkg/db/generated,使用 pgx/v5:
1 | sql: |
生成的 db.Queries 不知道业务含义,只持有一个 DBTX:
1 | type DBTX interface { |
这段代码解释了数据访问层的边界:SQL 是人工编写的,参数和返回行由 sqlc 生成成强类型 Go 方法,WithTx 则把同一组查询切换到事务连接。业务代码不需要手写 rows.Scan,也不会把 SQL 字符串散落在 Handler 中。
主库与只读副本
先从只有一个数据库的情况看起:创建任务、修改状态、查询列表,都访问同一台 PostgreSQL。这台负责接收写入的数据库就是主库。随着请求增多,大量查询会与写入争用数据库的计算资源和连接,任务创建、状态更新等操作也可能因此变慢。
只读副本是一台持续接收主库变更、供应用查询的数据库。PostgreSQL 的物理流复制通过传输并重放 WAL(记录数据库变更的日志),让副本逐步获得与主库相同的数据。把部分查询交给副本,就能分担主库的读取压力。不过,变更从主库传到副本需要时间:用户刚创建一个任务,主库已经保存成功,副本却可能还没收到它;此时若从副本刷新列表,新任务就会暂时“消失”。
因此,Multica 按业务对数据新鲜度的要求选择数据库。需要马上看到刚提交的变化时,查询继续走主库,这里称为“强一致读取”;允许稍后刷新补齐变化时,才可以走副本,这里称为“最终一致读取”。例如,守护进程定期获取工作区列表,可以在下一次刷新时补齐变化;真正访问工作区资源时,各接口仍独立检查权限。副本成功返回旧数据不会触发自动回退,所以能否接受这种延迟,必须由业务主动判断。
代码中的分工也沿着这个思路展开。pgx/v5 是 Go 访问 PostgreSQL 的驱动,它提供的 pgxpool 会维护一组可复用的连接,避免每次查询都重新连接数据库。服务默认只创建主库连接池;配置 DATABASE_REPLICA_URL 后,再为副本创建独立连接池。sqlc 生成的 db.Queries 可以接收任意一个连接池,所以相同的查询方法既能在主库执行,也能在副本执行。创建副本连接时,pgx 会检查目标连接是否只读;未配置副本或副本配置无效时,服务仍使用主库。
选择工作由 server/internal/dbreader/selector.go 中的 Selector 完成。普通业务仍直接使用主库查询对象;允许延迟的业务则显式调用 dbreader.Read,声明 EventualConsistency,并提供一个可以重复执行、不会改变数据的查询。Selector 先尝试副本,如果连接失败或数据库暂时不可用,就在主库上重新执行一次。PostgreSQL 副本重放日志时也可能与正在执行的查询冲突并取消查询,这种情况同样可以回退主库;权限错误、SQL 错误或请求取消则直接返回。驱动提供的错误类型和 PostgreSQL 的 SQLSTATE 错误码,让代码能够区分这些情况。
如果副本持续不可用,每个请求都先尝试它会增加等待时间。Selector 因此会在发现可用性故障后,暂停访问副本两秒,让请求直接走主库;之后放行一个真实查询试探,成功后恢复副本读取。这就是代码中的“熔断与恢复”。可选的 Prometheus 指标会记录查询走了哪台数据库、为什么回退,帮助观察副本是否分担了压力。这套方案把复制交给 PostgreSQL,把连接管理交给 pgx,把一致性选择和故障回退留在应用层。
Service 组合业务操作
Handler 负责 HTTP 输入输出,Service 负责跨表校验、事务、状态变化和事件。以 IssueService.Create 为例,源码中的事务流程依次是:
TxStarter.Begin开启事务,使用s.Queries.WithTx(tx)得到事务内查询对象。- 校验父 Issue、项目、标签和自定义属性是否属于当前工作区。
- 锁定并检查重复 Issue。
- 锁定工作区并分配
issue.number,计算看板位置。 - 插入 Issue,必要时同时保存来源上下文、属性和标签关系;普通附件在提交后幂等绑定,媒体门控场景的延迟 Task 则与 Issue 一起提交。
- 提交事务后发布
EventIssueCreated,再排入 Agent Task 或唤醒 Squad Leader。
核心代码结构如下:
1 | tx, err := s.TxStarter.Begin(ctx) |
这里的提交边界很关键:Issue、编号、标签和必要的 Task 必须先成为一个一致状态;事件广播、分析上报和普通的 Agent 排队发生在提交之后,避免客户端收到一个数据库中还不存在的 Issue。
AutopilotService 和 TaskService 使用同样的 TxStarter 接口。Autopilot 会在事务中创建运行、配额预留和规则版本;TaskService 会在事务中锁定 Chat session、创建消息和队列任务。不同入口(HTTP、Lark、Webhook、后台调度器)最终都调用 Service,因此不会各自实现一套不一致的写入规则。
并发与删除
数据访问层不仅是“执行 SELECT/INSERT”。Issue 更新查询会使用 FOR UPDATE 保护并发字段合并;队列领取使用行锁和 SKIP LOCKED;Chat session 在追加消息、切换 Runtime 和删除时有专门锁;配额表通过 ON CONFLICT DO UPDATE 锁住工作区时间段。
删除同样由业务层掌控。删除 Issue 前,服务会锁定 Issue,清理附件、评论、Task、唤醒和来源上下文,并处理子 Issue 与重复标记;删除工作区则执行前面提到的固定清理计划。数据库中的索引和约束负责缩小查询范围、拒绝明显非法状态,跨表生命周期和权限仍由 Service 明确编排。
一次请求的完整路径
以“成员创建一个分配给 Agent 的 Issue”为例,调用链可以按下面的顺序阅读:
1 | HTTP Handler |
客户端看到的 Issue 列表来自 issue.sql 的工作区过滤和分页查询;时间线来自 comment、activity_log 和实时事件;Agent 页面把 agent、agent_runtime、skill 和任务统计组合起来;运行详情再读取 agent_task_queue、task_message 和 task_usage。这些页面没有各自维护一份“真相”,而是通过查询层重新投影同一组数据库事实。
理解这套模型后,阅读源码可以从三条线索开始:先从 001_init.up.sql 和 models.go 找到实体与字段,再从 pkg/db/queries 找到某个用例需要的 SQL,最后进入 internal/service 看事务、锁、权限和事件如何把多张表组合成一次业务操作。这样既能看清每张表的职责,也不会把数据库字段误认为完整的业务流程。
小结
Multica 的数据模型围绕三条主线展开:工作区限定数据和权限范围,任务(issue)承载协作内容与当前状态,运行(agent_task_queue)记录智能体的一次执行。评论、消息和用量补充过程记录,Chat、自动化与外部渠道复用这套协作和执行模型。
数据访问层用 SQL 和 sqlc 表达查询,用 Service 组织跨表操作,再通过事务、锁和应用层校验维护一致性。只读副本分担允许延迟的查询,主库承担写入和需要及时看到结果的读取;理解这些边界,就能从表结构一路读到完整的业务流程。