The daemon communicates with frontends (TUI, web UI) over WebSocket using
JSON-encoded messages. The protocol is defined in mew-protocol.
Wire format
Section titled “Wire format”Messages are tagged enums serialized with serde(tag = "type"):
{"type": "Prompt", "text": "hello", "attachments": []}{"type": "SessionReady", "session_id": "sess_01J...", "model": "deepseek-v4-flash"}Encoding/decoding via encode_json / decode_json in mew-protocol.
Client → Server messages
Section titled “Client → Server messages”| Message | Fields | Purpose |
|---|---|---|
NewSession |
cwd?: String |
Create a fresh session |
AttachSession |
session_id: String |
Attach to an existing session |
ListSessions |
List all known sessions | |
Prompt |
text: String, attachments: Vec<Attachment> |
Send a user prompt |
Cancel |
Cancel the current turn | |
PermissionResponse |
request_id: u64, decision: PermissionDecision |
Respond to a permission prompt |
AskUserResponse |
request_id: u64, answers: Vec<String> |
Respond to an ask-user question |
SlashCommand |
command: String |
Run /clear, /compact, etc. on the daemon |
ListModels |
Request the available model list | |
SwitchModel |
provider: String, model: String |
Switch to a different model |
SetThinkingVariant |
variant: String |
Set or clear thinking variant (“off” or “none” disables) |
Ping |
Liveness check; daemon replies with Pong |
|
ListProjects |
List known project directories (for project picker UI) | |
RegenerateTitle |
session_id: String |
Regenerate the session title from the first user message via LLM |
Server → Client messages
Section titled “Server → Client messages”Session lifecycle
Section titled “Session lifecycle”| Message | Fields | Purpose |
|---|---|---|
SessionReady |
session_id, model?, provider? |
Session ready for prompts |
SessionHistory |
messages: Vec<Message> |
Full message replay on resume |
SessionList |
sessions: Vec<SessionInfo> |
Response to ListSessions |
SessionCleared |
Context cleared (broadcast to all clients) | |
SessionTitleChanged |
session_id, title |
Daemon generated a title |
Streaming events
Section titled “Streaming events”| Message | Fields | Purpose |
|---|---|---|
Provider |
event: ProviderEventWire |
Raw provider event (PartStart, PartDelta, etc.) |
ToolStart |
call_id |
Tool execution started |
ToolEnd |
call_id, success |
Tool execution finished |
ToolProgress |
call_id, chunk |
Intermediate tool output |
PartUpdated |
part_id, part |
A part’s content or state changed |
Request/response pairs
Section titled “Request/response pairs”| Message | Fields | Purpose |
|---|---|---|
PermissionRequest |
request_id, tool_name, input |
Ask user to approve a tool call |
AskUserRequest |
request_id, call_id, questions |
Ask user free-text questions |
RequestResolved |
request_id |
A pending request was resolved by any client |
Subagent events
Section titled “Subagent events”| Message | Fields | Purpose |
|---|---|---|
SubagentStart |
parent_call_id, name, child_session_id, display_name? |
Subagent spawned |
SubagentStatus |
parent_call_id, tool_name, message |
Subagent progress update |
SubagentEnd |
parent_call_id, child_session_id, outcome |
Subagent finished |
| Message | Fields | Purpose |
|---|---|---|
ModelList |
models: Vec<ModelInfo> |
Response to ListModels |
ModelSwitched |
provider, model |
Confirms model switch |
ThinkingVariantChanged |
variant?: String |
Confirms thinking variant (null = disabled) |
TodosUpdated |
todos: Vec<Todo> |
Session todo list changed |
PersonaSwitchRequested |
name |
switch_persona tool was called |
JobUpdate |
job_id, command, state |
Background shell job changed |
SlashResult |
text |
Slash command produced text output |
Error |
message |
Error before or outside a turn |
ErrorEvent |
message |
Terminal error during a turn |
Pong |
version: String |
Response to Ping; carries daemon version |
ProjectList |
projects: Vec<ProjectInfo> |
Response to ListProjects; deduped project directories with session counts |
Session model
Section titled “Session model”The daemon owns sessions via SessionManager (mew-daemon/src/session.rs).
Connections attach to sessions. Key behaviors:
- Broadcasting:
session.broadcast(msg)sends to all attached clients and removes any that have disconnected. - Turn serialization:
turn_lockensures only one turn runs at a time. A secondPromptwhile a turn is in progress receives an error. - Cancellation: each turn gets a fresh
CancellationToken.Cancelfrom any client cancels the current turn. - Permission/ask-user requests: go to all clients. Any client can
respond.
RequestResolveddismisses the modal everywhere.
See mew-daemon/src/session.rs for the full Session struct definition.
Connection lifecycle
Section titled “Connection lifecycle”Client connects → NewSession or AttachSession → SessionReady + SessionHistory → Prompt → Provider events stream (PartStart → PartDelta → ... → MessageEnd) → (optional) PermissionRequest → PermissionResponse → RequestResolved → (optional) ToolStart → ToolProgress → ToolEnd → (optional) more turnsClient disconnects → detach_client → if last client: cancel turn, remove session from activeIdle sessions can be resumed from disk. The session writer persists every
message to ~/.local/share/mew/sessions/<id>/session.jsonl. On resume,
Agent::load_messages replays the history.
AgentEvent → ServerMessage translation
Section titled “AgentEvent → ServerMessage translation”translate_event (mew-daemon/src/lib.rs) converts each AgentEvent into
zero or more ServerMessages:
AgentEvent::Provider(pe)→ServerMessage::Provider { event: ProviderEventWire::from(pe) }AgentEvent::PermissionRequest { call, tx }→ assigns arequest_id, stashes theoneshot::Senderinsession.pending_permissions, emitsServerMessage::PermissionRequestAgentEvent::AskUser { questions, tx }→ same pattern withpending_ask_user
ServerMessage → AgentEvent translation
Section titled “ServerMessage → AgentEvent translation”For the TUI daemon-client (mew-daemon/src/client.rs), translate_server_message
reverses the translation. Channel-bearing messages reconstruct oneshot
channels and return AgentEvent::PermissionRequest { call, tx } with a
fresh oneshot::Sender that the client maps to ClientMessage::PermissionResponse.
Messages that don’t map to AgentEvent (model list, session list, etc.)
return Vec::new() and are handled by the DaemonClient directly.
Adding a new message type
Section titled “Adding a new message type”- Add the variant to
ClientMessageorServerMessageinmew-protocol/src/lib.rs - Add a roundtrip test in the protocol test module
- Handle the new message in
handle_connection(mew-daemon/src/lib.rs) - Update
translate_server_messageinmew-daemon/src/client.rsif it’s aServerMessagethe TUI daemon-client needs to handle - Add the TypeScript type + dispatch to
mew-web-client/src/index.ts - Wire the store action in
mew-web-ui/src/stores/session.ts