Skip to content

Message Types

This page documents the message type system that determines how each message gets stored, filtered, and displayed. Understanding these types helps you debug missing messages, write channel plugins, and trace what the LLM receives in its context.

Message Types

Type Purpose Stored in DB Sent to LLM Channel Display
user Human messages Plain text
assistant LLM responses With assistant name
system Persistent context Distinct rendering
tool_result Command outputs 🔧 prefix + ✅/❌
host Operational notifications FILTERED 🏠 prefix

System Context Types

There are two distinct types of system context:

  1. system_notices (Ephemeral)
  2. Recomputed on each agent run
  3. Examples: git warnings, uncommitted changes, deployment state
  4. Passed via ContainerInput.system_notices
  5. Container appends to SDK system_prompt parameter
  6. NOT stored in database

  7. message_type='system' (Persistent)

  8. Stored in database as regular messages
  9. Part of conversation history
  10. Sent to LLM as part of message list
  11. For context that should persist across sessions

Database Schema

CREATE TABLE messages (
    id TEXT,
    chat_jid TEXT,
    sender TEXT,
    sender_name TEXT,
    content TEXT,
    timestamp TEXT,
    is_from_me INTEGER,
    message_type TEXT DEFAULT 'user',
    metadata TEXT,  -- JSON
    PRIMARY KEY (id, chat_jid)
);

CREATE INDEX idx_messages_by_chat ON messages(chat_jid, timestamp);

Metadata Field

The metadata column stores structured JSON data for additional context:

  • tool_result: {"exit_code": 0}
  • Future: tool_use_id, error details, etc.

Data Flow

Storage Layer

# Host message (operational)
await store_message_direct(
    id="host-123",
    chat_jid="[email protected]",
    sender="host",
    sender_name="host",
    content="⚠️ Agent error occurred",
    timestamp=datetime.now(UTC).isoformat(),
    is_from_me=True,
    message_type="host",  # Will be filtered out
)

# Tool result (command output)
await store_message_direct(
    id="cmd-123",
    chat_jid="[email protected]",
    sender="command_output",
    sender_name="command",
    content="Command output...",
    timestamp=datetime.now(UTC).isoformat(),
    is_from_me=True,
    message_type="tool_result",
    metadata={"exit_code": 0},
)

Retrieval & Filtering

from pynchy.router import format_messages_for_sdk

# Retrieve messages from DB
messages = await get_messages_since(chat_jid, since_timestamp)

# Convert to SDK format (filters out host messages)
sdk_messages = format_messages_for_sdk(messages)
# Host messages are automatically excluded

Container Integration

# Host sends SDK messages and ephemeral system notices
ContainerInput(
    messages=sdk_messages,              # SDK message list (host messages filtered)
    system_notices=["Git warning..."],  # Ephemeral context
    ...
)

The container receives messages (a list of SDK-format messages with host messages already filtered out) and system_notices (ephemeral context appended to the system prompt).

Key Adapters

HostMessageBroadcaster

Handles operational notifications:

  • Stores the message with message_type='host'
  • Broadcasts to channels with 🏠 emoji
  • Emits an event for the TUI
  • Never forwards the message to the LLM

UserMessageHandler

Handles user message ingestion:

  • Stores the message with message_type='user'
  • Emits to the event bus
  • Broadcasts to all channels