Skip to main content

Group Chat Design

File-based group chat feature supporting human users and multiple AI agents in real-time discussions and collaboration.


Overview

PropertyValue
Moduleviben-core/gateway
StorageFile System (not database)
StatusDraft v2
PriorityP1

Core Concepts

User View vs Agent View

ViewFileContent
User Viewmessages.ui.jsonlMessage stream seen by users, without tool call details
Agent Viewagents/<agent-id>/messages.rollout.jsonlRaw messages from an Agent, including tool calls

After a user sends a message:

  1. All Agents in the group chat think in parallel
  2. User view only shows "Agent is thinking..."
  3. Each Agent gives an answer after thinking
  4. Tool calls happen in the background, not shown in user view

Participant Types

TypeIdentifierDescription
humanUser IDHuman user, sends messages via UI
agentAgent IDAI agent, such as Claude, GPT, etc.

File System Structure

Storage Location

<workspace>/.viben/group-chats/

Directory Structure

group-chats/
└── <group-chat-id>/
├── config.yaml # Group chat configuration
├── files/ # Group files (shared files)
├── pictures/ # Group album (shared images)
└── sessions/ # Conversation records
└── <session-id>/
├── config.yaml # Session configuration
├── messages.ui.jsonl # User view messages (append-only)
├── responses.jsonl # Current round agent responses (cleared each round)
└── agents/
└── <agent-id>/
├── messages.rollout.jsonl # Agent message records (with tool calls)
└── subagents/ # Sub-agent messages (if any)
└── agent-<subagent-id>.jsonl

Key File Descriptions

FileLifecyclePurpose
messages.ui.jsonlappend-onlyComplete conversation history from user view
responses.jsonlcleared each roundTemporary storage for current round agent responses, used to build next round context
messages.rollout.jsonlappend-onlyComplete agent message records (with tool calls)
subagents/agent-*.jsonlappend-onlySub-agent message records (following Claude Code design)

Data Models

config.yaml (Group Chat Configuration)

# <group-chat-id>/config.yaml
id: "gc-uuid"
name: "Code Review Discussion"
description: "Code review for PR #123"
created_by: "user-1"
created_at: "2026-02-10T12:00:00Z"
updated_at: "2026-02-10T12:00:00Z"

# Group chat members
members:
- id: "user-1"
type: human
display_name: "John Doe"
role: owner
joined_at: "2026-02-10T12:00:00Z"

- id: "claude-code"
type: agent
display_name: "Claude Code"
model: "claude-sonnet-4-20250514"
role: member
joined_at: "2026-02-10T12:00:00Z"

- id: "cursor"
type: agent
display_name: "Cursor AI"
model: "gpt-4o"
role: member
joined_at: "2026-02-10T12:00:00Z"

# Group chat settings
settings:
# Message broadcast mode
broadcast_mode: all # all | mention_only
# Whether to show agent thinking process
show_thinking: false
# Number of history messages to load
history_limit: 10

config.yaml (Session Configuration)

# sessions/<session-id>/config.yaml
id: "session-uuid"
group_chat_id: "gc-uuid"
title: "Discussion Session 1"
created_at: "2026-02-10T12:05:00Z"
updated_at: "2026-02-10T12:30:00Z"

# Agents active in this session
active_agents:
- claude-code
- cursor

# Session status
status: active # active | archived

messages.ui.jsonl (User View Messages)

One JSON message per line, for UI rendering:

{"id":"msg-1","type":"user","sender_id":"user-1","sender_name":"John","content":"Please help me review this code","timestamp":"2026-02-10T12:05:00Z"}
{"id":"msg-2","type":"agent_thinking","agent_id":"claude-code","agent_name":"Claude Code","status":"thinking","timestamp":"2026-02-10T12:05:01Z"}
{"id":"msg-3","type":"agent_thinking","agent_id":"cursor","agent_name":"Cursor AI","status":"thinking","timestamp":"2026-02-10T12:05:01Z"}
{"id":"msg-4","type":"agent_response","agent_id":"claude-code","agent_name":"Claude Code","content":"This code has several issues...","timestamp":"2026-02-10T12:05:30Z"}
{"id":"msg-5","type":"agent_response","agent_id":"cursor","agent_name":"Cursor AI","content":"I agree with Claude, also...","timestamp":"2026-02-10T12:05:35Z"}

