Slark 项目的默认技术决策与常量。开发中遇到本文档覆盖的问题时,以此为准;发现不合理时通过 PR 修改本文档 + 关联代码。
所有决策编号为
D-N,可以在代码注释和 commit message 中引用(例 "Fix D-4 budget overflow")。
本文档是战略文档 product-brief.md v1.0.1 的实现层映射。每条 D-N 要么是 product-brief 里某条核心决策的技术细节补充(常量、默认值、schema),要么是 product-brief 未覆盖的纯实现约束(错误 UI / 启动方式 / token 预算等)。
| D-N | 对齐的 product-brief 章节 | 性质 |
|---|---|---|
| D-1 / D-2 | §D-7 多项目并发隔离(状态派生 per-channel) | 细节补充 |
| D-3 | §D-7(agent_activity schema) |
细节补充 |
| D-4 / D-5 | §5.1 ContextBuilder / spawn-per-message | 细节补充 |
| D-6 | §D-7 K-2(链式触发 per-thread 计数) | 细节补充 |
| D-7 | §D-5 Responsibility metadata 之一 | 细节补充 |
| D-8 | §D-8 Slark 无 Agent 独立 workspace | 细节补充 |
| D-9 | §D-3 Goal 驱动的 Create Project 流程 | 细节补充 |
| D-10 / D-11 | 无对应战略条目(纯实现) | 纯实现 |
| D-12 | §C-1 Cursor Adapter 流式策略 | 细节补充 |
| D-13 | §D-2 Server = Project | v1.0 新增锚点 |
| D-14 | §D-3 Goal 是一等公民 | v1.0 新增锚点 |
| D-15 | §6 System Agents | v1.0 新增锚点 |
| D-16 | §D-4 Workflow 即甬道 | v1.0 新增锚点 |
| D-17 | §D-5 Responsibility 即 Step × Agent | v1.0 新增锚点 |
| D-18 | §D-7 多 Project 并发隔离六层契约 | v1.0 新增锚点 |
| D-19 | §D-3 Team Architect 兜底三件套 | v1.0 新增锚点 |
| D-20 | §5.3 四个运营闭环 | v1.0 新增锚点 |
| D-21 | "Cursor 风格 open folder" 心智 | v1.1 新增(Per-Project Storage 重构) |
| D-22 | §D-7 多项目隔离的物理实现 | D-21 衍生 |
| D-23 | §D-3 / §D-7 协作友好的 git 入仓建议 | D-21 衍生 |
| D-24 | §11 Schema 总清单 | D-21 衍生 |
| D-25 | §D-2 Project lifecycle Q-11 决议 | D-21 衍生 |
冲突规则:本文档与 product-brief 冲突时,以 product-brief 为准。本文档只负责承载"实现细节",不决定产品定位。
关于"目标状态" vs "当前实际状态":本文档描述的是目标决策。当前代码可能因为兼容旧版本仍处于过渡状态(例如 D-1 / D-8 描述了"已废除"但代码仍保留 v0 兼容字段)。实施进度与技术债以
docs/project-status.md为准,本文档不重复维护。
使用以下 5 个状态替代原版的 Online/Thinking/Working/Hibernating/Offline:
| 状态 | 含义 | UI 状态点颜色 |
|---|---|---|
idle |
Agent 已配置、CLI 已检测可用、当前无进程 | 🟢 绿色 |
thinking |
已 spawn 进程,正在消费 stdin 或等待首个事件 | 🟠 橙色 |
working |
进程已产出 text_delta 或 tool_started |
🟠 橙色 |
error |
上一次 spawn 失败/超时/非 0 退出 | 🔴 红色 |
stopped |
用户显式 Stop,需手动 Start 才能再次响应 | ⚫ 灰色 |
目标状态:原 agents.status 单值字段废除(v0 设计的隔离缺陷 K-1);状态改为 per-channel 派生:
- 新建
agent_runs(id, agent_id, channel_id, status, started_at, ended_at)表记录每次 spawn - 查询 Agent 在指定 channel 的当前状态:
SELECT status FROM agent_runs WHERE agent_id=? AND channel_id=? AND ended_at IS NULL - Sidebar 全局状态点派生自 "any active run";DM Header 等精细位置显示 per-channel 状态
- 详见
product-brief.md §D-7 K-1
⚠ 当前过渡状态(TD-2 / TD-3,见
docs/project-status.md):agent_runs表已写入并是 engine 端事实来源;agents.status字段仍保留并双写以兼容旧前端。Sidebar StatusDot 仍读agents.status,未按 channel 派生。Sprint 2 一并清理。
spawn-per-message模型下没有"长期在线"的进程,删除Online/Hibernating避免误导idle对应原版 "Online"(已注册可用)thinking/working拆分保留,前端可据此切换加载图标- per-channel 派生解决同一 Agent 并发在多个 channel 时状态互相覆盖问题
spawn
idle ─────────────► thinking ─────────────► working ─────────────► idle
▲ │ │ │
│ │ timeout / crash │ timeout / crash │
│ ▼ ▼ │
│ error ────── retry ────► thinking │
│ │
│ │
│ user click Stop │
└─────────── stopped ◄─────────────────────────────────────────────────────┘
│
│ user click Start
▼
idle
CLI 事件(Phase 0 统一后的 CLIEvent) |
Agent 状态变更 |
|---|---|
spawn 前 |
idle → thinking |
首个 text_delta 或 tool_started |
thinking → working |
tool_started 后续(已在 working) |
保持 working |
done(进程正常退出) |
working → idle |
error(非 0 退出 / 超时 / parse 失败) |
任意 → error |
| 用户点 Stop | 任意 → stopped(同时 kill 进程) |
用户点 Start(从 stopped 或 error) |
→ idle |
所有状态变更通过 WebSocket agent_status 事件广播,前端 useAgentStore 订阅更新。
agent_activity 表用于 Profile → Activity Tab 实时日志。不等同于消息流,只记录"元事件"。
- 原 v0 schema 缺
channel_id字段,UI 混展不同 channel 的活动(K-3) - v1.0.1 起
agent_activity必须包含channel_id TEXT REFERENCES channels(id) ON DELETE CASCADE - 索引:
idx_activity_agent_channel(agent_id, channel_id, created_at DESC) - Activity Tab 提供 "filter by channel" 下拉
| 触发 | 写一条 activity | 示例 detail |
|---|---|---|
| spawn 开始 | type=thinking |
"Spawning cursor with model=composer-2-fast" |
| 首个 text/tool 事件 | type=working |
"Started generating response" |
每个 tool_started |
type=working |
"Shell: ls -la /path" |
每个 tool_completed |
type=output |
"Shell completed (exit=0)" |
done(正常结束) |
type=idle |
"Completed in 12.3s" |
error |
type=error |
"Timeout after 300s" |
不记录:
text_delta(每个字符/片段都记录会爆炸)- 心跳事件
- 用户消息本身(那是 messages 表的事)
- 每个 agent 保留最近 500 条 activity(全 channel 合并),超出从头删除
- Profile → Activity Tab 分页加载(每页 50 条)
| 常量 | 默认值 | 说明 |
|---|---|---|
MAX_CONTEXT_TOKENS |
8000 |
单次 spawn 注入给 CLI 的总 token 上限(保守值,所有主流模型都 OK) |
DESCRIPTION_BUDGET |
2000 |
Agent description 占用的预算上限,超过截断 |
HISTORY_BUDGET |
5500 |
对话历史占用预算,按最近消息倒序填充直到用完 |
CURRENT_MESSAGE_BUDGET |
500 |
当前触发消息占用预算(几乎不会超) |
- Token 估算用 "4 字符 ≈ 1 token" 的粗估(不引入 tokenizer 依赖)
- 从最新消息往前累加字符数到
HISTORY_BUDGET * 4,超出就丢弃 description如果超限,从中间截断并用...代替(保留开头 + 结尾的职责说明)
上述常量写在 packages/shared/src/constants.ts,用户暂不在 UI 中配置。未来可加入 Advanced Settings。
| 常量 | 默认值 | 说明 |
|---|---|---|
MAX_CONCURRENT_PROCESSES |
3 |
同时运行的 CLI 进程数上限 |
PROCESS_TIMEOUT_MS |
300000(5 分钟) |
单次 spawn 的超时时间 |
QUEUE_STRATEGY |
FIFO |
超出并发时的等待队列策略 |
QUEUE_MAX_SIZE |
20 |
队列最大长度,超出直接拒绝并返回 error |
- 请求来时:并发 < 3 → 立刻 spawn;并发 = 3 → 进入队列;队列满 → 立刻返回
agent_status = error, detail="queue_full" - 进程结束后:检查队列,FIFO 弹出下一个
- 用户点 Stop:从队列中移除(如果在队列中),或 kill 进程(如果已 spawn)
| 常量 | 默认值 | 说明 |
|---|---|---|
MAX_CHAIN_DEPTH |
10 |
一次链式触发最多传递多少层 |
MAX_AGENT_CONSECUTIVE_TRIGGERS |
3 |
同一 agent 在同一 thread 内连续被触发次数上限 |
MAX_MENTIONS_PER_MESSAGE |
5 |
单条消息 @mention 他人的上限(超出不触发后面的) |
所有计数按 thread 作用域,不按 agent 全局(K-2 修正):
chain_depth沿messages.parent_id树累加,切换到不同 thread 时重置为 0consecutive_triggers只在同一 thread 内累加,同一 Agent 在不同 thread 的触发互不相关- 这保证:Agent 同时被两个 thread 各触发 2 次,不会误伤(只有单个 thread 里连续触发才计数)
- 消息带
chain_depthmetadata(从 0 开始) - Message Router 在触发下一 Agent 前检查:
- 当前 thread 消息数 >=
MAX_CHAIN_DEPTH→ 停止触发 + 发 system 消息"Chain depth limit reached" - 同一 agent 在当前 thread 内连续被触发 >= 3 → 停止 +
"Possible infinite loop detected"
- 当前 thread 消息数 >=
- User 主动 @mention 不受
consecutive限制(只防 Agent 间循环)
TypeScript 类型(在 packages/shared/src/types.ts 中定义):
type MessageMetadata = {
// 提及的 agent 列表(正则解析自 content)
mentions?: Array<{ name: string; agent_id: string | null }>;
// 关联的 task(当 Task 状态变更产生系统消息时)
task_ref?: {
id: number;
title: string;
status: 'todo' | 'in_progress' | 'in_review' | 'done';
assignee_agent_id: string | null;
};
// 链式触发深度(0 = 用户首次发起)
chain_depth?: number;
// 链式触发中的"上游"消息 id
triggered_by_message_id?: string;
// CLI 工具调用记录(存在此消息的响应内)
tool_calls?: Array<{
tool: string;
args: Record<string, unknown>;
result?: string;
success?: boolean;
duration_ms?: number;
}>;
// 消息是否正在流式输出(未完成时为 true,完成后移除或置 false)
streaming?: boolean;
// Agent 响应的元信息(仅 sender_type=agent 时存在)
agent_meta?: {
runtime: string; // "codex" / "claude" / "cursor"
model: string; // 实际使用的模型
total_duration_ms: number;
input_tokens_estimate?: number;
output_tokens_estimate?: number;
};
// System 消息的事件类型(仅 sender_type=system 时存在)
system_event?:
| { type: 'task_created'; task_id: number; title: string }
| { type: 'task_claimed'; task_id: number; agent: string }
| { type: 'task_moved'; task_id: number; from: string; to: string; by: string }
| { type: 'agent_error'; agent: string; message: string }
| { type: 'chain_limit_reached'; detail: string };
};- metadata_json 字段非必需(
NULL合法) - 插入时序列化 JSON;读取时按类型反序列化
- 前端渲染消息卡片时,根据 metadata 决定 task badge、@mention 样式等
v0 设计给每个 Agent 分配 ~/.slark/agents/{agent_id}/ 作为 cwd 沙盒 + 记忆目录。该设计在 v1.0 全面废弃。
Slark 不提供 Agent 独立 workspace。原因:
- 聚焦"编程协作" + 关闭"自主学习型 Agent" 口子后,Agent 的记忆通过其他机制承载(见下)
- 原沙盒同时兼 cwd 和记忆职责,混淆了"当下工作目录"和"长期私人空间"
- 多 Project 并发时沙盒成为共享 cwd,两 Project 会冲突(K-5)
// CLIRunner 构造 spawn 参数
const project = projectsRepo.byId(channel.project_id);
const cwd = project.workspace_path; // 必填(D-13),不再有兜底projects.workspace_path是NOT NULL- 不存在"纯聊天 Project"的回退路径(N-11)
⚠ 当前过渡状态(TD-4,见
docs/project-status.md):CLIRunner 在 channel 没有 project_id 时仍有~/.slark/agents/{id}/兜底;Create Agent 也仍 mkdir 沙盒目录。所有运行时已切到project.workspace_path,但死代码未删。所有 v0 channel 都迁完后清理。
原本想让 workspace 承载的"Agent 长期记忆",在 v1.0 通过以下机制分摊:
| 原职责 | v1.0 新归属 |
|---|---|
| 跨对话对话历史 | Slark messages 表 + ContextBuilder 注入(D-4) |
| Agent 人格(长期 description) | agents.description 字段;由 Coach 演化(§D-6 Evolution Loop) |
| 项目知识(team_rules) | projects.team_rules + decisions / lessons 表(D-20) |
| 项目级 Agent 约束 | CLI 原生的 <workspace>/AGENTS.md / CLAUDE.md / .cursor/rules/(Slark 不插手) |
原 3 Tab(PROFILE / WORKSPACE / ACTIVITY)改为 2 Tab(PROFILE / ACTIVITY);FEEDBACK Tab 在 Sprint 5 上线作为第 3 个 Tab。
v0 → v1.0 升级时直接删除 ~/.slark/slark.db 和 ~/.slark/agents/(见 product-brief.md §11 迁移策略)。
首次启动(slark.db 不存在时)不预置任何 Project / Channel / Agent。
projects: (空)
channels: (空)
agents: (空)
channel_agents: (空)
messages: (空)
tasks: (空)
- 前端检测
/api/projects返回空 → 显示 Welcome 页 - Welcome 页引导用户点
[+ New Project]→ Create Project 三步向导(详见PLAN.md Sprint 1 §1.3.2) - 第一步填 Name + Goal + Workspace
- 第二步由 Team Architect System Agent 自动推荐团队(D-15 / D-19)
- 用户 Approve 后才创建第一批数据
- v0 预置的
#general+ 默认 Assistant Agent 是"聊天室"心智的残留 - Slark v1.0 定位是 Programmable AI Team OS,Project 是一等公民(D-13),必须由 Goal 驱动创建
- 任何预置都会误导用户理解 Slark 的正确用法
MVP 只实装 Cursor 适配器。Create Agent / Team Architect 的 Runtime 下拉展示 6 个选项:
| Runtime | MVP 状态 |
|---|---|
| Cursor CLI | ✅ 可选,需本地安装 cursor-agent |
| Codex CLI | ❌ 标 "coming soon" 且 disabled |
| Claude Code | ❌ 标 "coming soon" 且 disabled |
| Kimi CLI | ❌ 标 "coming soon" 且 disabled |
| Copilot CLI | ❌ 标 "coming soon" 且 disabled |
| Gemini CLI | ❌ 标 "coming soon" 且 disabled |
- Welcome 页允许继续 Create Project(不阻断)
- Step 2 Team Architect 走兜底三件套(D-19)
- 黄色警告条提示安装
cursor-agent后再配 Agent runtime
- 开发模式:
pnpm dev(并发启动 web + server) - 监听地址:
http://localhost:4178(web) +ws://localhost:4179(server WebSocket)+http://localhost:4179(server REST) - 端口冲突自动降级到下一个可用端口
- 打包为 Electron / Tauri 应用
- 单一可执行文件启动
- 系统托盘常驻
| 变量 | 默认 | 说明 |
|---|---|---|
SLARK_HOME |
~/.slark |
数据目录 |
SLARK_PORT_WEB |
4178 |
前端端口 |
SLARK_PORT_SERVER |
4179 |
后端端口 |
SLARK_LOG_LEVEL |
info |
日志级别 |
SLARK_CLI_TIMEOUT_MS |
300000 |
覆盖默认 CLI 超时 |
| 场景 | UI 表现 |
|---|---|
| CLI spawn 失败 | 频道内产生 system 消息,红色文字:"⚠ @Agent failed to start: {error_message}" |
| CLI 超时 | 频道内 system 消息:"⏱ @Agent timed out after 5min" + Agent 状态点变红 |
| CLI parse error | 频道内 system 消息:"🐛 @Agent output could not be parsed" |
| 网络断开(WS 掉线) | 全局 banner:"⚠ Connection lost. Retrying..." |
| SQLite 写入失败 | Toast 通知:"Failed to save message. Check disk space." |
| 场景 | UI 表现 |
|---|---|
| Agent 正在 thinking(未出字) | 消息占位卡片 + 三点加载动画 "...";Agent 状态点变橙色 |
| Agent 正在 working(tool_call) | 消息卡片显示当前工具 "🔧 Running: ls -la" |
| 初次加载频道历史 | 骨架屏(4-5 条消息占位) |
| 发送消息 | Send 按钮禁用 + 加载图标 |
| 场景 | UI 表现 |
|---|---|
| 从未选择频道 | 欢迎页:"Welcome to Slark" + 使用指引 |
| 空频道(无消息) | "No messages yet. Start a conversation." |
| 空 Tasks | "No tasks in this channel. Click + New Task to create one." |
| 空 Threads 全局页 | "No threads yet." |
| 未安装任何 CLI | 引导安装:"Install Codex / Claude Code / Cursor CLI to get started." |
以下决策等到具体 Phase 时再定:
推荐:Tailwind + Radix UI Primitives(无样式 accessibility 原语,样式全自写)
理由:slock.ai 的 Neo-Brutalism 风格(2px 黑边 + 硬阴影 + 零渐变 + 粉黄强色)与 shadcn/ui 默认风格冲突严重,深度定制 shadcn 的成本 ≥ 自写;但完全裸写需要自行处理 a11y(Focus trap、Dialog、Menu 等)。Radix 是最佳中间方案。
推荐:组件清单逐项核验 + 关键页面并排截图
理由:Playwright 视觉回归太重;纯人工太主观。折中:每个组件在 PR 描述中附上 before/after 截图,对照参考截图做肉眼检查。关键 3-5 个页面(Channel Main / DM / Profile / Create Dialog / Thread)做并排对比。
待 Phase 0 跑完后根据实际 JSON 输出更新 docs/cli-event-format.md。
不在 v1.0 MVP(Sprint 1~7)范围,列入 product-brief §7 R-23。
Cursor CLI 不启用 --stream-partial-output,即 CursorAdapter.capabilities.supportsTextDelta = false。
Cursor 的 --stream-partial-output 会产生两阶段输出:
- 多条
assistantchunk(模型实时 draft,片段化) - 最后 1 条
assistant完整 replay(经 Composer 重新整理过,与前面累加的 chunks 内容不完全相同) result事件(result.result= 最终版 = 最后那条 assistant)
这导致前端会看到"流式显示了一段 draft → 突然被覆盖为整理后的最终版"的跳动,UX 不好。
关闭后的实际表现:
thinking.delta事件仍然流式(用户看到"思考过程"实时更新)assistant事件一次性给出完整文本,emit 为text.completed- 前端在 thinking 阶段显示灰色思考动画,切换到 final answer 时一次性展示
- TTFB:5-15s(见 Phase 0 基线),但 thinking 阶段 4.5s 就开始流式反馈
- 未来优化方向:Runner 层加"伪流式"切片 emit(固定间隔把 text.completed 分段 emit 为 text.delta),可获得打字机效果而不引入 replay 风险
这一组决策对应 product-brief v1.0.1 的 6 层架构 / 4 Loop / 6 System Agents。每条都是锚点,详细产品语义以 product-brief.md 为准;此处只承载"实现层约束"。
对齐:product-brief.md §D-2 Server = Project
CREATE TABLE projects (
id TEXT PRIMARY KEY,
name TEXT NOT NULL UNIQUE, -- URL slug (lowercase, dash/underscore only)
display_name TEXT,
workspace_path TEXT NOT NULL, -- 必填!绝对路径
goal TEXT NOT NULL, -- 必填!详见 D-14
team_rules TEXT, -- 可选团队协作规则
color TEXT,
created_at INTEGER NOT NULL
);
ALTER TABLE channels ADD COLUMN project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE agents ADD COLUMN project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE;/p/{projectName}/channel/{channelId}
/p/{projectName}/dm/{dmId}
/p/{projectName}/tasks
/p/{projectName}/intelligence (Sprint 4 新增)
暂不做(见 product-brief §7 R-19,Sprint 8+)。
对齐:product-brief.md §D-3 Goal 是一等公民
- 必填:Create Project 无 Goal 则拒绝
- 长度上限 500 字符(Q-3 决议):UI 显示字符计数,超出截断并提示
- 注入位置:ContextBuilder 将 Goal 放在 prompt 最顶部(高于 description 和 team_rules)
[Project Goal]
<project.goal>
[Team Rules]
<project.team_rules>
[Your Role]
<agent.description>
...
对齐:product-brief.md §6 System Agents
| Agent | Sprint | 职责 | 实现 |
|---|---|---|---|
| Team Architect | Sprint 1 | Goal → 推导 Team | 复用 CursorAdapter + 特殊 description(Slark 内置,不暴露) |
| Scribe | Sprint 4 | 沉淀 decisions / lessons | 同上 |
| Evaluator | Sprint 5 | 定期评估 Agent 产出 | 后台 cron 触发 |
| Coach | Sprint 5 | 提 description 修改建议 | 依赖 Evaluator 输出 |
| Onboarder | Sprint 6 | 分析 codebase 生成 onboarding 包 | Project 创建时触发 |
| Facilitator | Sprint 7 | 主持 Workflow Design Session | 多轮对话,产出 YAML |
- Runtime:Q-1 决议,全部用 Cursor Agent(复用 CLIAdapter 架构)
- 不出现在 Sidebar:用户看不到它们作为"成员",避免 @mention 触发
- 权限:可写 System-owned 表(decisions / lessons / agent_feedback / project_onboarding),不可改用户的 agents / channels / messages 直接内容
- token 配额:Q-5 待决议(Sprint 4 启动前拍板),建议独立配额
对齐:product-brief.md §D-4 Workflow 即甬道
CREATE TABLE workflows (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
name TEXT NOT NULL,
description TEXT,
trigger_command TEXT UNIQUE, -- 如 "/new-feature"
definition_yaml TEXT NOT NULL, -- YAML 源码
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE workflow_runs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
workflow_id TEXT NOT NULL REFERENCES workflows(id),
channel_id TEXT NOT NULL REFERENCES channels(id),
thread_id TEXT,
status TEXT NOT NULL CHECK(status IN ('running','completed','aborted','failed')),
current_step TEXT,
started_by TEXT,
started_at INTEGER NOT NULL,
ended_at INTEGER,
state_json TEXT
);name: feature-development
trigger:
command: "/new-feature"
steps:
- id: <string>
owner: "@AgentName" | "local-user"
action: "approve_or_reject" | omitted (执行步骤)
on_complete: <next step id>
on_approve: <next step id>
on_reject: <next step id>
input: <prev step id> # 可选
output: <string tag> # 可选origin字段不保留(N-14)——所有 Workflow 一视同仁- Workflow YAML 版本控制(Q-4 待决议)建议靠 Export,不入 SQL
对齐:product-brief.md §D-5 Responsibility 即 Step × Agent
CREATE TABLE responsibilities (
id INTEGER PRIMARY KEY AUTOINCREMENT,
workflow_id TEXT NOT NULL REFERENCES workflows(id) ON DELETE CASCADE,
step_id TEXT NOT NULL,
agent_id TEXT REFERENCES agents(id), -- 可为 'local-user'
role TEXT NOT NULL CHECK(role IN ('executor','approver','reviewer','informed')),
authority TEXT CHECK(authority IN ('must_approve','optional_approve','no_authority')),
created_at INTEGER NOT NULL
);executor= R(责任人)approver= A(可一票否决)reviewer= C(可评论,不阻塞)informed= I(仅通知)
- Sprint 2 起:从 Workflow YAML 自动推导(
owner→executor) - Sprint 3 起:支持 UI 手动编辑 override
- Sprint 3 起:
'local-user'成为合法 agent_id,作为系统一等 "Agent"
对齐:product-brief.md §D-7 六层隔离模型
| 层级 | 隔离 | 机制 |
|---|---|---|
| 进程层 | 完全 | spawn-per-message(每次独立 PID) |
| Channel 级(对话历史) | 完全 | WHERE channel_id = ? |
| Project 级(workspace / Tasks / Knowledge) | 完全 | 按 channel.project_id 路由 |
| Agent 身份(description) | 共享 | 设计如此,Agent 是"一个人" |
| CLI 原生记忆(AGENTS.md) | 项目级 | CLI 自管,按 cwd 查找 |
| 运行时副作用 | 部分隔离 | 见必须修正的坑(K-N) |
| # | 问题 | 对应 D-N |
|---|---|---|
| K-1 | agents.status 单值冲突 |
D-1 修订(per-channel 派生) |
| K-2 | 链式触发计数维度 | D-6 修订(per-thread) |
| K-3 | agent_activity 无 channel_id |
D-3 修订 |
| K-4 | env_vars 只在 Agent 级 | 待 Sprint 2 新增 project_agent_overrides 表 |
| K-5 | ~/.slark/agents/{id}/ 共享 cwd |
D-8 v1.0 修订(废除沙盒) |
| K-6 | 并发池全局共享 | D-5 现状接受,Sprint 8+ 可加 per-Project 配额 |
对齐:product-brief.md §D-3 兜底策略(Q-2 / Review 5)
- 未安装
cursor-agent(cursor-agent --version失败) - Team Architect spawn 超时(独立 30s 超时,不受
PROCESS_TIMEOUT_MS300s 影响) - 返回 JSON 解析失败
const FALLBACK_TEAM: TeamSuggestion = {
agents: [
{ name: 'Architect', role: 'Architect', description: '...', runtime: '', model: '', reasoning: 'medium' },
{ name: 'Dev', role: 'Developer', description: '...', runtime: '', model: '', reasoning: 'medium' },
{ name: 'Reviewer', role: 'Reviewer', description: '...', runtime: '', model: '', reasoning: 'medium' },
],
rationale: 'Default team (Team Architect unavailable). Please configure runtime/model for each agent before use.',
};- Create Project Step 2 仍展示三张 Agent 卡片
- 顶部黄色警告条:
"Team Suggestion unavailable, showing default team. Please configure runtime per agent after approval." - 安装引导链接:
Install cursor-agent指向 Cursor 官网 / IDE 内的 "Install CLI" 菜单 - 用户 Approve 后进入 Agent Profile 必须手动选 runtime(字段为空不可 spawn)
对齐:product-brief.md §5.3 四个运营闭环
| Loop | 触发 | 产出 | Sprint 落地 |
|---|---|---|---|
| Onboarding | Create Project | project_onboarding |
Sprint 6 |
| Delivery | Workflow Run 结束 | decisions / lessons |
Sprint 4 |
| Evolution | 周期性(每 24h) | agent_feedback |
Sprint 5 |
| Reuse | Agent spawn 前 | ContextBuilder 注入 | Sprint 4(与 Scribe 同步) |
-- Sprint 4
CREATE TABLE decisions (...);
CREATE TABLE lessons (
id, project_id, kind, title, body, audience, tags_json,
source_message_id, recorded_by, confidence, review_status, use_count, ...
);
-- Sprint 5
CREATE TABLE agent_feedback (
id, agent_id, period_start, period_end,
observations, suggested_description, diff_summary,
applied, applied_at, approved_by, ...
);
-- Sprint 6
CREATE TABLE project_onboarding (
project_id PRIMARY KEY, overview, tech_stack_json, conventions, ...
);
CREATE TABLE agent_skills (
agent_id, project_id, skill_key, touch_count, last_touched, ...
);详细字段与行为约束见 product-brief §11 Schema 总清单 和各 Sprint 启动前拍板的 Q-N。
对齐:docs/per-project-storage-design.md v0.3(Q-10 ~ Q-14 决议)
项目级数据从中央 ~/.slark/slark.db 迁移到 每个 workspace 的 <path>/.slark/ 目录:
<workspace>/
└── .slark/
├── project.json ← 元数据(id / name / display_name / goal / team_rules / color / created_at)
├── slark.db ← 该 project 的全部业务数据(channels / agents / messages / tasks / workflows / decisions / lessons / observations / feedback / onboarding / skills / sessions)
├── slark.db-wal
├── slark.db-shm
├── knowledge/
│ ├── decisions.jsonl ← 仅 review_status='approved' 同步到 jsonl(Q-13),便于入 git
│ └── lessons.jsonl
├── observations/ ← reserved;evaluator 过程数据,默认 ignore
├── .gitignore ← 默认配置 slark.db* / observations/ 不入 git
└── README.md ← 自动生成的目录说明
中央 ~/.slark/projects.json 仅维护 recent list(id / path / lastOpened),不存任何业务数据。
- DB Handle Pool(
db/index.ts):LRU max=20,30min idle close;openProjectDb(workspacePath)懒加载。 - 资源反查:
findDbByResource('channels'|'agents'|...)跨 open db 反查,避免改 URL 形态。 - Warm-up:server 启动期 openProjectDb 全部 recent project,让
/api/threads、/api/saved等全局视图能聚合。 - 全局事件(D-21 + WS):
hub.broadcastGlobal({ type: 'project_list_changed' | 'knowledge_updated' })推送到所有 socket,前端 sidebar / inbox 自动 refresh。
开发阶段不向前兼容(Q-12):旧 ~/.slark/slark.db 启动期检测到时仅 warn,不迁移;用户手动 mv 即可释放磁盘。
对齐:product-brief.md §D-7 多项目隔离六层契约 + D-21
D-21 把项目隔离从逻辑层(WHERE project_id = ?) 升级到 物理层(独立 SQLite 文件):
| 隔离维度 | v1.0(中央 db) | v1.1+(per-project db) |
|---|---|---|
| Channel ID 命名空间 | 全局唯一(nanoid) | per-db 唯一即可 |
| Agent name 命名空间 | per-project unique(应用层校验) | per-db 唯一(schema 层 UNIQUE) |
| 数据库文件 | 1 份共享 | N 份独立 |
| 备份/恢复粒度 | 整个 Slark | 单个 project(拷贝目录即可) |
| 并发写入 | 单 db lock 串行化 | 各 project 独立 lock |
| 删除项目副作用 | DELETE CASCADE 大量行 | rm -rf .slark/,无副作用 |
资源反查约束:因为 channel_id / agent_id 不再全局唯一,server 启动期必须 warm-up 所有 recent project;运行时未 warm-up 的资源(LRU 淘汰后未访问)通过下次访问 lazy 重新打开恢复。
对齐:product-brief.md §D-3 协作场景
<workspace>/.slark/ 自动写入 .gitignore + README.md,明确推荐入仓边界:
| 文件 | git 策略 | 理由 |
|---|---|---|
project.json |
commit | 团队对项目元数据(goal / team_rules)的共识 |
knowledge/*.jsonl |
commit | reviewed 决策 + 经验,跟代码 review 一起走 |
slark.db* |
ignore | 个人对话历史;冲突合并不可行 |
observations/ |
ignore | 实时 evaluator 噪声 |
README.md |
optional | 不强制;用户可自由扩展 |
仅在 review_status='approved' 状态变化时整体重写 decisions.jsonl / lessons.jsonl(非增量 append)。代价是每次 approve 重写 100 行级别的小文件,IO 可忽略。不双向同步:用户手动改 jsonl 不会回写 db(Q-14)。
对齐:D-21 / D-22
- 删除
projects表(元数据迁移到project.json) - 所有 v1.0 的
project_id列、FK、相关 INDEX 全部移除 agents.name UNIQUE自然变成 per-project 唯一(无需应用层校验)workflows.trigger_command UNIQUE自然变成 per-project 唯一project_onboarding改为 singleton(id INTEGER PRIMARY KEY CHECK (id = 1))agent_skills.UNIQUE(agent_id, skill_key)替换原UNIQUE(agent_id, project_id, skill_key)
返回给前端的 Project / Channel / Agent 等 DTO 仍含 project_id 字段,由 server 端从 db handle 反查注入,前端不感知存储变化。
对齐:Q-11 决议(docs/per-project-storage-design.md)
| 操作 | 触发 | 副作用 | UI 入口 |
|---|---|---|---|
| Open | 用户选 workspace 路径 | 检测 <path>/.slark/:存在则 reopen(读 project.json + open db),不存在则新建(写 project.json + 初始 db + .gitignore + README.md + #general channel) |
Sidebar 顶部 "📂 Open project folder" / WelcomePage / OpenProjectDialog |
| Close | 用户从 Sidebar / Settings 选 Close | 仅从 ~/.slark/projects.json 移除 + 关闭 db handle;保留磁盘文件 |
Sidebar Switcher ⋯ → Close / Settings Danger Zone "Close project" |
| Delete .slark/ | 用户从 Settings 选 Delete + 输入项目名 slug 校验 | server 端 confirm_name === project.name 校验通过后 rm -rf <path>/.slark/ + 从 recent 移除;代码仓库本身不动;不可撤销 |
Settings Danger Zone "Delete .slark/ folder"(独立模态 + 文本输入校验) |
POST /api/projects/open → { project, is_new }
POST /api/projects/:id/close → 204
POST /api/projects/:id/delete-storage → body: { confirm_name },匹配才执行;否则 400
旧 POST /api/projects 已废除;旧 DELETE /api/projects/:id 由 close + delete-storage 双按钮替代。
| 版本 | 日期 | 变更 |
|---|---|---|
| v0 | ~2026-04-22 | 初版 D-1 ~ D-12 + O-1 ~ O-4,支撑 v0 MVP |
| v1.0.1 | 2026-04-23 | 对齐 product-brief.md v1.0.1:修订 D-1 (状态 per-channel)、D-3 (activity 加 channel_id)、D-6 (链式 per-thread)、D-8 (workspace 废除)、D-9 (seed 不预置);新增 D-13 ~ D-20 (Project / Goal / System Agents / Workflow / Responsibilities / 隔离契约 / 兜底三件套 / 4 Loop) |
| v1.1 | 2026-04-30 | 文档体系简化:在头部声明"实施进度以 project-status.md 为准";D-1 / D-8 加"⚠ 当前过渡状态"标注(指向 TD-N);不再在本文档维护落地进度 |
| v1.2 | 2026-05-08 | Per-Project Storage 重构落地:新增 D-21 ~ D-25(开发阶段不向前兼容;rm 旧 db 即可) |