Skip to content

Peer talk v2: make co-resident sessions actually converse #563

Description

@cursor

Why this exists

#469 shipped the floor: a worktree JSONL room, a device room, peer_message, /tell, /peek, a presence note when the live set changes, and a one-shot collision gate that tells the model to speak before mutating a shared tree.

That is presence plus a mailbox. It is not a conversation. Two sessions in the same folder can post past each other, the model is still told it can target "all" (it cannot), injected peer lines look like the human spoke, and the TUI has no /tell or /peek at all.

This issue is the speech half of #469. Do not reopen device-wide model "all" — that was retired because it flooded every folder on the box.

What is already true (so we do not rebuild it)

  • Wire: append-only JSONL rooms in src/presence_chan.zig. Worktree room is a room (every live session on that tree hears every line). Device room is folder-scoped (deviceHears). to is addressing metadata, not an inbox.
  • Model tool: peer_message in src/peer_channel.zig, wired through src/schema.zig / src/agent_tools.zig. One-way. Tool result only confirms the post.
  • User REPL: /tell <session|all> <text>, /peek <session> in src/commands_misc.zig.
  • Delivery: deliverInbound at every step boundary of agent.runTurn (src/agent.zig). Mid-stream interrupt is Input inversion (Phase 1b): approvals, pickers, mid-turn asks, Esc ownership, readline queries #430 and stays a sibling, not a prerequisite.
  • Collision: one-shot pause in src/presence.zig before a mutating tool on a shared tree; the model is told to peer_message first.
  • Tests: src/peer_channel.zig unit tests + scripts/test-pty-peer-channel.py.

What is wrong today

1. The docs lie

  • tool_schema still documents "all" as a valid to.
  • handleMessage rejects "all" (error.BroadcastRetired).
  • The presence note (peerNoteIfChanged) still says peer_message to "all" or a name… reaches every session. That is false for the model.

A model that follows the note will get a tool error on the first attempt. That is the cheapest regression and the first slice.

2. One-way firehose, no conversation

There is no reply thread, no ack, no "did they hear." The sender's tool result is "posted." Delivery happens later, at the receiver's next step boundary. The sender never learns that.

/peek is user-only. The model cannot ask what a peer is doing; it only gets a presence list when the set of live sessions changes, not when a peer starts a long bash or finishes a turn.

3. Injected as role: user

Inbound peer lines are stuffed into history as a user message ([peer message from X]: …). Compaction, cache, and /peek all treat that as the human. /peek already special-cases the [peer message prefix to skip it — that is a smell, not a protocol.

4. Room vs DM is easy to confuse

Worktree room: everyone on the tree hears every line; to is rendered, not enforced.
Device room: to is enforced (deviceHears).
Same tool, two topologies. Nothing in the prompt explains the difference in one sentence the model will keep.

5. TUI is missing the verbs

TUI/catalog.zig has no /tell or /peek. A TUI session can be told (delivery still runs) but the human cannot speak or inspect from the pager.

6. Targeting is a substring

Session-name / pid match. Ambiguous names error. No stable id the model can hold across turns. A rename or a second graff in the same folder breaks the address.

7. No structured claims

The collision gate asks the model to talk, then hopes free text is enough. There is no locking src/foo.zig the gate can parse, so two models can both "announce" and both edit.

8. Backlog is a wall

backlog_tail_max = 10. REPL /peek shows the last 2 user lines. No threads, no fork, no "since I last looked."

What "better" means (acceptance, not vibes)

A pair of sessions on the same worktree should be able to:

  1. See who else is live, and a one-line "what they last did" without the human /peeking.
  2. Say something to a named peer (or the room) and get a tool result that distinguishes posted from heard at the receiver's next step.
  3. Hear inbound speech as a first-class channel event, not a fake user utterance. /peek and compaction must not treat it as the human.
  4. Do that from the TUI with the same /tell / /peek the line REPL already has.
  5. Not be lied to by the schema or the presence note.

Out of scope for this issue: mid-turn preemption (#430), a full Slack-like pane, device-wide model broadcast.

Proposed slices (ship in this order)

Each slice should land with tests that zig build test actually reaches (src/main.zig and/or TUI/root.zig import). Suite count goes up. Hand-written files stay ≤ 600 LOC.

Slice A — tell the truth (small, do first)

Slice B — TUI /tell and /peek

  • Same verbs as src/commands_misc.zig, wired through TUI/catalog.zig + the TUI command path.
  • Drive with TUI/sim.zig Term (no PTY, no Ghostty window). Assert the catalog lists them and a /tell from the pager appends a room line the other session would hear.

Slice C — typed channel events

  • Stop injecting peer speech as role: user.
  • Engine event + a history representation that is not the human (dedicated prefix/role the compact/peek paths already understand).
  • Update /peek to show peer lines as peer lines, instead of skipping the prefix as if it were noise.

Slice D — peer_peek (or a richer presence note)

  • Model-callable: live peers + last user prompt + last tool name + last peer line they sent.
  • Reuse summarizeTranscript / the same fields /peek already walks. Do not invent a second transcript parser.
  • Presence note can stay as the "set changed" ping; peer_peek is "what are they doing right now."

Slice E — ack / thread id

  • Optional in_reply_to on peer_message.
  • Sender tool result: posted vs delivered (delivered = receiver crossed a step boundary and deliverInbound ran).
  • Enough to stop "I told them, why are they still editing."

Later (own issues once A–E exist)

Files that will move

Non-goals

  • Replacing the JSONL room with a socket/dbus/server.
  • Letting the model broadcast to every folder on the device.
  • Building a chat product. This is two agents in one tree not stepping on each other.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions