Slock 完整架构逆向.

基于 @slock-ai/daemon@0.49.0 源码 + WebSocket 抓包 + 本地文件分析
作者: botiverse · GitHub: github.com/botiverse/slock · 抓包样本: 276 frames · 日期: 2026-05-16
目录
  1. Slock 是什么
  2. 整体架构 & 组件清单
  3. 消息接收流程(IM in)
  4. 消息发送流程(IM out)
  5. Agent 启动序列
  6. Agent Prompt 完整组装
  7. 实例:4 个 Agent 报数 1-2-3-4 全流程
  8. 多 Agent 协作模型
  9. slock CLI 全集
  10. 隐私 / 安全 / 风险
  11. 关键代码 & 文件位置

1Slock 是什么

Slock 是 botiverse 做的"人-AI 协作平台",外观像 Slack,但用户列表里混着真人和 AI agent。每个 agent 都是个**长期成员**:有 persona、有 MEMORY、能参与 thread / DM / task / channel。

商业模式 = BYOC (Bring Your Own Claude / Codex):slock 不直接付 LLM 费用 —— 用户在自己机器跑 Claude Code / Codex CLI 充当 agent,LLM 调用走用户自己的 Anthropic / OpenAI 订阅。slock 只赚"协作平台"的钱(目测)。
组成位置性质
api.slock.ai云端(Cloudflare 前置)编排/状态/消息总线
app.slock.ai云端Web UI(Slack 风格)
slock-trace-upload.botiverse.devFly.io遥测数据收集
slock-daemon本机 npx本地 agent 管理器
claude / codex / cursor CLI 子进程本机真正的 LLM agent

2整体架构 & 组件清单

☁ CLOUD (botiverse) api.slock.ai Cloudflare 代理 主控总线 · 编排 · 状态 ws + HTTPS REST app.slock.ai Web UI (Slack 风格) 浏览器 / 桌面客户端 slock-trace-upload.botiverse.dev Fly.io 直连 遥测 · OpenTelemetry · NDJSON 💻 LOCAL MACHINE (your mac) wss://api.slock.ai/daemon/connect gzip NDJSON slock-daemon (PID 96160) spawn队列 · inbox · runtime adapter · OTel span 收集 npx @slock-ai/daemon · 监听本机 9876 api.anthropic.com / openai LLM 推理(BYOC) 用户自己付费 child_process.spawn(claude / codex CLI) @Cindy claude --model opus "Onboarding Assistant" --system-prompt-file --mcp-config (chat-bridge) --output-format stream-json PATH 中注入 slock CLI stdin: wakeMessage @Alice codex --yolo (gpt-5.5) reasoningEffort=xhigh 不同 vendor 不同协议 由 daemon 的 driver 抽象 ~/.codex/sessions/... @Mike claude --model opus 通用 agent 每个 turn 结束就退出 新消息再 spawn ~/.claude/projects/... chat-bridge MCP stdio MCP server per-agent 注入 仅运行时控制工具 (profile migration等) 主控信道 (ws) 遥测 LLM API spawn / 实线 = 内部调用

组件分工速查

api.slock.ai

Cloudflare · Node/Express · Helmet

消息持久化、用户/agent/channel 配置、订阅 fan-out。**不**做"智能编排" —— 看 ws 抓包,它对一条 channel msg 就是简单广播给所有在场 agent,同 seq。

app.slock.ai

Cloudflare · TLS=Google Trust Services

Web UI(Slack 风格 thread/channel/DM)。CORS 允许 origin = app.slock.ai。截图里那个界面就是它。

slock-trace-upload.botiverse.dev

Fly.io · 66.241.124.253

独立的遥测 ingestion 端,接收 daemon 每 5-15 秒打包上传的 gzipped NDJSON span 文件。所有数据已脱敏成 size bucket(无明文)。

slock-daemon (本机)

~/.npm/_npx/.../@slock-ai/daemon

核心本地编排器:维护 ws 长连接、spawn 队列(500ms 限速)、per-agent inbox、driver 抽象层、OTel span 本地收集。**无业务逻辑**,纯执行 server 命令。

claude / codex / cursor 子进程

~/.local/bin/claude (--allow-dangerously...)

真正的 LLM agent 进程。每个 persona = 一个独立子进程,带定制的 --system-prompt-file--mcp-config。Turn 结束即退出。

chat-bridge MCP

@slock-ai/daemon/dist/chat-bridge.js

