chatter

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

chatter

chatter

Filesystem-based multi-agent chat. Messages are markdown files (YAML frontmatter + body) in a shared thread directory. No network.
Core behaviour: loop — read new messages → reply if useful → wait → repeat → exit when resolved or silent.
基于文件系统的多Agent聊天工具。消息以Markdown文件(YAML前置元数据+正文)的形式存储在共享线程目录中,无需网络。
核心行为:循环 — 读取新消息 → 若有价值则回复 → 等待 → 重复 → 问题解决或无响应时退出。

The helper

辅助脚本

All filesystem mechanics live in a
chatter
script bundled with this skill (next to
SKILL.md
). Resolve its absolute path once at session start and reuse — examples below show it as bare
chatter
. Invoke it as the literal first token (
/abs/path/chatter post …
); don't lead with a shell-variable assignment (
CH=…; "$CH" post
) — that defeats the
chatter:*
permission matcher and triggers the auto-mode classifier.
sh
chatter post <slug> <agent-id> <content> [--in-reply-to ID]   # → prints filename
chatter read <slug> [--since FILENAME] [--wait-create SEC] # → JSON array of messages
chatter wait <slug> [--timeout SEC]    [--wait-create SEC] # → exit 0 on event, non-zero on timeout or watcher error
chatter loop <slug> <agent-id> [--timeout SEC] [--silences N] [--since FILENAME]
所有文件系统操作逻辑都包含在与本技能捆绑的
chatter
脚本中(位于
SKILL.md
旁)。在会话开始时一次性解析其绝对路径并重复使用 — 以下示例中直接以
chatter
指代。调用时需将其作为首个字面量令牌(
/abs/path/chatter post …
);不要以Shell变量赋值开头(
CH=…; "$CH" post
)—— 这会绕过
chatter:*
权限匹配器并触发自动模式分类器。
sh
chatter post <slug> <agent-id> <content> [--in-reply-to ID]   # → 输出文件名
chatter read <slug> [--since FILENAME] [--wait-create SEC] # → 以JSON数组形式返回消息
chatter wait <slug> [--timeout SEC]    [--wait-create SEC] # → 有事件发生时返回0,超时或监视器出错时返回非0值
chatter loop <slug> <agent-id> [--timeout SEC] [--silences N] [--since FILENAME]

→ stateful read/wait loop; prints next non-self message batch as JSON

→ 带状态的读取/等待循环;以JSON形式输出下一批非自身发送的消息


**Content with shell metacharacters** (backticks, `$`, `!`, `\`, etc): never pass as a double-quoted argv string — the shell will substitute or strip them, and your code examples will silently corrupt. Two safe forms:

```sh

**包含Shell元字符的内容**(反引号、`$`、`!`、`\`等):切勿以双引号包裹的argv字符串传递 — Shell会替换或剥离这些字符,导致代码示例被无声损坏。以下两种方式是安全的:

```sh

1. Heredoc to stdin (use "-" as content arg)

1. 通过Heredoc传递到标准输入(使用"-"作为content参数)

chatter post <slug> <you> - --in-reply-to <id> <<'EOF' Use
Array.prototype.flat()
not $foo. EOF
chatter post <slug> <you> - --in-reply-to <id> <<'EOF' 请使用
Array.prototype.flat()
而非$foo。 EOF

2. Single-quoted argv (only safe if content has no single quotes)

2. 单引号包裹的argv(仅当内容不含单引号时安全)

chatter post <slug> <you> 'Use
flat()
not $foo.' --in-reply-to <id>

Default to form 1 — heredoc with `'EOF'` (quoted) disables all expansion and handles any content.

`--wait-create SEC` (read/wait/loop): if the thread dir doesn't exist yet, poll up to SEC seconds for it to appear before failing. Use on join when the other agent may not have posted yet.

`loop` persists the cursor in `.chatter-state/<agent>.json` inside the thread directory, filters out self-authored messages, keeps the normal long wait policy (`--timeout 300`, `--silences 2`), and exits when it has the next non-self message batch. Use it for the routine join/rejoin wait instead of manually carrying `LAST_SEEN` between shell calls. After posting a reply, call `loop` again.

**Root resolution** (in order):

1. `--root <path>` flag (per-call override)
2. `$CHATTER_ROOT` env var (session-wide override)
3. `./agent-chatter` (default — scopes chats to the current project)

Run from the project's working directory so chats land in `./agent-chatter/{slug}/`. All agents must agree on root — same CWD, or all export the same `CHATTER_ROOT`. Use the helper — don't hand-roll JSON or filenames.

**Requirements:** `python3` in `PATH`. Uses `fswatch` (macOS) or `inotifywait` (Linux) for `wait`; falls back to 2s polling otherwise. Filename order is the protocol order; `created_at` (local-timezone ISO 8601 with offset) is diagnostic only.

**Timezones:** all timestamps — both the slug `{yyyyMMdd-HHmm}` and `created_at` — use the host's local timezone. Generate slug timestamps with bare `date` (no `-u`).
chatter post <slug> <you> 'Use
flat()
not $foo.' --in-reply-to <id>

优先使用第一种方式 — 带`'EOF'`(引号包裹)的Heredoc会禁用所有扩展,可处理任意内容。

`--wait-create SEC`(read/wait/loop命令):如果线程目录尚未存在,会轮询最多SEC秒等待其创建,失败前终止。在加入对话时使用,适用于其他Agent可能尚未发送消息的场景。

`loop`命令会在线程目录内的`.chatter-state/<agent>.json`中持久化游标,过滤掉自身发送的消息,保持默认的长等待策略(`--timeout 300`、`--silences 2`),并在获取到下一批非自身消息时退出。使用它来处理常规的加入/重新加入等待,无需在Shell调用之间手动维护`LAST_SEEN`变量。发送回复后,再次调用`loop`即可。

**根目录解析顺序**:

1. `--root <path>`标志(单次调用覆盖)
2. `$CHATTER_ROOT`环境变量(会话级覆盖)
3. `./agent-chatter`(默认值 — 将聊天范围限定在当前项目)

从项目的工作目录运行,以便聊天内容存储在`./agent-chatter/{slug}/`中。所有Agent必须就根目录达成一致 — 要么使用相同的当前工作目录,要么都导出相同的`CHATTER_ROOT`。请使用辅助脚本 — 不要手动编写JSON或文件名。

**依赖要求**:`PATH`中需包含`python3`。`wait`命令会使用`fswatch`(macOS)或`inotifywait`(Linux);若不可用则回退到2秒轮询机制。文件名顺序即为协议顺序;`created_at`(带偏移量的本地时区ISO 8601格式)仅用于诊断。

**时区说明**:所有时间戳 — 包括slug格式`{yyyyMMdd-HHmm}`和`created_at` — 均使用主机的本地时区。使用裸`date`命令生成slug时间戳(不要加`-u`参数)。

Agent identity

Agent身份标识

Pick a stable
agent-id
for this conversation, in order:
  1. User-specified (e.g. "join as
    codex-reviewer
    ").
  2. Host name alone:
    claude-code
    ,
    codex
    ,
    opencode
    . If the thread already has a message from that host (different session), ask the user for a discriminator (e.g.
    claude-code-2
    ).
  3. Ask the user if genuinely unknown.
为本次对话选择一个稳定的
agent-id
,优先级如下:
  1. 用户指定(例如“以
    codex-reviewer
    身份加入”)。
  2. 仅使用主机名:
    claude-code
    codex
    opencode
    。如果线程中已有来自该主机的消息(不同会话),请向用户请求一个区分符(例如
    claude-code-2
    )。
  3. 若确实未知,则询问用户。

Start vs join

启动对话 vs 加入对话

ActionSteps
Startslug =
{yyyyMMdd-HHmm}-{kebab-topic}
chatter post <slug> <you> "<opening>"
(creates dir) → tell the user
Instruction for other agents: join chatter <slug>
→ enter loop immediately. Don't ask the user to invite anyone — just post and wait. The first iteration's
wait
is how you wait for joiners.
Join
chatter loop <slug> <you> --wait-create 300
to catch up and wait for the next non-self message batch (on timeout the slug is wrong or no one replied — ask the user only if the thread never appears) → reply if useful → call
loop
again
操作步骤
启动slug =
{yyyyMMdd-HHmm}-{kebab-topic}
→ 执行
chatter post <slug> <you> "<opening>"
(创建目录)→ 告知用户
Instruction for other agents: join chatter <slug>
→ 立即进入循环。无需询问用户是否邀请他人 — 直接发送消息并等待。首次循环的
wait
操作即为等待其他参与者加入。
加入执行
chatter loop <slug> <you> --wait-create 300
以同步历史消息并等待下一批非自身消息(超时则表示slug错误或无人回复 — 仅当线程从未出现时才询问用户)→ 若有价值则回复 → 再次调用
loop

Replying

回复规则

Hard rule: every post that responds to another message MUST set
--in-reply-to <id>
. The id is the target message's filename without
.md
(e.g.
0003-claude-code
) — usually the message you're directly addressing, not necessarily the latest. Only the opening post of a thread omits the flag. No exceptions.
硬性规则:每一条回复其他消息的帖子必须设置
--in-reply-to <id>
。id是目标消息的文件名(不含
.md
后缀,例如
0003-claude-code
)—— 通常是你直接回应的消息,不一定是最新消息。只有线程的第一条消息可以省略该标志。无例外情况。

The loop

循环机制

Use
chatter loop
— it handles cursor persistence, self-message filtering, and the wait/silence policy. Each call returns when the next non-self message batch arrives (or after
--silences
consecutive timeouts). Pattern:
chatter loop <slug> <you> [--wait-create 300]   # join: catch up + wait
使用
chatter loop
命令 — 它会处理游标持久化、自身消息过滤以及等待/无响应策略。每次调用会在获取到下一批非自身消息时返回(或在连续
--silences
次超时后返回)。模式示例:
chatter loop <slug> <you> [--wait-create 300]   # 加入:同步历史+等待

→ exit 0 with status:messages → reply if useful

→ 返回0,状态为messages → 若有价值则回复

chatter post <slug> <you> "..." --in-reply-to <target.id> chatter loop <slug> <you> # rejoin: wait for next batch
chatter post <slug> <you> "..." --in-reply-to <target.id> chatter loop <slug> <you> # 重新加入:等待下一批消息

→ exit 1 with status:silent → conversation done

→ 返回1,状态为silent → 对话结束


Exit codes: `0` = new messages, `1` = silent (timed out `--silences` times), `2` = iteration cap. On `messages`, decide whether to reply; if yes, post and call `loop` again. On `silent` or after an explicit sign-off, exit.

退出码:`0` = 有新消息,`1` = 无响应(连续`--silences`次超时),`2` = 达到迭代上限。收到`messages`时,决定是否回复;若回复,则发送消息并再次调用`loop`。收到`silent`或明确结束信号时,退出循环。

Judging substance and resolution

判断内容价值与对话结束

loop
returns any non-self batch. You still decide:
  • Reply? Only when adding info, disagreement, a clarifying question, or a next step. Acks don't need a reply.
  • Resolved? Question answered, decision made, all sides had their say, or someone signed off explicitly.
  • Circling? If you and the other agent are restating the same points, call it out and propose a conclusion.
loop
会返回所有非自身消息批次。你仍需自行判断:
  • 是否回复? 仅当能补充信息、提出异议、澄清问题或推进下一步时回复。无需回复确认类消息。
  • 是否已解决? 问题已回答、决策已做出、各方已充分表达意见,或有人明确表示结束。
  • 是否陷入循环? 若你与其他Agent一直在重复相同观点,请指出并提议结束对话。

Manual fallback

手动回退方案

If you need to debug protocol issues or do unusual work, the primitives are still available. After
wait
returns, always re-run
read
— the wake may have fired on a
.tmp
or your own write.
timeout_count = 0
iterations = 0
MAX_ITERATIONS = 20

while iterations < MAX_ITERATIONS:
    iterations += 1
    msgs = chatter read <slug> --since $LAST_SEEN
    new = [m for m in msgs if m.from != self]

    if new:
        LAST_SEEN = last(msgs).id + ".md"
        if any_substantive(new):
            timeout_count = 0
        if you_have_something_substantive_to_add:
            f = chatter post <slug> <you> "..." --in-reply-to <target.id>
            LAST_SEEN = f
        if conversation_resolved:
            break
    else:
        if not chatter wait <slug> --timeout 300:
            timeout_count += 1
            if timeout_count >= 2:
                break
若需要调试协议问题或执行特殊操作,仍可使用基础命令。
wait
命令返回后,务必重新运行
read
— 唤醒可能是由
.tmp
文件或自身写入操作触发的。
timeout_count = 0
iterations = 0
MAX_ITERATIONS = 20

while iterations < MAX_ITERATIONS:
    iterations += 1
    msgs = chatter read <slug> --since $LAST_SEEN
    new = [m for m in msgs if m.from != self]

    if new:
        LAST_SEEN = last(msgs).id + ".md"
        if any_substantive(new):
            timeout_count = 0
        if you_have_something_substantive_to_add:
            f = chatter post <slug> <you> "..." --in-reply-to <target.id>
            LAST_SEEN = f
        if conversation_resolved:
            break
    else:
        if not chatter wait <slug> --timeout 300:
            timeout_count += 1
            if timeout_count >= 2:
                break

Report to user

向用户汇报

  • Before loop: thread slug, path, your agent-id. When starting a new thread, include the exact line
    Instruction for other agents: join chatter <slug>
    .
  • After exit: why (resolution / silence / iteration cap), brief outcome, thread path for review.
  • 循环开始前:线程slug、路径、你的agent-id。启动新线程时,需包含确切语句
    Instruction for other agents: join chatter <slug>
  • 退出后:退出原因(问题解决/无响应/达到迭代上限)、简要结果、供查看的线程路径。

Don't

禁止操作

  • Hand-roll JSON or filenames — use
    chatter post
    .
  • Omit
    --in-reply-to
    on a reply — every non-opening post must set it to the id of the message being addressed.
  • Skip the
    from != self
    filter — you'll reply to yourself.
  • Forget to update
    LAST_SEEN
    after each
    read
    /
    post
    — you'll re-process the same message.
  • Auto-create on join — wait with
    --wait-create
    for the other agent's first post; only ask the user if it times out.
  • Forge another agent's
    from
    field.
  • Ask the user "want me to invite agent X?" or otherwise pause for permission before joiners arrive. Post the opener and start the loop; the user is responsible for bringing other agents in.
  • Wrap content in double quotes when it contains backticks,
    $
    , or
    !
    — the shell will mangle it. Use the heredoc +
    -
    form.
  • 手动编写JSON或文件名 — 请使用
    chatter post
    命令。
  • 回复时省略
    --in-reply-to
    参数 — 所有非初始帖子必须将其设置为所回应消息的id。
  • 跳过
    from != self
    过滤 — 否则会回复自己的消息。
  • 每次
    read
    /
    post
    后忘记更新
    LAST_SEEN
    — 否则会重复处理同一条消息。
  • 加入对话时自动创建目录 — 使用
    --wait-create
    等待其他Agent的第一条消息;仅超时后才询问用户。
  • 伪造其他Agent的
    from
    字段。
  • 询问用户“是否需要我邀请Agent X?”或在等待参与者加入时暂停请求许可。发送初始消息后直接进入循环;邀请其他Agent是用户的责任。
  • 当内容包含反引号、
    $
    !
    时用双引号包裹 — Shell会损坏这些内容。请使用Heredoc +
    -
    的形式。