UI Message Types

type UIMessageType =
| "user" // User message
| "agent_thinking" // Agent is thinking
| "agent_response" // Agent response
| "system" // System message (member join/leave, etc.)

interface UIMessage {
id: string
type: UIMessageType
timestamp: string

// user message
sender_id?: string
sender_name?: string
content?: string

// agent_thinking message
agent_id?: string
agent_name?: string
status?: "thinking" | "done" | "error"

// agent_response message
// agent_id, agent_name, content

// system message
event?: "member_joined" | "member_left" | "session_created"
data?: Record<string, unknown>
}

responses.jsonl (Current Round Agent Responses)

Temporary storage for current round agent final responses, excluding tool call process:

{"agent_id":"claude-code","agent_name":"Claude Code","content":"This code has several issues...","timestamp":"2026-02-10T12:05:30Z"}
{"agent_id":"cursor","agent_name":"Cursor AI","content":"I agree with Claude, also...","timestamp":"2026-02-10T12:05:35Z"}

Lifecycle:

  • After user sends message, responses.jsonl is cleared
  • After each agent completes response, append to responses.jsonl
  • When user sends next message, read responses.jsonl to build other agents' response context

messages.rollout.jsonl (Agent Message Records)

Complete agent message records, including tool calls. Key point: Messages sent to an agent prepend other agents' responses.

{"role":"system","content":"You are Claude Code, a code review assistant..."}
{"role":"user","content":"Please help me review this code","name":"John"}
{"role":"assistant","content":"Let me analyze this code first...","tool_calls":[{"id":"tc-1","type":"function","function":{"name":"read_file","arguments":"{\"path\":\"src/main.rs\"}"}}]}
{"role":"tool","tool_call_id":"tc-1","content":"fn main() { ... }"}
{"role":"assistant","content":"This code has several issues..."}
{"role":"user","content":"[Cursor AI]: I agree with Claude, also...\n\n[User]: How should I fix it specifically?"}
{"role":"assistant","content":"Okay, let me give you specific suggestions..."}

Message Building Logic (example for sending to Claude):

1. Read non-Claude responses from responses.jsonl
2. Format as: "[Cursor AI]: xxx\n\n[User]: user's new message"
3. Append as new user message to messages.rollout.jsonl

Gateway API

REST Endpoints

Group Chat Management

POST /api/workspaces/:workspace_id/group-chats Create group chat
GET /api/workspaces/:workspace_id/group-chats List group chats
GET /api/workspaces/:workspace_id/group-chats/:id Get group chat details
PATCH /api/workspaces/:workspace_id/group-chats/:id Update group chat config
DELETE /api/workspaces/:workspace_id/group-chats/:id Delete group chat

Member Management

GET /api/group-chats/:id/members List members
POST /api/group-chats/:id/members Add member
DELETE /api/group-chats/:id/members/:member_id Remove member

Session Management

POST /api/group-chats/:id/sessions Create new session
GET /api/group-chats/:id/sessions List sessions
GET /api/group-chats/:id/sessions/:session_id Get session details
DELETE /api/group-chats/:id/sessions/:session_id Delete session

Messages

GET /api/group-chats/:id/sessions/:session_id/messages
?view=ui|agent&agent_id=xxx&limit=10&before=timestamp

POST /api/group-chats/:id/sessions/:session_id/messages
Send message (triggers all agent responses)

WebSocket Endpoints

Group Chat Real-time Communication

WS /api/group-chats/:id/sessions/:session_id/ws

Connection Parameters:

?member_type=human&member_id=user-1

Client Commands:

// Send message
{ "type": "send_message", "content": "Hello everyone!" }

// Switch view
{ "type": "switch_view", "view": "ui" | "agent", "agent_id": "claude-code" }

// Interrupt agent thinking
{ "type": "interrupt", "agent_id": "claude-code" }

Server Events:

// Agent starts thinking
{ "type": "agent_thinking", "agent_id": "string", "agent_name": "string" }

// Agent thinking progress (optional, for token streaming)
{ "type": "agent_progress", "agent_id": "string", "delta": "string" }

// Agent completes response
{ "type": "agent_response", "agent_id": "string", "agent_name": "string", "content": "string" }

// Agent error
{ "type": "agent_error", "agent_id": "string", "error": "string" }

// View data (returned after view switch)
{ "type": "view_data", "view": "ui" | "agent", "messages": Message[] }

Interaction Flow

User Sends Message

View Switching

Users can switch between different views in the UI title bar:

ViewData SourceCan Send MessagesDescription
Group Chat View (default)messages.ui.jsonlYesClean message stream, normal user interaction
Agent Viewagents/<id>/messages.rollout.jsonlNo (read-only)View specific Agent's tool call process

Switch Behavior:

  • Stay at same message position (same conversation round) after switching
  • Message input box is disabled in Agent view
  • Requires manual switch, no automatic switching

Session Switching

Title bar can switch sessions (similar to other chat page session switching):

┌──────────────────────────────────────────────────┐
│ Code Review ▼ │ Session 1 ▼ │ Group View ▼ │
├──────────────────────────────────────────────────┤
│ │
│ [Message List] │
│ │
└──────────────────────────────────────────────────┘

API Request/Response Examples

Create Group Chat

Request:

POST /api/workspaces/ws-1/group-chats
Content-Type: application/json

{
"name": "Code Review Discussion",
"description": "Code review for PR #123",
"members": [
{ "type": "human", "id": "user-1", "display_name": "John", "role": "owner" },
{ "type": "agent", "id": "claude-code", "display_name": "Claude Code", "model": "claude-sonnet-4-20250514" },
{ "type": "agent", "id": "cursor", "display_name": "Cursor AI", "model": "gpt-4o" }
]
}

Response:

{
"id": "gc-uuid",
"name": "Code Review Discussion",
"path": "/workspace/path/.viben/group-chats/gc-uuid",
"members": [
{ "type": "human", "id": "user-1", "display_name": "John", "role": "owner" },
{ "type": "agent", "id": "claude-code", "display_name": "Claude Code" },
{ "type": "agent", "id": "cursor", "display_name": "Cursor AI" }
],
"created_at": "2026-02-10T12:00:00Z"
}

Send Message

Request:

POST /api/group-chats/gc-uuid/sessions/session-1/messages
Content-Type: application/json

{
"content": "Please help me review this code"
}

Response (returns immediately, agent responses pushed via WebSocket):

{
"message": {
"id": "msg-uuid",
"type": "user",
"content": "Please help me review this code",
"timestamp": "2026-02-10T12:05:00Z"
},
"agents_triggered": ["claude-code", "cursor"]
}

Get Message History

Request:

GET /api/group-chats/gc-uuid/sessions/session-1/messages?view=ui&limit=10

Response:

{
"messages": [
{ "id": "msg-1", "type": "user", "sender_name": "John", "content": "Please help me review this code", "timestamp": "..." },
{ "id": "msg-2", "type": "agent_response", "agent_name": "Claude Code", "content": "This code has several issues...", "timestamp": "..." },
{ "id": "msg-3", "type": "agent_response", "agent_name": "Cursor AI", "content": "I agree...", "timestamp": "..." }
],
"has_more": true
}

Switch to Agent View

Request:

GET /api/group-chats/gc-uuid/sessions/session-1/messages?view=agent&agent_id=claude-code&limit=10

Response:

{
"messages": [
{ "role": "system", "content": "You are Claude Code..." },
{ "role": "user", "content": "Please help me review this code", "name": "John" },
{ "role": "assistant", "content": null, "tool_calls": [{ "id": "tc-1", "function": { "name": "read_file", "arguments": "..." } }] },
{ "role": "tool", "tool_call_id": "tc-1", "content": "fn main() { ... }" },
{ "role": "assistant", "content": "This code has several issues..." }
],
"has_more": false
}

Comparison with Existing Systems

ComparisonRegular Chat SessionGroup Chat
Participants1 User + 1 Agent1 User + N Agents
Agent ResponseSerialParallel
Tool CallsShown in UIHidden in background
StorageDatabaseFile System
ViewSingleSwitchable