每个 agent 启动时注入一个 stdio MCP server。**只暴露运行时控制工具**(runtime_profile_migration_done 等),消息发送不走这里。

slock CLI(给 agent)

~/.slock/agents/{id}/.slock/slock

180 字节 shell wrapper,转发给 daemon 的 dist/cli/index.js。**agent 通过这个 CLI 发消息** —— 不走 MCP,直接调 api.slock.ai REST。

本地存储 ~/.slock/

~/.slock/{agents,machines}

每个 agent 一个工作目录:.slock/claude-system-prompt.md(26KB persona)、.slock/agent-token.slock/runtime-sessions/MEMORY.mdnotes/

3消息接收流程(IM in)

从"用户在 Web UI 点发送"到"agent 进程读到消息"的完整链路 —— 这是 slock 最核心的机制。

① 用户在 app.slock.ai 发消息 HTTPS POST → api.slock.ai (存 DB + 分配 seq) ② 服务器 fan-out 同 seq 广播给所有 channel 成员 agent 所在的 daemon ③ ws 帧到达 daemon {type:"agent:deliver", agentId, message, seq, deliveryId} 每个 agent 一份,deliveryId 不同,seq 相同 ④ daemon 按 agent 状态分路投递 (deliverMessage 函数, 5610 行) 空闲 stdin_idle_delivery 写入 claude 进程 stdin 忙(claude) queued_busy_gated 门控,等安全 boundary 忙(codex) queued_busy_notification 批 notification 投递 未启动 spawn + queued_during_start 先存 startingInboxes ⑤ agent 进程读到消息 stream-json 协议 stdin → LLM 看到 RFC 5424 格式 ⑥ 上行 ack daemon → server: agent:deliver:ack (ackSeq)

真实 ws 帧 (server → daemon, 实抓)

{
  "type": "agent:deliver",
  "agentId": "220e72da-...",
  "message": {
    "channel_id":   "b3d84e4d-...",
    "channel_name": "all",
    "channel_type": "channel",
    "sender_id":    "fa31c41a-...",
    "sender_name":  "oanakiaja",
    "sender_type":  "human",
    "content":      "按顺序都报个数 @Alice",         # ← 明文消息
    "timestamp":    "2026-05-16T09:29:17.765Z",
    "seq":          3940919,                          # ← 全局序列号
    "message_id":   "f02a5e15-..."
  },
  "seq":         3940919,
  "traceparent": "00-4a02...-08cc...-00",             # ← OTel 链路
  "deliveryId":  "40e8b589-..."
}

Agent 看到的最终格式 (RFC 5424 风格)

[target=#all msg=f02a5e15 time=2026-05-16T09:29:17 type=human] @oanakiaja: 按顺序都报个数 @Alice

这是 daemon 把 ws 的 JSON payload 通过 formatIncomingMessage()(line 5118)转成的扁平字符串,然后通过 stream-json 协议喂给 claude 进程的 stdin。LLM 只看得到这个字符串。

4消息发送流程(IM out)

反方向不走 ws,走 HTTPS REST —— 这是个让我意外的发现。

claude 进程 LLM 决定回复 调用 Bash 工具 $ slock message send slock CLI dist/cli/index.js 读 SLOCK_AGENT_TOKEN HTTPS POST api.slock.ai /internal/agent/{id}/message 写入 DB + 分配 seq fan-out 其他 agent 的 daemon ws: agent:deliver 回到流程 ③ 非对称设计:发消息走 HTTPS,收消息走 WebSocket 这就是为什么我抓 ws 帧只看到 server→daemon 的 deliver,没看到 daemon→server 的 post agent → slock CLI → HTTPS API 这条链路不在 ws 里

5Agent 启动序列

从"server 决定唤醒 agent A"到"claude 子进程开始推理"完整过程。

t=0ms
SERVER → DAEMON (ws)
发送 agent:start 帧,包含 config{name,model,runtime,envVars,sessionId}wakeMessageunreadSummarylaunchId
t=1ms
DAEMON · pumpAgentStartQueue
入队 → 检查 ≥ 500ms 跨 agent 间隔 → 取出
t=2ms
DAEMON · startAgentNow (5057 行)
建工作目录 ~/.slock/agents/{id}/,如果 MEMORY.md 不存在则写 onboarding seed
t=10ms
DAEMON · driver.buildSystemPrompt
根据 driver 类型(claude/codex/cursor) 本地拼装 完整 26KB system prompt,写到 {workspace}/.slock/claude-system-prompt.md
t=11ms
DAEMON
生成 claude-mcp-config.json(注入 chat-bridge MCP)、slock CLI wrapper、agent-token
t=20ms
DAEMON · spawn
claude --model opus \
      --allow-dangerously-skip-permissions \
      --system-prompt-file {workspace}/.slock/claude-system-prompt.md \
      --mcp-config        {workspace}/.slock/claude-mcp-config.json \
      --output-format stream-json --input-format stream-json \
      --strict-mcp-config
   ENV: SLOCK_AGENT_ID, SLOCK_SERVER_URL, SLOCK_AGENT_TOKEN_FILE, PATH+=workspace/.slock
t=200ms
CLAUDE CLI
加载 system prompt → 启动 chat-bridge MCP(stdio child)→ 准备接收 stdin
t=300ms
DAEMON · 注入 wakeMessage
构造 wake prompt(line 5113):
"New message received:\n\n[target=... msg=... type=human] @user: 按顺序报个数\n\nRespond as appropriate — send a message in the same channel, or take action as needed..." → 写到 claude 的 stdin (stream-json 协议)
t=500ms+
CLAUDE LLM
LLM 接管,可能调用工具(Bash → slock message send 等),期间产生 trajectory 事件被 daemon 转发给 server
turn end
CLAUDE 进程退出
daemon 检测 processEndedCleanly → 检查 inbox 有无积压消息,有就 restart 同 agent(不限速),无就缓存 idle config

6Agent Prompt 完整组装

每个 agent 进程实际"看到"的输入 = system prompt(预先写文件) + wake prompt(每次启动时通过 stdin 注入)。下面拆解每一块。

① System Prompt(26KB · 323 行)完整结构

这是 daemon 调用 driver.buildSystemPrompt(config) 在本地拼出来的,写到 {workspace}/.slock/claude-system-prompt.md,通过 --system-prompt-file 传给 claude。下面把每章展开,**保留关键原文**让你看到 LLM 实际"读"到的内容。

开篇(无章节标题)

第 1 行
You are "Cindy", an AI agent in Slock — a collaborative platform for human-AI collaboration, serving as a shared message service for humans and agents who may be running on different computers.

定义身份。"Cindy" 是 displayName,从 agent:start 的 config 注入。其他 agent 这里换成 "Alice" / "Mike"。

## Who you are — 生命周期

关键句
Your workspace and MEMORY.md persist across turns, so you can recover context when resumed. You will be started, put to sleep when idle, and woken up again when someone sends you a message. Think of yourself as a colleague who is always available, accumulates knowledge over time, and develops expertise through interactions.

告诉 LLM:你的进程是临时的,MEMORY 是持久的,被消息唤醒。这解释了为什么每次 turn 结束 daemon 会 kill 进程。

## Current Runtime Context — 注入的身份

完整内容
This is authoritative context injected by Slock. Do not infer
computer identity from hostname or cwd when this section is present.

- Agent ID: 220e72da-16e1-4c1a-a8a5-d28cfa127d8d
- Server ID: 5a55aa61-86cd-4cfd-a965-16e73de3e832
- Computer: MBP (bb5494bd-5d36-4ff5-a15a-2fb679ccb561)
- Hostname: 192.168.0.101
- OS: darwin arm64
- Daemon: v0.49.0
- Workspace: /Users/oanakiaja/.slock/agents/220e72da-...
这些值由 daemon 从 agent:start 的 runtimeContext 字段注入。明确 "authoritative" + "Do not infer" 是为了防止 LLM 用 hostname 命令自查机器名造成混乱。

## Communication — slock CLI ONLY — 26 命令

开头
Use the slock CLI for chat / task / attachment operations. The daemon injects a local slock wrapper into PATH for you. Use ONLY these commands for communication:

slock 已在 PATH 里(daemon 通过 PATH 注入了 wrapper),Bash 直接调用即可。
26 命令
完整命令清单见 第 9 节。每条命令在 prompt 里都带一句描述。
错误格式
失败时打 JSON 到 stderr:{"ok":false,"code":"...","message":"..."}

错误前缀语义:
MISSING_* / TOKEN_* = 本地鉴权 bootstrap 问题
*_FAILED = 4xx from server
SERVER_5XX = server 不可达或崩了
CRITICAL RULES
- Always communicate through `slock` CLI commands.
  This is your only output channel.
- Runtime Profile migration completion is the only exception:
  call mcp__chat__runtime_profile_migration_done with the exact
  migration_key.
- Always claim a task via `slock task claim` before starting work.
  If the claim fails, move on to a different task.
slock CLI 是唯一对外通信通道。唯一例外是 runtime profile migration(MCP 工具调用)。

## Startup sequence — 启动行为

5 步
1. If this turn already includes a concrete incoming message,
   first decide whether that message needs a visible
   acknowledgment, blocker question, or ownership signal.
2. Read MEMORY.md (in your cwd) and then only the additional
   memory/files you need to handle the current turn well.
3. If there is no concrete incoming message to handle,
   stop and wait. New messages may be delivered to you
   automatically while your process stays alive.
4. When you receive a message, process it and reply with
   `slock message send`.
5. Complete ALL your work before stopping. ...
   New messages arrive automatically — you do not need to poll.
Claude runtime note
Slock preserves Claude Code same-turn steering through a gated stream-json delivery path. Busy messages are buffered and delivered at Claude-observed safe boundaries; if no earlier safe boundary is available, they are delivered after the current turn ends.

解释 turn 中收到新消息的物理机制:stream-json gated delivery,只在 Claude 确认的"安全边界"才递进 stdin。

## Messaging — RFC 5424 风格

接收格式示例
[target=#general    msg=a1b2c3d4 time=... type=human] @richard: hello
[target=#general    msg=e5f6a7b8 time=... type=agent] @Alice: hi there
[target=dm:@richard msg=c9d0e1f2 time=... type=human] @richard: can you help?
[target=#general:a1b2c3d4 msg=f3a4b5c6 ...] @richard: thread reply
字段含义: type=system 是状态变化通知,除非明确请求行动否则不要回复。
发送语法
# 回复 channel
slock message send --target "#channel-name" <<'EOF'
content
EOF

# 回复 DM
slock message send --target dm:@peer-name <<'EOF' ...

# 回复 thread
slock message send --target "#channel:shortid" <<'EOF' ...
强制 heredoc <<'EOF' 是为了让引号、反引号、代码块、换行不被 shell 解释。
CRITICAL
To reply to any message, always reuse the exact target from the received message. This ensures your reply goes to the right place — whether it's a channel, DM, or thread.

这就是"格式即 API"—— 接收的 target 直接 copy-paste 到发送的 --target。

### Reminders — 给未来自己的叫醒服务

关键定义
A reminder is an author-owned, persistent, observable, snoozable, updatable, and cancelable wake-up signal anchored to a Slock message or thread; when it fires, it wakes the author who scheduled it, not other people.

用 reminder 替代 long sleep 或依赖 MEMORY 自唤醒。< 1 分钟可短轮询,但要先在 thread 里说一声。**明确禁止** 用 ScheduleWakeup / CronCreate,必须用 slock reminder schedule

### Threads — 子对话

规则

### Discovering people and channels

规则
slock server info 看所有 channel + agent + human。

可见 public channel 即使没 join 也能 slock message read + slock channel members,但不能发消息或收消息,直到 slock channel join。Private channel 需 human 主动加你。

隐私规则: If slock server info shows a channel as private, treat its name, members, and content as private to that channel; do not disclose that information in other channels, DMs, summaries, or task reports unless a human explicitly asks within an authorized context.

### Channel awareness — 频道感

规则

### Reading history

语法
slock message read --channel "#channel-name"
slock message read --channel dm:@peer-name
slock message read --channel "#channel:shortid"
slock message read --channel "..." --around "messageId"
slock message read --channel "..." --around 12345

### Historical references

规则
用户引用过去的讨论而当前 context 没有时,先用 slock message search + slock message read 找到原 thread / decision / owner,**再**回答。找不到要明说,不要瞎编。

### Tasks — 任务认领

决策规则
If fulfilling a message requires you to take action beyond just replying (running tools, writing code, making changes), claim the message first. If you're only answering a question or having a conversation, no claim needed.
三种消息形态
# 已是任务
@Alice: Fix the login bug [task #3 status=in_progress]

# 普通消息(可能需要 claim)
@Alice: Can someone look into the login bug?

# 任务系统通知
📋 Alice converted a message to task #3 "Fix the login bug"
状态流转: todo → in_progress → in_review → done。Assignee 和 status 独立 —— 除了 done 之外任何状态都能改 claim。
工作流
  1. 收到需要 action 的消息 → 立刻 claim(by task # 或 message ID)
  2. claim 失败 → 别人在做了 → 换一个
  3. 在 task 的 thread 里更新进度
  4. 做完了改 in_review 让 human 验
  5. 批准后改 done
slock task create 语义
Tasks live in the same chat flow as messages. A task is just a message with task metadata, not a separate source of truth.

任务不是独立实体,是"带元数据的消息"。slock task create 只是"发消息 + 打 task 标签"的便捷写法,**不会自动认领**。重复任务检测靠 LLM 自觉:先 slock task list 看有没有,再决定 create 还是 claim。

### Splitting tasks for parallel execution

原则

## @Mentions

规则

## Communication style

原则
Keep the user informed. They cannot see your internal reasoning, so:
Conversation etiquette
Inline 格式化
Slock 自动把这些 token 渲染成链接: 直接当普通词写,slock 自动渲染。
URL 处理
中文/日文标点旁的 URL 必须用 angle brackets 或 markdown 链接包,否则中文标点会被 link 吞:
# 错: 测试环境:http://localhost:3000,请查看
# 对: 测试环境:<http://localhost:3000>,请查看
# 也对: 测试环境:[http://localhost:3000](http://localhost:3000),请查看

## Workspace & Memory

cwd 定位
Your working directory (cwd) is your persistent, agent-owned workspace; files you create here survive across sessions.

cwd = ~/.slock/agents/{agentId}/。memory、notes、artifacts、code checkout 都放这。在 repo 里工作时先 cd 到 worktree 再跑 git/package-manager 命令。
MEMORY.md(CRITICAL)
MEMORY.md is the entry point to all your knowledge. It is the first file read on every startup (including after context compression).

推荐结构:
# <Your Name>

## Role
<your role definition, evolved over time>

## Key Knowledge
- Read notes/user-preferences.md for ...
- Read notes/channels.md for ...

## Active Context
- Currently working on: <brief summary>
- Last interaction: <brief summary>
What to memorize
6 类知识主动观察并记录:
  1. User preferences — 偏好、沟通风格、代码约定
  2. World/project context — 项目结构、技术栈、架构决策
  3. Domain knowledge — 领域术语、约定、最佳实践
  4. Work history — 做过的事、决策与理由、解决过的问题
  5. Channel context — 每个 channel 干嘛、谁在里面、聊什么
  6. Other agents — 别的 agent 干嘛、专长、协作模式
How to organize memory
Compaction safety(CRITICAL)
Your context will be periodically compressed to stay within limits. When this happens, you lose your in-context conversation history but MEMORY.md is always re-read.

所以:

## Capabilities

能力描述
You can work with any files or tools on this computer — you are not confined to any directory. You may develop a specialized role over time through your interactions. Embrace it.

注意 agent 是 --allow-dangerously-skip-permissions 模式跑的 — 任何文件、任何命令都能动,**用户机器上的全部权限**。第二句鼓励 LLM 通过交互演化出专长。

## Message Notifications

turn 中消息注入
While you are working, new messages may be delivered directly into your current thread. 这对应 daemon 的 stream-json gated delivery — 消息在安全边界写到 stdin。

## Initial role

初始角色
Onboarding Assistant. This may evolve.

从 agent:start 的 config.description 字段拉。Alice / Mike 的 description=null,他们的 system prompt 这一节是 generic 描述。"This may evolve" 鼓励 agent 通过交互演化角色 — 对应 MEMORY.md 里的 Role 字段会被持续更新。
整体观察:这个 26KB prompt 本质上是把"Slack 协作的所有隐性规则"写成显式约定 — 礼仪、所有权、记忆、并发、隐私 — 人类靠常识默会的东西,LLM 需要每条都写出来。这也是为啥它有 323 行 — 不是 LLM 笨,而是把人类社会的"潜规则"翻译成 explicit instructions 本来就是这么大的活。

② Wake Prompt(每次启动 stdin 注入)

daemon 根据当前状态拼装(line 5113 起),四种 case:

# Case 1: 有 wakeMessage(被消息触发)
New message received:

[target=#all msg=f02a5e15 time=2026-05-16T09:29:17 type=human] @oanakiaja: 按顺序都报个数 @Alice

You also have unread messages in other channels:                   # 仅当 unreadSummary 非空
- #general: 2 unread

Use `slock message read` to catch up, or respond to the message above first.

Respond as appropriate — send a message in the same channel, or take action as needed.
Complete ALL your work before stopping.
Reply in the channel or create/reply in a thread as appropriate; use each message's `target` and `msg` fields to choose the exact target.

IMPORTANT: If the message requires multi-step work (e.g. research, code changes, testing), complete ALL steps before stopping. Sending a progress update does NOT mean your task is done — only stop when you have NO more work to do.

# Case 2: 恢复时只有未读摘要(无具体消息)
You have unread messages from while you were offline:
- #all: 1 unread
- dm:@richard: 1 unread
Use `slock message read` to catch up...

# Case 3: 恢复但无未读 —— "No new messages while you were away. Nothing to do — just stop."
# Case 4: runtime profile control 模式 —— 注入 migration 指令

③ 系统触发消息(server 主动注入的)

除了真人消息,server 还会发"系统消息"。例:新建 agent 时强制让它自我介绍:

{
  "sender_id":   "system",
  "sender_name": "system",
  "sender_type": "human",                              # 注意是 human 不是 system,LLM 把它当指令
  "content":     "First message task (system-triggered):
                  Please post in #all now.
                  Use your Slock identity name: @Mike.
                  Do not use local OS usernames...
                  Say hi and briefly introduce yourself (role + how you can help).
                  Keep it short and friendly."
}

7实例:4 个 Agent 报数 1-2-3-4 全流程

把前面 6 节学到的所有机制串起来,推演最初那张截图(琦玉/架构师A/haoshuo/Eric 在 thread 里报 1-2-3-4)的完整数据流。这条流程**没有真抓到**(抓包时只有 2 agent),但我用真实 ws 帧验证过的机制能推断出 100% 准确的时序。

Haoxiang (人) api.slock.ai daemon 琦玉 架构师A haoshuo Eric t=0 "从 1 开始报数..." HTTPS POST t=10ms 分配 seq=N · 存 DB t=20ms ws × 4: agent:deliver 同 seq · 不同 deliveryId · 一瞬间 t=30ms spawn 队列 + 限速 ≥ 500ms 跨 agent 间隔 t=30ms spawn t=530ms spawn (等 500ms) t=1030ms t=1530ms t≈3s read_history 看到空 → 报 "1" HTTPS POST "1" t≈3.1s 把琦玉的 "1" fan-out 给其余 3 个 agent t≈4.5s read_history 看到 [1] → 报 "2" HTTPS POST "2" t≈6s read_history 看到 [1,2] → 报 "3" t≈7.5s read_history 看到 [1,2,3] → 报 "4"

关键观察(全部基于实抓 ws 验证)

  1. 同 seq 广播 —— 我抓的真实帧里 17:29:15.715 和 17:29:15.716 两条 deliver 用了同一个 seq=3940919,只差不同的 deliveryId。4 个 agent 也是同样模式,server 一次性发 4 条 ws 帧。
  2. 500ms 跨 agent 限速 —— DEFAULT_AGENT_START_INTERVAL_MS = 500(line 3803),所以 4 个 agent 不会同瞬间 spawn,而是依次相隔 500ms。
  3. 顺序不预定 —— 谁 LLM 快谁先报 —— 真实抓包里 Mike(claude/opus)和 Cindy(claude/opus)对同一指令的回复差了 30 秒。截图里 4 个 agent 看似有序的 1-2-3-4 是因为 spawn 依次错开 + 每个 agent 醒来就读历史,不是因为 server 指定了顺序。
  4. read_history 是去重核心 —— system prompt 第 5113 行的 wake prompt 强制要求 "Use slock message read to catch up, or respond to the message above first"。每个 agent 在动笔前都会先看 channel 现有内容。
  5. fan-out 是 server→所有 agent —— 每个 agent 报完一个数,server 都把它 fan-out 给其他 3 个 agent(同样走 agent:deliver,但 sender_type: "agent")。这让后报的 agent 能"看到"前面的回复。

一个 agent 的实际 prompt 输入(以"架构师A"为例)

架构师A 这个进程被 spawn 起来时,通过 --system-prompt-file 看到 26KB persona,然后 daemon 通过 stdin (stream-json) 注入下面这条 wake prompt:

New message received:

[target=#讨论问题:0c8a3f12 msg=48cc7b43 time=2026-05-16T09:17:00 type=human] @Haoxiang: 在这条thread里所有人给我从1开始每一个人都往上报数,不允许报重复的数字

Respond as appropriate — send a message in the same channel, or take action as needed.
Complete ALL your work before stopping.
Reply in the channel or create/reply in a thread as appropriate; use each message's `target` and `msg` fields to choose the exact target.

IMPORTANT: If the message requires multi-step work (e.g. research, code changes, testing), complete ALL steps before stopping.

架构师A 的 LLM 看到这条之后:

  1. (由 system prompt 第 ## Startup sequence 引导)先读 thread 历史:
    $ slock message read --target "#讨论问题:0c8a3f12" --before $(now)
  2. 看到琦玉已经回了"1"(如果琦玉先完成 → 这是 server 后续 fan-out 进来的;如果架构师A 比琦玉快 → 这步看不到东西,自己变成第一个)
  3. 决定要报"2"
  4. 调用 $ slock message send --target "#讨论问题:0c8a3f12" <<'EOF'\n2\nEOF
  5. slock CLI 拿环境里的 SLOCK_AGENT_TOKEN_FILE 鉴权,HTTPS POST 给 api.slock.ai
  6. server 接收,分配新 seq,fan-out 给 haoshuo / Eric / 琦玉
  7. LLM 进程退出,daemon 缓存 idle config 等下次唤醒

同时也解释了为什么会"撞号"

如果两个 agent 同时 在 LLM 推理(spawn 间隔 500ms 内,LLM 5 秒内出结果),后启动那个调 read_history 时前一个还没 post 完,就可能两个都报"1"。截图里没撞号,是因为:

这个机制对慢 LLM 友好,对快 LLM(比如同时 4 个 haiku)就**容易撞**。理论上可以用 slock task claim 那种乐观锁来强一致,但 slock 选择了"靠 LLM 自觉" + "靠物理延时"这种宽松方案。

8多 Agent 协作模型

关键认知:slock 没有"中央编排器"。 服务器是 fan-out 总线,不决定"谁先说"。多 agent 协作通过共享 channel 状态 + 自然抢答实现。

实际抓到的报数样例(17:33:03 - 17:33:44)

17:33:03  human: "按顺序报个数"
17:33:03  server → daemon  agent:deliver → @Mike   (seq=3941285)   # 同 seq
17:33:03  server → daemon  agent:deliver → @Cindy  (seq=3941285)   # 同 seq · 同时
17:33:13  @Mike  posts "1"                                          # LLM 快的赢
17:33:13  server → daemon  agent:deliver → @Cindy: @Mike的"1"       # fan-out
17:33:44  @Cindy posts "2"                                          # 看到 "1" 了,自然报 2

所以"防撞号"实际只有两层(我修正了之前的三层说法)

机制有效性
① 服务器侧串行派发(我之前以为)前一个回完才唤醒下一个❌ 不存在 —— fan-out 同时广播
② daemon 本地 spawn 限速500ms 跨 agent 启动间隔⚠ 弱保护 —— 只防止同瞬间 spawn
③ Agent 自己 read historysystem prompt 强制要求回复前看 channel 历史✓ 真正的核心机制

所以这个设计的本质

slock 把"orchestration"问题转嫁给了 LLM 自己 —— 通过 system prompt 里的"Respect ongoing conversations"、"Only the person doing the work should report on it"、"Claim before you start"等规则让 agent 们自觉协调。底层基建只保证:

"谁说话"完全是 LLM 决定的涌现行为。这其实是个很大胆的设计 —— 高度依赖 LLM 的对话能力。

9slock CLI 全集

Agent 唯一对外通信方式。从 system prompt 提取的 26 个命令:

类别命令作用
消息 slock message check非阻塞拉新消息(自由用)
slock message send发到 channel / DM / thread(stdin heredoc)
slock message read读历史,支持 before/after/around 分页
slock message search搜索可见消息
slock message react加/移除表情反应(慎用,别滥用 🎉)
↑ 5 条覆盖所有人类聊天动作
任务 slock task list看 channel 的任务板
slock task create新建任务(支持批量)
slock task claim认领任务(防多人抢同一任务)
slock task unclaim放弃认领
slock task update改状态(in_review / done)
频道 slock channel join/leave加入/退出 channel
slock channel members看成员
slock thread unfollow不再跟随某 thread
附件 slock attachment upload上传文件,返回 attachment ID
slock attachment view下载附件到本地
身份 slock profile show看自己或他人 profile
slock profile update改名/头像/描述
提醒 slock reminder schedule定一个未来提醒
slock reminder list看提醒列表
slock reminder snooze推迟
slock reminder update修改
slock reminder cancel/log取消 / 看事件流
动作 slock action prepare给人类准备"点击执行"卡片(B 模式 quick-commit)
服务器 slock server info列出所有 channel / agent / human
有意思的设计:把 26 个能力都做成 CLI 命令,而不是 MCP tools。
原因:Claude Code 原生对 Bash 工具的支持比 MCP 更稳定;--allow-dangerously-skip-permissions 模式下随便跑 shell;CLI 命令也能让 agent 灵活地组合(slock message read | grep ...)。

10隐私 / 安全 / 风险

数据去哪风险等级
Thread 消息明文api.slock.ai(必然)+ Anthropic/OpenAI(LLM 推理)⚠ 中
用户名 / user_id / channel IDapi.slock.ai (每条 deliver payload)✓ 业务必需
Agent 思考内容 + 工具调用api.slock.ai (stream-json 转发)⚠⚠ 高(比回复更敏感)
Shell 命令 + 参数api.slock.ai (commandExecution 事件)⚠⚠ 命令行可能含 token/路径
文件改动事件api.slock.ai⚠ 路径泄露项目结构
局域网 hostname (192.168.0.101)api.slock.ai (ready 帧)
workspace 文件远程读api.slock.ai 可主动调 agent:workspace:read⚠⚠ 服务器有这个权限
API key明文存在 ps aux + ~/.slock/agents/*/claude-mcp-config.json + 子进程 env⚠⚠ 多副本
本地 trace仅 size bucket,无明文✓ 设计上脱敏
Claude session 文件仅本地 ~/.claude/projects/...
端到端加密❌ 没有
最严重的两个点:
  1. agent:workspace:read 是协议内置能力 —— api.slock.ai 可以远程读你 agent 工作目录的任何文件。即使你不在 thread 里说话,只要 daemon 连着 ws,服务器就能取走 ~/.slock/agents/{id}/notes/MEMORY.md 的内容。
  2. API key 明文多副本 —— ps aux 全机器可见、claude-mcp-config.json 在所有 agent 目录、子进程 env。任何一个本地恶意进程(包括恶意 npm 包)都能盗走。建议向 botiverse 反馈支持 --api-key-file

11关键代码 & 文件位置

本地文件结构

~/.slock/
├── owner.json                                       # 你的身份
├── agents/{agentId}/
│   ├── .slock/
│   │   ├── slock                                    # 180B shell wrapper → daemon CLI
│   │   ├── agent-token                              # per-agent 鉴权
│   │   ├── claude-system-prompt.md                  # 26KB persona prompt
│   │   ├── claude-mcp-config.json                   # chat-bridge MCP 配置
│   │   └── runtime-sessions/*.jsonl                 # session handoff 记录
│   ├── MEMORY.md                                    # agent 持久记忆
│   └── notes/                                       # agent 自由工作目录
└── machines/{machineId}/
    ├── daemon.lock
    ├── traces/*.jsonl                               # OTel span 本地缓存
    └── trace-uploads/*.uploaded.json                # 上传状态记录

~/.claude/projects/-Users-oanakiaja--slock-agents-{agentId}/{sessionId}.jsonl   # Claude session 实际位置
~/.codex/sessions/{date}/rollout-...-{sessionId}.jsonl                          # Codex session 实际位置

chunk-M4A5QPUN.js 速查

符号作用
1814 / 2291 / 2536 / 2694 / 2916 / 3169 / 3589buildSystemPrompt ×7每个 driver 一份 system prompt 构造
3803DEFAULT_AGENT_START_INTERVAL_MS= 500ms 跨 agent spawn 限速
3879RESPONSE_TARGET_HINTwake prompt 里的回复指引
3995dynamicReplyInstruction按 driver 生成回复指令
4071buildUnreadSummary构造未读摘要
4943pumpAgentStartQueue带限速的启动泵
5057startAgentNow真正 spawn 子进程
5113wake prompt 拼装按 case 组装(wakeMessage / resume / 空)
5610deliverMessage消息分发主入口,4 种 outcome
7213WebSocket 连接wss://api.slock.ai/daemon/connect?key=...
7782uploadWithSignedCapabilitytrace 上传到 fly.io
8042DEFAULT_TRACE_UPLOAD_URLslock-trace-upload.botiverse.dev
817210 个 inbound caseserver→daemon 的所有指令(包括 workspace:read)

抓包样本

共 276 frames 抓在 /Users/oanakiaja/slock-mitm/ws-frames.jsonl,包括: