Skip to content

feat: add reply-to message threading#30

Merged
Killea merged 4 commits intoKillea:mainfrom
bertheto:feat/reply-threading
Mar 3, 2026
Merged

feat: add reply-to message threading#30
Killea merged 4 commits intoKillea:mainfrom
bertheto:feat/reply-threading

Conversation

@bertheto
Copy link
Contributor

@bertheto bertheto commented Mar 2, 2026

Problem

Messages in AgentChatBus are purely sequential (identified by seq number only). There is no way for a message to explicitly reference a parent message, making it impossible to model reply chains, threaded discussions, or contextual annotations within a thread.

This is a universal messaging primitive present in all major platforms (Slack, Discord, GitHub comments, email threads).

Solution

Add an optional reply_to_msg_id field to the Message model. When set, it references a parent message that must exist in the same thread. The field is stored as a dedicated column (not in metadata) for queryability.

Changes

  • src/db/models.py: reply_to_msg_id: Optional[str] = None added to Message dataclass
  • src/db/database.py: additive migration ALTER TABLE messages ADD COLUMN reply_to_msg_id TEXT + index idx_messages_reply
  • src/db/crud.py: safe fallback in _row_to_message() for older DBs; new msg_get() function; msg_post() validates parent exists and belongs to the same thread; emits msg.reply SSE event when reply_to_msg_id is set
  • src/mcp_server.py: reply_to_msg_id property added to msg_post inputSchema
  • src/tools/dispatch.py: handle_msg_post() passes/returns reply_to_msg_id; handle_msg_list() includes field; ValueError from validation returned as structured error
  • src/main.py: MessageCreate.reply_to_msg_id; POST response and GET message list include the field
  • src/static/index.html + src/static/css/main.css: visual reply-quote block (.msg-reply-quote) shown above message content when reply_to_msg_id is present
  • tests/test_reply_threading.py: 18 tests (12 unit with in-memory DB + 6 integration against real server)

Backward Compatibility

  • reply_to_msg_id defaults to null — all existing msg_post calls are unchanged
  • Existing databases are handled by a safe column presence check in _row_to_message()
  • The new field is additive in all API responses; existing consumers can ignore it

Validation Rules

  • reply_to_msg_id must reference an existing message ID in the same thread
  • Cross-thread references raise ValueError (HTTP 400 via REST, structured error via MCP)
  • Non-existent message IDs raise ValueError (HTTP 400)

SSE Event

A new msg.reply event is emitted when a reply is posted:

{
  "type": "msg.reply",
  "payload": {
    "msg_id": "...",
    "reply_to_msg_id": "...",
    "thread_id": "...",
    "author": "agent-name",
    "seq": 5
  }
}

Add optional reply_to_msg_id field to messages, allowing any message
to reference a parent message in the same thread.

Changes:
- models.py: add reply_to_msg_id: Optional[str] = None to Message dataclass
- database.py: additive migration ADD COLUMN reply_to_msg_id TEXT + index
- crud.py: _row_to_message() fallback for older DBs, new msg_get() function,
  msg_post() validation (parent must exist in same thread), SSE msg.reply event
- mcp_server.py: reply_to_msg_id property in msg_post inputSchema
- dispatch.py: handle_msg_post passes/returns reply_to_msg_id, handle_msg_list
  includes reply_to_msg_id, ValueError caught and returned as structured error
- main.py: MessageCreate.reply_to_msg_id, POST/GET endpoints include field
- index.html + main.css: visual reply-quote block (.msg-reply-quote)
- tests/test_reply_threading.py: 18 tests (12 unit + 6 integration)
- tests/conftest.py: compatibility check updated for UP-14

Backward compatible: reply_to_msg_id defaults to null, existing calls unchanged.
Existing DBs are handled by a safe column fallback in _row_to_message().
@bertheto bertheto force-pushed the feat/reply-threading branch from 8e2e610 to 72d5b66 Compare March 2, 2026 14:19
bertheto added 3 commits March 2, 2026 15:47
The function was present in the feat/metrics-endpoint branch (PR Killea#27) but was
not included when feat/reply-threading was created. Adding it back so that
test_metrics.py passes in CI.
@Killea Killea merged commit d375547 into Killea:main Mar 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants